diff --git a/2020/gctf/sprint/README.md b/2020/gctf/sprint/README.md index 69984c8..96c032c 100644 --- a/2020/gctf/sprint/README.md +++ b/2020/gctf/sprint/README.md @@ -1,5 +1,16 @@ # Google CTF 2020 Write-up: Sprint +In this challenge, we're given a binary to reverse engineer. We find +that it's an interpreter for a Turing-complete language made entirely +of `sprintf` format strings, along with a program in this language. +We write a disassembler for this language, and use it to +reverse engineer the embedded program. We find that the embedded +program is a game, and map out a series of moves to win the game. + +![The galaxy brain meme. Small brain: Writing a game in a high-level +language. Medium brain: Writing a game in x86 assembly. Large brain: +Writing a game in sprintf format strings](brain_meme.jpg) + ## Picking apart the binary We're given a single binary file. Running it through `objdump`, we @@ -149,7 +160,7 @@ In order: to by vararg 3. That is, it writes it to the lower two bytes of `regs[0]`. 3. `%1$65498s` writes 65498 spaces, so the total number of spaces so - far is 65536, or `0x10000`. + far is 65536, or `0x10000`. 4. `%1$28672s` writes 28672 spaces, or `0x7000` spaces. 5. `%9$hn` counts the number of characters written so far, and writes it to the `short int` pointed to by vararg 9. That is, the lower @@ -346,7 +357,7 @@ Now we can see the output: ### Using `gdb` Now we have yet another chunk of assembly to reverse. It will be -useful to be able to use `gdb` to step through this code, so lets +useful to be able to use `gdb` to step through this code, so let's understand how to do that. The call to `sprintf` is at `main+555`, so by putting a breakpoint there, we can step through the embedded code one instruction at a time. To view the 10 registers, we can examine @@ -404,7 +415,9 @@ but I'll leave that out for brevity. We'll jump straight to the first place where we read the user input. Instead of picking apart the code before that, we can just use `gdb` -to examine the state of the program at this point. +to examine the state of the program at this point. Since the earlier +code doesn't reference the user input, the state here will always be +the same. 0374: mov 0xe000, %r2 # Address of user input 039a: mov 0x0, %r3 # Counter, initialized to 0 @@ -444,7 +457,7 @@ Next we load some data into registers 0536: mov 0x0, %r2 0558: mov 0x0, %r3 057b: mov 0xf100, %r1 - 05a1: mov (%r1), %r4 # Read from 0xf100, value = 0x11 + 05a1: mov (%r1), %r4 # Read from 0xf100, value = 0x11 05c6: mov 0x1, %r5 05e9: mov 0x0, %r9 @@ -588,7 +601,7 @@ can't ever enter an invalid direction, and we can't ever step on nonzero terrain. We also see that we need to step on exactly nine checkpoints. -The register `%r9` appears to hold a "reason code" for why we lose. +The register `%r9` appears to hold a reason code for why we lose. This could be useful for debugging later. There's still more code after, but for now our task is clear: we need diff --git a/2020/gctf/sprint/brain_meme.jpg b/2020/gctf/sprint/brain_meme.jpg new file mode 100644 index 0000000..10a366a Binary files /dev/null and b/2020/gctf/sprint/brain_meme.jpg differ