2022 TQLCTF ezvm

该博客详细分析了一道使用 Unicorn 引擎模拟 shellcode 的 CTF 题目。文章介绍了程序分析,包括 unicorn 函数的作用,如 uc_open、uc_mem_map 和 uc_reg_write。程序通过 open 函数创建自定义 FILE 结构体,并模拟了 read、write 和 close 功能。漏洞在于 strcpy 的 off-by-null,允许攻击者修改堆地址以实现堆块重叠,进而控制程序执行,但由于 open 函数限制,无法直接获取 flag。博主分享了解题思路和难点,以及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这道题使用了unicorn引擎来对输入的shellcode进行模拟,我们可以直接通过unicorn的文档来查看程序中涉及到的函数的作用和调用方式。

程序分析

首先来对程序的整体功能进行一个简要的分析:
在这里插入图片描述其中一些函数的名字经过了修改,其中INIT函数作用是开沙箱
然后读入32位shellcode
接下来是一系列的unicorn引擎中的函数,我们根据unicorn的用户手册简要介绍一下:
uc_open:创建新的Unicorn实例,这里只需要知道是32位x86架构即可
uc_mem_map:从名字和参数也能看出来,开辟空间,当做正常map即可
uc_mem_write:将一段字节码写入指定内存地址
uc_mem_read:从内存中读取字节
uc_reg_read:读取寄存器的值,rsi为寄存器id,rdx为指向保存寄存器值的指针
uc_reg_write:将值写入寄存器,rsi为寄存器id,rdx指向寄存器将被修改成的值的指针
uc_hook_add:注册hook事件的回调,当hook事件被触发将会进行回调

现在再来理解这段代码,其实就是开个沙盒,映射两个内存空间,然后读入一段shellcode,注册一个hook函数,最后执行这段shellcode

我们来看看这段hook函数:
在这里插入图片描述我们可以看到,这段hook函数实现了一个菜单,有四个函数,名字已经改好了。
当我们调用syscall指令的时候,会根据rax来执行不同的函数
然后我们重点关注一下open函数
在这里插入图片描述
uc_reg_read和uc_reg_write的参数识别不出来,可以直接观察汇编代码,这里我们其实可以选择不去过多关注它,其作用我们直接理解成是在识别shellcode并为寄存器赋值,然后关注类似uc_mem_read以及uc_mem_write这类函数的参数即可,这里我在注释中写了uc_mem_read的参数都是什么。

可以看出来,有两个reg_read,意味着open函数在执行的时候我们要设置rdi和rsi两个寄存器的值,通过观察汇编代码可以知道rdi是filename所在地址,rsi是size

我们进入mem_read后的那个函数中接着看
在这里插入图片描述
这个函数创建了一个大小为0x48的结构体,作为自定义的一个简单的FILE结构,其中包含什么东西呢:
在这里插入图片描述这样的一个结构体创建出来之后,程序的功能就非常清晰了,open函数,其实就是指定一个name和一个size,然后创建一个结构体,将name用strcpy函数复制到结构体中的name字段,按顺序分配一个fd,对size以及三个自定义的函数指针赋值。

在创建之前还会比较,如果指定的name已经出现过则直接返回。默认已经有了三个文件,即stdin,stdout和stderr,这三个文件的函数指针是libc中的实际的read,write和close,而剩下的通过open函数创建的file,三个函数的函数指针指向的都是自定义的函数。

而除了open函数之外的三个函数,作用也很简单,就是通过这三个函数指针调用对应的函数,所以这里不展开分析。

这三个自定义的函数其功能也很简单,在实现的时候其实也都是依据真实的read,write来设计的,而close的参数也没变,仍是fd指针,只不过功能变成了释放fd对应file中的chunk。

所以最后我们可以发现,实际上就是给了我们执行shellcode的权限,但是开了沙箱,同时open函数不是真的open服务器上的文件,而是创建一个指定大小的chunk,否则直接orw就结束了hhhh

大致分析清楚了之后我们开始做这道题

准备工作

首先做一些准备工作,由于我们会大量的使用到那四个函数,并且需要写汇编代码,所以提前将他们封装起来以便后续使用

def sysopen(nameaddr, size):
    return '''
    mov rax, 2
    mov rdi, {name}
    mov rsi, {size}
    syscall
    '''.format(name=hex(nameaddr), size=hex(size))

def sysclose(fd):
    return '''
    mov rax, 3
    mov rdi, {fd}
    syscall
    '''.format(fd=fd)

def sysread(fd, addr, size):
    return '''
    mov rax, 0
    mov rdi, {fd}
    mov rsi, {addr}
    mov rdx, {size}
    syscall
    '''.format(fd=fd, addr=hex(addr), size=hex(size))

def syswrite(fd, addr, size):
    return '''
    mov rax, 1
    mov rdi, {fd}
    mov rsi, {addr}
    mov rdx, {size}
    syscall
    '''.format(fd=fd, addr=hex(addr), size=hex(size))

漏洞分析

那么这道题的漏洞出在哪里呢,虽然我们可以执行四个syscall,但是由于open函数不真实,导致无法拿到flag,所以要寻找程序新的漏洞点。
在这里插入图片描述
关注一下这里,可以看到程序使用了strcpy将指定地址的name赋值到file结构体中,虽然说a1限定了最多24字节,但是由于strcpy在赋值的时候会自动在结尾补一个\x00,所以其实当name恰好等于24字节的时候,会造成一个off-by-null,我们来看看name下面存的是什么,如果造成了off-by-null会有什么后果:
在这里插入图片描述
可以看到是堆地址,这就意味着我们可以通过修改堆地址来进行后续一系列操作了,让我们大致的梳理一下思路,首先通过修改堆地址造成堆块重叠,然后修改fd为rwx段上的地址,申请过来,写入orw的shellcode,然后同样的手法将free_hook申请过来,写上shellcode的地址,就结束了。

这里有几个难点,一个是合适的堆块不是很好找,要自己慢慢调,第二个就是不能先打free_hook,因为修改完free_hook之后会立刻free,所以必须先布置好shellcode。

最后提一嘴,学长用的tcg打的,exp比我的简洁很多,属实牛逼。

附上exp:

from re import L
from pwn import *
from ctypes im
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值