Themida的简单脱壳


很久没有写过文章了,看到还有人问Themida脱壳,简单写几句。不涉及VM。
脱的是NP1012的GameMon,Ring0玩不过,郁闷,更郁闷的是眼睛吃不消。

1. Dump点搜索:

   以前的版本,挂在用CreateFile加载的kernel32!Sleep,就很容易找到dump
   位置,这个似乎不同了,也不知道是什么版本.

   先随便找个地方下断,比如对第1个区段下写入断点,目的是等VM所在内存分
   配,然后在VM内搜索出口:

   popad
   popfd
   ret

   对找到的所有结果下断,写个脚本记录一下调用过程:

   var  counter
   var  ret_addr

   mov  counter,0
   eob  l_record
   eoe  l_record
   run

l_record:
   mov ret_addr,[esp]  
   log ret_addr

   esto

   结果:

........
00DDCBD3   Breakpoint at 00DDCBD3
           ret_addr = 0088A795
00E42EF0   Breakpoint at 00E42EF0
           ret_addr = 0088B084
00E4F835   Breakpoint at 00E4F835
           ret_addr = 0088BAA2 <---- 这个
00E42EF0   Breakpoint at 00E42EF0
           ret_addr = 02FA1A7E
00E4F835   Breakpoint at 00E4F835
           ret_addr = 0046228E
00DDCBD3   Breakpoint at 00DDCBD3
           ret_addr = 00402750
00E4F835   Breakpoint at 00E4F835
           ret_addr = 00460327
00E42EF0   Breakpoint at 00E42EF0
           ret_addr = 00402790
00E5D56F   Breakpoint at 00E5D56F
           ret_addr = 00410D1E
           Thread 000005E4 terminated, exit code 0
           Thread 00000184 terminated, exit code 0
           Thread 000003DC terminated, exit code 0
   
  最后几项的返回地址在原程序了,还有个02FA1A7E是模仿的GetVersion。
  Ctrl-F2重来,在0088BAA2下断,还有一两次SMC解码(大概有,记不清了),
  很容易找到dump点:

0088C432   /0F86 01000000    jbe GameMon.0088C439
0088C438   |F5               cmc
0088C439   \9D               popfd
0088C43A    C3               retn
   
-> 这里:

007D4BF3    68 54235132      push 32512354
007D4BF8  ^ E9 B9C6FFFF      jmp GameMon.007D12B6
007D4BFD    68 C4711C55      push 551C71C4
007D4C02  ^ E9 AFC6FFFF      jmp GameMon.007D12B6
007D4C07    FB               sti
007D4C08    D125 06E03800    shl dword ptr ds:[38E006],1


2. 避开IAT加密

写脚本挂到CreateFile加载的kernel32!VirtualAlloc,记录分配size,
当出现连续分配0x1000,0x2000,0x10000,最后的1个出来就到了.

00EDA072   Hardware breakpoint 1 at 00EDA072
           counter = 00000007
           ret_addr = 007D787D
           cbSize = 00001000
00EDA072   Hardware breakpoint 1 at 00EDA072
           counter = 00000008
           ret_addr = 007D789F
           cbSize = 00002000
00EDA072   Hardware breakpoint 1 at 00EDA072
           counter = 00000009
           ret_addr = 007D78BF <---- 这里
           cbSize = 00010000 | UNICODE "=::=::\"

007D78BF    8985 A9271406    mov dword ptr ss:[ebp+61427A9],eax
007D78C5    8BB5 31321406    mov esi,dword ptr ss:[ebp+6143231]            ; GameMon.007CE59A
007D78CB    8B9D 41051406    mov ebx,dword ptr ss:[ebp+6140541]
007D78D1    89B5 05081406    mov dword ptr ss:[ebp+6140805],esi            ; GameMon.007CE59A
007D78D7    899D C9021406    mov dword ptr ss:[ebp+61402C9],ebx
007D78DD    8B9D 41051406    mov ebx,dword ptr ss:[ebp+6140541]
007D78E3    8B0B             mov ecx,dword ptr ds:[ebx]
007D78E5    83F9 00          cmp ecx,0
007D78E8    0F84 DF0A0000    je GameMon.007D83CD

其中007D78C5 mov esi,dword ptr ss:[ebp+6143231] 为IAT相关数据.

007CE59A     84B059E0    DD0B4D7D    4000A3B1    E000A52F
007CE5AA     A000A54A    E000C9EF    FFFFFFFF    DDDDDDDD
007CE5BA     10945DAF    FD0B4D7D    200043CA    8000BC4E
........
.......
007D065A     EEEEEEEE    DDDDDDDD    F684BB2D    DD0B4D91
007D066A     0001BDBE    AAAAAAAA    FFFFFFFF    DDDDDDDD
007D067A     EEEEEEEE    DDDDDDDD    00000000    00000000
007D068A     07B540EB    0823007D    087A007D    FFFF007D
   
数据以FFFFFFFF DDDDDDDD EEEEEEEE DDDDDDDD结束,包括API Name的Hash,
对应要patch的JMP [IAT]代码地址。fly的做法是patch代码,对于
JMP [IAT]代码的修复,我是另写的程序,代码后面贴。


把这段数据binary copy保存下来,Themida用过要清0。先取干净的IAT。
走几步:

007D7A5B    61               popad
007D7A5C    B9 5D71A45E      mov ecx,5EA4715D <--------- key1
007D7A61    BA 6D5D107F      mov edx,7F105D6D <--------- key2
007D7A66    AD               lods dword ptr ds:[esi]
007D7A67    89B5 05081406    mov dword ptr ss:[ebp+6140805],esi            ; kernel32.77EAF541

记下这2个值。下面和以前一样,避开IAT加密,NOP 4个JE。

007D7B83    83BD 4D251406 01 cmp dword ptr ss:[ebp+614254D],1
007D7B8A    0F84 39000000    je GameMon.007D7BC9
007D7B90    3B8D 712E1406    cmp ecx,dword ptr ss:[ebp+6142E71]            ; kernel32.77E40000
007D7B96    0F84 2D000000    je GameMon.007D7BC9
007D7B9C    3B8D 85041406    cmp ecx,dword ptr ss:[ebp+6140485]            ; USER32.77D10000
007D7BA2    0F84 21000000    je GameMon.007D7BC9
007D7BA8    3B8D 650E1406    cmp ecx,dword ptr ss:[ebp+6140E65]            ; ADVAPI32.77DA0000
007D7BAE    0F84 15000000    je GameMon.007D7BC9
007D7BB4    8D9D BA422606    lea ebx,dword ptr ss:[ebp+62642BA]
007D7BBA    FFD3             call ebx                                      ; GameMon.007C5348

PATCH自校验:

007D7A28    C1E9 08          shr ecx,8
007D7A2B    33C8             xor ecx,eax
007D7A2D    4A               dec edx
007D7A2E  ^ 0F85 EAFFFFFF    jnz GameMon.007D7A1E
007D7A34    8BC1             mov eax,ecx
007D7A36    F7D0             not eax
007D7A38    3985 E5021406    cmp dword ptr ss:[ebp+61402E5],eax  <----- 校验
007D7A3E    0F84 17000000    je GameMon.007D7A5B
007D7A44    83BD D11E1406 00 cmp dword ptr ss:[ebp+6141ED1],0

得到干净的IAT后,保存tree。

Ctrl-F2重来,停在dump点。把前面保存的数据binary paste
到原位置(这些步骤可以写脱壳机来做,原来也搞过,但有些细节老变,有bound dll的
也不同,就算了)。运行Patch修复被改为NOP/CALL的原JMP [IAT]代码,Dump,Fix IAT。

代码以前的,有点乱,懒得去改了.


int main()
{

  HANDLE  hThemida  = 0;
  DWORD  dwBegin    = 0x007CE59A;        // iat数据起始地址
  DWORD  dwEnd    = 0x007D0682;        // iat数据结束地址
  PDWORD  lpBuff    = 0;
  DWORD  dwCounter  = (dwEnd - dwBegin) / 4;
  DWORD  imageBase  = 0x00400000;

  
  //  

  hThemida = OpenProcess(PROCESS_ALL_ACCESS,FALSE,0x68C);
  if(!hThemida)
  {
    printf("Failed to open the process!\r\n");
    return 0;
  }
  
  lpBuff = (PDWORD)new BYTE[dwEnd - dwBegin];
  ReadProcessMemory(hThemida,
          (PVOID)dwBegin,
          lpBuff,
          dwEnd - dwBegin,
          NULL);


  DWORD i = 0;

  DWORD key1 = 0x5EA4715D;  // ecx  ; NP1012 GameMon数据,什么版本的Themida?
  DWORD key2 = 0x7F105D6D;  // edx

  DWORD lastData  = 0;    // 上1个数据
  DWORD currData  = 0;    // 当前数据
  DWORD iatAddr;      // IAT地址
  DWORD patchAddr;    // PATCH地址
  BYTE  opcode[6];


  while(TRUE)  // 每轮循环处理1个API
  {
    if(i >= dwCounter)
      break;
    
    // 0xEEEEEEEE,0xDDDDDDDD dll结束标记

    if((0xEEEEEEEE == lpBuff[i]) && (0xDDDDDDDD == lpBuff[i + 1]))
    {
      i += 2;    // 跳过2个DWORD
      continue;
    }

    // 取DWORD
    currData = lpBuff[i] ^ lastData;
    
    _asm mov eax,currData
    _asm ror eax,3
    _asm mov currData,eax

    currData -= key2;

    _asm mov eax,currData
    _asm rol eax,0x10
    _asm mov currData,eax
    
    currData ^= key1;
    lastData = lpBuff[i];

    i++;

    if(currData >= 0x10000)  // currData为API名hash
    {
      //
      // 取API地址,若为特殊API另行处理(4个JE)...
      //

      iatAddr = lpBuff[i];
      
      _asm mov eax,iatAddr
      _asm rol eax,5
      _asm mov iatAddr,eax
      
      iatAddr += key1;
      iatAddr += imageBase;

      i++;
      
      //
      // 将API地址保存到IAT...
      //

      // 若后续的2个DWORD为FFFFFFFF,DDDDDDDD代表该API
      // 不需要PATCH代码(JMP [IAT])

      while(!((0xFFFFFFFF == lpBuff[i]) && (0xDDDDDDDD == lpBuff[i + 1])))
      {
        // 下一个数据为Patch地址
        
        patchAddr = lpBuff[i];
        
        _asm mov eax,patchAddr
        _asm rol eax,3
        _asm mov patchAddr,eax
          
        patchAddr += imageBase;
        
        if(0xAAAAAAAA == lpBuff[i + 1])  // jmp [iat]
        {
          *((PWORD)opcode) = 0x25FF;
          i += 2;  // 跳过patch地址及AAAAAAAA
        }
        else // call [iat]
        {
          *((PWORD)opcode) = 0x15FF;
          i += 1;  // 跳过patch地址
        }
        
        *(PDWORD)&opcode[2] = iatAddr;
        
        WriteProcessMemory(hThemida,(PVOID)patchAddr,opcode,6,NULL);
      }
      
      // 到这里,即发现FFFFFFFF,DDDDDDDD
      i += 2; // 跳过标记
    }
    else
    {
      if(0xBBBBBBBB == lpBuff[i])
      {
        //
        // 不处理的API,将API真实地址送入IAT...
        //
        i++;  // 跳过BBBBBBBB
        
        iatAddr = lpBuff[i];
        
        _asm mov eax,iatAddr
        _asm rol eax,5
        _asm mov iatAddr,eax
          
        iatAddr += key1;
        iatAddr += imageBase;
        
        i++;  // 跳过IAT地址
        
        //
        while(!((0xFFFFFFFF == lpBuff[i]) && (0xDDDDDDDD == lpBuff[i + 1])))
        {
          patchAddr = lpBuff[i];
        
          _asm mov eax,patchAddr
          _asm rol eax,3
          _asm mov patchAddr,eax
          
          patchAddr += imageBase;
  
          if(0xAAAAAAAA == lpBuff[i + 1])  // jmp [iat]
          {
            *((PWORD)opcode) = 0x25FF;
            i += 2;  // 跳过patch地址及AAAAAAAA
          }
          else // call [iat]
          {
            *((PWORD)opcode) = 0x15FF;
            i += 1;  // 跳过patch地址
          }
          
          *(PDWORD)&opcode[2] = iatAddr;
          
          WriteProcessMemory(hThemida,(PVOID)patchAddr,opcode,6,NULL);
          
        }
        
        // 到这里,即发现FFFFFFFF,DDDDDDDD
        i += 2; // 跳过标记
      }
      else
      {
        //
        // 不满足currData >= 0x10000,但下1个值又不是BBBBBBBB
        // 断不下,实际代码的处理是落到
        // if(currData >= 0x10000)同样的代码
        //
        TRACE("Wrong\n");
      }
    }
  }

  delete [] (PBYTE)lpBuff;
  
  CloseHandle(hThemida);

  //
  return nRetCode;
}