WUSTCTF2020-funnyre

分析

elf文件,__libc_start_main函数启动 main 函数,init 函数中执行了三个函数,但对我们要求解的flag无影响,影响的是byte_603049,对应main函数中最后的 puts(s) 中的 s 变量。(废话咯)

直接来看 main 函数,发现存在花指令,如下图所示:

第一处很明显,两个跳转都几乎是零距离跳转且第二个指令永假(根本不会执行),nop掉这两个指令。然后是第二处的call指令,跳转地址很明显是错误的,将其转成data,将call的硬编码(E8)改成90(nop),然后将data转成code(一直转,直到没有代码区没有数据)。最终结果如下图所示:

花指令有五处,但都一样。修改完后,main函数如下图所示:

首先判断flag的格式和长度,然后一堆for循环进行异或、加、取反运算,最后与已知字符串相比较。

整个过程杂而不难。有两种比较推荐的方法。

IDAPython根据指令去逆向

第一种是写代码识别指令来进行响应的逆操作,指令中的操作数都用统一的格式,即byte ptr [rdx + rax +5]

我们要做的就是在main函数范围内一行一行的读取汇编指令,通过byte ptr [rdx + rax +5](因为这部分存储了flag)找到对flag进行加密的指令,然后识别操作码以区分不同运算,最后取出另一个操作数进行逆运算。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def _add(cipher, k):
return [(each - k)&0xff for each in cipher]#&0xff 防止 byte 溢出造成错误
def _xor(cipher, k):
return [each ^ k for each in cipher]
def _not(cipher):
return [~each for each in cipher]

cipher = [0xD9, 0x2C, 0x27, 0xD6, 0xD8, 0x2A, 0xDA, 0x2D, 0xD7, 0x2C, 0xDC, 0xE1, 0xDB, 0x2C, 0xD9, 0xDD, 0x27, 0x2D, 0x2A, 0xDC, 0xDB, 0x2C, 0xE1, 0x29, 0xDA, 0xDA, 0x2C, 0xDA, 0x2A, 0xD9, 0x29, 0x2A]
end = 0x401DA9
start = 0x400605
i = PrevHead(end)
while i > start:
if GetMnem(i) == 'xor' and GetOpnd(i, 0) == "byte ptr [rdx+rax+5]":
k = int(GetOpnd(i, 1).rstrip('h'), 16)
cipher = _xor(cipher, k)
if GetMnem(i) == 'add' and GetOpnd(i, 0) == "byte ptr [rdx+rax+5]":
k = int(GetOpnd(i, 1).rstrip('h'), 16)
cipher = _add(cipher, k)
if GetMnem(i) == 'not' and GetOpnd(i, 0) == "byte ptr [rdx+rax+5]":
cipher = _not(cipher)
i = PrevHead(i)

print(''.join(chr(i) for i in cipher))
# PrevHead(addr)用于获取指定地址之前一条指令的起始地址
# GetMnem(addr) 获取地址i处的操作指令
# GetOpnd(addr, n)获取某地址处的操作数(第一个参数是地址,第二个是操作数索引)

然后再IDA界面中的菜单栏 File ->Script file,执行该py文件。如果识别不出代码中用到的函数,请修改IDA所在目录下的\cfg\idapython.cfg文件,将AUTOIMPORT_COMPAT_IDA695的值修改为YES。(参考关于IDA7.5 IDApython api差异问题及解决办法 - 『脱壳破解区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

其他指令参考:总结idapython在逆向中的应用 - 『脱壳破解区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

符号执行工具约束求解

第二种是使用符号执行工具约束求解,简单来讲就是符号化待求解变量,并将这些加密过程等价为方程,然后解这些方程组。

linux下需要用到python的angr库,同时需要配置python虚拟环境。虚拟环境安装的指令如下:

1
2
3
4
5
6
7
8
//安装venv,venv是python管理虚拟环境的推荐工具
sudo apt install python3-venv
//在当前目录下创建一个虚拟环境的文件夹
python3 -m venv autoblue-env(文件名)
//激活虚拟环境
source autoblue-env/bin/activate
//退出虚拟环境
deactivate

在虚拟环境中下载angr即可正常使用。(参考[环境搭建][Python]Kali中使用venv - 简书 (jianshu.com)

然后是脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import angr
import claripy
# 加载二进制文件
p = angr.Project("./attachment", load_options={"auto_load_libs": False})
# 获取factory接口,该接口提供重要分析对象,如 blocks(程序基本块) / state(实例镜像,模拟执行某个时刻的状态) / SimulationManager(管理 state,执行 运行、模拟等操作) 等
f = p.factory
# 模拟eip=0x400605时刻的状态
state = f.entry_state(addr=0x400605)
# 创建符号变量
flag = claripy.BVS("flag", 8*32)
# 将符号变量存放到指定地址中
state.memory.store(0x603055+0x300+5, flag)
# 指定所需使用到的寄存器的值。因为byte ptr [rdx + rax +5]指向flag,所以指定rdx和rax
state.regs.rdx = 0x603055+0x300
state.regs.rdi = 0x603055+0x300+5
# 创建SM(Simulation Managers) 用于管理 state,执行 运行、模拟等操作
sm = p.factory.simulation_manager(state)

print("[+] init ok")
# 调用 explore 方法,探索执行路径,可以设置 find 和 avoid 参数,以便找到符合我们预期的路径。
# 0x401DAE在程序中是puts(s)处,是我们所期望能执行到的路径。
sm.explore(find=0x401DAE)
if sm.found:
print("[+] found!")
x = sm.found[0].solver.eval(flag, cast_to=bytes)#字节为单位转换
print(x)

angr中更多方法请参考angr 系列教程(一)核心概念及模块解读 - 先知社区 (aliyun.com)

运行结果如下:


WUSTCTF2020-funnyre
http://example.com/2023/09/04/CTF/WUSTCTF2020-funnyre/
作者
gla2xy
发布于
2023年9月4日
许可协议