Skip to content

Latest commit

 

History

History
executable file
·
112 lines (72 loc) · 6.3 KB

File metadata and controls

executable file
·
112 lines (72 loc) · 6.3 KB

babygame02

Overview

200 points

Category: Binary Exploitation

Tags : #writewhatwhere #bufferunderflow #returnaddress #outofbounds #integermath

Description

Break the game and get the flag. Welcome to BabyGame 02! Navigate around the map and see what you can find! The game is available to download here. There is no source available, so you'll have to figure your way around the map. You can connect with it using nc saturn.picoctf.net <port>.

Approach

Began by disassembling the game binary within Ghidra and analysing the output.

Also checked the binary security with pwntools checksec :

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)

This challenge is a variant of the babygame01 challenge, the flag win condition differs in that the win() function is not explicitly called in babygame02, hence a modification of the return address or similar means of execution is required.

The binary security indicates no stack canaries are in use, so we won't need to be careful of those.

Visualising the stack frame of main() from the disassembly to show placement and relative offsets of local variables and other elemts on the stack. I've augmented the variables with their purpose obtained from analysis of functions (main(), init_player() and move_player()) :

         (stack top - lower address)
   
0xffffca90  |------------------| ($ESP - 0xAA0)
            |                  |
0xffffca98  | player_y_pos     | [$EBP - 0xAA0]  int
            | player_x_pos     | [$EBP - 0xA9C]  int
0xffffcaa3  | map_buf          | [$EBP - 0xA95]  char[2700] 
            | input_char       | [$EBP - 0x9]    char
0xffffd530  | Saved $ECX       | [$EBP - 0x8]
0xffffd534  | Saved $EBX       | [$EBP - 0x4]
0xffffd538  | Previous $EBP    | <--- $EBP
            |------------------|
            |                  |

move_player(player_y_pos, input_ch, map_buf) contains the same exploitable line of code that forming a write-what-where primative. We control the player X and Y position and hence the address to write to, aswell as the byte to write through the ability to change the player_title. As this is similar to babygame01, refer to my write up for further details.

map_buf[(player_y_pos * 0x5a) + player_x_pos)] = player_title

This time we must overwrite a return address on the stack with the addres of the win() function to dumps the flag instead of overwriting a local variable of main().

The behaviour of move_player() is slightly different in babygame02, in that before moving the player it writes to the previous player position (before move), clearing it with '.' (0x2e) character before updating and writing the new player position. This means cycling this exploit to write multiple bytes is not possible as it corrupts the previous byte written with the clear. Therefore we need to find a location where only one write operation is required to divert the flow of execution to the win() function address.

Function addresses from disassembly :

`win()` address   = 0x08049770
`main()` address  = 0x08049709

The address for win() is after the stack management code at which what looks like a "NOP slide" can be seen, so anywhere in that series of NOPs was a good enough target.

The address for main() above is the instruction immeditely afder the CALL move_player() (i.e. the return address from move_player()).

Again, using gdb and the Ghidra disassembly to visual the stack frame of move_player() :

            |                        |
0xffffca78  | old EBP                | <--- $EBP
            |------------------------|
0xffffca7c  | return address         |  = `main()+149` : 0x8049709
            |------------------------|
0xffffca80  | param1 - &player_y_pos |
0xffffca84  | param2 - input ch      |
0xffffca88  | param3 - map_buf       |
            |------------------------|
0xffffca8c  |        ?               |
0xffffca90  |------------------------|    
0xffffca90  | main() stack frame     |
            |                        |

So, first off the value of the last byte of the target address we want to replace in our return address from move_player() is 0x70 (or character p), we can use the in game command lp to set our player position title to this value such that it gets written on player move to the map_buf.

Now to calculate the underflow required to hit our return address on the stack from the map_buf.

(return_address = 0xffffca7c) - (map_buf = 0xffffcaa3) = -39 bytes underflow required

Due to the new behaviour of the move_player() function that clears the previous player position, we do not really want to be iteratively moving our character from { 0, 0 } through to an X position of -39, as this would be overwritting values in the stack along the way (including our player_x_pos, player_y_pos local variables).

Therefore, the approach taken was to move the player into a valid X-Position, that satisfies the -39 bytes overflow when Y-Position is moved to -1. Therefore we only ever write the one byte outside of map_buf, our target byte.

map_buf[(player_y_pos * 0x5a) + player_x_pos)] = player_title
map_buf[(-1 * 90) + player_x_pos)] = 0x70 ('p')

player_x_pos = -39 - (-1 * 90) = 51

Also keep in mind the player starts at position { 4, 4 } as initialised within init_player(), so we must keep this in mind when issuing move commands to move into position.

Solution

Plan of attack :

  1. set the player_title character to the last byte of win() return address, using the l command
  2. move player into the target position on the x-axis, from the starting position of 4
  3. move player into position on the y-axis from starting position of 4, underflowing at y_pos = -1

Final exploit :

echo -e $(python3 -c 'print("lp" + "d"*(51-4) + "w"*5)') | ./game

echo -e $(python3 -c 'print("lp" + "d"*(51-4) + "w"*5)') | nc saturn.picoctf.net 52597

Had to run a few times on the remote server to work, possibly due to the process exiting before fully flushing stdout via nc?? not sure.