第2篇 Obsidium 1.3.0.4学习手记
前面那篇文章,实例程序是没重定位表的,因此Obsidium对外壳没有重定位。将实例程序TraceMe.exe重新编译一下,加上重定位表,再用
Obsidium 1.304加壳保护。(由于重新编译过了,因此这个实例与上篇是2个不同的程序,请勿参照上篇的而误导)。
加壳时,将一些常用的保护都选上了,这篇教学没有SDK部分,有关SDK的处理,请参考heXer的笔记:
Obsidium1.2.5.0主程序脱壳记录点滴
附件:obsidium1.304实例下载
一.OD反跟踪
Obsidium在1.304加上了许多反跟踪代码,可以用隐藏OD插件HideOD躲过这些反跟踪。插件下载:
http://bbs.pediy.com/showthread.php?s=&threadid=19170
只需要将HideNtDebgBit,CheckRemoteDebuggerPresent,Process32Next三项勾上即可隐藏OD。
除了用插件隐藏外,还必须将OD类标识改掉,下面的红字:
000B5FE0 CFC2 B5F7 CAD4 A1A3 0000 0000 004F 6C6C 7944 4247 .............OllyDBG
000B5FF4 0000 6F6C 6C79 6462 6700 2E69 6E69 0049 434F 5F41 ..ollydbg..ini.ICO_A
000B6008 4141 4D41 494E 004D 4149 4E4D 454E 5500 4F4C 4C59 AAMAIN.MAINMENU.
OLLY
000B601C 4442 4700 CEDE B7A8 B4B4 BDA8 D6F7 D2AA B4B0 BFDA
DBG .................
这段是Obsidium 1.3.0.4利用CheckRemoteDebuggerPresent检测调试器代码,是goldenegg以前整理的。
解码时会有长长的一列除零错误。大约有两百多个除0。
运行后会启动一个线程,伪代码是这样子的:
while(1)
{
Sleep(500);
if(CheckRemoteDebuggerPresent()) ExitProcess();
if(IsDebugPresent()) ExitProcess();
DebugBreak(); //异常时中seh自动把eip指向循环开始。
ExitProcess();
}
当然,这些API都是以加密形式运行的。
这样运行起来后,od的log中就可以看到长长的一大列int3中断在ntdll.DebugBreak,且一直不停的增多下去。
上面说的“API都是以加密形式运行的”是怎么回事呢?
如果用PE编辑工具查看加壳后的文件,会发现未加壳的文件和加壳后的文件的输入表不一样,加壳后的输入表一般所引入的DLL和API函数很
少,甚至只有Kernel32.dll以及GetProcAddress这个API函数,有些连这个函数都没有。壳实际上还需要其他的API函数来完成它的工作,为了
隐藏这些API,它一般只在壳的代码中用显式链接方式动态加载这些API函数。
一般的壳是通过LoadLibrary加载DLL,GetProcAddress加载函数的:
HMODULE GetModuleHandle(
LPCTSTR lpModuleName // DLL文件名地址
);
FARPROC GetProcAddress(
HMODULE hModule, // DLL模块句柄
LPCSTR lpProcName // 函数名
);
如果外壳输入表里没有LoadLibrary,可能是通过暴力搜索获得Kernel32.dll基址的,相关文章请参考看雪论坛精华集。外壳调用函数,
一般是通过GetProcAddress函数实现的,因此对这设断能很快找到感兴趣的东西。但现在的加密壳为了更好了隐藏自己操作,己不调用系
统提供的GetProcAddress函数实现了,而是自己专门写一段代码来实现GetProcAddress功能。Obsidium 1.3.0.4就是这情况。为了让大家
更好的理解,我们先来寻找Obsidium自己的GetProcAddress函数。用VirtualAlloc来做为入点,因为外壳都用这函数分配临时空间放代码,
因此用的比较频繁。
记住,不要将断点设在VirtualAlloc第一字节,因为外壳程序会检测第一字节是否有断点的,我一般喜欢将断点设在函数尾部。按Ctrl+G
跳到VirtualAlloc:
7C809A81 k> 8BFF mov edi,edi
7C809A83 55 push ebp
7C809A84 8BEC mov ebp,esp
7C809A86 FF75 14 push dword ptr ss:[ebp+14]
7C809A89 FF75 10 push dword ptr ss:[ebp+10]
7C809A8C FF75 0C push dword ptr ss:[ebp+C]
7C809A8F FF75 08 push dword ptr ss:[ebp+8]
7C809A92 6A FF push -1
7C809A94 E8 09000000 call kernel32.VirtualAllocEx
7C809A99 5D pop ebp
7C809A9A C2 1000 retn 10 //在这按F2设个断点
设置内存访问异常,2次后会调用VirtualAlloc函数。返回到如下代码,取消花指令干扰后,得到的干净代码如下:
00403688 6A 00 push 0
0040368A 6A 56 push 56
0040368C 6A 00 push 0
0040368E 68 4A0DCE09 push 9CE0D4A
00403693 FFB6 98000000 push dword ptr [esi+98]
00403699 FF56 54 call [esi+54]
0040369C 90 nop
0040369D 90 nop
0040369E 90 nop
0040369F 90 nop
004036A0 8B55 0C mov edx, [ebp+C]
004036A3 90 nop
004036A4 90 nop
004036A5 90 nop
004036A6 90 nop
004036A7 81C2 00040000 add edx, 400
004036AD 90 nop
004036AE 90 nop
004036AF 90 nop
004036B0 90 nop
004036B1 6A 40 push 40
004036B3 68 00300000 push 3000
004036B8 52 push edx
004036B9 6A 00 push 0
004036BB 50 push eax
004036BC FF96 84000000 call [esi+84] //这里调用VirtualAlloc,我们从这里CALL返回
向上看,估计00403699就是Obsidium自己实现的GetProcAddress代码。
重新加载程序,通过2个内存异常后,bp 00403688 设断,会中断:
00403688 6A 00 push 0
0040368A 6A 56 push 56
0040368C 6A 00 push 0
0040368E 68 4A0DCE09 push 9CE0D4A
00403693 FFB6 98000000 push dword ptr [esi+98]
00403699 FF56 54 call [esi+54]
取消断点,按F7进入00403699 这个CALL:
0040AC80 55 push ebp
0040AC81 8BEC mov ebp, esp
0040AC83 81EC 18010000 sub esp, 118
……
0040ADA9 8B0483 mov eax, [ebx+eax*4]
0040ADAC 03C1 add eax, ecx
0040ADAE EB 57 jmp short 0040AE07
……
0040AE88 5F pop edi
0040AE89 5E pop esi
0040AE8A 5B pop ebx
0040AE8B 8BE5 mov esp, ebp
0040AE8D 5D pop ebp
0040AE8E C2 1400 retn 14 //函数地址通过EAX返回
上面这段代码就是Obsidium自己实现的GetProcAddress代码,整个外壳程序调用的APi函数几乎都是通过这个函数得到。
可以将断点设在0040AE8E这一行,直接运行外壳程序,观察EAX指向的字符串,这些就是外壳将要调用的API函数。
二.寻找OEP
设置内存访问异常,加载程序从头跟踪:
第1次异常:00403055 8B00 mov eax, [eax]
第11次异常 00404D73 8B00 mov eax, [eax]
为了能少跟踪些代码,我们尽量找到离OEP最近的异常,再设置整除异常,一次后来到:
(因为两次整除异常后程序界面己出来,因此一次就行了,此时再将整除异常取消)
00405354 F7F2 div edx //整除除异常
查看堆栈窗口:
0012FF88 0012FFE0
0012FF8C 00405391 //注意这里
在命令行设断:bp 00405391
按Shift+F9跳过异常来到:
00405391 55 push ebp
00405392 8BEC mov ebp, esp
00405394 90 nop
00405395 90 nop
00405396 90 nop
00405397 90 nop
00405398 8B4D 08 mov ecx, [ebp+8]
0040539B 90 nop
0040539C 90 nop
0040539D 90 nop
0040539E 90 nop
0040539F 8B01 mov eax, [ecx]
004053A1 90 nop
004053A2 90 nop
004053A3 90 nop
004053A4 90 nop
004053A5 90 nop
004053A6 90 nop
004053A7 90 nop
004053A8 90 nop
………………
00405431 8D940A EFAB0CFF lea edx, [edx+ecx+FF0CABEF]
00405438 90 nop
00405439 90 nop
0040543A 90 nop
0040543B 90 nop
0040543C 90 nop
0040543D 90 nop
0040543E 8990 B8000000 mov [eax+B8], edx //记下EDX值,设断跟进
0040543E这句中,EDX中就是 CONTEXT.EIP的值,记EDX的值00405563,对其设断,来到:
00405563 E8 CB000000 call 00405633 ; 这是一个加密CALL,按F7进去
{
…………
00405692 8BEC mov ebp, esp
00405694 49 dec ecx
00405695 C061 F9 72 shl byte ptr [ecx-7], 72
00405699 0224FE add ah, [esi+edi*8]
0040569C C3 retn //按F4,走出这段循环代码
}
走出上面的代码,重新来到00405563这个地址处,代码己被SMC处理成一个跳转:
00405563 /EB 04 jmp short 00405569 //所以开始这也可按一下F4即可
……………… (按F8单步走,这段有花指令,不要去除,会有自检验)
00405612 FFE7 jmp edi //来到此处,跟进
来到:
008FB40D E8 00000000 call 008FB412
008FB412 EB 01 jmp short 008FB415
008FB414 2F das
008FB415 5D pop ebp
008FB416 EB 03 jmp short 008FB41B
…………
//1.304己没有将Stole code放在这里
008FB480 - E9 4A5FAFFF jmp 009513CF//来到这里,跳到伪OEP
{
009513CF 64:A1 00000000 mov eax, fs:[0]//伪OEP
009513D5 50 push eax
009513D6 64:8925 0000000>mov fs:[0], esp
009513DD 83EC 58 sub esp, 58
009513E0 53 push ebx
009513E1 56 push esi
009513E2 57 push edi
009513E3 8965 E8 mov dword ptr ss:[ebp-18],esp
009513E6 FF15 44503F00 call dword ptr ds:[955044]
}
到OEP后,此时的基址己不是加壳前的00400000,Obsidium己将程序代码重定位了,这里的程序基址是950000,不同的系统此值不同,这
个值是Obsidium外壳调用VirtualAlloc函数分配的一空间。但同一系统,重复运行实例程序,其基址都是相同的。这就给我们跟踪调试带来
方便了。
停到009513CF伪OEP后,查看堆栈:
0012FFB4 0095208C
0012FFB8 009550D8
0012FFBC FFFFFFFF
此时只要根据VC6文件的头部特征就可还原出被抽取的代码:
push ebp
mov ebp, esp
push -1
push 009550D8
push 0095208C
接下来分析IAT的位置,这句"009513E6 call dword ptr ds:[955044] "就是调用系统的某个API,在数据窗口下命令:
d 955044
00954FF4 00 00 00 00 00 00 00 00 00 00 00 00
00 00 B6 00 ..............?
00955004 0C 00 B6 00 18 00 B6 00 24 00 B6 00 30 00 B6 00 ..?.?$.?0.?
00955014 3C 00 B6 00 48 00 B6 00 54 00 B6 00 60 00 B6 00 <.?H.?T.?`.?
00955024 6C 00 B6 00 78 00 B6 00 84 00 B6 00 90 00 B6 00 l.?x.?????
00955034 9C 00 B6 00 A8 00 B6 00 B4 00 B6 00 18 02 B6 00 ???????
00955044 22 02 B6 00 D8 00 B6 00 E4 00 B6 00 2C 02 B6 00 "?????,?
00955054 FC 00 B6 00 08 01 B6 00 14 01 B6 00 20 01 B6 00 ???? ?
00955064 2C 01 B6 00 38 01 B6 00 44 01 B6 00 50 01 B6 00 ,?8?D?P?
00955074 5C 01 B6 00 68 01 B6 00 74 01 B6 00 80 01 B6 00 \?h?t???
00955084 8C 01 B6 00 98 01 B6 00 A4 01 B6 00 B0 01 B6 00 ????????
00955094 BC 01 B6 00 C8 01 B6 00 D4 01 B6 00 E0 01 B6 00 ????????
009550A4 36 02 B6 00 42 02 B6 00 4E 02 B6 00 5A 02 B6 00 6?B?N?Z?
009550B4 66 02 B6 00 72 02 B6 00 7E 02 B6 00 8A 02 B6 00 f?r?~???
009550C4 96 02 B6 00 A2 02 B6 00 AE 02 B6 00 BA 02 B6 00 ????????
009550D4 C6 02 B6 00 FF FF FF FF 97 14 95 00 AB 14 95 00 ??????.??.
初步猜测IAT的范围就在955000~9550D8这个范围。
修复OEP后,这时可以Dump取文件,然后重建输入表后即可脱壳完毕,由于是在基址为955000这个基址Dump取的,脱壳后的文件基址也得为这个值。因此总感觉不完美,程序原来基址是400000这个值,我们可以对外壳动点手脚,让其代码不重定位,这样就可得到基址是400000的文件,下面我们就来跳过外壳的重定位。
三.寻找Stole code
由于上一步己构造好OEP处的代码了,这一步可以省略,但为了教学完整性,在这将Obsidium 1.304处理Stole code的过程列出。
前面说过,到达伪OEP时,堆栈代码:
0012FFB4 0095208C
0012FFB8 009550D8
0012FFBC FFFFFFFF
定位处理Stole code方法就是根据外壳程序是何时生成这三个堆栈数据而跟踪的。经过跟踪,是在第10次异常后开始处理Stole code代码的。
设置内存访问异常,重新加载程序,10次内存访问异常:
008F6F91 8B02 mov eax, [edx] //第10次异常中断此处
查看堆栈数据:
0012FE40 0012FE60 指针到下一个 SEH 记录
0012FE44 008F7034 SE 句柄 //异常后将跳到008F7034地址处
因此,跳到008F7034地址,按F2设断,再按Shift F9运行程序,中断这里:(下面代码花指令己取消)
008F7034 55 push ebp
008F7035 8BEC mov ebp, esp
008F7037 90 nop
008F7038 90 nop
008F7039 90 nop
008F703A 90 nop
008F703B 90 nop
008F703C 90 nop
008F703D E8 00000000 call 008F7042
008F7042 90 nop
008F7043 90 nop
008F7044 90 nop
008F7045 90 nop
008F7046 58 pop eax
008F7047 90 nop
008F7048 90 nop
008F7049 90 nop
008F704A 90 nop
008F704B 8B55 10 mov edx, [ebp+10]
008F704E 90 nop
008F704F 90 nop
008F7050 90 nop
008F7051 90 nop
008F7052 90 nop
008F7053 8D40 87 lea eax, [eax-79]
008F7056 90 nop
008F7057 90 nop
008F7058 90 nop
008F7059 8982 B8000000 mov [edx+B8], eax//contex.eip关键点,对EAX设断
运行到008F7059一行,对EAX地址设断,中断后到这里:
008F6FC9 /EB 04 jmp short 008F6FCF
…………
008F6FE4 5B pop ebx
008F6FE5 5E pop esi
008F6FE6 8BE5 mov esp, ebp
008F6FE8 5D pop ebp
008F6FE9 C3 retn //从走这出这个CALL
来到:(以下代码花指令己去除)
00404A7B 90 nop
00404A7C 90 nop
00404A7D 90 nop
00404A7E F747 0C 0400000>test dword ptr [edi+C], 4
00404A85 90 nop
00404A86 90 nop
00404A87 90 nop
00404A88 74 26 je short 00404AB0
00404A8A 90 nop
00404A8B 90 nop
…………
00404AE7 8D7C07 14 lea edi, [edi+eax+14]
00404AEB 90 nop
00404AEC 90 nop
00404AED 90 nop
00404AEE 90 nop
00404AEF 90 nop
00404AF0 ^ E9 75FBFFFF jmp 0040466A//跟进
来到:(以下代码花指令己去除)
0040466A 90 nop
0040466B 90 nop
0040466C 90 nop
0040466D 90 nop
0040466E 90 nop
0040466F 90 nop
00404670 90 nop
00404671 90 nop
00404672 90 nop
00404673 90 nop
00404674 90 nop
00404675 90 nop
00404676 8B45 0C mov eax, [ebp+C]
00404679 90 nop
0040467A 90 nop
0040467B 90 nop
0040467C 90 nop
0040467D 90 nop
0040467E 90 nop
0040467F 8B80 A02AEE00 mov eax, [eax+EE2AA0]
…………
00404A6E /74 40 je short 00404AB0
00404A70 |90 nop
00404A71 |90 nop
00404A72 |90 nop
00404A73 |90 nop
00404A74 |90 nop
00404A75 |90 nop
00404A76 |90 nop
00404A77 |90 nop
00404A78 |FF55 FC call [ebp-4] ; 关键,跟进
{
//这段代码不得改动,有自校验,按F8跟
008F6F28 E8 1F040000 call 008F734C
008F6F2D 6B99 161A9156 A>imul ebx, [ecx+56911A16], -5B
008F6F34 4F dec edi
……
008F73DA ^\0F85 AAFFFFFF jnz 008F738A
008F73E0 EB 03 jmp short 008F73E5
008F73E2 5B pop ebx
008F73E3 216F 61 and [edi+61], ebp
008F73E6 C3 retn//如果看到这里,按F4设断,可来到这里
}
008F73E6返回后,来到:(以下代码花指令己去除)
008F6F28 90 nop
008F6F29 90 nop
008F6F2A 90 nop
008F6F2B 90 nop
008F6F2C 90 nop
008F6F2D E8 00000000 call 008F6F32
008F6F32 90 nop
008F6F33 90 nop
008F6F34 90 nop
008F6F35 90 nop
008F6F36 90 nop
………………
008F701B 61 popad
008F701C 90 nop
008F701D 90 nop
008F701E 90 nop
008F701F 9D popfd
008F7020 90 nop
008F7021 90 nop
008F7022 90 nop
008F7023 90 nop
008F7024 90 nop
008F7025 90 nop
008F7026 90 nop
008F7027 90 nop
008F7028 90 nop
008F7029 90 nop
008F702A 55 push ebp //stolen code第一句
008F702B 90 nop
008F702C 90 nop
008F702D 90 nop
008F702E 90 nop
008F702F 90 nop
008F7030 90 nop
008F7031 8BEC mov ebp, esp //stolen code第二 句
008F7033 90 nop
008F7034 90 nop
008F7035 90 nop
008F7036 90 nop
008F7037 6A FF push -1 //stolen code第三句
008F7039 90 nop
008F703A 90 nop
008F703B 90 nop
008F703C 90 nop
008F703D 68 D8503F00 push 9550D8 //stolen code第四句
008F7042 90 nop
008F7043 90 nop
008F7044 90 nop
008F7045 68 8C203F00 push 95208C //stolen code第五句
008F704A 90 nop
008F704B 90 nop
008F704C 90 nop
008F704D 90 nop
008F704E 90 nop
008F704F 9C pushfd
008F7050 60 pushad
四.跳过基址重定位,修复内存映像文件
重新加载程序,按Ctrl+G跳到VirtualAlloc:
7C809A81 ker> 8BFF mov edi,edi
7C809A83 55 push ebp
7C809A84 8BEC mov ebp,esp
7C809A86 FF75 14 push dword ptr ss:[ebp+14]
7C809A89 FF75 10 push dword ptr ss:[ebp+10]
7C809A8C FF75 0C push dword ptr ss:[ebp+C]
7C809A8F FF75 08 push dword ptr ss:[ebp+8]
7C809A92 6A FF push -1
7C809A94 E8 09000000 call kernel32.VirtualAllocEx
7C809A99 5D pop ebp
7C809A9A C2 1000 retn 10 //这里按F2设断
同一系统多次运行实例程序,外壳申请的映像空间的地址是一样的,笔者机子当时基址是:9500000,设好VirtualAlloc断点后,
运行程序,当第三次调用VirtualAlloc函数时,返回的值是9500000。
00404EEF FF93 84000000 call [ebx+84] //VirtualAlloc
00404EF5 90 nop 返回这里,eax=950000
00404EF6 90 nop
00404EF7 90 nop
00404EF8 90 nop
00404EF9 90 nop
00404EFA 90 nop
00404EFB 85C0 test eax, eax
00404EFD 90 nop
00404EFE 90 nop
00404EFF 90 nop
00404F00 90 nop
00404F01 90 nop
00404F02 90 nop
00404F03 0F84 B2030000 je 004052BB
下面开始解码过程。
解码后这里处理reloc:
00405234 8B43 10 mov eax, [ebx+10] ; 新imagebase
00405237 90 nop
00405238 90 nop
00405239 90 nop
0040523A 90 nop
0040523B 90 nop
0040523C 2B43 40 sub eax, [ebx+40] ; 求与原来的基址差值,以重定位相关代码要,改成
xor eax,eax
0040523F 90 nop
00405240 90 nop
00405241 90 nop
00405242 90 nop
00405243 FF77 08 push dword ptr [edi+8]
00405246 FF77 04 push dword ptr [edi+4]
00405249 FF73 7C push dword ptr [ebx+7C]
0040524C 50 push eax
0040524D FF73 10 push dword ptr [ebx+10]
00405250 FF53 74 call [ebx+74]
为了脱壳后使base定位在400000h,将0040523C这句改成:
0040523C 33C0 xor eax,eax
接下来就到OEP处,修复Stole code再Dump取程序。
此时取消VirtualAlloc断点,再经过7次内存访问异常,再单步跟踪就可到伪OEP了:
009513CF 64:A1 00000000 mov eax,dword ptr fs:[0]
009513D5 50 push eax
009513D6 64:8925 00000000 mov dword ptr fs:[0],esp
009513DD 83EC 58 sub esp,58
009513E0 53 push ebx
009513E1 56 push esi
009513E2 57 push edi
009513E3 8965 E8 mov dword ptr ss:[ebp-18],esp
009513E6 FF15 44504000 call dword ptr ds:
[405044]
此时查看上面的红字[405044],己不是开始的[955044]了,外壳没有再重定位相关代码与数据。
在修复键入OEP代码前,先按Alt+M打开内存映射窗口,找到基址这个地址(我当前的基址950000),同时记下映像的大小0xB000,点击右键/设置权限/完整权限,这样操作后,就可在OD里键入代码了。
运行LordPE,选中实例进程,先用 Dump Full功能抓取映像,存为dumpedfull.exe。别忘设置LordPE的Options/Full dump:paste header from disk这项,从磁盘文件里取PE的各项信息。不然dumped.exe的程序一些PE头数据是错误的,例如没图标等。
接下来再抓取950000这个基址,大小是0xB000的映像,这个映像就是程序的代码段、数据段。同样用LordPE,在右键选Dump partial,将你程序OEP处的基址填进,大小是0xB000。保存为dumppartial.dmp文件。
下面就利用dumpedfull.exe和dumppartial.dmp构造一个完整的dumped.exe。
1).修正PE头
用LordPE打开dumpedfull.exe,查看区块:
ㄔNumber Name VirtSize RVA PhysSize Offset Flagㄔ
1 00001000 00001000 00001000 00001000 C0000040
2 .rsrc 00001000 00002000 00001000 00002000 C0000040
3 0000C000 00003000 0000C000 00003000 E0000060
我们只需要PE头这部分的数据,就是0x1000以前的数据,用Hex Workshop十六进制工具打开dumpedfull.exe,将光标定在文件头,菜单执行Edit/Select Block,输入1000,选中PE头部分,点击复制。
再用Hex Workshop接着打开dumppartial.dmp文件,这个文件前0x1000字节己被清空,我们要做的就是将dumpedfull.exe文件头部分复制过来。
同样的操作,将光标定在文件头,菜单执行Edit/Select Block,输入1000,选中PE头部分,点击粘贴,将刚才复制的头部数据粘贴过来。
将dumppartial.dmp改名为dumped.exe。
用LordPE打开修正过的dumped.exe,查看区块,点击右键wipe section header,将第2、3项删除。
同时将第1项修正,大小改为0xb000,属性选上“执行代码”,E0000040。
ㄔNumber Name VirtSize RVA PhysSize Offset Flagㄔ
1 .text 0000B000 00001000 0000B000 00001000 E0000040
2).资源修复
用dREAMtHEATER的DT_FixRes工具打开dumpedfull.exe,点击Dump标签,NewRVA填上C000,FileAlignment填上1000,点击Dump Resource,将资源文件提取出来rsrc.bin。
再用LordPE打开dumped.exe,查看区块,右键执行Load section from disk,将资源rsrc.bin导入,同时在数据目录表里修正资源项的RVA为C000.
ㄔNumber Name VirtSize RVA PhysSize Offset Flagㄔ
1 .text 0000B000 00001000 0000B000 00001000 E0000040
2 rsrc.bin 00002000 0000C000 00002000 0000C000 E00000E0
将修复好的dumped.exe先放一边,等获得正确的输入表再修复就可运行了。
五.输入表的修复
重新加载程序,仅设置内存访问异常。当外壳程序VirtualAlloc调用为映像申请内存后,就可以用命令:
D 955000 查看IAT的数据了。
注意:不同机子,VirtualAlloc申请的值会不同,根据前面的分析得知,基址+5000 就是IAT的起始地址。
当第11次异常后,D 955000 查看的数据己有值,因此重新来过,当第10次异常后,停下:
008F6F29 8B02 mov eax, [edx]
中断后数据窗口:
00955000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00955010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
在 00955000选中几个字节,点击鼠标右键,设置“断点/内在写入”
再按Shift+F9通过异常继续执行程序(请不要按F9,否则以后程序可能会出错的),停在如下:
008F76E2 893E mov [esi], edi
别忘取消内存断点。为了让大家看的明白,用花指令去除了垃圾代码,并且将多余的NOP也省略,剩下的代码为:
008F76E2 893E mov [esi], edi//不断这里,将IAT里填充数据
008F76E7 83C7 0C add edi, 0C
008F76EE 83C6 04 add esi, 4
008F76F7 41 inc ecx
008F76FD 3B4D 0C cmp ecx, [ebp+C]
008F7704 ^ 0F82 68FFFFFF jb 008F7672
008F7712 833E 00 cmp dword ptr [esi], 0
008F771B 0F85 7D000000 jnz 008F779E
008F7726 C607 60 mov byte ptr [edi], 60
008F772D 66:895F 01 mov [edi+1], bx
008F7734 8BC1 mov eax, ecx
008F773C 3345 FC xor eax, [ebp-4]
008F7743 66:8947 03 mov [edi+3], ax
008F774A C1CB 10 ror ebx, 10
008F7753 8857 06 mov [edi+6], dl
008F775B 885F 05 mov [edi+5], bl
008F7762 C647 07 E9 mov byte ptr [edi+7], 0E9
008F776A C1CB 10 ror ebx, 10
008F7772 8B45 14 mov eax, [ebp+14]
008F777A 2BC7 sub eax, edi
008F7781 83E8 0C sub eax, 0C
008F7787 8947 08 mov [edi+8], eax
008F7790 893E mov [esi], edi
008F7798 83C7 0C add edi, 0C
008F77A2 8BC7 mov eax, edi
008F77A8 2B45 18 sub eax, [ebp+18]
008F77AF 5F pop edi
008F77B0 5E pop esi
008F77B1 5B pop ebx
008F77B2 8BE5 mov esp, ebp//下面这几句没有花指令,中断可以向下翻,直接F4走出这段代码
008F77B4 5D pop ebp
008F77B5 C2 1400 retn 14
走出上面代码,来到如下(花指令己去除,代码重新排版整理):
008F738D FF73 34 push dword ptr [ebx+34]
008F7390 50 push eax
008F7391 FF76 0C push dword ptr [esi+C]
008F7394 FF75 F8 push dword ptr [ebp-8]
008F7397 E8 09040000 call 008F77A5 //我们从这个CALL出来
008F739F 0145 EC add [ebp-14], eax
008F73AF 8B46 10 mov eax, [esi+10]
008F73B5 8B56 14 mov edx, [esi+14]
008F73BE 0343 10 add eax, [ebx+10]
008F73C4 0353 48 add edx, [ebx+48]
008F73CB FF36 push dword ptr [esi]
008F73CD 53 push ebx
008F73CE 52 push edx
008F73CF 50 push eax
008F73D0 FF76 0C push dword ptr [esi+C]
008F73D3 E8 E4010000 call 008F75BC //普通加密函数
008F73DE 85C0 test eax, eax
008F73E3 0F84 18010000 je 008F7501
008F73F2 837D F0 00 cmp dword ptr [ebp-10], 0
008F73F9 74 43 je short 008F743E //如跳,则这个DLL里没有特殊函数
008F7405 8B46 10 mov eax, [esi+10]
008F740D 8B56 14 mov edx, [esi+14]
008F7414 0343 10 add eax, [ebx+10]
008F741D 0353 48 add edx, [ebx+48]
008F7426 FF75 EC push dword ptr [ebp-14]
008F7429 53 push ebx
008F742A 52 push edx
008F742B 50 push eax
008F742C FF76 0C push dword ptr [esi+C]
008F742F E8 A5020000 call 008F76D9 //特殊加密函数
008F7438 0145 EC add [ebp-14], eax
008F7445 33C0 xor eax, eax
008F744D 8946 0C mov [esi+C], eax
008F7455 8946 10 mov [esi+10], eax
008F745B 83C6 18 add esi, 18
008F7461 FF45 F8 inc dword ptr [ebp-8]
008F7467 FF4D FC dec dword ptr [ebp-4]
008F746F ^ 0F85 05FDFFFF jnz 008F717A
008F747B 33C0 xor eax, eax
008F7481 5F pop edi
008F7482 5E pop esi
008F7483 5B pop ebx
008F7484 8BE5 mov esp, ebp
008F7486 5D pop ebp
008F7487 C3 retn
1.处理普通加密函数
按F7进入008F73D3 call 008F75BC ,这段代码就是外壳为了防止某些函数指针加密出错,这里将加密的指针还原的。
按照以前版本的通用修改方法如下:(花指令己除及中间的NOP指令己忽略)
008F75BC 55 push ebp
008F75BD 8BEC mov ebp, esp
008F75BF 53 push ebx
008F75C0 56 push esi
008F75C1 57 push edi
008F75C7 8B75 10 mov esi, [ebp+10]
008F75D0 8B7D 0C mov edi, [ebp+C]
008F75D7 8B5D 14 mov ebx, [ebp+14]
008F75E4 test word ptr [esi], 20 // test [esi],8
008F75EC je 008F7685 // jnz 008F7685
008F75F8 66:F706 0200 test word ptr [esi], 2
008F7602 75 47 jnz short 008F764B
008F760E 33C0 xor eax, eax
008F7613 66:C706 0400 mov word ptr [esi], 4
008F761E 6A 01 push 1
008F7620 50 push eax
008F7621 FF76 04 push dword ptr [esi+4]
008F7624 50 push eax
008F7625 FF75 18 push dword ptr [ebp+18]
008F7628 FF53 54 call [ebx+54]
008F762F 85C0 test eax, eax
008F7635 0F84 86000000 je 008F76C1 // je 008F7685
008F7641 8907 mov [edi], eax
008F7646 EB 3D jmp short 008F7685
008F7650 66:C706 0400 mov word ptr [esi], 4
008F7659 0FB756 02 movzx edx, word ptr [esi+2]
008F7661 6A 01 push 1
008F7663 52 push edx
008F7664 6A 00 push 0
008F7666 FF76 04 push dword ptr [esi+4]
008F7669 FF75 18 push dword ptr [ebp+18]
008F766C FF53 54 call [ebx+54]
008F7674 85C0 test eax, eax
008F7679 74 0A je short 008F76C1 //je 008F7685
008F767E 8907 mov [edi], eax
008F7688 83C6 08 add esi, 8
008F768F 83C7 04 add edi, 4
008F7698 FF4D 08 dec dword ptr [ebp+8]
008F769E ^ 0F85 3CFFFFFF jnz 008F75E0
这种方法在以前版本有效,但在这个版本就失效了,生成的IAT中有部分函数出错:(在数据窗口,右键/长型/地址显示:)
00405000 >7C80C729 kernel32.lstrcpyA
00405004 >7C81EE79 kernel32.lstrcmpA
00405008 >7C838CB9 kernel32.GetStringTypeA
0040500C >7C80CEC4 kernel32.LCMapStringW
00405010 >7C832E2B kernel32.LCMapStringA
00405014 >7C809CAD kernel32.MultiByteToWideChar
00405018 >00B60048 <====这里放上的是错误函数
0040501C >00B60054 <====这里放上的是错误函数
00405020 >7C9379FD ntdll.RtlReAllocateHeap
00405024 >7C809A81 kernel32.VirtualAlloc
00405028 >7C9305D4 ntdll.RtlAllocateHeap
0040502C >7C81E82A kernel32.GetOEMCP
00405030 >7C809943 kernel32.GetACP
00405034 >7C812BE6 kernel32.GetCPInfo
00405038 >7C80B529 kernel32.GetModuleHandleA
0040503C >7C801EEE kernel32.GetStartupInfoA
00955040 00B6021B <====特殊函数,上面的008F729F call 008F7549 这个函数处理
00955044 00B60225 <====特殊函数
00405048 >00B600D8 <====这里放上的是错误函数
0040504C >7C801E16 kernel32.TerminateProcess
00955050 00B6022F <====特殊函数
00405054 >7C862B8A kernel32.UnhandledExceptionFilter
00405058 >7C80B357 kernel32.GetModuleFileNameA
0040505C >00B60114 <====这里放上的是错误函数
00405060 >00B60120 <====这里放上的是错误函数
00405064 >7C80A0C7 kernel32.WideCharToMultiByte
00405068 >00B60138 <====这里放上的是错误函数
0040506C >00B60144 <====这里放上的是错误函数
00405070 >7C80C6CF kernel32.SetHandleCount
00405074 >7C812CA9 kernel32.GetStdHandle
00405078 >7C811069 kernel32.GetFileType
0040507C >00B60174
00405080 >7C812851 kernel32.GetVersionExA
00405084 >7C811110 kernel32.HeapDestroy
00405088 >7C812929 kernel32.HeapCreate
0040508C >7C809B14 kernel32.VirtualFree
00405090 >7C93043D ntdll.RtlFreeHeap
00405094 >7C957A40 ntdll.RtlUnwind
00405098 >00B601C8 <====这里放上的是错误函数s
0040509C >7C80A480 kernel32.GetStringTypeW
经过分析,问题出在这:
008F75F8 66:F706 0200 test word ptr [esi], 2
008F7602 75 47 jnz short 008F764B
查看ESI指向的数据如下:
008F0984 02 00 6C 00 10 E4 37 3D 02 00 6C 00 16 68 FD 1F l??l栖>
008F0994 02 00 47 00 FA FD D2 A4 02 00 4C 00 2A 39 97 BA G??L??
008F09A4 02 00 4C 00 89 69 EB 25 02 00 4D 00 D6 68 87 83 L榉?M?>
008F09B4 80 00 00 00 04 00 00 00 80 00 00 00 0A 00 00 00 ?..?...
008F09C4 02 00 48 00 25 3C 50 91 02 00 56 00 28 A7 07 B5 H?酐V??
008F09D4 02 00 48 00 5A 56 8D 4C 02 00 47 00 4B AB 0D 19 H??G??
008F09E4 02 00 47 00 6D 2F CD E2 02 00 47 00 BA 5B 49 4E G??G?>
008F09F4 02 00 47 00 ED 52 B7 AF 02 00 47 00 8C 68 8D A0 G??G?>
008F0A04 08 00 00 00 03 00 00 00 08 00 00 00 00 00 00 00 .....
008F0A14 40 00 00 00 03 00 00 00 02 00 54 00 11 04 59 C8 @..TБ?
02:普通函数标志,用008F73D3 call 008F75BC函数解密
08:特殊函数标志,用008F742F call 008F76D9函数恢复
80:特殊函数标志
对于80这个标志,用“008F7498 call [ebx+54]”这个函数也处理,但得到的结果是错误,
猜测作者在Obsidium1.3里新增了一种处理函数方法。
在这,我们还是用以前的方法,将几处代码修改好:
008F75E4 test [esi],8
008F75EC jnz 008F7685
008F75F8 66:F706 0200 test word ptr [esi], 2
008F7602 /75 47 jnz short 008F764B
008F760E 33C0 xor eax, eax //
设断,让程序执行,查看数据面板窗口
008F7613 66:C706 0400 mov word ptr [esi], 4
008F761E 6A 01 push 1
008F7620 50 push eax
008F7621 FF76 04 push dword ptr [esi+4]
008F7624 50 push eax
008F7625 FF75 18 push dword ptr [ebp+18]
008F7628 FF53 54 call [ebx+54]
008F762F 85C0 test eax, eax
008F7635 je 008F7685
008F7641 8907 mov [edi], eax
008F7646 EB 3D jmp short 008F7685
008F7650 66:C706 0400 mov word ptr [esi], 4
008F7659 0FB756 02 movzx edx, word ptr [esi+2]
008F7661 6A 01 push 1
008F7663 52 push edx
008F7664 6A 00 push 0
008F7666 FF76 04 push dword ptr [esi+4]
008F7669 FF75 18 push dword ptr [ebp+18]
008F766C FF53 54 call [ebx+54]
008F7674 85C0 test eax, eax
008F7679 74 0A je 008F7685
008F760E中断后,查看数据面板窗口:
00955000 7C80C729 kernel32.lstrcpyA
00955004 7C81EE79 kernel32.lstrcmpA
00955008 7C838CB9 kernel32.GetStringTypeA
0095500C 7C80CEC4 kernel32.LCMapStringW
00955010 7C832E2B kernel32.LCMapStringA
00955014 7C809CAD kernel32.MultiByteToWideChar
00955018 00B60048 //这个是80标志的函数
……
为了了解代码是如何处理80标志的函数,我们跟进00955018这个IAT地址所指的 00B60048函数。按Ctrl+G打开地址框,
输入0B60048(注意:地址首位如果是字母,别忘加个0):
00B60048 60 pushad //在这按右键新建EIP源,将当前EIP指针指到此处
00B60049 66:BF B55B mov di, 5BB5
00B6004D B2 80 mov dl, 80
00B6004F - E9 E350D9FF jmp 008F5137
除去花指令后的代码:
008F52C7 90 nop
008F52C8 90 nop
008F52C9 90 nop
008F52CA 90 nop
008F52CB 90 nop
008F52CC 0FB6D2 movzx edx, dl
008F52CF 90 nop
008F52D0 90 nop
008F52D1 90 nop
008F52D2 0FB7C7 movzx eax, di
……
008F5399 8D34C6 lea esi, [esi+eax*8] //来到这里,查看ESI
008F53A0 0FB706 movzx eax, word ptr [esi]
008F53AC 83F8 04 cmp eax, 4
008F53B5 0F84 78010000 je 008F5533
008F53C4 83F8 01 cmp eax, 1
008F53CA 0F84 11010000 je 008F54E1
008F53DA 3D 80000000 cmp eax, 80
008F53E3 /0F84 AE070000 je 008F5B97//这里会跳
到008F5209这句查看ESI:
008F09B4 80 00 00 00 04 00 00 00 80 00 00 00 0A 00 00 00 ?..?...
这个就是上面的那个数据表,80就是特殊函数标志,后面的04就是与函数名有关的参数。
008F5BBB 8BD5 mov edx, ebp
008F5BBD 90 nop
008F5BBE 90 nop
008F5BBF 90 nop
008F5BC0 90 nop
008F5BC1 90 nop
008F5BC2 90 nop
008F5BC3 039485 9401EC00 add edx, [ebp+eax*4+EC0194]
008F5BCA 90 nop
008F5BCB 90 nop
008F5BCC 90 nop
008F5BCD 90 nop
008F5BCE FFE2 jmp edx
继续走,来到如下:
008F5D23 90 nop
008F5D24 90 nop
008F5D25 90 nop
008F5D26 8B83 C4010000 mov eax, [ebx+1C4]
008F5D2C 90 nop
008F5D2D 90 nop
008F5D2E 90 nop
008F5D2F 90 nop
008F5D30 90 nop
008F5D31 90 nop
008F5D32 ^ E9 E7FDFFFF jmp 008F5B1E
查看 ebx+1C4,会发现一组代码的地址,原来程序是根据不同的函数参数确定函数名,你可以Ctrl+G输入下面的地址查看代码:
008F0390 008F223D 008F22C7 008F23AB 008F232D
008F03A0 008F2423 008F2489 008F24EF 008F2555
008F03B0 008F2633 008F269F 008F2705 008F27E9
008F03C0 008F285B 008F25C1 008F2777 008F28C7
008F03D0 008F2159 008F21CB 008F20A1 008F20FD
008F03E0 008F1FD5 008F203B 008F1E73 008F1EF1
008F03F0 008F1F63 008F2951 008F29C3 008F2A47
008F0400 008F2AC5 008F2B2B 008F1C98 00000000
上面每个地址是一段代码的入口,处理某个API函数,下面要做的就是找到这些代码共同出口点,为补丁程序做准备。继续:
008F5B26 8038 CC cmp byte ptr [eax], 0CC
008F5B2E 74 1B je short 008F5B4B
008F5B38 894424 1C mov [esp+1C], eax
008F5B3C 90 nop
008F5B3D 90 nop
008F5B3E 90 nop
008F5B3F 90 nop
008F5B40 61 popad
008F5B41 90 nop
008F5B42 90 nop
008F5B43 90 nop
008F5B44 90 nop
008F5B45 FFE0 jmp eax //根据参数不同,跳到不同代码里获取函数地址
这次跟进eax为008F2423值,jmp 008F2423地址,来到:
008F2423 55 push ebp
008F2424 8BEC mov ebp, esp
008F2426 83EC 08 sub esp, 8
008F2429 53 push ebx
008F242A 56 push esi
008F242B 57 push edi
008F242C E8 00000000 call 008F2431
008F2431 5B pop ebx
008F2432 8BF3 mov esi, ebx
008F2434 8B9B 4FF4FFFF mov ebx, [ebx-BB1]
008F243A 8975 F8 mov [ebp-8], esi
008F243D 8DBE A7070000 lea edi, [esi+7A7]
008F2443 BE 04000000 mov esi, 4
008F2448 8B07 mov eax, [edi]
008F244A 85C0 test eax, eax
008F244C 74 14 je short 008F2462
008F244E 8D55 FC lea edx, [ebp-4]
008F2451 FF75 08 push dword ptr [ebp+8]
008F2454 52 push edx
008F2455 FFD0 call eax
008F2457 83F8 01 cmp eax, 1
008F245A 74 21 je short 008F247D
008F245C 83C7 04 add edi, 4
008F245F 4E dec esi
008F2460 ^ 75 E6 jnz short 008F2448
008F2462 8B75 F8 mov esi, [ebp-8]
008F2465 FF75 08 push dword ptr [ebp+8]
008F2468 FFB6 57090000 push dword ptr [esi+957] //esi+957指向的就是正确的函数名,按F7跟进
008F246E FF93 84000000 call [ebx+84]
008F2474 5F pop edi
008F2475 5E pop esi
008F2476 5B pop ebx
008F2477 8BE5 mov esp, ebp
008F2479 5D pop ebp
008F247A C2 0400 retn 4
008F2468这个CALL进入会来到外壳自己的领空,其地址是4xxxxx:
0040C633 /EB 01 jmp short 0040C636 //在这可以用mov eax, [esp+4] 指令取得函数的地址
0040C635 |5D pop ebp
0040C636 \EB 01 jmp short 0040C639
0040C638 04 60 add al, 60
0040C63A EB 04 jmp short 0040C640
这就意味着不同的函数,最终都会来到这里,这为补丁程序取得函数名地址提供了可能。
补丁思路:
1.处理标志为80函数时,跳到IAT指定的地址处执行
2.补丁0040C633,让其跳会IAT普通函数处理代码里。
当然如果特殊函数不多,你不怕麻烦可以不用这方法来处理,手动跟踪那些没被识别出的函数,找到正确的函数指针。
按这思路构造的代码如下,红色部分是处理标志为80的函数:
(由于每次跟踪,外壳这段代码地址不同,下面这段代码是第二次运行抓取的,因此同一指令和上文提供的地址不同,大家以汇编代码来识别)
008F7564 test word ptr ds:[esi],8
008F756C jnz 008F7605
008F7578 test word ptr ds:[esi],2
008F7582 jnz short 008F75CB
008F7584 test word ptr ds:[esi],80 //如是80标志的函数就处理
008F7589 je short 008F7605
008F758B mov eax,dword ptr ds:[esi+4]
008F758E pushad
008F758F mov edx,954FF8 //954FF8地址是内存中的一空白,这里是IAT前面部分,用以保存临时变量
008F7594 mov dword ptr ds:[edx],esp //将esp的值保存在954FF8这个地址变量里
008F7596 jmp dword ptr ds:[edi] //跳到IAT指定的地址运行
008F7598 nop
008F7599 nop
008F759A nop //IAT指定地址运行结束后,让它返回到这里
008F759B mov eax,dword ptr ss:[esp+4] //取得函数地址
008F759F mov esp,dword ptr ds:[954FF8] //恢复堆栈
008F75A5 mov dword ptr ss:[esp+1C],eax //eax的结果放进堆栈里,以便popad时,会放到eax寄存器里
008F75A9 popad
008F75AA mov word ptr ds:[esi],4
008F75AF mov dword ptr ds:[edi],eax //将得到的函数地址放进IAT
008F75B1 nop
008F75B2 nop
……(中间全是NOP指令)
008F75C6 EB 3D jmp short 008F7605 //继续处理下一个函数
008F75D0 66:C706 0400 mov word ptr ds:[esi],4
008F75D9 0FB756 02 movzx edx,word ptr ds:[esi+2]
008F75E1 6A 01 push 1
008F75E3 52 push edx
008F75E4 6A 00 push 0
008F75E6 FF76 04 push dword ptr ds:[esi+4]
008F75E9 FF75 18 push dword ptr ss:[ebp+18]
008F75EC FF53 54 call dword ptr ds:[ebx+54]
008F75F4 85C0 test eax,eax
008F75F9 74 0A je short 008F7605
008F75FE 8907 mov dword ptr ds:[edi],eax
008F7608 83C6 08 add esi,8
008F760F 83C7 04 add edi,4
008F7618 FF4D 08 dec dword ptr ss:[ebp+8]
008F761E ^ 0F85 3CFFFFFF jnz 008F7560
008F7629 33C0 xor eax,eax //在这设个断点,执行完上述代码,80标志的函数就还原了
008F762F 40 inc eax
008F7636 5F pop edi
008F7637 5E pop esi
008F7638 5B pop ebx
008F7639 5D pop ebp
008F763A C2 1400 retn 14
上面这段代码有几点要注意的:
第一个就是954FF8地址,我这取的是IAT(IAT起始地址是955000)前面那个空间,
由于不同系统分配的内存地址可能不同,所以你得根据你的情况将这个值定好。
第二个就是008F7582~008F75C6之间代码别忘全部NOP掉,用新增的代码替换。
第三就是处理函数的每个代码入口参数不一样,因此必须将ESP保存到一变量了。
上面修改代码二进制如下,实际操作时,可以用OD的二进制粘贴功能将代码复制过去,别忘了还要根据你的情况修正954FF8变量地址。
66 F7 06 80 00 74 7A 8B 46 04 60 BA F8 4F 95 00 89 22 FF 27 90 90 90 8B 44 24 04 8B 25 F8 4F 95 00 89 44 24 1C 61 66 C7 06 04 00 89 07另外,再按Ctrl+G跳到0040C633 ,这是所有代码的出口,将其改成:
0040C633 - E9 62AF4E00 jmp 008F759 //别忘设个断,所有函数处理完毕回调用这里的代码,你再撤消选择将其还原
2.处理特殊加密函数
走出上面的普通函数,进入特殊函数处理的CALL:
008F7424 90 nop
008F7425 90 nop
008F7426 FF75 EC push dword ptr [ebp-14]
008F7429 53 push ebx
008F742A 52 push edx
008F742B 50 push eax
008F742C FF76 0C push dword ptr [esi+C]
008F742F E8 A5020000 call 008F76D9//进入这个处理特殊函数的CALL
F7走进:
008F76D9 55 push ebp
008F76DA 8BEC mov ebp, esp
008F76DC 53 push ebx
008F76DD 56 push esi
008F76DE 57 push edi
008F76DF 8B75 10 mov esi, [ebp+10]
…………
008F771A 83F8 03 cmp eax, 3
008F771D 74 12 je short 008F7731
008F771F 83F8 04 cmp eax, 4
008F7722 ^ 75 CA jnz short 008F76EE
008F7724 8B45 14 mov eax, [ebp+14]
008F7727 8B90 E8000000 mov edx, [eax+E8]
008F772D 8917 mov [edi], edx
008F772F ^ EB BD jmp short 008F76EE
008F7731 8B45 14 mov eax, [ebp+14]
008F7734 68 C5B1662D push 2D66B1C5
008F7739 6A 00 push 0
008F773B FF50 18 call [eax+18]
008F773E 50 push eax
008F773F 53 push ebx
008F7740 E8 98020000 call 008F79DD
008F7745 53 push ebx
008F7746 E8 19020000 call 008F7964
008F774B 8BCB mov ecx, ebx
008F774D 8D5C03 01 lea ebx, [ebx+eax+1]
008F7751 8BC1 mov eax, ecx
008F7753 EB 2B jmp short 008F7780
008F7755 8B45 14 mov eax, [ebp+14]
008F7758 68 0F1ACF4C push 4CCF1A0F
008F775D 6A 00 push 0
008F775F FF50 18 call [eax+18] //解密函数,按F7进入
{
0040CE93 90 nop
0040CE94 90 nop
0040CE95 90 nop
0040CE96 60 pushad
0040CE9B 83EC 04 sub esp, 4
0040CEA3 E8 00000000 call 0040CEA8
0040CEAC 5B pop ebx
0040CEB1 8BEB mov ebp, ebx
0040CEB7 8B5B E7 mov ebx, [ebx-19]
0040CEC4 8B4424 28 mov eax, [esp+28]
0040CED4 33C9 xor ecx, ecx
0040CEDC 8B8483 98000000 mov eax, [ebx+eax*4+98]
0040CEE9 8B5424 2C mov edx, [esp+2C]
0040CEF3 51 push ecx
0040CEF4 51 push ecx
0040CEF5 51 push ecx
0040CEF6 52 push edx
0040CEF7 50 push eax
0040CEF8 FF53 54 call [ebx+54] //Obsidium自己实现的GetProcAddress
0040CF01 85C0 test eax, eax
0040CF06 0F84 56020000 je 0040D162
0040CF0C 90 nop //
改成 MOV [EDI], EAX 同时设断
0040CF0D 90 nop
0040CF11 8BF0 mov esi, eax
}
008F7762 /EB 1C jmp short 008F7780
008F7764 |8B45 14 mov eax, [ebp+14]
008F7767 |68 A41A86D0 push D0861AA4
008F776C |6A 00 push 0
008F776E |FF50 18 call [eax+18]
008F7771 |EB 0D jmp short 008F7780
008F7773 |8B45 14 mov eax, [ebp+14]
008F7776 |68 E313B41D push 1DB413E3
008F777B |6A 00 push 0
008F777D |FF50 18 call [eax+18]
008F7780 \C603 B8 mov byte ptr [ebx], 0B8
008F7783 8943 01 mov [ebx+1], eax
008F7786 8B45 14 mov eax, [ebp+14]
008F7789 8B90 A4010000 mov edx, [eax+1A4]
008F778F 8D43 0A lea eax, [ebx+A]
008F7792 2BD0 sub edx, eax
008F7794 C643 05 E9 mov byte ptr [ebx+5], 0E9
008F7798 8953 06 mov [ebx+6], edx
008F779B 891F mov [edi], ebx //
这句NOP掉,因为处理完,会用其地址覆盖[edi](edi指向IAT)
在:0040CF0C 90 nop //改成 MOV [EDI], EAX //同时设断
这一行,会中断三次,得到三个特殊函数:
GetCommandLineA
GetVersion
GetCurrentProcess
所有的特殊函数处理完毕后,再执行,程序返回的EDI值己不指向IAT了,因此必须将MOV [EDI], EAX 这句删除,还原代码。
处理完kernel32.dll后将处理user32.dll,程序会再循环回到那个解密普通函数的CALL程序再次执行的。user32.dll没有特殊函数,出来后:
08F73D3 E8 E4010000 call 008F75BC //普通加密函数
008F73DE 85C0 test eax, eax
008F73E3 0F84 18010000 je 008F7501
008F73F2 837D F0 00 cmp dword ptr [ebp-10], 0
008F73F9 74 43 je short 008F743E //如跳,则这个DLL里没有特殊函数
008F7405 8B46 10 mov eax, [esi+10]
008F740D 8B56 14 mov edx, [esi+14]
008F7414 0343 10 add eax, [ebx+10]
008F741D 0353 48 add edx, [ebx+48]
008F7426 FF75 EC push dword ptr [ebp-14]
008F7429 53 push ebx
008F742A 52 push edx
008F742B 50 push eax
008F742C FF76 0C push dword ptr [esi+C]
008F742F E8 A5020000 call 008F76D9 //特殊加密函数
008F7438 0145 EC add [ebp-14], eax
008F7445 33C0 xor eax, eax
008F744D 8946 0C mov [esi+C], eax
008F7455 8946 10 mov [esi+10], eax
008F745B 83C6 18 add esi, 18
008F7461 FF45 F8 inc dword ptr [ebp-8]
008F7467 FF4D FC dec dword ptr [ebp-4]
008F746F ^ 0F85 05FDFFFF jnz 008F717A
008F747B 33C0 xor eax, eax //将断点设在,处理所有的DLL就会中断在这
008F7481 5F pop edi
008F7482 5E pop esi
008F7483 5B pop ebx
008F7484 8BE5 mov esp, ebp
008F7486 5D pop ebp
008F7487 C3 retn
所有的DLL处理完毕查看一下IAT:
00955000 7C80C729 kernel32.lstrcpyA
00955004 7C81EE79 kernel32.lstrcmpA
00955008 7C838CB9 kernel32.GetStringTypeA
0095500C 7C80CEC4 kernel32.LCMapStringW
00955010 7C832E2B kernel32.LCMapStringA
00955014 7C809CAD kernel32.MultiByteToWideChar
00955018 7C801D77 kernel32.LoadLibraryA
0095501C 7C80AC28 kernel32.GetProcAddress
00955020 7C9379FD ntdll.RtlReAllocateHeap
00955024 7C809A81 kernel32.VirtualAlloc
00955028 7C9305D4 ntdll.RtlAllocateHeap
0095502C 7C81E82A kernel32.GetOEMCP
00955030 7C809943 kernel32.GetACP
00955034 7C812BE6 kernel32.GetCPInfo
00955038 7C80B529 kernel32.GetModuleHandleA
0095503C 7C801EEE kernel32.GetStartupInfoA
00955040 7C812C8D kernel32.GetCommandLineA
00955044 7C8114AB kernel32.GetVersion
00955048 00B600D8 //这个没找出,这个函数的特殊标志是40
0095504C 7C801E16 kernel32.TerminateProcess
00955050 7C80E00D kernel32.GetCurrentProcess
00955054 7C862B8A kernel32.UnhandledExceptionFilter
00955058 7C80B357 kernel32.GetModuleFileNameA
0095505C 7C81DC3F kernel32.FreeEnvironmentStringsA
00955060 7C81485F kernel32.FreeEnvironmentStringsW
00955064 7C80A0C7 kernel32.WideCharToMultiByte
00955068 7C81CC23 kernel32.GetEnvironmentStringsA
0095506C 7C812C78 kernel32.GetEnvironmentStringsW
00955070 7C80C6CF kernel32.SetHandleCount
00955074 7C812CA9 kernel32.GetStdHandle
00955078 7C811069 kernel32.GetFileType
0095507C 7C81486A kernel32.GetEnvironmentVariableA
00955080 7C812851 kernel32.GetVersionExA
00955084 7C811110 kernel32.HeapDestroy
00955088 7C812929 kernel32.HeapCreate
0095508C 7C809B14 kernel32.VirtualFree
00955090 7C93043D ntdll.RtlFreeHeap
00955094 7C957A40 ntdll.RtlUnwind
00955098 7C810F9F kernel32.WriteFile
0095509C 7C80A480 kernel32.GetStringTypeW
009550A0 00B601E0
009550A4 77D1A8AD USER32.wsprintfA
009550A8 77D6AC1E USER32.GetDlgItemTextA
009550AC 77D1BE71 USER32.EnableWindow
009550B0 77D1DA60 USER32.SetFocus
009550B4 77D31F4C USER32.MessageBeep
009550B8 77D21324 USER32.LoadIconA
009550BC 77D3C2BF USER32.SendDlgItemMessageA
009550C0 77D1DAEA USER32.DestroyWindow
009550C4 77D24816 USER32.GetDlgItem
009550C8 77D2F39A USER32.SendMessageA
009550CC 77D26250 USER32.EndDialog
009550D0 77D3B11C USER32.DialogBoxParamA
最后还有一个函数没获得:00955048 00B600D8
因此手工跟。在00B600D8新建EIP:
00B600D8 60 pushad
00B600D9 66:BF A15B mov di, 5BA1
00B600DD B2 80 mov dl, 80
00B600DF - E9 C351D9FF jmp 008F52A7
……
008F53D2 83F8 40 cmp eax, 40
008F53D5 90 nop
008F53D6 90 nop
008F53D7 90 nop
008F53D8 0F84 AB010000 je 008F5589
008F57E9 6A 00 push 0
008F57EB 6A 45 push 45
008F57ED 6A 00 push 0
008F57EF 68 CC971025 push 251097CC
008F57F4 FF37 push dword ptr [edi]
008F57F6 FF53 54 call [ebx+54]//经过这个CALL,获得EAX 7C81CAA2 kernel32.ExitProcess
执行到
008F747B 33C0 xor eax, eax 这句后,就可不执行了,接下来运行ImportREC修复输入表。
将IAT的地址RVA:555000,大小D4填进,Get Imports后就能得到正确的输入表。
Obsidium这种重定位的壳,ImportREC计算是按脱壳前文件的基址,例如这个实例脱壳前的基址是00400000,IAT的VA地址是955000,则ImportREC里的RVA填955000-400000=555000。
如果外壳重定位的基址低于外壳基址,计算方法还是一样的。例假设这个实例重定位后的映像基址是3F0000,则IAT的VA为3F5000,ImportREC里的RVA填3F5000-00400000=FFFF5000(多余的F舍去)。
这样ImportREC就能工作了,但最后Fix Dump会出错,提示“Invalid dump file!Can't match RVA to Offset in the dump file”
解决办法很简单,此时点击Save Tree,将IAT保存到文件里,打开这个文本文件:
FThunk: FFFF5000 NbFunc: 00000028
1 FFFF5000 kernel32.dll 03AD lstrcpy
1 FFFF5004 kernel32.dll 03A7 lstrcmp
1 FFFF5008 kernel32.dll 01B0 GetStringTypeA
1 FFFF500C kernel32.dll 0235 LCMapStringW
将FFFF用0000全部替换:
FThunk: 00005000 NbFunc: 00000028
1 00005000 kernel32.dll 03AD lstrcpy
1 00005004 kernel32.dll 03A7 lstrcmp
1 00005008 kernel32.dll 01B0 GetStringTypeA
最后ImportREC打开进程,点击Load Tree,这次Fix Dump就能成功修复文件了。
注:也可尝试在Options里将“Use PE Header From Disk”默认的选项去除,直接用内存中的基址进行计算。
kanxue
看雪技术论坛 www.pediy.com
2005.12.18