写几句PhantOm1.20学习笔记


PhantOm是我见过的唯一能过ExeCryptor的插件,听fly的意思还有,估计是
h,s,f等人揣着不拿出来。

驱动没有什么特殊的,就不说了,有用的东西在ring3,正好近来对改OD有兴趣,
学习学习写插件,不过我没有看全。

有段Patch被调试进程PEB内_RTL_USER_PROCESS_PARAMETERS内一些数据,在
0040DF74,这我没有做过,但对ExeCryptor不重要。

主要问题是ExeCryptor使用DRx解码,以前xDREAM提到过OD会破坏CONTEXT内
的调试寄存器,如果Hook掉OD的WaitForDebugEvent,ContinueDebugEvent,
读出DRx比较一下,可以发现确实不同了.PhantOm的处理是这样的:

Patch OD调用插件ODBG_Pluginmainloop的代码:


.text:0043966A                   loc_43966A: 
.text:0043966A 68 14 57 4D 00       push    offset DebugEvent
.text:0043966F E8 D8 D4 05 00       call    sub_496B4C    

把push改成了JMP,跳到自己补的一段代码:

ATA:00412554 PhantOmCodes:    
DATA:00412554                 nop
DATA:00412555                 nop
DATA:00412556                 mov     eax, ds:4D5714h
DATA:0041255B                 cmp     eax, 8
DATA:0041255E                 jz      short __PassThrough
DATA:00412560                 mov     eax, ds:4D5720h
DATA:00412565                 cmp     eax, EXCEPTION_ACCESS_VIOLATION
DATA:0041256A                 jz      short __PassThrough
DATA:0041256C                 cmp     eax, 0C000001Eh
DATA:00412571                 jz      short __PassThrough
DATA:00412573                 cmp     eax, EXCEPTION_GUARD_PAGE
DATA:00412578                 jz      short __PassThrough
DATA:0041257A                 cmp     eax, EXCEPTION_ILLEGAL_INSTRUCTION
DATA:0041257F                 jz      short __PassThrough
DATA:00412581                 cmp     eax, EXCEPTION_INT_DIVIDE_BY_ZERO
DATA:00412586                 jz      short __PassThrough
DATA:00412588                 nop
DATA:00412589                 push    4D5714h
DATA:0041258E                 jmp     near ptr 39C4E7h ;  jmp 0043966F
DATA:00412593 ; ---------------------------------------------------------------------------
DATA:00412593
DATA:00412593 __PassThrough:                          ; CODE XREF: DATA:0041255Ej
DATA:00412593                                         ; DATA:0041256Aj
DATA:00412593                                         ; DATA:00412571j
DATA:00412593                                         ; DATA:00412578j
DATA:00412593                                         ; DATA:0041257Fj
DATA:00412593                                         ; DATA:00412586j
DATA:00412593                 mov     ebx, DBG_EXCEPTION_NOT_HANDLED
DATA:00412598                 push    ebx
DATA:00412599                 mov     eax, ds:4D571Ch
DATA:0041259E                 push    eax
DATA:0041259F                 mov     edx, ds:4D5718h
DATA:004125A5                 push    edx
DATA:004125A6                 call    dword ptr ds:50D2B8h ; ContinueDebugEvent
DATA:004125AC                 jmp     near ptr 39BEF4h ; jmp 0043907C

如果是一些特殊的调试事件或异常,则直接调用ContinueDebugEvent,不让OD处理了,
否则跳回到43966F。比较奇怪的是最后那句跳到0043907C,不大明白,为什么是这里:

.text:00439077                 call    j_GetTickCount
.text:0043907C                 mov     [ebp+var_34], eax
.text:0043907F                 cmp     stream, 0
.text:00439086                 jnz     short loc_439091
.text:00439088                 cmp     dword_4D9E40, 0
.text:0043908F                 jz      short loc_4390D4

不过这样做是有问题的,我一边看一边整理自己的代码,改成插件形式,如果这样Patch,会导致
当被调试程序(特别是加过壳的)处于运行状态时直接关OD,OD会崩溃。没有深究原因,不过写成
直接Hook掉OD的WaitForDebugEvent不存在这个问题,调用ContinueDebugEvent后返回FALSE就行。

另外,我觉得PhantOm这样处理不是很好,一是可能需要添加更多的异常判断,另外OD要用的调试
异常如int3,单步等不好处理,所以想换个方式。

OD里面的断点实现,包括把代码起始字节替换为CC,使用调试寄存器,设置EFLAGS的TF,及设置内
存页为PAGE_NOACCESS,PAGE_GUARD等,我们可以试试把在OD里的操作造成的调试异常与被调试程
序故意造成的异常区分开来,这样只要不是OD的都Pass。写了几个函数:

1. 测试EXCEPTION_BREAKPOINT是否由于在OD下断所致:

BOOL IsMyInt3BP(DWORD addr)
{
  //addr是否存在激活int3断点

  t_table *bptable = 0;
  t_bpoint *bpoint = 0;

  bptable = (t_table *)_Plugingetvalue(VAL_BREAKPOINTS);
  if(bptable) 
  {
    bpoint=(t_bpoint *)_Findsorteddata(&(bptable->data), addr);
    
    if((bpoint) && (bpoint->type & (~TY_DISABLED)))
    {
      return TRUE;
    }
    else
      return FALSE;
  }
  else
  {
    _Addtolist(0, 1, "Failed to get bptable");
    return FALSE;
  }
}


2. 测试EXCEPTION_SINGLE_STEP是否由于下硬件断点或设置TF位所致:

BOOL IsMyHardwareBP(DWORD dwThreadId)
{
  //测试当前单步异常是否为Olly的操作引起,PDK未提供函数访问硬件断点

  /* Olly保存硬件断点的地址通过_Sethardwarebreakpoint可以找到
  004D8D70     004112E7    00000001    00000001    00000000
  004D8D80     00000000    00000000    00000000    004112E8 ...
  */

  BOOL bFound = FALSE; 
  CONTEXT ctx;
  HANDLE hThread = 0;
  DR6 iDr6;
  DR7 iDr7;
  t_thread* descriptor = 0;
  t_hardbpoint *hdbptable = (t_hardbpoint*)0x4D8D70;


  memset(&ctx, 0, sizeof(ctx));
  hThread = g_OpenThread(THREAD_GET_CONTEXT, FALSE, dwThreadId);

  if(hThread)
  {
    ctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_DEBUG_REGISTERS;
    
    if(GetThreadContext(hThread, &ctx))
    {
      iDr6.data = ctx.Dr6;
      iDr7.data = ctx.Dr7;

      //是否由Dr0-Dr3引起调试故障/陷阱
      if(((iDr7.u.L0 == 1) || (iDr7.u.G0 == 1)) && 
        (iDr6.u.B0 == 1) &&
        (hdbptable[0].addr == ctx.Dr0))
      {
        //_Addtolist(0, 1, "Trigger by Dr0=%08X", hdbptable[0].addr);
        bFound = TRUE;
      }
      else 
      if(((iDr7.u.L1 == 1) || (iDr7.u.G1 == 1)) && 
          (iDr6.u.B1 == 1) &&
          (hdbptable[1].addr == ctx.Dr1))
      {
        //_Addtolist(0, 1, "Trigger by Dr1=%08X", hdbptable[1].addr);
        bFound = TRUE;
      }
      else 
      if(((iDr7.u.L2 == 1) || (iDr7.u.G2 == 1)) && 
          (iDr6.u.B2 == 1) &&
          (hdbptable[2].addr == ctx.Dr2))
      {
        //_Addtolist(0, 1, "Trigger by Dr2=%08X", hdbptable[2].addr);
        bFound = TRUE;
      }
      else 
      if(((iDr7.u.L3 == 1) || (iDr7.u.G3 == 1)) && 
        (iDr6.u.B3 == 1) &&
        (hdbptable[3].addr == ctx.Dr3))
      {
        //_Addtolist(0, 1, "Trigger by Dr3=%08X", hdbptable[3].addr);
        bFound = TRUE;
      }
      else
      if(descriptor = _Findthread(dwThreadId))
      {  
        //若线程处于Olly置的单步方式(TF),返回TRUE
        if(descriptor->reg.singlestep & 1)
        {
          //_Addtolist(0, 1, "singlestep is true");
          bFound = TRUE;
        }
        else
        {
          //_Addtolist(0, 1, "not my fault ;-)");
        }
      }

      
      //不检测iDr6.BD,如果因DR7.GD置位,访问DRx引发调试故障,
      //应该在驱动处理,用户代码永远不会直接访问DRx
    }

    CloseHandle(hThread);
  }

  return bFound;
}


3. 测试EXCEPTION_ACCESS_VIOLATION和STATUS_GUARD_PAGE_VIOLATION是否因为在OD
   下内存访问断点所致:

  BOOL IsMyMemoryBP(DWORD addr)
{
  //测试addr是否落在Olly内存访问断点页面范围

  DWORD dwBeginPage = *(PDWORD)0x4D8144;
  DWORD dwEndPage = *(PDWORD)0x4D8148;

  if(dwBeginPage == 0) //未设置内存断点
    return FALSE;
  else if(addr < dwBeginPage)
    return FALSE;
  else if(addr > (dwEndPage + 0x1000))
    return FALSE;
  else
    return TRUE;
}

在4D8144前面一点就是内存断点的精确地址,但不能用这个来判断,否则访问内存页内其他
地址的异常没人处理,被调试程序崩溃了。注意这个函数的参数应该是:
lpDebugEvent->u.Exception.ExceptionRecord.ExceptionInformation[1]

不感兴趣的的调试事件直接返给debuggee(我还动了点别的;-)。用到的2个联合如下:

//调试寄存器DR6,DR7

typedef union _DR6
{
  struct
  {
    unsigned B0      : 1;  // b0
    unsigned B1      : 1;  // b1
    unsigned B2      : 1;  // b2
    unsigned B3      : 1;  // b3
    unsigned unused1  : 9;
    unsigned BD      : 1;  // b13
    unsigned BS      : 1;  // b14
    unsigned BT      : 1;  // b15
    unsigned unused2  : 16;
  }u;
  
  DWORD data;
} DR6;


typedef union _DR7
{
  struct
  {
    unsigned L0      : 1;  // b0
    unsigned G0      : 1;  // b1
    unsigned L1      : 1;  // b2
    unsigned G1      : 1;  // b3
    unsigned L2      : 1;  // b4
    unsigned G2      : 1;  // b5
    unsigned L3      : 1;  // b6
    unsigned G3      : 1;  // b7
    unsigned LE      : 1;
    unsigned GE      : 1;
    unsigned unused1  : 3;
    unsigned GD      : 1;  // b13
    unsigned unused2  : 2;
    unsigned RW0    : 2;  // b16-17
    unsigned Len0    : 2;  // b18-19
    unsigned RW1    : 2;  // b20-21
    unsigned Len1    : 2;  // b22-23
    unsigned RW2    : 2;  // b24-25
    unsigned Len2    : 2;  // b26-27
    unsigned RW3    : 2;  // b28-29
    unsigned Len3    : 2;  // b30-31
  } u;
  
  DWORD data;
} DR7;

不过这样做有个缺点,那些需要拦截异常来获取介入点的脚本不能用,嗯这只是,
Just a game,或许可以给用户提供交互式的设置。

近来做什么都没有耐心,文章写得前言不搭后语,见谅。