从 2003 年开始,IDA PRO 开始支持调试器功能,这很好的弥补了静态分析的不足。但
在很多情况下,它的功能仍然弱于动态分析。IDA PRO 的调试器目前支持 32 位和 64 位下
MS Windows 可执行程序,它支持在 Windows,Linux,Mac OS x 下进行本地和远程调试。然
而,因为调试器的 API 需要熟悉我们的 SDK 和使用基于事件的模型,这对我们有的用户来
说有些困难和并不容易操作。
· 由于 API 使用了基于事件的模型,这使得它很难用我们常用的方法来设计一个顺序
执行(线性模型)的程序。使用者被强制设计一个事件句柄,来实现一个有限状态
机(插件的核心逻辑)。很多时候这是个强大的方式,但可能对某些简单的任务来
说又过于复杂了。
· 因为 API 只能在插件层使用,一个简单的调试器操作需要写一个插件,这比写一个
简单的 IDC 脚本需要花费更多的时间和精力。
IDA 5.2 针对这两个问题做了改进。以往的基于事件的模型依然可用,同时也可通过使
用 get_debugger_event()这个函数来支持简单的线性模型。这个函数暂停插件(或脚本)
的执行直到一个新的调试器时间发生。使用者可以指定他是只对进程挂起事件感兴趣或是对
所有事件。也可以进行定时设置,如果没有其它事件发生,超时后程序可以继续执行。
新功能允许我们抛弃以往的事件模型(除了在那些事件逻辑优先线性逻辑的那些情况),
通过编写 IDC 脚本来控制调试器。例如,启动调试器,运行到指定位置,输出一些数据和
两次单步运行,如下所示意:
AppBpt(some_address); StartDebugger("","",""); // start debugger with default params GetDebuggerEvent(WFNE_SUSP, -1); // ... and wait for bpt Message ("Stopped at %a, event code is %x\n", GetEventEA(), GetEventId()); StepInto(); // request a single step GetDebuggerEvent(WFNE_SUSP, -1); // ... and wait for app to execute StepInto(); // request a single step GetDebuggerEvent(WFNE_SUSP, -1); // ... and wait for app to execute
在 IDA 5.1 中这需要使用一个事件句柄来处理一个小的有限状态机自动执行,一共需要
超过 200 行的代码。请注意,在上面的例子中,为了清晰,错误处理代码被忽略。在实际
生活中,你应该需要检查一些不希望的情况例如单步 StepInto()之后的发生异常的情况。
为了演示使用新的方式来编写脚本是如何简单,我们重写了 UUNP 解压器插件的核心
功能。原程序需要大概 600 行代码,并有一个复杂的逻辑。新的脚本只需要 100 行的代码
(其中几乎有一半是注释和空行)。更重要的是,脚本易懂并可根据你的需要随意修改。它
展示了 IDA5.2 的新调试器功能的使用。
#include <idc.idc> //-------------------------------------------------------------------------- static main() { auto ea, bptea, tea1, tea2, code, minea, maxea; auto r_esp, r_eip, caller, funcname; // Calculate the target IP range. It is the first segment. // As soon as the EIP register points to this range, we assume that // the unpacker has finished its work. tea1 = FirstSeg(); tea2 = SegEnd(tea1); // Calculate the current module boundaries. Any calls to GetProcAddress // outside of these boundaries will be ignored. minea = MinEA(); maxea = MaxEA(); // Launch the debugger and run until the entry point if ( !RunTo(BeginEA()) ) return Failed(-1); // Wait for the process to stop at the entry point code = GetDebuggerEvent(WFNE_SUSP, -1); if ( code <= 0 ) return Failed(code); // Set a breakpoint at GetProcAddress bptea = LocByName("kernel32_GetProcAddress"); if ( bptea == BADADDR ) return Warning("Could not locate GetProcAddress"); AddBpt(bptea); while ( 1 ) { // resume the execution and wait until the unpacker calls GetProcAddress code = GetDebuggerEvent(WFNE_SUSP|WFNE_CONT, -1); if ( code <= 0 ) return Failed(code); // check the caller, it must be from our module r_esp = GetRegValue("ESP"); caller = Dword(r_esp);
if ( caller < minea || caller >= maxea ) continue; // if the function name passed to GetProcAddress is not in the ignore-list, // then switch to the trace mode funcname = GetString(Dword(r_esp+8), -1, ASCSTR_C); // ignore some api calls because they might be used by the unpacker if ( funcname == "VirtualAlloc" ) continue; if ( funcname == "VirtualFree" ) continue; // A call to GetProcAddress() probably means that the program has been // unpacked in the memory and now is setting up its import table break; } // trace the program in the single step mode until we jump to // the area with the original entry point. DelBpt(bptea); EnableTracing(TRACE_STEP, 1); for ( code = GetDebuggerEvent(WFNE_ANY|WFNE_CONT, -1); // resume code > 0; code = GetDebuggerEvent(WFNE_ANY, -1) ) { r_eip = GetEventEa(); if ( r_eip >= tea1 && r_eip < tea2 ) break; } if ( code <= 0 ) return Failed(code); // as soon as the current ip belongs OEP area, suspend the execution and // inform the user PauseProcess(); code = GetDebuggerEvent(WFNE_SUSP, -1); if ( code <= 0 ) return Failed(code); EnableTracing(TRACE_STEP, 0); // Clean up the disassembly so it looks nicer MakeUnknown(tea1, tea2-tea1, DOUNK_EXPAND|DOUNK_DELNAMES); MakeCode(r_eip);
AutoMark2(tea1, tea2, AU_USED); AutoMark2(tea1, tea2, AU_FINAL); TakeMemorySnapshot(1); MakeName(r_eip, "real_start"); Warning("Successfully traced to the completion of the unpacker code\n" "Please rebuild the import table using renimp.idc\n" "before stopping the debugger"); } //-------------------------------------------------------------------------- // Print an failure message static Failed(code) { Warning("Failed to unpack the file, sorry (code %d)", code); return 0; }