IDA 教程-脚本化的调试器

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




 

你可能感兴趣的:(IDA 教程-脚本化的调试器)