Themida1.3.5.5已经算是比较旧的版本了吧, 我想这个壳应该可以代表1.3.x以前的比如1.2什么的. 不带驱动的壳. 好吧, 就从它开始. 这个壳确实. VM很少. 很清晰. 是的, 从低版本的Themida开始调戏是对的.这一篇我写下, 如何定位到IAT的, 下一篇完整分析下..

首先用Themida加壳, 下面我说Themida的时候, 就是说Themida1.3.5.5了.. 我加了一个delphi7. 再加一个VC6. 一个VC8.然后一个一个脱.

载入后, 再code段下一个硬件写入断点, 然后在VirtualAlloc返回的地方下硬件执行断点, VirtualAlloc是用于定位IAT表操作的. code段的硬件写入断点用处很多. 包括这个VirtualAlloc也是通过分析code段的硬件写入断点分析到的..

然后就会断再VirtualAlloc返回的地方了. 看看eax的值是多少, 在数据区dump. 然后跑起来. 再次断点到VirtualAlloc的返回处, 这时间我们需要做的就是看看刚才的数据区变化没有. 是不是开始是4D5A什么的. 不是就再次在数据区dump eax的值. 如是几次. 段下来的我们就可以发现这是一个PE文件了. 当然网上也还有一种方法挺不错的, 就是在VirtualAlloc下断点以后, 观察内存的分配情况, 如果VirtualAlloc断下4次, 而内存却有5块, 那么最后这次就是kernel32.dll的内存了. 这个方法也很先进.

将这个PE文件dump出来, 发现原来这个程序就是kernel32.dll的复制品. OK找到这个DLL里面的VirtualAlloc函数. 应该都会找吧, 重定位法. 不行就利用特征码搜索也可以..

OK在重定位的VirtualAlloc上面段下来以后我们可以做的事情就是等待, 等待到eax == GetProcAddress就可以了. 为什么是这个? 其实这个跟上面的对代码段下硬件断点就可以跟出来.. 只是次数比较多. 利用代码断点跟出来以后就可以想想更快的办法了. 这些办法都不是我想的. 网上很多. 只是我想他们是怎么得到的. 是的… 他们肯定也是跟出来的..

当这个VirtualAlloc是GetProcAddress的时候, 我们在跑两次, 然后跟出来, 就到了解密IAT的地方了. 是不是很兴奋? 这个Themida的版本比较低, 我们一眼就可以看出来出来IAT的地方..好吧, 我们来试试, 假如说. 直接给code下硬件写入断点,如何定位到处理IAT的地方.首先删除所有断点, 然后在Code断下硬件写入断点, 第一次中断在这里..

006910C9 /0F84 07000000 je delphi7_.006910D6

006910CF |8B03 mov eax, dword ptr [ebx]

006910D1 |8B5B 04 mov ebx, dword ptr [ebx+4]

006910D4 |8918 mov dword ptr [eax], ebx

006910D6 \8BC9 mov ecx, ecx

006910D8 8D8D A04D2C07 lea ecx, dword ptr [ebp+72C4DA0]

006910DE 6A 00 push 0

仔细看看没有什么用处, 接着跑..

00692C9A 8B9D D5181B07 mov ebx, dword ptr [ebp+71B18D5]

00692CA0 8B85 0D1B1B07 mov eax, dword ptr [ebp+71B1B0D]

00692CA6 8903 mov dword ptr [ebx], eax

00692CA8 51 push ecx

00692CA9 52 push edx

00692CAA 8D85 D5411B07 lea eax, dword ptr [ebp+71B41D5]

00692CB0 FFD0 call eax

第2次到这里, 好像也没有什么神奇的地方. 接着跑.

00692CB0 FFD0 call eax

00692CB2 8BFA mov edi, edx

00692CB4 8BF1 mov esi, ecx

00692CB6 8BD1 mov edx, ecx

00692CB8 8BC8 mov ecx, eax

00692CBA F3:A4 rep movs byte ptr es:[edi], byte ptr [esi] ; 这里开始填充代码

00692CBC C685 392E1B07 5>mov byte ptr [ebp+71B2E39], 56

00692CC3 68 396D1FD4 push D41F6D39

00692CC8 FFB5 81071B07 push dword ptr [ebp+71B0781]

00692CCE 8D85 C3002907 lea eax, dword ptr [ebp+72900C3]

00692CD4 FFD0 call eax

这个就很明显了, 这里开始填充代码了. 为什么我觉得这里就是要填充代码了呢??因为我看到esi里面的有一个boolean这是delphi代码的特征. 还有就是ecx 是整个代码段的大小. 根据这条指令显然就是这里了.. 但是现在我们还没有到我们想要到的地方. 也就是处理IAT的地方. 一般壳都是填充完了代码以后, 接着填充IAT. 那么我们现在跑起来就要小心了. 接下来的位置很有可能就是处理IAT的.

00692D39 /0F84 07000000 je delphi7_.00692D46

00692D3F |8B03 mov eax, dword ptr [ebx]

00692D41 |8B5B 04 mov ebx, dword ptr [ebx+4]

00692D44 |8918 mov dword ptr [eax], ebx

00692D46 \B8 414B0000 mov eax, 4B41

00692D4B 8BC0 mov eax, eax

00692D4D 83BD 11281B07 0>cmp dword ptr [ebp+71B2811], 0

00692D54 75 09 jnz short delphi7_.00692D5F

再断一次就到这里了. 这里我们把整个寄存器看完也没有看到与API函数地址有关的东西, 估计不是这个. 接着跑.

0069AACE C1C0 05 rol eax, 5

0069AAD1 05 B6282501 add eax, 12528B6

0069AAD6 0385 F5031B07 add eax, dword ptr [ebp+71B03F5]

0069AADC 8B8D DD261B07 mov ecx, dword ptr [ebp+71B26DD]

0069AAE2 8908 mov dword ptr [eax], ecx

0069AAE4 AD lods dword ptr [esi]

0069AAE5 C746 FC 0000000>mov dword ptr [esi-4], 0

0069AAEC 89B5 95211B07 mov dword ptr [ebp+71B2195], esi

到这里依然没有.发现什么有价值的东西, 接着跑.

0069AB98 AA stos byte ptr es:[edi]

0069AB99 E9 24000000 jmp delphi7_.0069ABC2

0069AB9E 58 pop eax

0069AB9F AA stos byte ptr es:[edi]

0069ABA0 807F FF E9 cmp byte ptr [edi-1], 0E9

0069ABA4 0F85 18000000 jnz delphi7_.0069ABC2

0069ABAA 83BD BED32C07 0>cmp dword ptr [ebp+72CD3BE], 0

到这里, 发现这个代码的逻辑是给代码段那些地方修复那些nop. 改成jmp..函数地址. 这就是传说中的修复Themida 90了.显然到这里的时候, 黄花菜都已经凉了, 已经过趟了. 为什么在前面几次断下的时候我们没有找到呢? 是什么误导我们了么?仔细跟下这个修复90的代码, 发现最后他给我们修复成这样的.

00401368 - E9 93EC4802 jmp 02890000

看来我们前面的思路错了. 我们应该找寄存器中有02890000附近的地址.. 重新跑一下..

0069AACE C1C0 05 rol eax, 5

0069AAD1 05 B6282501 add eax, 12528B6

0069AAD6 0385 F5031B07 add eax, dword ptr [ebp+71B03F5]

0069AADC 8B8D DD261B07 mov ecx, dword ptr [ebp+71B26DD]

0069AAE2 8908 mov dword ptr [eax], ecx

0069AAE4 AD lods dword ptr [esi]

0069AAE5 C746 FC 0000000>mov dword ptr [esi-4], 0

0069AAEC 89B5 95211B07 mov dword ptr [ebp+71B2195], esi

发现是这次, 也就是填写90稍微前面那一次, 这个就比较符合逻辑了. 呵呵. 这时候的ecx = 02890000没错. 就是这里了.可是这里我们也没有看到处理IAT的代码啊? 往前面看看??前面实在是太长了. 我真的不知道哪里是? 所以我就找了一个地方这个地方就像我们的函数头. 前面全是0.. 也就是说是这段代码的开始处, 我下执行断点.

006998C2 0000 add byte ptr [eax], al

006998C4 5A pop edx

006998C5 8B68 81 mov ebp, dword ptr [eax-7F]

006998C8 26:67:14 BD adc al, 0BD

006998CC 8BC0 mov eax, eax

006998CE 83BD 11281B07 0>cmp dword ptr [ebp+71B2811], 0

006998D5 75 09 jnz short delphi7_.006998E0

006998D7 83BD 751B1B07 0>cmp dword ptr [ebp+71B1B75], 0

006998DE 74 19 je short delphi7_.006998F9

第一次我在pop edx上面没有段下来, 然后我下来点, 我在cmp dword ptr [ebp+71B2811], 0上断下来了. 然后单步.知道下面这段代码开始的地方. 我们看到都是在不断的获取函数的地址. 有ReadFile WriteFile. ExitThead CreateThead..等等, 很想准备开始处理的架势了. 然后突然一个长跳. 到下面的代码. 我知道要开始了..

0069A122 6A 04 push 4

0069A124 68 00100000 push 1000

0069A129 68 00100000 push 1000

0069A12E 6A 00 push 0

0069A130 FF95 111C1B07 call dword ptr [ebp+71B1C11]

0069A136 8985 49041B07 mov dword ptr [ebp+71B0449], eax

0069A13C 8D85 CE9C2C07 lea eax, dword ptr [ebp+72C9CCE]

是的, 就这样. 我们精确的定位到了开始出来IAT表的函数位置. 现在就是分析这个函数了.. 网上有很多资料. 很值得学习.但是我觉得我自己跟一下会更好.

下面还有一种方法精确定位到Themida解码表的位置.. 这种方法更加的先进.前面我们说了在代码解压出来以后会出现有很多nop的地方. 然后我们在很多nop的地方下硬件写入断点,

0069AB98 AA stos byte ptr es:[edi]

0069AB99 E9 24000000 jmp delphi7_.0069ABC2

0069AB9E 58 pop eax

0069AB9F AA stos byte ptr es:[edi]

0069ABA0 807F FF E9 cmp byte ptr [edi-1], 0E9

0069ABA4 0F85 18000000 jnz delphi7_.0069ABC2

0069ABAA 83BD BED32C07 0>cmp dword ptr [ebp+72CD3BE], 0

然后中断在这里.. 这个有可能是E8, 或者E9.. 这里stosd [edi]就是存储跳转地址的地方. 然后网上又说了. esi指向的就是跳转表. 结构是这样的.

00693A82 0EA87DCB

00693A86 37F6FEC7

00693A8A 8000023E

00693A8E AAAAAAAA

00693A92 FFFFFFFF

00693A96 DDDDDDDD

00693A9A EEEEEEEE

00693A9E DDDDDDDD

00693AA2 54F2367E

00693AA6 77F6FEC7

00693AAA 000005E3

00693AAE AAAAAAAA

每个API解码以FFFFFFFFDDDDDDDD结束, 每个DLL以AAAAAAAA结束. 然后里面的3个dword的结构是这样的, 第一个是解码出API.第2个dword是解码出API写入IAT表地址. 第3个dword解码出调用这个API的地址. 如果第3个不是结束符, 那么说明这个API还被其他地方调用.. 网上一哥们, 把这个结构也弄出来了.真的是太牛了.

3. IAT 数据的结构大概如下 <不准确>

// 数据 – 所有的IAT数据,结构如下

struct stTmdIAT

{

1
2
3
4
5
  DWORD apiNameHashVal;           // 导入API函数的名称的HASH值(未解密)

  DWORD iatAddress;               // 该导入API函数的IAT地址 (未解密) ===> 如果后面紧跟AAAAAAAA表示FF25 jmp \[iat\]类型的。。

 DWORD callApiInstructions\[n\];     // 被保护程序中调用该API函数的指令地址数组, 以FFFFFFFF结束

};

结构也知道了. 那么我们怎么才能够断在读取第一个API解码地址处呢? 这个就是esi被赋值的地方? 在哪里赋值的呢? 发现还是跟那个VirtualAlloc以后在GetProcAddress上面, 跑两次就差不多到赋值的地方. 也就是说. 这样我们还是定位到了调戏IAT的地方..主要来说, 还是要分析这一大片解析IAT的代码, 只是方法多了, 给了我们很多不一样的视角, 更加方便.