{"id":13815077,"url":"https://github.com/Valentin-Metz/writeup_factorio","last_synced_at":"2025-05-15T07:31:54.242Z","repository":{"id":216452710,"uuid":"731900873","full_name":"Valentin-Metz/writeup_factorio","owner":"Valentin-Metz","description":"Writeup of a remote code execution in Factorio by supplying a modified save file.","archived":false,"fork":false,"pushed_at":"2024-01-10T13:40:02.000Z","size":1285,"stargazers_count":85,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-19T10:48:26.704Z","etag":null,"topics":["binary-exploitation","factorio","pwn"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Valentin-Metz.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-12-15T06:32:04.000Z","updated_at":"2024-07-04T07:30:07.000Z","dependencies_parsed_at":null,"dependency_job_id":"e5c9f3a6-8768-44e5-820b-ed9b8361ac4d","html_url":"https://github.com/Valentin-Metz/writeup_factorio","commit_stats":null,"previous_names":["valentin-metz/writeup_factorio"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Valentin-Metz%2Fwriteup_factorio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Valentin-Metz%2Fwriteup_factorio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Valentin-Metz%2Fwriteup_factorio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Valentin-Metz%2Fwriteup_factorio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Valentin-Metz","download_url":"https://codeload.github.com/Valentin-Metz/writeup_factorio/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254295941,"owners_count":22047173,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["binary-exploitation","factorio","pwn"],"created_at":"2024-08-04T04:02:55.235Z","updated_at":"2025-05-15T07:31:52.956Z","avatar_url":"https://github.com/Valentin-Metz.png","language":"Python","readme":"# Abstract\n\n**From save game to remote code execution**\n\nIn September 2023 we found a buffer overflow vulnerability in Factorio.\nThis vulnerability allows for arbitrary code execution when loading or previewing a modified save file.\nWe have reported the vulnerability alongside a proof-of-concept to the Factorio team,\nand a fix has been released with game version 1.1.94 on October 30th 2023.\n\n# Factorio:\n\n[![factorio](img/factorio-logo.png)](https://factorio.com/)\n[Factorio](https://factorio.com/) is a factory automation game.\nIt has sold about 3.5 million copies on multiple distribution platforms and operating systems;\nincluding Windows, Linux, macOS, the Steam Deck and the Nintendo Switch.\n\nThe game is programmed in C++ in a custom engine.\nIt supports a large online multiplayer and modding community.\n\nIt's very popular among computer science students,\nas it's the best parts of programming without any of the boring/exhausting parts.\n\n## Exploring the binary:\n\nOpening factorio in IDA to reverse engineer was a fun project.\nSince we are interested in security critical aspects,\nunderstanding the parser was a first goal.\nMost of it seemed pretty sane and uses C++ dynamic sized types.\n\nReverse engineering a game isn't fundamentally different from a normal binary.\nYou open it in a disassembler, attach a debugger, find resources online, and piece stuff together piece by piece.\nAt this point a big thanks to the developers for giving us debug symbols;\nalthough we do recommend that they enable PIE for future builds.\n\nFollowing strings and function names,\nwe eventually found the code responsible for loading save files.\nThis function loads a [PropertyTree](https://wiki.factorio.com/Property_tree) from a save file.\nIn order to be efficient, it allocates enough memory for the entire data section of the file in advance.\n\n![Screenshot of IDA with the line containing the bug being highlighted](img/bug.png)\n\n## The bug:\n\nThe bug lies in way this number of bytes to allocate is computed (see marked line `36`).\n\nBecause the result of `data_length + 1` is cast to a 32-bit unsigned integer,\nwe can enter a number that causes a wrap-around to zero after the addition.\nAs the deserializer is given a deserialization length of `data_length`,\nit will attempt to read `data_length` bytes into a buffer of size `1`,\noverwriting a massive section of the heap in the process.\n\nSpecifically, this will always give us an overflow of (a multiple of) 4GiB.\nSince we only read 4 bytes as size,\nthe only way we will overflow if this is if we are exactly one byte short of 4 gigabyte.\nSadly this means that in order for our exploit to work,\nthe map file also needs to be exactly this big.\nOtherwise, the MapDeserializer will complain that there is not enough data in order to attempt deserialization.\n\nIn case you were wondering why `new[]` with a size of zero does not return a `nullptr`;\nthere exists a small piece to implement a custom `operator new[]` in this C++ project,\nwhich first checks if the size to allocate is zero,\nand if so, sets the allocation size to 1 (i.e. no allocation will return nullptr).\nIt will then go into a while true loop,\nwhere it will first attempt to allocate memory with `malloc`,\nand if that fails, attempt to remedy the situation with `std::get_new_handler`.\n\n![Screenshot of the new handler in IDA](img/newhandler.png)\n\n## The exploit:\n\nWe have developed the proof of concept exploit on an `amd64` linux machine,\nand are using the **linux native** version of factorio.\n\n### Recon:\n\nIf we inspect the factorio binary with `checksec`, we get the following:\n\n![checksec](img/checksec.png)\n\nThe game itself is written in C++ and compiled as a **non position independent executable**.\nThis allows us to hardcode any addresses we need for our exploit,\nwithout having to worry about ASLR.\n\nAs we have an overflow of ~4GiB in size, we will overwrite a massive section of the program heap.\n\nThe first step is to create a fake save file with modified size specifications,\nfilled with a [non-repeating pattern](https://en.wikipedia.org/wiki/De_Bruijn_sequence).\nOnce we preview this save file, factorio will crash with a segmentation fault.\nIf we attach a debugger, we can observe multiple crashes, in multiple threads:\n![music_mixer_thread](img/music_mixer_thread.png)\nThis appears to a thread responsible for audio.\nIt's attempting to read from an invalid address (we have seeded `rsi` with our pattern).\nThis does not appear to be immediately exploitable, so we continue our search.\n\n![main_thread](img/worker_thread.png)\nThe next thread is a worker thread.\nImmediately we notice multiple interesting things:\n\n1. The thread is attempting to execute a jump\n   ![call](img/jump.png)\n2. The jump target is read from the location pointed at by `rbx + 0x40`\n3. `RBX` is pointing into our pattern --\u003e we control the jump target\n   ![rbx](img/rbx.png)\n4. `RCX` is pointing into our pattern a few bytes after `RBX`\n   ![rcx](img/rcx.png)\n\n--\u003e We can use this to perform a stack pivot (with the gadget `0x2043fa4: mov rsp, rcx; ret;`),\nbuild a ROP chain and execute arbitrary code.\n\n### Chain construction:\n\nWe can not simply write shellcode into our pattern, as the heap is marked as non-executable.\nInstead, we will use a technique\ncalled [return oriented programming](https://en.wikipedia.org/wiki/Return-oriented_programming).\nEssentially, we will reuse a series of existing instructions in the factorio binary to construct our exploit.\n\n[Ropper](https://github.com/sashs/Ropper) reports `638593 gadgets found`.\n\nAs we have plenty of space on the chain and don't want to bother ourselves with the libc,\nwe manually execute a syscall.\n\nWe need 5 gadgets for the main chain:\n\n1. `0x40e86b: pop rax; ret;` --\u003e load syscall number into `rax`\n2. `0x40e150: pop rdi; ret;` --\u003e load first argument into `rdi`\n3. `0x40e2d4: pop rsi; ret;` --\u003e load second argument into `rsi`\n4. `0x42c4d6: syscall;` --\u003e execute syscall (`sys_execve` - syscall number `59`)\n5. `0x1c73b08: mov qword ptr [rax], rsi; ret;` --\u003e modify memory (used to specify target program and arguments)\n\nWe will execute our target program `get_flag`:\n\n```bash\n/bin/get_flag\n```\n\nIn addition to the main chain, we need our stack pivot gadget:\n\n- `0x2043fa4: mov rsp, rcx; ret;`\n\nAs the load location of our modified save file is a bit flaky,\nwe insert a ret slide,\nwhich is easier to target than our main chain.\n\nWe place the address of the ret slide and the address of the stack pivot gadget\nin the first section of our savefile in an interleaving, repeating pattern.\nThe jump will now target the pivot gadget,\nwhich will point the stack pointer at our ret slide.\nWe place the ret slide itself in the second section of our save file,\nso that it is loaded at a somewhat predictable location after the binary base.\nAt the end of the ret slide we place the main chain to execute our target program.\n\nNow, once a modified save file is previewed,\nthe game will attempt to load its content to memory,\noverwrite the heap with our data,\npivot the stack of a waiting worker thread onto our ROP-chain,\nand execute our target program - giving us control over the target machine.\n\n![so_long_and_thanks_for_all_the_fish](img/so-long-and-thanks-for-all-the-fish.png)\n","funding_links":[],"categories":["Python"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FValentin-Metz%2Fwriteup_factorio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FValentin-Metz%2Fwriteup_factorio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FValentin-Metz%2Fwriteup_factorio/lists"}