一个古老软件工具的逆向重生
2018/7/9
1. 背景
XXX是一个用于收集和分析映像文件信息的小工具,其开发时间大约在2001年,现在不能在WIN7下运行。
但是,当前项目仍需要使用该工具。因此,试图通过逆向分析后使之重生。
XXX的运行环境是win32。
2. 逆向分析
用IDA生成XXX的C程序,基本情况如下:
• 原始编译器是GNU C++。
• 12348物理行C代码。
• 共有252个函数(其中2函数反编译失败),其中绝大部分是C库函数。
• 所有函数复原了原始的符号名称。
• 大部分全局变量复原了原始的符号名称。
• 使用了lex生成词法分析代码,但没有lex的源文件。
总之,该工具的代码不难理解。
3. 修改
3.1. 预处理
用自己开发的IDA_Assistant对IDA生成的C程序进行修剪处理。
3.2. 删除C库函数
开始只删除熟知的库函数,如printf等。后来发现,在main函数后面的都是库函数,就一起删除了。
库函数的全局变量与应用程序的全局变量混在一起,只能经过确认后予以删除。
3.3. 复原lex代码
YY_BUFFER_STATE是lex中的一个重要数据结构,但IDA的反编译结果中没有这个结构,对这个数据结构的操作变成了意义不明的数组操作或指针操作。以下是函数yy_scan_buffer的反编译结果:
_DWORD *__cdecl yy_scan_buffer(int a1, unsigned int a2)
{
_DWORD *v2; // ebx
if ( a2 <= 1 || *(_BYTE *)(a2 + a1 - 2) || *(_BYTE *)(a2 + a1 - 1) )
return 0;
v2 = yy_flex_alloc(0x28u);
if ( !v2 )
yy_fatal_error(8148);
v2[1] = a1;
v2[3] = a2 - 2;
v2[2] = a1;
v2[5] = 0;
*v2 = 0;
v2[4] = a2 - 2;
v2[6] = 0;
v2[7] = 1;
v2[8] = 0;
v2[9] = 0;
yy_switch_to_buffer((int)v2);
return v2;
}
变量v2的类型应是YY_BUFFER_STATE,
对v2的赋值应是对结构分量的赋值,
因此要修改为:
v2->yy_buf_pos = a1;
v2->yy_buf_size = a2 - 2;
v2->yy_ch_buf =a1;
v2->yy_is_our_buffer = 0;
v2->yy_input_file = 0;
v2->yy_n_chars = a2 - 2;
v2->yy_is_interactive = 0;
v2->yy_at_bol = 1;
v2->yy_fill_buffer = 0;
v2->yy_buffer_status = 0;
3.4. 复原lex数据
lex生成的代码中有大量数据,其类型是short或int数据,但IDA把它们都翻译为int变量或int数组。例如:
int yy_chk = 0; // weak
int yy_nxt[40] =
{
2293760,
786452,
//以下略
};
实际应该是:
short int yy_chk[80] = {
0, 0, 0x20, 0x2B, 0x20, 2, 2, 0xD, 0x13, 2, 2, 2, 0x15, 0x16, 0x17, 0x13,
0x18, 0x16, 0xD, 0x15, 0x17, 0x19, 0x1C, 0x18, 0x1A, 0x1B, 0x1D, 0x1E, 0x2A, 0x29, 0x28, 0x1F,
0x27, 0x1C, 0x26, 0x19, 0x1E, 0x1A, 0x1D, 0x1F, 0x1B, 0x24, 0x24, 0x24, 0x2C, 0x25, 0x2C, 0x21,
0x12, 0x11, 0x0F, 0x0E, 0x0C, 0x03, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0xB48D, 0x26, 0x0, 0x8D00, 0x27BC, 0, 0,
};
short int yy_nxt[80] =
{
0, 0x23, 0x14, 0xC, 0x21, 5, 5, 0xE, 0x14, 5, 5, 5, 0x14, 0x14, 0x14, 0x15,
0x14, 0x17, 0x0F, 0x16, 0x18, 0x14, 0x14, 0x19, 0x14, 0x14, 0x14, 0x14, 0xB, 0xA, 9, 0x14,
8, 0x1D, 7, 0x1A, 0x1F, 0x1B, 0x1E, 0x20, 0x1C, 4, 4, 4, 0x12, 6, 0x12, 0x22,
0x14, 0x13, 0x11, 0x10, 0x0D, 0x23, 3, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23,
0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x9090, 0x9090, 0x9090, 0x9090, 0x9090, 0x9090, 0x9090,
};
3.5. 辨别字符串
IDA的反编译结果中有许多如下语句:
yy_fatal_error(8148);
yy_fatal_error(8332);
从函数yy_fatal_error的定义获悉其输入参数应是字符串指针。经分析发现8148、8332实际是内存地址,8148对应16进制1FD4,在list文件中找到:
.text:00001FD4 aOutOfDynamicMe db 'out of dynamic memory in yy_scan_bu'
.text:00001FD4 db 'ffer()',0
.text:00001FFE align 10h
因此要改为:
yy_fatal_error(“out of dynamic memory in yy_scan_buffer()”);
3.6. 推导数据结构
根据XXX中的函数addFunc、getFunc、checkFunc、newFunc等,推导出以下数据结构定义:
typedef struct _func_info
{
int func_addr;
int func_kind;
char *func_name;
struct _func_info *next_func;
} func_info_t;
上述定义中的分量名根据代码中的全局变量名和代码的操作拟定的。例如,程序中有全局变量:
int address
int kind;
这两个变量出现在以下函数调用语句中:
v12 = newFunc(address, kind, v11);
在函数newFunc中address和kind赋给func_info_t的分量,因此可拟定分量名称。
3.7. 修改代码
IDA的反编译结果中有些代码令人费解,有些实际是错的,因此要予以纠正。
例如,反编译中有以下代码:
char byte_1129F[]; // weak
char nm_dir[]; // idb
if ( byte_1129F[strlen(nm_dir)] == 92 )
数组byte_1129F和nm_dir的定义中没有给出尺寸。在list文件中有:
.bss:0001129F ; char byte_1129F[]
.bss:0001129F byte_1129F db ?
.bss:000112A0 public _nm_dir
.bss:000112A0 ; char nm_dir[]
.bss:000112A0 _nm_dir dd ?
根据相邻变量的地址,可推导出byte_1129F有一个字节、nm_dir有272个字节,由此导出那行if语句应是:
if ( nm_dir[strlen(nm_dir)-1] == '\\' )
并且byte_1129F是不存在的。
3.8. 小结
完成上述工作后,源程序物理行数为1300。
然后在VS2010中编译、链接、生成可执行文件。
用这个逆向重生的可执行文件处理样例输入,产生与原始工具相同的输出。
本项任务历时两天,约10小时。