从 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;
}