https://memorycorruption.net/posts/rce-lua-factorio/ Memory Corruption Home Posts Tags menu * Home * Posts * Tags * * theme Bytecode Breakdown: Unraveling Factorio's Lua Security Flaws Dynamic languages are safe from memory corruptions bugs, right? 29/06/2024 * Research * Pwn * Lua Rocket Man Some months ago I exploited a vulnerability in the Lua implementation of Factorio that allowed a malicious server to obtain arbitrary execution on clients. As the vulnerability has been patched for months already (Factorio versions below 1.1.101 are affected), is time to share the details with the community. I think this is a very interesting topic, that can serve as an introduction to understand other dynamic languages such as Javascript , where similar ideas are used for exploitation. For this reason, this is an in-depth explaination of the vulnerability, so that it can be used by others as a reference to understand how these attacks work. In addition to this, at the end of the post you will find a challenge to practice the techniques explained in this post in a gamified environment, directly in your browser. You can jump directly to the challenge: Your Turn What is Factorio? Factorio is a game in which you automate a factory to build a rocket and escape from a planet. Based on their website, they have sold more than 3,500,000 copies of the game , making it a juicy target for security researchers YouTube video How is Lua used in the game? Lua is used in Factorio to implement some game logic and to create mods and custom maps that can be downloaded from in-game or from their website . The modding community is very active, so there are thousands of mods available, some with even more than half a million downloads Alien Biomes mod The Alien Biomes mod has 551K downloads Based on this information, it might seem that the surface of the Lua interpreter in the game is limited to local exploits that require the user to download a malicious mod. That would already be an issue, as compromising one mod (either finding a vulnerability in it / compromising the source) has the potential to reach millions of users , but we are missing a small detail that exposes the lua interpreter to the network, opening the door to more interesting attacks The more the merrier On the Factorio wiki there is a very important implementation detail of the multiplayer mode: Factorio multiplayer code uses deterministic lockstep to synchronize clients. This is a method of synchronizing a game from one computer to another by sending only the user inputs that control that game, rather than networking the state of the objects in the game itself. It means that all player's games need to simulate every single tick of the game identically. If any computer does something ever-so-slightly different, a desynchronization (desync) occurs. The game includes processes to ensure any issues with network traffic do not cause desyncs. That means that if one player executes some Lua code, the rest of the players must execute it in order to preserve the syncronization of the game. Failing to do so will result in a desync state, disconnecting the client from the game with an error message, as also seen in the wiki Desync Error So we now know that any Lua code we execute is also executed by the rest of players. What are our options to execute lua code? After some research, we end up with two options: 1. Use the /c command to execute Lua code in a server (if we have permission to do it) 2. Creating a custom map that contains lua code so it gets executed when a client connects to the server As both options require privileges on a server, we might as well go for the second path. As the game also features an in-game server browser, an attacker could make it visible there to atract victims. Going Deeper General Exploitation Path Now that we have a clear path to reach the Lua interpreter from the network, let's take a quick look at the general exploitation path that we will follow: 1. We host a Factorio server that is serving a malicious map. This map will contain our exploit as part of the Lua code that defines the scenario of the map 2. When a client connects to our server, they download the map and execute the Lua code associated with it (as we have seen before, as state is not shared, clients need to execute the Lua code to ensure syncronization between them) 3. Our payload will leverage weaknesses in the Lua implementation to craft fake objects 4. These fake objects will allow us to leak/corrupt memory to alter the behaviour of the program 5. We follow one of the many techniques to gain code execution by leveraging these powerful primitives A small leak will sink a great ship As one can imagine, the official Lua interpreter contains modules that allow scripts to interact with the host in multiple common ways, such as opening files, executing commands, getting environment variables... While this might be desirable on normal circumstances, is definitely not okay when executing untrusted code. For this reason, a basic hardening recommendation is to completely disable these modules when compiling Lua for those sensitive environments. This is the case in Factorio too, where only the following modules are compiled: * debug - Provides Access to Debug functionalities * math - Interface to standard C Math * bit32 - Bitwise operations * string - Manipulation of strings * table - Manipulation of tables * base - Core Functions of Lua, such as print However, the devil is in the details, and while modules that have names like os with functions like execute are easily recognizable are dangerous, others like load or loadstring that are part of the base module might be seem as benign, while they are arguably the most powerful functions of Lua. Why are these functions so powerful? Because they allow executing bytecode. Who controls the Bytecode controls the future Lua is an interpreted language, but it doesn't execute the code we write as it is, first, it is compiled. This might be a surprise to some, as it seems to be incompatible with the classic view of an interpreted language. However, details are important, Lua doesn't compile to machine code, that is, code that your CPU understands. Instead, it compiles into Lua bytecode, which is a representation of the code that can only be executed by the Lua interpreter, making it still an interpreted Language. Source code is useful for humans, as it is easily readable, but text is hard to work with for computers, so bytecode is a more useful representation for them. Let's see this in practice so we get a clearer view of how this works. If we have the following code: print("MemoryCorruption") Lua is going to generate and execute the following bytecode: All the bytecode snippets in this post were created with luac -l -l