0x00 前言

在逆向工程中,常常会遇到加/解密函数,运气好的话发现是常见算法,找个在线工具直接解就好;运气不好的话就得慢慢厘清算法细节,并尝试写脚本还原算法。之前翻四哥 scz 博客 [1] 的时候发现了 flare-emu 这个模拟执行工具,留了个印象,最近逆向时有个解密字符串的需求,就翻出这个工具试用了一下,体验很棒,这里整理了一些基础用法分享给大家。

0x01 安装与准备工作

安装步骤:

  1. 安装依赖包 unicorn :pip install unicorn
  2. 把 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

image-20220122225628124

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中执行效果如下:

image-20220122205441730

可以验证结果是正确的:

image-20220122205921587

0x03 示例 B: 执行一段代码

有时候你可能并不想执行一个完整的函数,只是想执行一段代码:

image-20220122211621046

这可以通过给函数 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中执行效果如下:

image-20220122211919491

0x04 示例 C: 结合 xref 批量添加注释

解密函数常常被多个地方调用,手动挨个处理就很麻烦:

image-20220122230240604

结合 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中执行效果如下:

image-20220122223553380

image-20220122230538854

0x05 参考