0x00 前言
在逆向工程中,常常会遇到加/解密函数,运气好的话发现是常见算法,找个在线工具直接解就好;运气不好的话就得慢慢厘清算法细节,并尝试写脚本还原算法。之前翻四哥 scz 博客 [1] 的时候发现了 flare-emu 这个模拟执行工具,留了个印象,最近逆向时有个解密字符串的需求,就翻出这个工具试用了一下,体验很棒,这里整理了一些基础用法分享给大家。
0x01 安装与准备工作
安装步骤:
- 安装依赖包 unicorn :
pip install unicorn
- 把 flare-emu 代码仓库 [2] 中的 flare_emu.py、flare_emu_ida.py 以及 flare_emu_hooks.py 三个文件复制到 ida 的 plugins 目录下。
为了便于演示,我写了一个测试程序 [3] ,模拟了恶意软件在内存中解密C2地址的行为。
其中的解密字符串算法如下:
1
2
3
4
5
6
7
8
9
|
char* xor_str(const char* input) {
char key[4] = { '2', '0', '2', '2'};
int input_len = strlen(input);
char* output = (char*) malloc(input_len);
for (int i = 0; i < strlen(input); i++) {
output[i] = input[i] ^ key[i % (sizeof(key) / sizeof(char))];
}
return output;
}
|
在 VS 中编译得到 demo.exe,拖入 ida 中,xor_str 函数地址为:0x00000140001070
0x02 示例 A: 执行一个函数
1
2
3
4
5
6
7
8
9
10
11
12
|
import flare_emu
import hexdump
test_input = "https://b1tg.github.io"
# 初始化
eh = flare_emu.EmuHelper()
# 模拟执行地址 0x00000140001070 处的函数(xor_str),传入字符串作为参数
eh.emulateRange(0x00000140001070, skipCalls=False, registers={'arg1': test_input })
# 获取寄存器rax中的值(返回值)
ret = eh.getRegVal( "rax" )
# 展示内存数据
hexdump.hexdump(eh.getEmuBytes(ret, 0x20 ))
|
注意 skipCalls=False 是必须设置的,因为这个选项默认被置为 True,会导致 xor_str 内部的 malloc 函数不起作用。
Tips: IDA 中执行脚本的方式有两种:按 Alt+F7 弹出对话框选择脚本执行或者直接在底部的 python console 中粘贴代码。
在ida中执行效果如下:
可以验证结果是正确的:
0x03 示例 B: 执行一段代码
有时候你可能并不想执行一个完整的函数,只是想执行一段代码:
这可以通过给函数 emulateRange 传入待选参数 endAddr 做到:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import flare_emu
import hexdump
eh = flare_emu.EmuHelper()
# 传入待选参数 endAddr,和第一个参数共同标识一段地址区间
eh.emulateRange(0x140001124, endAddr=0x140001130, skipCalls=False)
ret = eh.getRegVal( "rax" )
print("==== rax ====")
hexdump.hexdump(eh.getEmuBytes(ret, 0x20 ))
print("=============")
# 提取字符串
print( "%s" %( eh.getEmuString(ret) ))
|
在ida中执行效果如下:
0x04 示例 C: 结合 xref 批量添加注释
解密函数常常被多个地方调用,手动挨个处理就很麻烦:
结合 ida 的 xref 功能,可以批量给引用解密函数的地方打上注释:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import flare_emu
import hexdump
eh = flare_emu.EmuHelper()
for addr in XrefsTo(0x140001070, 0):
addr_call = addr.frm
addr_before = prev_head(addr_call) # 前一个指令
addr_after = next_head(addr_call) # 后一个指令
# 校验前一个指令是在传参,符合 lea rcx, unk_xxx
if print_insn_mnem(addr_before) == "lea" and print_operand(addr_before, 0) == "rcx":
#print("0x{:x} => 0x{:x}".format(addr_before, addr_call))
eh.emulateRange(addr_before, endAddr=addr_after, skipCalls=False)
ret = eh.getRegVal( "rax" )
print( "decrypted at 0x%x: %s" %( addr_call ,eh.getEmuString(ret) ))
# 设置注释
set_cmt(addr_call, "decrypted: " + eh.getEmuString(ret).decode(), 0)
|
在ida中执行效果如下:
0x05 参考