Friday, July 19, 2024
Google search engine
HomeUncategorizedBytecode Breakdown: Unraveling Factorio's Lua Security Flaws

Bytecode Breakdown: Unraveling Factorio’s Lua Security Flaws

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

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.

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

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments