标题:使用含通用PE脱壳插件的IDA4.9专业版脱壳被压缩的可执行体
(原文自数据救援2005http://www.datarescue.com/idabase/unpack_pe/unpacking.pdf)
使用含通用PE脱壳插件的IDA4.9专业版脱壳被压缩的可执行体(C)数据拯救2005
从4.9 版开始,IDA专业版就带有通用PE脱壳插件,其源代码可在IDA专业版软件开发工具包内使用。这个指南将说明在实践中如何使用该插件,并将简要描述它在内部如何工作。
被压缩的应用程序
如果我们执行范例程序,这是出现在屏幕上的画面。
完全合法,但是不管怎样,如果我们在IDA专业版里打开这个可执行体,会出现下面的警告:
IDA探测到不寻常输入段,并告诉我们文件可能被加壳。
如果我们看了输入窗口就会观察到:
我们的程序仅仅从KERNEL32.DLL输入了三个函数。我们可以意识到GetProcAddress()动态链接库的一个常见函数LoadLibrary ()更多可能将是被脱壳引擎用来重建原始可执行体的输入。
使用通用PE脱壳插件
现在我们通过插件子菜单开始脱壳
出现插件选项对话框:
在这个对话框,我们可以调整地址范围,一旦到达,将导致调试器把程序执行挂起, 也有可能详细说明一个文件未加壳的资源存储在哪里。点击OK后,插件开始运行程序,在到达先前定义范围的内部地址前将会自我脱壳。接着出现下一个对话框指示脱壳结束,并提供结果的存储器快照。
注意在脱壳过程中到达的两个断点。后面我们要作更多说明。
为了重建程序的原始输入部,插件程序创建了新段。
一旦脱壳,我们就可以看到更为典型的start()函数结构
不管怎样,我们再试着改进以获得尽可能最好的反汇编。
申请签名
如果在程序脱壳后我们看到发现的串,就会明白程序是用Visual C++编译器编译的。 我们来申请关联FLIRT(Fast Library Identification and Recognition Technology)库签名
最终的反汇编列表如下:
更好一些,是不是?
幕后
我们现在来深入了解在实践中如何使用SDK 调试程序API函数以实现脱壳。
主要想法是开启一个进程针对于由调试程序引起的各种事件做出恰当的反应,直到我们能够确定程序被正确脱壳。所以首先我们设置一个句柄来接收调试程序事件,并启动一个进程直至其入口点:
if ( !hook_to_notification_point(HT_DBG, callback, NULL) )
{
warning("Could not hook to notification point\n");
return;
}
// Let's start the debugger 开始调试
if ( !run_to(inf.beginEA) )
{
warning("Sorry, could not start the process");
unhook_from_notification_point(HT_DBG, callback, NULL);
}
事件将被送到我们的通知句柄,定义如下:
static int idaapi callback(void * /*user_data*/,
int notification_code,
va_list va)
{
switch ( notification_code )
{
case dbg_process_start:
...
case dbg_library_load:
...
case dbg_run_to:
...
case dbg_bpt:
...
case dbg_trace:
...
case dbg_process_exit:
...
...
}
return 0;
}
当我们通过调用到run_to()开启进程,我们会收到相应的dbg_run_to事件,指示run_to()已被正确执行。我们现在在被压缩程序的入口点,并把断点设置在GetProcAddress()动态链接库函数上(假设脱壳引擎在重新创建原始应用程序的原始输入表前已终止工作。)
case dbg_run_to: // Parameters: thread_id_t tid
dbg->stopped_at_debug_event(true);
gpa = get_name_ea(BADADDR, "kernel32_GetProcAddress");
...
else if( !add_bpt(gpa) )
{
bring_debugger_to_front();
warning("Sorry, can not set bpt to kernel32.GetProcAddress");
goto FORCE_STOP;
}
else
{
++stage;
set_wait_box("Waiting for a call to GetProcAddress()");
}
continue_process();
break;
在到达我们设GetProcAddress()断点时,我们会收到一个dbg_bpt事件。我们可以从堆栈中提取返回地址,删除第一个断点,并在返回地址上设置第二个断点,以便一旦GetProcAddress()函数返回时可以被通知到。
case dbg_bpt: // A user defined breakpoint was reached.
// Parameters: thread_id_t tid
// ea_t breakpoint_ea
{
/*tid_t tid =*/ va_arg(va, tid_t);
ea_t ea = va_arg(va, ea_t);
...
if ( ea == gpa )
{
regval_t rv;
if ( get_reg_val("esp", &rv) )
{
ea_t esp = rv.ival;
invalidate_dbgmem_contents(esp, 1024);
ea_t ret = get_long(esp);
...
if ( !del_bpt(gpa) || !add_bpt(ret) )
error("Can not modify breakpoint");
你还记得在脱壳中我们看到的发生在地址0x7C80AC28 和0x00040C68D的两个断点信息吗?第一个是我们的GetProcAddress()断点,第二个是我们在返回地址下的断点。在接下来的反汇编里,你可以看到通向我们GetProcAddress()断点的调用。我们现在只需执行指令直到脱壳引擎重建程序的原始寄存器内容,然后跳转到脱壳程序的真实入口地址。
目前步进跟踪是在我们到达先前定义地址前的最简单的方法。所以我们只要到达第二个断点时就开启步进跟踪
del_bpt (ea);
if ( !is_library_entry(ea) )
{
deb(IDA_DEBUG_PLUGIN, "%a: reached unpacker code, switching to trace mode\n",
ea);
enable_step_trace(true);
...
set_wait_box("Waiting for the unpacker to finish");
}
else
{
warning("%a: bpt in library code", ea); // how can it be?
add_bpt(gpa);
}
对每一步指令我们要检查地址是否匹配先前定义的范围。如果到达界限,停止跟踪,从新分析脱壳后的代码,调整入口点,重新建立输入表,保存资源,然后-----最后拍摄快照。
case dbg_trace: // A step occured (one instruction was executed). This event
// notification is only generated if step tracing is enabled.
// Parameter: none
..
/*tid_t tid =*/ va_arg(va, tid_t);
ea_t ip = va_arg(va, ea_t);
if ( oep_area.contains(ip) )
{
// stop the trace mode
enable_step_trace(false);
// reanalyze the unpacked code
set_wait_box("Reanalyzing the unpacked code");
do_unknown_range(oep_area.startEA, oep_area.endEA, false);
auto_make_code(ip);
noUsed(oep_area.startEA, oep_area.endEA);
auto_mark_range(oep_area.startEA, oep_area.endEA, AU_FINAL);
// mark the program's entry point
move_entry(ip);
set_wait_box();
...
set_wait_box("Recreating the import table");
invalidate_dbgmem_config();
...
create_impdir();
set_wait_box("Storing resources to 'resource.res'");
if ( resfile[0] != '\0' )
extract_resource(resfile);
set_wait_box();
if ( take_memory_snapshot(true) )
goto FORCE_STOP;
用户在他的IDA数据库获得存储器信息转储后,就可以像平常一样开始分析脱壳后的代码了。
不要犹豫,在SDK里面进一步看看源代码以获得所有执行的详细资料。
IDA 4.9专业版脱壳教程
阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开
发者可享99元/年,续费同价!