现在混淆的主要目的之一就有让逆向分析人员不清楚函数的调用流程,给你一堆函数,加了高强度的OLLVM,更不能看了。那么Trace跟踪技术就显得很重要的,如果清楚了函数调用流程,那么逐个分析,距离成功不就很快了。
万事开头难,逆向程序难在不知道从哪开始。
前几天做了一道AIS3的题目,内含50个加密函数,加密的流程很简单,关键是对这50个加密函数进行了ollvm控制流平坦化魔改(去除也很简单),主要是想抛砖引玉,锻炼和练习trace的技术。这样在以后遇到高强度的混淆干扰也能有一战的能力。
题目附件如下:
[stateful]
本文的重点在于总结trace技巧,题目本身不算很难。
分析
打开题目,进入main函数
{width="5.75in" height="3.4270833333333335in"}
发现逻辑不是很难,进入state_machine函数
好家伙,一大坨
{width="5.75in" height="3.4166666666666665in"}
尝试使用OBPO插件去除,发现直接卡死。使用D810也是卡死。
更高级的玩法使用Unicorn进行去除,类似deflat
本文的重点是在不去除平坦化的前提下去trace函数调用流程
{width="5.75in" height="3.09375in"}
发现有50多个state函数,并且每个函数的功能很简单,我们的目的是:
trace每一个函数,并在梳理调用流程的过程中,输出关键的加密流程,从而写出解密流程
注意调试的时候,记得传入参数
{width="5.75in" height="2.4895833333333335in"}
Trace
方法一:手动trace
最简单粗暴的方法,对每一个state函数下断点,然后运行程序,逐一拿到调用流程。
如果函数过多,这种方法就不太行了
{width="5.75in" height="2.1770833333333335in"}
最终笔者运行拿到了调用的流程
a1[14] += a1[35] + a1[8];
a1[9] -= a1[2] + a1[22];
*a1 -= a1[18] + a1[31];
a1[2] += a1[11] + a1[8];
a1[6] += a1[10] + a1[41];
a1[14] -= a1[32] + a1[6];
a1[16] += a1[25] + a1[11];
a1[31] += a1[34] + a1[16];
a1[9] += a1[11] + a1[3];
a1[17] += *a1 + a1[7];
a1[5] += a1[40] + a1[4];
a1[37] -= a1[29] + a1[3];
a1[23] += a1[7] + a1[34];
a1[39] -= a1[25] + a1[38];
a1[27] += a1[18] + a1[20];
a1[20] += a1[19] + a1[24];
a1[15] += a1[22] + a1[10];
a1[30] -= a1[33] + a1[8];
a1[1] -= a1[29] + a1[13];
a1[19] += a1[10] + a1[16];
*a1 += a1[33] + a1[16];
a1[36] += a1[11] + a1[15];
a1[24] += a1[20] + a1[5];
a1[7] += a1[21] + *a1;
a1[1] += a1[15] + a1[6];
a1[30] -= a1[13] + a1[2];
a1[1] += a1[16] + a1[40];
a1[31] += a1[1] + a1[16];
a1[32] += a1[5] + a1[25];
a1[13] += a1[25] + a1[28];
a1[7] += a1[10] + *a1;
a1[21] += a1[34] + a1[15];
a1[21] -= a1[13] + a1[42];
a1[18] += a1[29] + a1[15];
a1[4] += a1[7] + a1[25];
*a1 += a1[28] + a1[31];
a1[2] += a1[34] + a1[25];
a1[13] += a1[26] + a1[8];
a1[41] -= a1[3] + a1[34];
a1[37] += a1[27] + a1[18];
a1[4] += a1[27] + a1[25];
a1[23] += a1[30] + a1[39];
a1[18] += a1[26] + a1[31];
a1[10] -= a1[12] + a1[22];
a1[4] += a1[6] + a1[22];
a1[37] += a1[12] + a1[16];
a1[15] += a1[40] + a1[8];
a1[17] += a1[38] + a1[24];
a1[8] += a1[14] + a1[16];
a1[5] += a1[37] + a1[20];
其实手都快残了
方法二:IDA-trace
程序动态调试的时候才可以使用trace功能
{width="5.75in" height="2.40625in"}
{width="5.75in" height="2.8854166666666665in"}
IDA自动进行trace跟踪,然后稍等片刻
{width="5.75in" height="2.6041666666666665in"}
可以发现成功的trace了调用了流程
{width="5.75in" height="2.4375in"}
但是有一点不方便的是,有了调用流程,但是我们还要进入每一个函数,提取加密的流程才行。
IDA快捷键Ctrl+F5可以导出整个程序的伪代码
然后进一步提取和分析
{width="5.75in" height="3.6875in"}
这里可以使用IDA-python自动下断点
Go
import idc
bpt_addr = 0x5599F331ADA7
bpt_size=1
idaapi.add_bpt(bpt_addr,bpt_size)
print("Final")
当然还不够,我们要达到的效果是,触发断点然后输出相关加密信息到output函数窗口,就是有断点回调函数
import idaapi
# 定义回调函数
def my_bpt_callback(bptno):
print("Breakpoint %d hit!" % bptno)
# 添加断点
bpt_addr = 0x5599F331ADA7
bpt_size=1
bpt = idaapi.add_bpt(bpt_addr,bpt_size)
# 设置断点回调
idaapi.add_bpt_chngev_cnd(bpt, idaapi.BPT_EXEC, my_bpt_callback)
#设置执行断点
-----------------------------------------------------------------------
idaapi.BPT_EXEC 表示执行事件
方法三:trace_natives
https://github.com/Pr0214/trace_natives
按照说明,进行输出,发现是这样的效果(IDA中,Edit-Plugins-traceNatives)
{width="5.75in" height="1.125in"}
解密
有了调用流程,剩下的就很简单了
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>
int main() {
unsigned char a1[] =
{
0x0F, 0x77, 0xEC, 0x33, 0x44, 0x16, 0x13, 0x59, 0x1D, 0x42,
0x84, 0x75, 0x5F, 0xE4, 0x83, 0xC0, 0x3B, 0xC1, 0x95, 0xCF,
0xDB, 0x33, 0x6C, 0xD2, 0xED, 0x72, 0x5F, 0x0D, 0x74, 0x41,
0x5B, 0x73, 0xA0, 0x33, 0x53, 0x24, 0x02, 0x59, 0x74, 0x60,
0x33, 0xCC, 0x7D
};
a1[5] -= a1[37] + a1[20];
a1[8] -= a1[14] + a1[16];
a1[17] -= a1[38] + a1[24];
a1[15] -= a1[40] + a1[8];
a1[37] -= a1[12] + a1[16];
a1[4] -= a1[6] + a1[22];
a1[10] += a1[12] + a1[22];
a1[18] -= a1[26] + a1[31];
a1[23] -= a1[30] + a1[39];
a1[4] -= a1[27] + a1[25];
a1[37] -= a1[27] + a1[18];
a1[41] += a1[3] + a1[34];
a1[13] -= a1[26] + a1[8];
a1[2] -= a1[34] + a1[25];
*a1 -= a1[28] + a1[31];
a1[4] -= a1[7] + a1[25];
a1[18] -= a1[29] + a1[15];
a1[21] += a1[13] + a1[42];
a1[21] -= a1[34] + a1[15];
a1[7] -= a1[10] + *a1;
a1[13] -= a1[25] + a1[28];
a1[32] -= a1[5] + a1[25];
a1[31] -= a1[1] + a1[16];
a1[1] -= a1[16] + a1[40];
a1[30] += a1[13] + a1[2];
a1[1] -= a1[15] + a1[6];
a1[7] -= a1[21] + *a1;
a1[24] -= a1[20] + a1[5];
a1[36] -= a1[11] + a1[15];
*a1 -= a1[33] + a1[16];
a1[19] -= a1[10] + a1[16];
a1[1] += a1[29] + a1[13];
a1[30] -= a1[33] + a1[8];
a1[15] -= a1[22] + a1[10];
a1[20] -= a1[19] + a1[24];
a1[27] -= a1[18] + a1[20];
a1[39] += a1[25] + a1[38];
a1[23] -= a1[7] + a1[34];
a1[37] += a1[29] + a1[3];
a1[5] -= a1[40] + a1[4];
a1[17] -= *a1 + a1[7];
a1[9] -= a1[11] + a1[3];
a1[31] -= a1[34] + a1[16];
a1[16] -= a1[25] + a1[11];
a1[14] += a1[32] + a1[6];
a1[6] -= a1[10] + a1[41];
a1[2] -= a1[11] + a1[8];
*a1 += a1[18] + a1[31];
a1[9] += a1[2] + a1[22];
a1[14] -= a1[35] + a1[8];
printf("%s", a1);
return 0;
}
得到flag
AIS3{4re_YOu_@_sTATEfUl_0r_StA03L3S$_ctF3R}