题目是一道 LLVM 题,给出opt
程序、SAPass.so
和quickstart.txt
。GLIBC 版本 2.27。
LLVM 是一个编译器套件项目,用来开发编译器前端和后端,我们常用的 gcc 就是一种编译器。LLVM 的核心是 IR 中间语言:它将源代码编译成机器代码时,首先将源代码翻译为对应的 IR 代码,然后再将 IR 代码转换成目标机器代码。
opt
是 LLVM 提供的 IR 代码分析与优化工具。用户可以利用 LLVM API 编写一种名为Pass
的动态库。使用opt
运行Pass
后,可以遍历、增加、修改或删除 IR 代码中的各种元素(例如函数、变量、调用参数等)。
题目中的SAPass.so
就是一种FunctionPass
,用于遍历 IR 代码中的所有函数。
SAPass.so
的核心代码位于sub_19D0
函数。其他代码主要用于初始化Pass
,与题目无关。
opt
遍历 IR Bytecode 中所有调用的函数,然后依次传递给sub_19D0
函数。
sub_19D0
函数的逻辑流程是:首先检查函数名称是否为B4ckDo0r
;若是,根据在B4ckDo0r
函数里面调用的特定函数,执行一些指令来控制两个全局变量的内容:qword_2040F8
(这里命名为CALL_ADDRESS
)和unk_204100
(命名为KEY
)。
指令名称有:save
、takeaway
、stealkey
、fakekey
和run
。
原型:void save(char* a, char* b){};
用malloc
分配一块大小为0x18
的内存,将内存地址储存于CALL_ADDRESS
变量中,然后将 a 和 b 字符串分别复制到CALL_ADDRESS
、CALL_ADDRESS+8
指向的内存位置上。
忽略。似乎是对比用户输入与CALL_ADDRESS
指向的内存数据是否一致。
原型:void stealkey(){};
将CALL_ADDRESS
指向地址的前 8 字节数据复制到KEY
中。
原型:void fakek
程序架构 aarch64,GLIBC 版本 2.31。题目附件提供一个静态编译版本的 qemu(qemu-aarch64-static
)。
使用malloc
分配一个CHANNEL
结构体,输入 0x100 字节数据作为它的key
。创建后,插入HEAD
全局变量(qword_12018
)指向的链表首部。
struct CHANNEL
{
char key[256];
struct CHANNEL *next_channel;
struct NODE *next_node;
};
主要是创建并向CHANNEL
添加NODE
结构体。
首先根据输入的key
,从HEAD
指向的链表搜索
CHANNEL
;找到对应的CHANNEL
后,用malloc
分配0< size <= 0x200
的堆内存,编辑堆内存,最后创建NODE
结构体并将其添加到CHANNEL
的next_node
指针指向的链表尾部。
struct NODE
{
char *ptr;
struct NODE *next;
};
没什么用。主要是打印CHANNEL
的NODE
链表第一个元素,然后将元素从链表移除。
从HEAD
链表移除CHANNEL
。
根据输入的key
:
CHANNEL
且key
相符,用free
释放HEAD
指向的CHANNEL
并清空HEAD
。key
与HEAD
指向的CHANNEL
相符,将HEAD
指向下一个CHANNEL
,然后释放原来的CHANNEL
。HEAD
链表。若key
与某个next_channel
指针指向的CHANNEL
相符,用free
释放这个CHANNEL
。UnResiger 遍历HEAD
链表时,没有将释放后的next_channel
指针清空,导致 use-after-free 或者 double-free。
题目限制比较多:
NODE
只能在分配的时候编辑一次,不能用free
释放。HEAD
链表上面的指针地址。比赛期间我没有解出这道题。后来看了师兄的 EX