Category - CISCN2021

程序分析

题目是一道 LLVM 题,给出opt程序、SAPass.soquickstart.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)。

title

指令名称有:savetakeawaystealkeyfakekeyrun

1. save

原型:void save(char* a, char* b){};

malloc分配一块大小为0x18的内存,将内存地址储存于CALL_ADDRESS变量中,然后将 a 和 b 字符串分别复制到CALL_ADDRESSCALL_ADDRESS+8指向的内存位置上。

title

2. takeaway

忽略。似乎是对比用户输入与CALL_ADDRESS指向的内存数据是否一致。

3. stealkey

原型:void stealkey(){};

CALL_ADDRESS指向地址的前 8 字节数据复制到KEY中。

title

4. fakekey

原型:void fakek

程序分析

程序架构 aarch64,GLIBC 版本 2.31。题目附件提供一个静态编译版本的 qemu(qemu-aarch64-static)。

Register

使用malloc分配一个CHANNEL结构体,输入 0x100 字节数据作为它的key。创建后,插入HEAD全局变量(qword_12018)指向的链表首部。

  1. struct CHANNEL
  2. {
  3. char key[256];
  4. struct CHANNEL *next_channel;
  5. struct NODE *next_node;
  6. };

Write

主要是创建并向CHANNEL添加NODE结构体。

首先根据输入的key,从HEAD指向的链表搜索
CHANNEL;找到对应的CHANNEL后,用malloc分配0< size <= 0x200的堆内存,编辑堆内存,最后创建NODE结构体并将其添加到CHANNELnext_node指针指向的链表尾部。

  1. struct NODE
  2. {
  3. char *ptr;
  4. struct NODE *next;
  5. };

Read

没什么用。主要是打印CHANNELNODE链表第一个元素,然后将元素从链表移除。

UnResiger

HEAD链表移除CHANNEL

根据输入的key

  1. 若链表只有一个CHANNELkey相符,用free释放HEAD指向的CHANNEL并清空HEAD
  2. 否则,若keyHEAD指向的CHANNEL相符,将HEAD指向下一个CHANNEL,然后释放原来的CHANNEL
  3. 否则,遍历HEAD链表。若key与某个next_channel指针指向的CHANNEL相符,用free释放这个CHANNEL

BUG

UnResiger 遍历HEAD链表时,没有将释放后的next_channel指针清空,导致 use-after-free 或者 double-free

题目限制比较多:

  1. NODE只能在分配的时候编辑一次,不能用free释放。
  2. 不能直接操纵HEAD链表上面的指针地址。
  3. 不能泄露任何地址(当然没有必要泄露)。

比赛期间我没有解出这道题。后来看了师兄的 EX