己有2年时间没怎么研究壳了,为了不至于成为壳肓,准备花点时间学一下脱壳。将第一个目标锁定在Obsidium上,这款壳相对来说容易些。
首先声明一下,这篇文章没什么新的技术,Obsidium壳的分析论坛己有许多文章讨论了,写这篇文章主要是记录一下自己的学习过程,方便以
后查找。同时也将这篇文章送给刚入门的脱壳菜鸟们,所以文章中我将详细记录操作过程,同时尽可能说明为什么这么做,希望能通过这篇文
章让新手们举一反三。
第1篇 Obsidium 1.0.0.69学习手记
用Obsidium 1.0.0.69建个项目,选中Encrypt resoures,Debugger checks,compression,Remove bytes at OEP,Runtime patching,
Runtime tracing等选项。
为了降低难度,目标程序TraceMe1.exe没有重定位表,因此Obsidium加壳时没有重定位映像。
实例下载:obsidiumtest1.0.rar
一.寻找OEP及Stole code
思路:现在外壳程序都大量了调用异常(SEH)产生非正常的跳转,用以干扰调试,正是外壳这些连续的SEH也给脱壳者指明了一条道路。
OD对异常处理相当灵活,因此在跟踪外壳的过程中,可以利用合适的异常做为一个路标来指导跟踪。用OD加载程序,除了“整数除以0异
常外”(为什么选这个异常?一个个异常试出来的),忽略其他所有异常。然后用OD隐藏插件将OD隐藏,这个版的Anti比较弱。同时按
Alt+B查看一个断点窗口,清除所有断点,有些断点Obsidium会发现被跟踪)
按F9运行程序,遇到异常按Shift+F9通过,5次异常后程序就运行起来了。
第1次异常:
009005EE F7F0 div eax
009005F0 EB 02 jmp short 009005F4
009005F2 68 41EB01A2 push A201EB41
第2次异常:
0090145E F7F0 div eax
00901460 EB 04 jmp short 00901466
第3次异常:
009018C6 F7F0 div eax
009018C8 EB 02 jmp short 009018CC
第4次异常:
00901A62 F7F0 div eax
00901A64 EB 04 jmp short 00901A6A
第5次异常:
0040B5BC F7F0 div eax //停在这里
0040B5BE EB 04 jmp short 0040B5C4
当第5次异常执行后,程序就运行起来了。因此重新加载程序,第5次异常后准备单步跟踪下去。
当第5次异常,停在0040B5BC时,查看堆栈窗口,数据如下:
0012FF98 0012FFE0 Pointer to next SEH record
0012FF9C 0040B5ED SE handler//关键,如果程序异常后,系统将跳到此处执行
注:有关如何跟踪异常的技巧请参考加密与解密二版菜鸟学习笔记(2) - SEH 结构化异常处理
因此在命令行设断:bp 0040B5ED
0040B5ED /EB 04 jmp short 0040B5F3 //在这句设断点
0040B5EF |6965 26 F7C80000 imul esp, [ebp+26], 0C8F7
设好断点后,按Shift+F9让程序通过异常,就会在0040B5ED地址处中断。
中断后,别忘了取消断点。也不要忘了按Shift,而直接按F9了,在Obsidium1.3这样操作后,外壳后面可能会异常出错.此时会看到一堆代码很乱,因为这是有花指令的原故。
此时用花指令去除器,将花指令去除。
得到干净的代码如下:
0040B5ED 90 nop
0040B5EE 90 nop
0040B5EF 90 nop
0040B5F0 90 nop
0040B5F1 90 nop
0040B5F2 90 nop
0040B5F3 C8 000000 enter 0, 0
0040B5F7 8B45 08 mov eax, [ebp+8]
0040B5FA 90 nop
0040B5FB 90 nop
0040B5FC 90 nop
0040B5FD 90 nop
0040B5FE 8B00 mov eax, [eax]
0040B600 90 nop
……
0040B660 8B45 10 mov eax, [ebp+10]
0040B663 81EA F5C0B200 sub edx, 0B2C0F5
0040B669 90 nop
0040B66A 90 nop
0040B66B 90 nop
0040B66C 90 nop
0040B66D 90 nop
0040B66E 8D940A EFAB0CFF lea edx, [edx+ecx+FF0CABEF]
0040B675 90 nop
0040B676 90 nop
0040B677 90 nop
0040B678 90 nop
0040B679 90 nop
0040B67A 8990 B8000000 mov [eax+B8], edx ; CONTEXT.EIP,此时EDX的值0040BAEA
向下跟踪,其中0040B67A 这句比较熟悉,也是SEH的一个处理形式,eax是指向CONTEXT结构,其偏移B8就是新的EIP,异常后,程序会
跳到这个EIP执行,因此对EDX(此时值0040BAEA)设断:
bp edx或bp0040BAEA
中断后来到这里:(中断后,别忘了取消断点。 )
0040BAEA E8 AF000000 call 0040BB9E ; 这是一个加密CALL,按F7进去
{
//为了阅读方便,仅将相关代码重新整理排版列出,这段代码按F7走出即可
//这段代码是SMC方法修改0040BAEA地址一段代码
0040BB9E 60 pushad
0040BBA9 836C24 20 05 sub dword ptr [esp+20], 5 //esp+20初始值为0xEF
0040BBAE 8B4C24 20 mov ecx, [esp+20]
0040BBB7 C601 0E mov byte ptr [ecx], 0E //此时ECX的值就是0040BAEA
0040BBBE C741 01 454EB4E7 mov dword ptr [ecx+1], E7B44E45
0040BBC5 BE B4000000 mov esi, 0B4
0040BBCA F9 stc
0040BBCB 72 02 jb short 0040BBCF
0040BBCF BB 14F9DC24 mov ebx, 24DCF914
0040BBD4 8131 E5445F5F xor dword ptr [ecx], 5F5F44E5
0040BBDA F9 stc
0040BBDB 72 04 jb short 0040BBE1
0040BBE1 83C1 04 add ecx, 4
0040BBE4 F9 stc
0040BBE5 72 06 jb short 0040BBED
0040BBED C1C3 45 rol ebx, 45
0040BBF0 83EE 04 sub esi, 4
0040BBF3 ^ 0F85 DBFFFFFF jnz 0040BBD4
0040BBF9 F8 clc
0040BBFA 73 06 jnb short 0040BC02
0040BBFC CE into
0040BBFD 7E 12 jle short 0040BC11
0040BBFF 4F dec edi
0040BC00 AB stos dword ptr es:[edi]
0040BC01 58 pop eax
0040BC02 61 popad //在这按F4,再按F8走出这个CALL
0040BC03 C3 retn
}
走出上面的代码,重新来到0040BAEA 这个地址处:
0040BAEA /EB 01 jmp short 0040BAED ; 加密CALL
0040BAEC |11EB adc ebx, ebp
0040BAEE 0230 add dh, [eax]
小技巧:你也可以一开始就在0040BAEA 这行,按一下F4,即可中断到这里
按F8单步走,这段有花指令,不要去除,会有自检验,来到如下代码:
0040BB7F FFE7 jmp edi ; //关键! 跟进
0040BB81 EB 01 jmp short 0040BB84
0040BB83 6A EB push -15
0040BB85 030C10 add ecx, [eax+edx]
来到(己去除花指令):
00905AE4 E8 00000000 call 00905AE9
00905AE9 90 nop
00905AEA 90 nop
00905AEB 90 nop
00905AEC 90 nop
00905AED 90 nop
00905AEE 5D pop ebp
00905AEF 90 nop
00905AF0 90 nop
00905AF1 90 nop
00905AF2 90 nop
00905AF3 90 nop
00905AF4 81ED A0C1B200 sub ebp, 0B2C1A0
00905AFA 90 nop
00905AFB 90 nop
00905AFC 90 nop
00905AFD 90 nop
00905AFE 90 nop
00905AFF 64:67:8F06 0000 pop dword ptr fs:[0]
……
00905EA9 61 popad
00905EAA 90 nop
00905EAB 90 nop
00905EAC 90 nop
00905EAD 90 nop
00905EAE 90 nop
00905EAF 9D popfd
00905EB0 90 nop
00905EB1 90 nop
00905EB2 90 nop
00905EB3 90 nop
00905EB4 90 nop
00905EB5 90 nop
00905EB6 90 nop
00905EB7 90 nop
00905EB8 90 nop
00905EB9 90 nop
00905EBA 55 push ebp //Stole code第1句
00905EBB 90 nop
00905EBC 90 nop
00905EBD 90 nop
00905EBE 90 nop
00905EBF 8BEC mov ebp, esp //Stole code第2句
00905EC1 90 nop
00905EC2 90 nop
00905EC3 90 nop
00905EC4 90 nop
00905EC5 90 nop
00905EC6 6A FF push -1 //Stole code第3句
00905EC8 90 nop
00905EC9 90 nop
00905ECA 90 nop
00905ECB 68 D0404000 push 4040D0 //Stole code第4句
00905ED0 90 nop
00905ED1 90 nop
00905ED2 90 nop
00905ED3 90 nop
00905ED4 68 D41E4000 push 401ED4 //Stole code第5句
00905ED9 90 nop
00905EDA 90 nop
00905EDB 90 nop
00905EDC 64:A1 00000000 mov eax, fs:[0] //Stole code第6句
00905EE2 90 nop
00905EE3 90 nop
00905EE4 90 nop
00905EE5 90 nop
00905EE6 90 nop
00905EE7 90 nop
00905EE8 50 push eax //Stole code第7句
00905EE9 90 nop
00905EEA 90 nop
00905EEB 90 nop
00905EEC 90 nop
00905EED 64:8925 00000000 mov fs:[0], esp //Stole code第8句
00905EF4 90 nop
00905EF5 90 nop
00905EF6 90 nop
00905EF7 90 nop
00905EF8 90 nop
00905EF9 90 nop
00905EFA 83EC 58 sub esp, 58 //Stole code第9句
00905EFD 90 nop
00905EFE 90 nop
00905EFF 90 nop
00905F00 90 nop
00905F01 90 nop
00905F02 53 push ebx //Stole code第10句
00905F03 90 nop
00905F04 90 nop
00905F05 90 nop
00905F06 90 nop
00905F07 90 nop
00905F08 90 nop
00905F09 56 push esi //Stole code第11句
00905F0A 90 nop
00905F0B 90 nop
00905F0C 90 nop
00905F0D 90 nop
00905F0E 57 push edi //Stole code第12句
00905F0F 90 nop
00905F10 90 nop
00905F11 90 nop
00905F12 90 nop
00905F13 90 nop
00905F14 8965 E8 mov [ebp-18], esp //Stole code第13句
00905F17 90 nop
00905F18 90 nop
00905F19 90 nop
00905F1A 90 nop
00905F1B - E9 A6B4AFFF jmp TraceMe_.004013C6 //跳到伪OEP
伪OEP处的代码如下:
004013BF A9 db A9
004013C0 8B db 8B
004013C1 A4 db A4
004013C2 7F db 7F
004013C3 6D db 6D ; CHAR 'm'
004013C4 E4 db E4
004013C5 EE db EE
004013C6 FF db FF //跳到此处,伪OEP
004013C7 15 db 15
004013C8 44 db 44 ; CHAR 'D'
004013C9 40 db 40 ; CHAR '@'
004013CA 40 db 40 ; CHAR '@'
004013CB 00 db 00
此时按一下Ctrl+A让OD重新分析一下代码:
004013A0 7A db 7A ; CHAR 'z'
004013A1 4A db 4A ; CHAR 'J'
004013A2 1D db 1D
004013A3 66 db 66 ; CHAR 'f'
004013A4 83 db 83
004013A5 29 db 29 ; CHAR ')'
004013A6 . C3 retn
004013A7 DA db DA
004013A8 05 db 05
004013A9 50 db 50 ; CHAR 'P'
004013AA F3 db F3
004013AB 7F db 7F
004013AC 52 db 52 ; CHAR 'R'
004013AD 83 db 83
004013AE 59 db 59 ; CHAR 'Y'
004013AF 07 db 07
004013B0 65 db 65 ; CHAR 'e'
004013B1 39 db 39 ; CHAR '9'
004013B2 C4 db C4
004013B3 50 db 50 ; CHAR 'P'
004013B4 18 db 18
004013B5 18 db 18
004013B6 5E db 5E ; CHAR '^'
004013B7 24 db 24 ; CHAR '$'
004013B8 5D db 5D ; CHAR ']'
004013B9 0F db 0F
004013BA . 8DAC4B 375AA9>lea ebp, [ebx+ecx*2+8BA95A37]
004013C1 . A4 movs byte ptr es:[edi], byte ptr [esi>
004013C2 . 7F 6D jg short 00401431
004013C4 . E4 EE in al, 0EE
004013C6 . FF15 44404000 call [404044] //伪OEP
004013CC . 33D2 xor edx, edx
004013CE . 8AD4 mov dl, ah
004013D0 . 8915 28554000 mov [405528], ed
将被抽去的OEP代码填上去,正确的代码如下:
004013A0 55 push ebp
004013A1 8BEC mov ebp, esp
004013A3 6A FF push -1
004013A5 68 D0404000 push 004040D0
004013AA 68 D41E4000 push 00401ED4
004013AF 64:A1 0000000>mov eax, fs:[0]
004013B5 50 push eax
004013B6 64:8925 00000>mov fs:[0], esp
004013BD 83EC 58 sub esp, 58
004013C0 53 push ebx
004013C1 56 push esi
004013C2 57 push edi
004013C3 8965 E8 mov [ebp-18], esp
004013C6 . FF15 44404000 call [404044]
修补OEP代码后,就可以用LordPE将内存境像full dump另存为dumped.exe。
另外,Obsidium 的Stole code处理非常弱的,没有一点变形,平时只要根据程序的语言特征即可恢复。例如本例跟到伪OEP后,
记下堆栈数据:
0012FF4C 7C930738 ntdll.7C930738
0012FF50 FFFFFFFF
0012FF54 7FFD4000
0012FF58 A4653690
…………………………
0012FFA4 FFFFFFFF
0012FFA8 0012FF4C
0012FFAC 0012FFC0
0012FFB0 0012FFE0 Pointer to next SEH record
0012FFB4 00401ED4 SE handler //这个值记下来
0012FFB8 004040D0 TraceMe_.004040D0 //这个值记下来
再找一个VC的程序,将OEP的代码复制过来,将push 4040D0 ,push 401ED4 填上即可。
二.IAT的处理
继续跟踪程序,“004013C6 call [404044]”这句就是调用系统的某个API,其中404044就是IAT中的某个地址。
在数据窗口下命令:d 404044
00403FF4 00 00 00 00 00 00 00 00 00 00 00 00 DC 4B 90 00 ............躃?
00404004 E8 4B 90 00 F4 4B 90 00 00 4C 90 00 0C 4C 90 00 鐺?鬕?.L?.L?
00404014 18 4C 90 00 24 4C 90 00 30 4C 90 00 3C 4C 90 00 L?$L?0L?<L?
00404024 48 4C 90 00 54 4C 90 00 60 4C 90 00 6C 4C 90 00 HL?TL?`L?lL?
00404034 78 4C 90 00 84 4C 90 00 90 4C 90 00 DC 4D 90 00 xL?凩?怢?躆?
00404044 E6 4D 90 00 B4 4C 90 00 C0 4C 90 00 F0 4D 90 00 鍹?碙?繪?餗?
00404054 D8 4C 90 00 E4 4C 90 00 F0 4C 90 00 FC 4C 90 00 豅?銵?餖?麹?
00404064 08 4D 90 00 14 4D 90 00 20 4D 90 00 2C 4D 90 00 M?M? M?,M?
00404074 38 4D 90 00 44 4D 90 00 50 4D 90 00 5C 4D 90 00 8M?DM?PM?\M?
00404084 68 4D 90 00 74 4D 90 00 80 4D 90 00 8C 4D 90 00 hM?tM?M?孧?
00404094 98 4D 90 00 A4 4D 90 00 FA 4D 90 00 06 4E 90 00 楳??鶰?N?
004040A4 12 4E 90 00 1E 4E 90 00 2A 4E 90 00 36 4E 90 00 N?N?*N?6N?
004040B4 42 4E 90 00 4E 4E 90 00 5A 4E 90 00 66 4E 90 00 BN?NN?ZN?fN?
004040C4 72 4E 90 00 7E 4E 90 00 8A 4E 90 00 FF FF FF FF rN?~N?奛?
现在每个DWORD值都是0090xxxx的形式,其指向外壳代码里,程序调用API函数都由外壳程序代为处理。
为了让大家理解清楚,请看这张图:
这张图就是输入表的结构,程序加载内存后,只需要IAT部分,其他部分就不需要了,IAT中的每项都指向一个函数。没加壳的程序,IAT部分
是Windows系统来填充的。加壳程序情况不同了,外壳程序自己模拟Windows系统来填充IAT表(图中红圈,输入表其他部分在外壳里是没有的)
,在填充过程中,外壳可填充HOOK-API代码的地址,这样可间接地获得程序的控制权。
我们平时讨论的输入表重建工具ImpREC,就是通过这个IAT,重建整个输入表结构。由于Obsidium外壳己将IAT指向外壳代码里,用ImpREC
是不能获得正确的函数名了。我们必须想办法将各函数的真实地址填进IAT表里。例如:
图中的地址7C81485F 就是 kernel32 中的FreeEnvironmentStringsW 函数,地址7C81EE79就是kernel32中的lstrcmpA函数。
这些IAT的地址与你当前的操作系统版本有关,等得到这些真实函数地址后,用ImpREC重建一张完整的输入表。
再来确定IAT表的大小范围,IAT这张表是一个连续的表,其起始及结束应为0000 0000.上图的红色范围就是IAT表大小,起始地址就
404000,结束地址是4040D0。4040D0后面的地址虽然有数据,但其己不是00904xxx形式了,因此是其他数据。
IAT的地址:0x404000~0x4040D0 大小0xD0
获得IAT的范围后,下面就是想办法让Obsidium向这张表里填充正确的API函数地址。关闭程序,重新调试,加载程序,在命令行:
d 404000,此时的数据区全是0.按F9运行程序,当第5次异常后,会发现数据窗口己重新填充数据了:
00404000 DC 4B 90 00 E8 4B 90 00 F4 4B 90 00 00 4C 90 00 躃?鐺?鬕?.L?
00404010 0C 4C 90 00 18 4C 90 00 24 4C 90 00 30 4C 90 00 .L?L?$L?0L?
因此再重新加载程序来过,当第4次整除异常时:
00901A62 F7F0 div eax//第4次异常
00901A64 EB 04 jmp short 00901A6A
对00404000 下内在断点,在数据窗口00404000,右键/断点/内存写入。按Shit+F9跳过异常继续执行,会中断如下:
00904808 893E mov [esi], edi
0090480A EB 01 jmp short 0090480D
别忘记删除内存断点,此时单步跟踪即可。下面代码己将花指令去除,并重新排版整理过:
009047B6 C607 60 mov byte ptr [edi], 60 ; pushad
009047BD 66:895F 01 mov [edi+1], bx
009047C4 66:894F 03 mov [edi+3], cx ; mov bp,cx
009047CC C1CB 10 ror ebx, 10 ; 取bd指定(即mov)
009047D3 8857 06 mov [edi+6], dl
009047D9 885F 05 mov [edi+5], bl ; B3 00 mov bl, 0
009047E2 C647 07 E9 mov byte ptr [edi+7], 0E9 ; jmp
009047EB C1CB 10 ror ebx, 10 ; ebx=00B3BD66
009047F1 8B45 14 mov eax, [ebp+14]
009047F4 2BC7 sub eax, edi
009047F9 83E8 0C sub eax, 0C
00904800 8947 08 mov [edi+8], eax ; jmp [ebp+14]
00904808 893E mov [esi], edi ; 写IAT (开始就中断此行)
0090480D 83C7 0C add edi, 0C ; EDI->buff{0}
00904814 83C6 04 add esi, 4 ; IAT+4
0090481A 41 inc ecx ; 计数器
0090481E 3B4D 0C cmp ecx, [ebp+C] ; =26
00904826 ^ 72 8B jb short 009047B3
上面这段代码就是将己加密的API入口地址填进IAT中(上面这段代码不必看懂,只要知道其运行结果是构造如下的代码就行),各API入口代
码的形式:
xxxxxxxx 60 pushad
xxxxxxxx 66:BD 0200 mov bp, cx (CX是计数器,循环一次加1)
xxxxxxxx B3 00 mov bl, 0
xxxxxxxx ^ E9 56F0FFFF jmp [ebp+14]
过完00904826 一行,Ctrl+G,查看EDI处的汇编代码。如下
00904BDC 60 pushad
00904BDD 66:BD 0000 mov bp, 0
00904BE1 B3 00 mov bl, 0
00904BE3 ^ E9 6EF0FFFF jmp 00903C56
00904BE8 60 pushad
00904BE9 66:BD 0100 mov bp, 1
00904BED B3 00 mov bl, 0
00904BEF ^ E9 62F0FFFF jmp 00903C56
下面还有一些语句继续处理:
00904830 833E 00 cmp dword ptr [esi], 0
00904837 75 64 jnz short 0090489D
0090483C 893E mov [esi], edi ; 存进IAT
00904843 C607 60 mov byte ptr [edi], 60 ; pushad
0090484C 66:C747 01 66B8 mov word ptr [edi+1], 0B866 ; mov ax, 0
00904858 66:894F 03 mov [edi+3], cx ; mov ax,cx(初值是 26)
00904862 C647 05 B2 mov byte ptr [edi+5], 0B2 ; mov dl, 0
0090486C 8857 06 mov [edi+6], dl
00904874 C647 07 E9 mov byte ptr [edi+7], 0E9 ; jmp
0090487C 8B45 14 mov eax, [ebp+14]
0090487F 2BC7 sub eax, edi
00904886 83E8 0C sub eax, 0C
0090488F 8947 08 mov [edi+8], eax ; jmp 00903C56
00904896 90 nop
00904897 83C7 0C add edi, 0C
009048A2 8BC7 mov eax, edi
009048A7 2B45 18 sub eax, [ebp+18]
009048AF 5F pop edi
009048B0 5E pop esi
009048B1 5B pop ebx
009048B2 C9 leave
009048B3 C2 1400 retn 14
上面这段代码构造当前DLL中的最后一个IAT中的值:
00904DA4 60 pushad
00904DA5 66:B8 2600 mov ax, 26
00904DA9 B2 00 mov dl, 0
00904DAB ^ E9 A6EEFFFF jmp 00903C56
走出上面的CALL,来到如下(己去除花指令):
00904485 E8 FA020000 call 00904784 //我们从这返回
0090448F 0143 04 add [ebx+4], eax
0090449E 8B46 14 mov eax, [esi+14]
009044A6 8B56 10 mov edx, [esi+10]
009044AE 0303 add eax, [ebx]
009044B3 0353 28 add edx, [ebx+28]
009044BB FF76 04 push dword ptr [esi+4] ; DLL基址
009044BE 53 push ebx ;
009044BF 52 push edx ;
009044C0 50 push eax ; IAT地址
009044C1 FF76 0C push dword ptr [esi+C] ; API函数个数
009044C4 E8 6D010000 call 00904636 //对普通函数解密
009044CC 85C0 test eax, eax
009044D1 0F84 BB000000 je 00904592
009044DF 837D F0 00 cmp dword ptr [ebp-10], 0
009044E9 74 37 je short 00904522
009044F2 8B46 14 mov eax, [esi+14]
009044FB 8B56 10 mov edx, [esi+10]
00904504 0303 add eax, [ebx]
0090450C 0353 28 add edx, [ebx+28] ;
00904514 FF75 F4 push dword ptr [ebp-C] ;
00904517 53 push ebx ;
00904518 52 push edx ;
00904519 50 push eax ; IAT
0090451A FF76 0C push dword ptr [esi+C] ; API函数个数
0090451D E8 92010000 call 009046B4 //对特殊函数解密
00904528 83C6 18 add esi, 18
00904531 FF45 F8 inc dword ptr [ebp-8]
00904538 FF4D FC dec dword ptr [ebp-4]
00904541 ^ 0F85 1CFDFFFF jnz 00904263 //向上跳就继续处理下一个DLL的API函数
0090454A 33C0 xor eax, eax
00904552 5F pop edi
00904553 5E pop esi
00904554 5B pop ebx
00904555 C9 leave
00904556 C3 retn
上面这段代码,是Obsidium外壳为防止某些函数指针不能加密,而进行解密的函数。因此下面要做的就是将其修改一下,
让其成所有函数指针的解密器。
下面的修改方法来自辉仔Yock的文章(好像是他的文章首先提出这种修改方法)。
1.先来看看如何对普通函数解密的
009044C4 E8 6D010000 call 00904636 //按F7跟进
{
00904636 C8 000000 enter 0, 0
0090463A 53 push ebx
0090463B 56 push esi
0090463C 57 push edi
0090463D 8B5D 14 mov ebx, [ebp+14]
00904640 8B75 10 mov esi, [ebp+10]
00904643 8B7D 0C mov edi, [ebp+C]
00904646 8B5B 04 mov ebx, [ebx+4]
00904649 66:F706 2000 test word ptr [esi], 20 //这里改成test [esi], 8
0090464E 74 46 je short 00904696 //这里改成jne short 00904696
00904650 66:F706 0200 test word ptr [esi], 2
00904655 75 1F jnz short 00904676
00904657 66:C706 0400 mov word ptr [esi], 4
0090465C 8B45 14 mov eax, [ebp+14]
0090465F 6A 01 push 1
00904661 6A 00 push 0
00904663 FF76 04 push dword ptr [esi+4]
00904666 6A 00 push 0
00904668 FF75 18 push dword ptr [ebp+18]
0090466B FF50 40 call [eax+40] //解密函数
0090466E 85C0 test eax, eax
00904670 74 39 je short 009046AB //这里改成 je short 00904696
00904672 8907 mov [edi], eax
00904674 EB 20 jmp short 00904696
00904676 66:C706 0400 mov word ptr [esi], 4
0090467B 8B45 14 mov eax, [ebp+14]
0090467E 0FB756 02 movzx edx, word ptr [esi+2]
00904682 6A 01 push 1
00904684 52 push edx
00904685 6A 00 push 0
00904687 FF76 04 push dword ptr [esi+4]
0090468A FF75 18 push dword ptr [ebp+18]
0090468D FF50 40 call [eax+40] //解密函数
00904690 85C0 test eax, eax
00904692 74 17 je short 009046AB //这里改成 je short 00904696
00904694 8907 mov [edi], eax
00904696 83C6 08 add esi, 8
00904699 83C7 04 add edi, 4
0090469C FF4D 08 dec dword ptr [ebp+8]
0090469F ^ 75 A8 jnz short 00904649
009046A1 33C0 xor eax, eax
009046A3 40 inc eax
009046A4 5F pop edi
009046A5 5E pop esi
009046A6 5B pop ebx
009046A7 C9 leave
009046A8 C2 1400 retn 14
}
分析一下,当执行到00904649 test word ptr [esi], 20 这句时,查看ESI指向的数据:
00900B10 02 00 46 00 3A 8E 2F 1C 02 00 6C 00 3D 54 3F 6B .F.:?.l.=T?k
00900B20 02 00 47 00 4F 2E B8 88 02 00 57 00 89 E5 80 9A .G.O.笀.W.夊
00900B30 02 00 47 00 96 84 F8 62 02 00 47 00 C7 31 2C 96 .G.杽鴅.G.?,?
00900B40 02 00 4C 00 51 A3 52 57 02 00 4C 00 00 16 86 A3 .L.QW.L..啠
00900B50 02 00 4D 00 39 1E F1 72 02 00 4C 00 8D BD C1 3F .M.9駌.L.嵔?
00900B60 02 00 47 00 FF 1F 7C C9 02 00 48 00 FE A8 89 58 .G.|?.H.塜
00900B70 02 00 56 00 4A 0D CE 09 02 00 48 00 72 1D DB 5E .V.J.?.H.r踍
00900B80 02 00 47 00 70 65 86 B1 02 00 47 00 95 CB E2 AD .G.pe啽.G.曀猸
00900B90 08 00 00 00 03 00 00 00 08 00 00 00 00 00 00 00 .............
00900BA0 02 00 45 00 CC 97 10 25 02 00 54 00 8D BF 40 AB .E.虠%.T.嵖@?
其中红色的“08”是特殊函数标志,其他的02是普通函数标志。将00904649 这句改成test [esi], 8,
目的是比较当前是否为特殊函数,如是特殊函数不处理。
经过上面的修改,就能将普通的API函数解码出来,并填充到IAT中去。执行完009046A1 xor eax, eax一句后,
下命令:d 404000 就能查看出己被恢复的IAT数据。
(在数据窗口右键/长型/地址的方式查看)
00404000 7C81485F kernel32.FreeEnvironmentStringsW <------这些是解密的函数,函数的地址7C81485F己填充到IAT中
00404004 7C81EE79 kernel32.lstrcmpA
00404008 7C81CC23 kernel32.GetEnvironmentStringsA
0040400C 7C80A0C7 kernel32.WideCharToMultiByte
00404010 7C80A480 kernel32.GetStringTypeW
00404014 7C838CB9 kernel32.GetStringTypeA
00404018 7C80CEC4 kernel32.LCMapStringW
0040401C 7C832E2B kernel32.LCMapStringA
00404020 7C809CAD kernel32.MultiByteToWideChar
00404024 7C801D77 kernel32.LoadLibraryA
00404028 7C80AC28 kernel32.GetProcAddress
0040402C 7C9379FD ntdll.RtlReAllocateHeap
00404030 7C809A81 kernel32.VirtualAlloc
00404034 7C9305D4 ntdll.RtlAllocateHeap
00404038 7C80B529 kernel32.GetModuleHandleA
0040403C 7C801EEE kernel32.GetStartupInfoA
00404040 00904C9C <------ 这2个是特殊函数
00404044 00904CA8
…………
2.再来看看如何对特殊函数解密的
0090451D E8 92010000 call 009046B4
{
009046B4 C8 000000 enter 0, 0
009046B8 53 push ebx
009046B9 56 push esi
009046BA 57 push edi
009046BB 8B5D 14 mov ebx, [ebp+14]
009046BE 8B75 10 mov esi, [ebp+10]
009046C1 8B7D 0C mov edi, [ebp+C]
009046C4 8B5B 04 mov ebx, [ebx+4]
009046C7 66:833E 08 cmp word ptr [esi], 8
009046CB 0F85 97000000 jnz 00904768
009046D1 8B46 04 mov eax, [esi+4]
009046D4 83F8 00 cmp eax, 0
009046D7 74 44 je short 0090471D
009046D9 83F8 01 cmp eax, 1
009046DC 74 4E je short 0090472C
009046DE 83F8 02 cmp eax, 2
009046E1 74 58 je short 0090473B
009046E3 83F8 03 cmp eax, 3
009046E6 74 11 je short 009046F9
009046E8 83F8 04 cmp eax, 4
009046EB 75 7B jnz short 00904768
009046ED 8B45 18 mov eax, [ebp+18]
009046F0 05 EE1DB300 add eax, 0B31DEE
009046F5 8907 mov [edi], eax
009046F7 EB 6F jmp short 00904768
009046F9 8B45 14 mov eax, [ebp+14]
009046FC 68 C5B1662D push 2D66B1C5
00904701 6A 00 push 0
00904703 FF50 20 call [eax+20] //关键,解密函数,跟进
{ //己去除花指令并重新整理
004134F2 60 pushad
004134F7 E8 00000000 call 004134FC
00413500 5B pop ebx
00413507 81EB C85AB300 sub ebx, 0B35AC8
00413511 8B9B B45AB300 mov ebx, [ebx+B35AB4]
0041351F 8B4424 24 mov eax, [esp+24]
0041352D 33C9 xor ecx, ecx
00413535 8B4483 48 mov eax, [ebx+eax*4+48]
0041353E 8B5424 28 mov edx, [esp+28]
00413547 51 push ecx
00413548 51 push ecx
00413549 51 push ecx
0041354A 52 push edx
0041354B 50 push eax
0041354C FF53 40 call [ebx+40] //其实这个CALL是Obsidium自己实现的GetProcAddress
00413554 85C0 test eax, eax //解密后,EAX保存的就是函数地址
0041355A /0F84 DE010000 je 0041373E
00413560 90 nop //在这句加上:mov [edi], eax
00413561 90 nop //将正确的指针放进去IAT中
00413562 90 nop
00413563 90 nop
00413564 90 nop
00413565 90 nop
00413566 8BF0 mov esi, eax
……
}
00904706 50 push eax
00904707 53 push ebx
00904708 E8 D7030000 call 00904AE4
……
0090475A 2BD0 sub edx, eax
0090475C C643 05 E9 mov byte ptr [ebx+5], 0E9
00904760 8953 06 mov [ebx+6], edx
00904763 891F mov [edi], ebx //必须将这句NOP掉,不然会将IAT中原来正确API地址覆盖
00904765 83C3 0A add ebx, 0A
00904768 83C6 08 add esi, 8
0090476B 83C7 04 add edi, 4
0090476E 8B45 14 mov eax, [ebp+14]
00904771 8958 04 mov [eax+4], ebx
00904774 FF4D 08 dec dword ptr [ebp+8]
00904777 ^ 0F85 4AFFFFFF jnz 009046C7
0090477D 5F pop edi
0090477E 5E pop esi
0090477F 5B pop ebx
00904780 C9 leave
00904781 C2 1400 retn 14
}
经过修改后,就能得到正确的IAT了。
00904541 ^ 0F85 1CFDFFFF jnz 00904263 //向上跳就继续处理下一个DLL的API函数
0090454A 33C0 xor eax, eax //将断点设在,走出IAT处理过程
00904552 5F pop edi
00904553 5E pop esi
00904554 5B pop ebx
00904555 C9 leave
00904556 C3 retn
此时就可以用ImportREC重建输入表,将IAT的地址RVA:4000,大小D0填进,Get Imports后就能得到正确的输入表。
上图明显一个地方出错,因为各DLL之间的数据应为0,因此此处kernel32.dll与user32.dll之间那项,点击右键Cut thunks将这项删除。
另外,再将user32.dll中的40cc此处的项Cut thunks。再将OEP的RVA 13A0填上,Fix Dump即可得到修复后的程序。
三.暗桩修复
但是执行脱壳的程序,会出错,用OD加载脱壳后的程序,禁止各种异常。
执行后,会出现“不知如何回避位地地址00901FFC的命令,……”错误,按Shift+F9通过这个异常,会中断如下:
0901FFC FFFF ??? ; 未知命令
00901FFE FFFF ??? ; 未知命令
此时查看堆栈:
0012FED4 004026FA 返回到 dumped_.004026FA 来自 009016C3 //在这一行按回车键
0012FED8 004026A2 返回到 dumped_.004026A2 来自 dumped_.004026BE
会来到出错的代码行:
004026EC . FF15 34404000 call [<&kernel32.HeapAlloc>] ; \HeapAlloc
004026F2 > 5E pop esi
004026F3 . EB FF jmp short 004026F4 //去除
004026F5 . 15 B6804000 adc eax, 004080B6 //去除
004026FA . C3 retn
修改后再运行,再次出错:
00402581 . FF15 84404000 call [<&kernel32.HeapFree>] ; \HeapFree
00402587 > 5E pop esi
00402588 . EB FF jmp short 00402589 //nop
0040258A . 15 B2804000 adc eax, 004080B2 //nop
0040258F . C3 retn
退出时再次出错:
0040131C . FF15 B8404000 call [<&user32.DestroyWindow>] ; \DestroyWindow
00401322 . 5F pop edi
00401323 . B8 01000000 mov eax, 1
00401328 . 5E pop esi
00401329 . 81C4 F4000000 add esp, 0F4
0040132F . EB FF jmp short 00401330 //nop
00401331 . 15 B6804000 adc eax, 004080B6 //nop
00401336 . C2 1000 retn 10
原来外壳将垃圾代码加进来了,可能程序其他地方还有这些代码,在OD反汇编窗口搜索命令“adc eax, 004080B6”
00403B82 . 5F pop edi
00403B83 . C9 leave
00403B84 . EB FF jmp short 00403B85 //nop
00403B86 . 15 B6804000 adc eax, 004080B6 //nop
00403B8B . C3 retn
kanxue
看雪技术论坛 www.pediy.com
2005.12.18