题目是一道 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