专栏名称: 看雪学苑
致力于移动与安全研究的开发者社区,看雪学院(kanxue.com)官方微信公众帐号。
目录
相关文章推荐
看雪学苑  ·  FART 脱壳王:突破加壳APP的层层保护 ·  3 天前  
隆众资讯订阅号  ·  直播预告 | ... ·  5 天前  
隆众资讯订阅号  ·  直播预告 | ... ·  5 天前  
51好读  ›  专栏  ›  看雪学苑

学习脱壳穿山甲Armadillo 4.30a 的详细笔记

看雪学苑  · 公众号  · 互联网安全  · 2024-09-23 18:01

正文

需要用到的工具,Xp虚拟机,ArmaFP(查看壳的版本),dillodie1.6(脱壳机),吾爱OD,IDA8.3,脱壳程序(见附件),Beyond Compare(比较工具),LordPE,improtRec。




了解 Code Splicing


首先将脱壳程序GameJack.exe拖到打开的ArmaFP32中。



可以看到保护设置只有Strategic Code Splicing ,中文意思就是叫做代码拼接,刚看到这个意思表示有疑问,网上搜了一大堆都是讲怎么处理,没有讲他的实现是怎么样的,于是我用MASM32写了一个非常简单的小程序让Armadillo 4.4加壳,保护设置只选Code Splicing。

00401031 E9 CA 4F 0D 00 jmp loc_4D6000 ; WinMain函数头,Jmp后面的地址在脱壳后新加的区段,
00401031 sub_401031 endp ; 未脱壳的话是在壳程序申请的一段内存中,
00401031 ; 对付 Code Splicing 就要把这段代码拷贝到
00401031 ; 我们程序新开辟一个区段,然后将jmp后面的地址
00401031 ; 修改成我们的开辟的区段对应的地址中
00401036 loc_401036: ; CODE XREF: .spliced:loc_4D601F↓j
00401036 52 push edx ; 垃圾指令
00401037 5A pop edx ; 垃圾指令
00401038 E8 50 00 00 00 call sub_40108D
004D6000 loc_004D6000: ; CODE XREF: sub_401031↑j
004D6000 F7 D0 not eax ; 垃圾指令
004D6002 0F CF bswap edi ; 垃圾指令
004D6004 66 91 xchg ax, cx ; 垃圾指令
004D6006 7A 02 jp short loc_004D600A ; 垃圾指令
004D6008 7A 17 jp short near ptr loc_004D601F+2 ; 垃圾指令
004D600A
004D600A loc_004D600A: ; CODE XREF: 004D6006↑j
004D600A 0F CF bswap edi ; 垃圾指令
004D600C 0F CF bswap edi ; 垃圾指令
004D600E 7B 02 jnp short loc_004D6012 ; 垃圾指令
004D6010 7B 09 jnp short loc_004D601B ; 垃圾指令
004D6012
004D6012 loc_004D6012: ; CODE XREF: 004D600E↑j
004D6012 66 91 xchg ax, cx ; 垃圾指令
004D6014 0F CF bswap edi ; 垃圾指令
004D6016 F7 D0 not eax ; 垃圾指令
004D6018 55 push ebp ; 被偷走的正常指令
004D6019 8B EC mov ebp, esp ; 被偷走的正常指令
004D601B
004D601B loc_004D601B: ; CODE XREF: 004D6010↑j
004D601B 6A 00 push 0 ; 被偷走的正常指令
004D601D 6A 00 push 0 ; 被偷走的正常指令
004D601F
004D601F loc_004D601F: ; CODE XREF: 004D6008↑j
004D601F E9 12 B0 F2 FF jmp loc_401036 ; 跳回去




设置OD


分析完Code Splicing 的原理后开始第一步设置好调试器,StrongOD这样设置,Skip Some Exceptions 这个中文意思是跳过一些异常,Od也有自带的忽略异常,但是使用OD的忽略异常调试不了,所以按照以下设置:




还有硬件断点需要保护起来,OD插件-》Drx-Protect-》Config-》Drx勾上。



补充一下硬件断点对话框的使用。




寻找OEP


看过上一篇文章的朋友知道,Asprotect壳找OEP用最后一次异常代码段下断点法,在这里是行不通的,我这边没有使用原文的方法,我的思路是,程序都会把自己的代码放到内存中去执行,接着解密完宿主程序之后跳回宿主程序的OEP去执行,所以第一步先找到包含执行代码的这段内存,我们就在命令行下HE kernel32.VirtualAlloc,下硬件断点是因为有对函数头部F2断点的检测,接着F9,断下看看堆栈中VirtualAlloc 的参数 Size 等于 65000 这个大小像是一个可执行文件的大小,我猜测这应该是为存放代码开辟的一段内存,然后ALT+F9,可以看到EAX等于0,返回值等于0,说明没成功。继续F9跟前面一样看看EAX,这时候EAX,有值了,我的是00DC0000,你们的可能不一样,查看00DC0000这个地址的内存数据都是0,这时候说明内存申请成功,接下去就是要写入数据了,然后我们在命令行给这个地址下个硬件写入断点HW 00DC0000 ,F9,程序断在了004E43B3。

004E43B1 /72 29 jb short GameJack.004E43DC
004E43B3 |F3:A5 rep movs dword ptr es:[edi],dword ptr ds>
004E43B5 |FF2495 C8444E00 jmp dword ptr ds:[edx*4+0x4E44C8]

查看此时ECX的值,很小,显然这一句代码运行完,这段内存还没写完,还有其他地方往这边写入数据,这时候不去找其他地方往这片内存写数据,直接F9运行,接着断下这时候大概率这片内存已经数据完全写入了,删除硬件断点,ALT+M,查看内存找到00DC0000 这个段下F2断点。



程序断下了,在00DC0000 这个段内。


接着在代码段下F2断点,F9,这时候断在了00DF0338,
00DF0338 8B0C3A mov ecx,dword ptr ds:[edx+edi]
00DF033B 5B pop ebx ; GameJack.004E54D3
00DF033C 03D7 add edx,edi ; GameJack.00402000
00DF033E A1 A410E000 mov eax,dword ptr ds:[0xE010A4]
00DF0343 3148 58 xor dword ptr ds:[eax+0x58],ecx
00DF0346 A1 A410E000 mov eax,dword ptr ds:[0xE010A4]
00DF034B 3148 58 xor dword ptr ds:[eax+0x58],ecx
00DF034E A1 A410E000 mov eax,dword ptr ds:[0xE010A4]
00DF0353 8B16 mov edx,dword ptr ds:[esi]
00DF0355 8B48 74 mov ecx,dword ptr ds:[eax+0x74]
00DF0358 3348 6C xor ecx,dword ptr ds:[eax+0x6C]
00DF035B 3348 50 xor ecx,dword ptr ds:[eax+0x50]
00DF035E 030D BC10E000 add ecx,dword ptr ds:[0xE010BC] ; GameJack.00400000
00DF0364 85D2 test edx,edx
00DF0366 75 18 jnz short 00DF0380
00DF0368 8B50 70 mov edx,dword ptr ds:[eax+0x70]
00DF036B FF76 18 push dword ptr ds:[esi+0x18]
00DF036E 3350 6C xor edx,dword ptr ds:[eax+0x6C]
00DF0371 FF76 14 push dword ptr ds:[esi+0x14]
00DF0374 3350 04 xor edx,dword ptr ds:[eax+0x4]
00DF0377 FF76 10 push dword ptr ds:[esi+0x10]
00DF037A 2BCA sub ecx,edx
00DF037C FFD1 call ecx

然后F7一直单步走,当走到00DF039B的时候:

再单步一次来到了代码段,也就是OEP:




修复IAT


找到OEP之后我们看看有没有IAT加密,往下寻找,找到:

有硬编码的地址的指令,dd 0x47B4F8 。

可以看到:


两个模块之前的分隔00000000,变成了00DD6E36,可以看到IAT被做了手脚,到目前为止Code Splicing 线索还没有找到,从开头了解到类似JMP XXXXXXXX,XXXXXXXX代表一个内存地址,我们先修复IAT,接着再寻找Code Splicing,首先先确定一下IAT,IAT是一段存放API地址的一段数据,不同的模块之前用00000000隔开,这段IAT开始的地方是47A000,结束的地方是47B990。大小0x1990,我们CTRL+F2重新运行程序,在IAT开头下个硬件写入断点HW 47A000看看壳对IAT做了什么,F9运行,程序断下了,ALT+F9执行到用户代码,在数据窗口可以看到,在00DECC56断下:



继续F9运行,在
00DECC4E 8B8D 68CAFFFF mov ecx,dword ptr ss:[ebp-0x3598]
00DECC54 8908 mov dword ptr ds:[eax],ecx
00DECC56 8B85 10D9FFFF mov eax,dword ptr ss:[ebp-0x26F0] ; GameJack.0047A000

这时候47A000的值为00DDA6F4,dd 00DDA6F4 数据窗口中跟随,然后反汇编:


可以看到IAT函数被替换了,接着我们在这一句:
00DECC54 8908 mov dword ptr ds:[eax],ecx

下个F2断点,F9运行,断下,重复这样的操作,IAT在一个一个的解密,这时候我们要做到知己知彼,百战不胜。就需要看看IAT哪里加密了,因为最后我们还原iat后面需要对比是不是都还原了,这个壳已经有脱壳机了,所以我们可以拿到他的脱壳后的文件,打开dilloDIE.exe。



把 For Strategic Code Splicing 勾上,点击UnPack,选择目录下Gamejack,打开,这时候脱壳机对其脱壳,完成后会得到GameJack.exe.dDIE.exe,这个就是脱壳后的文件了。接下去就是分别打开脱壳后的,和没脱壳的,程序在OD上运行,然后复制他们的IAT表用Beyond Compare做对比,这边是对比后的样子。

0047A000 77DAEAF4 advapi32.RegCreateKeyExA
0047A018 77DCD5BB advapi32.RegCreateKeyA
0047A71C 7C8137D9 kernel32.FindFirstFileA
0047A720 7C801E16 kernel32.TerminateProcess
0047A724 7C810B8E kernel32.SetFilePointer
0047A72C 7C8111DA kernel32.GetVersion
0047A754 7C801D4F kernel32.LoadLibraryExA
0047A764 7C82F7A0 kernel32.FormatMessageA
0047A76C 7C812F1D kernel32.GetCommandLineA
0047A778 7C80FE82 kernel32.GlobalUnlock
0047A77C 7C80FF19 kernel32.GlobalLock
0047A784 7C80FD2D kernel32.GlobalAlloc
0047A78C 7C810D87 kernel32.WriteFile
0047A790 7C80180E kernel32.ReadFile
0047A798 7C810A77 kernel32.GetFileSize
0047A7A0 7C809B47 kernel32.CloseHandle
0047A7A4 7C801A24 kernel32.CreateFileA
0047A7A8 7C80ABDE kernel32.FreeLibrary
0047A7AC 7C80ADA0 kernel32.GetProcAddress
0047A7B0 7C801D77 kernel32.LoadLibraryA
0047A7C4 7C809915 kernel32.GetACP
0047A7CC 7C81CDDA kernel32.ExitProcess
0047A7E4 7C80B6A1 kernel32.GetModuleHandleA
0047A7EC 7C80BE89 kernel32.FindResourceA
0047A7F4 7C80CC97 kernel32.SetHandleCount
0047A804 7C813093 kernel32.IsDebuggerPresent
0047A81C 7C81CE03 kernel32.TerminateThread
0047A874 7C80945C kernel32.CreateFileMappingA
0047A878 7C80B905 kernel32.MapViewOfFile
0047A87C 7C80B974 kernel32.UnmapViewOfFile
0047B6B0 77D3212B USER32.GetWindowTextA
0047B7F0 77D5058A USER32.MessageBoxA

这些就是被加密的IAT函数,被加密后这些API地址会变成别的地址。所以咱们需要还原IAT,就需要了解壳是如何还原IAT的,首先需要找到往IAT写入数据的地方。



地址00DECC54(这个地址在你们机器上可能不一样)上mov dword ptr ds:[eax],ecx 这个指令就是,经过多次调试发现,IAT最终解密都是在这个地址完成的,这样我们可以重新运行程序,在0047A000 这个地址下个硬件写入断点,F9运行,这时第一次断下。



接着就要执行00DECC54上mov dword ptr ds:[eax],ecx这个指令,最终还原IAT了,我们可以在OD上看看00DECC54上代码有没被解密,可以看到:



还没解密,因为这个壳反调试方面做得还算很严密的,很多代码都是运行前是加密的,运行后又加密,而且还有校验,我们下个硬件写入断点HW 00DECC54,F9运行,程序断下,我们删掉硬件断点,在00DECC54上mov dword ptr ds:[eax],ecx,这条指令上下个F2断点,F9运行,程序断下,这时候F7单步一下,数窗口中查看EAX的值。



这就是恢复IAT表的开始,不是自上而下恢复的,这个很重要,记住这个地址0047A8C0,接下来我们找个没有恢复IAT函数的地址分析,就选0047A018这个地址,下断点的话要下HW 0047A014,F9运行,程序断在了00DECC56。

00DECC54 8908 mov dword ptr ds:[eax],ecx ;往IAT某个项目写入
00DECC56 8B85 10D9FFFF mov eax,dword ptr ss:[ebp-0x26F0]
00DECC5C 83C0 04 add eax,0x4 ;计算出下一个需要写入的IAT
00DECC5F 8985 10D9FFFF mov dword ptr ss:[ebp-0x26F0],eax
00DECC65 ^ E9 4DFCFFFF jmp 00DEC8B7 ; 跳回起点解密下一个IAT

F8单步走,走到00DECC5F的时候,此时Eax等于0047A018,这时候就是要去还原0047A018这个地址的IAT,当然这边是没有还原而是用别的函数代替,我们的任务是要还原,所以我们一边F8一边分析。

00DECA99 FF15 8863DF00 call dword ptr ds:[0xDF6388] ; msvcrt._stricmp 字符串比较函数,比较函数名是不是一样
00DECA9F 59 pop ecx
00DECAA0 59 pop ecx
00DECAA1 85C0 test eax,eax
00DECAA3 75 11 jnz short 00DECAB6 函数名相同则不跳
00DECAA5 8B85 58C2FFFF mov eax,dword ptr ss:[ebp-0x3DA8]
00DECAAB 8B40 08 mov eax,dword ptr ds:[eax+0x8]
00DECAAE 8985 68CAFFFF mov dword ptr ss:[ebp-0x3598],eax
00DECAB4 EB 02 jmp short 00DECAB8 运行到这原IAT将被替换
00DECAB6 ^ EB 9C jmp short 00DECA54 函数名不同则回循环

我猜测将00DECAA3 75 11 jnz short 00DECAB6 ,这个地址的代码改成
00DECAA3 75 11 jmp short 00DECAB6 ,是不是就可以还原IAT了,这时我们重新载入程序,在刚刚记住的这个地址0047A8C0下硬件写入断点HW 0047A8C0,F9运行,程序断下,接着F9运行,程序断下,这时程序刚好还原第一个IAT,这时我们将将00DECAA3 75 11 jnz short 00DECAB6 ,这个地址的代码改成 00DECAA3 EB 11 jmp short 00DECAB6 ,现在我们有个疑问代码是改了,但是我们不知道IAT什么时候全部被还原完了,根据壳的特性,代码执行前会先解密,执行完又会加密,我们就在00DECAA3 75 11 jmp short 00DECAB6,这个代码处,下个硬件写入断点HR 00DECAA3 ,F9运行,程序断在了00DC13C4这个地址。

00DC13C2 8B12 mov edx,dword ptr ds:[edx]
00DC13C4 8955 E4 mov dword ptr ss:[ebp-0x1C],edx

这段代码是访问 00DECAA3 这个地址的字节码,取出字节码放到EDX,可以看到此时EDX的值是858B11EB,其中EB是我们修改后的,没修改前是75,所以我们把EDX,改成
858B1175,这样就可以骗过壳的校验,同时我们还要把00DECAA3 EB 11 jmp short 00DECAB6 ,这个地址的代码改回 00DECAA3 75 11 jnz short 00DECAB6 ,删除所有断点,这时候我们按照前面找OEP的方法,在代码段下F2断点,F9运行,程序断下,单步到OEP,这时候我们再翻看IAT,可以看到没有被还原的统统被还原了,只是不同模块之前的分隔00000000,变成了别的地址,这个不影响程序运行,这个时候是不是忘了Code Splicing没有修复,这个先不着急,我们先按照正常脱壳流程走一边,先用ImportRec获取IAT表,再用LordPe Dump出来,再用ImportRec获取的IAT表,修复Dump出来的文件,最后用再用LordPe 重建PE。



其中重新排列文件不要勾,我们再将修复好的文件载入OD看看,这时候排除了IAT的因素的干扰,F9运行,
OD上显示已终止,可以看到现在EIP是035CDBB4,通过堆栈回溯,找到这段代码!



正如我们开始的时候学习的Code Splicing一样,通过JMP XXXXXXXX ,跳到一个内存地址执行被偷走的代码,我们先记下这个地址 004274F3 ,然后重新载入原来的程序,通过OD查看004274F3这个地址代码情况,可以看到都是0,我们就在004274F3这个地方下硬件写入断点 HW 004274F3,程序断下,再ALT+F9返回到用户代码,再倒回去看地址004274F3的代码情况。



代码是正常的,跟随这个JMP。



也是个正确的,这时我们需要追根溯源,回到EIP所在的代码,可以看到上一句执行的是msvcrt.memcpy函数,这是个代码复制函数,我们可以通过他的参数,可以找到他的源头,我们对这个函数的地址00DEB833,下个硬件执行断点HE 00DEB833,因为OD硬件断点有记忆功能,如果重新载入程序下个内存上不存在的地址的断点是下不了的,下完断点我们重新载入程序。



这边有一个问题需要注意一下,图中圈起来的红色圆点是打开和关闭这个硬断对话框的,另外一个圈起来的像减号的是隐藏这一行断点的按钮,点一下他就变加号了,同时这个断点消失了,就不能断下了,再点一下加号,这一行又出现了,最好每次重新载入程序点这个QUIT按钮关闭,再点红色按钮打开,同时下什么断点必须在这个对话框里面看的到,否则可能无效,说的好像比较听不懂,但是你们多试几次就知道了,回归正题,F9运行,程序断在了:
00DEB833 E8 2EA00000 call 00DF5866 ; jmp 到 msvcrt.memcpy

通过堆栈可以看到,从01600020这个地址复制到00402000,数据大小是00078000,通过计算004274F3对应的地址是1625513。



我们也在1625513这个地址下硬件写入断点HW 1625513,重新载入程序,F9运行,程序断下,接着再F9,再断下,再F8单步一下,运行到这边:


命令行dd 01625513 ,然后数据窗口选中01625513这一行反汇编,可以看到:



接着删除所有断点,再在01625513这个地址下硬件写入断点,F9运行,程序断下,再F9运行,断下,然后再F8单步一下,此时EIP在这边:



这时查看一下01625513这个地址的代码:



E9后面都是0,这应该是要写入东西的,删除所有断点,在
01625514下个硬件写入断点HW 01625514,F9运行,现在EIP在00DEB613。



下面我们分析一下这段代码处理的表的结构,这个结构是以00F97950为起始点,0为结尾标志,有很多个组,每组构成了一次代码拼接,一组有四项。

00F97950 0040299F 代码段jmpXXXXXXXX的位置
00F97954 038CD65D 代码段jmp后面的字节码
00F97958 03CD0009 内存中jmpXXXXXXXX的位置
00F9795C FC732996 内存中jmp后面的字节码

00F97960 004029C4 代码段jmpXXXXXXXX的位置
00F97964 038CD645 代码段jmp后面的字节码
00F97968 03CD003B 内存中jmpXXXXXXXX的位置
00F9796C FC732989 内存中jmp后面的字节码

下面是利用上面的数据修改jmp后面的字节码的代码:

.text:00DEB53C mov eax, [ebp-2A4Ch] ; 第一次取出表头地址00F97950 开始 一次循环 加 8个字节
.text:00DEB542 cmp dword ptr [eax], 0 ; 判断是否为0,为0就是结束的修改
.text:00DEB545 jz loc_DEB628
.text:00DEB54B mov eax, [ebp-2A4Ch]
.text:00DEB551 mov eax, [eax]
.text:00DEB553 mov [ebp-2A54h], eax
.text:00DEB559 mov eax, [ebp-2A4Ch]
.text:00DEB55F add eax, 4
.text:00DEB562 mov [ebp-2A4Ch], eax
.text:00DEB568 mov eax, [ebp-2A4Ch]
.text:00DEB56E mov eax, [eax]
.text:00DEB570 mov [ebp-2A50h], eax
.text:00DEB576 mov eax, [ebp-2A4Ch]
.text:00DEB57C add eax, 4
.text:00DEB57F mov [ebp-2A4Ch], eax
.text:00DEB585 mov eax, [ebp-27F4h] ; 取出400000
.text:00DEB58B add eax, [ebp-2A38h] ; 得到00402000
.text:00DEB591 cmp [ebp-2A54h], eax
.text:00DEB597 jbe short loc_DEB615 ; 取出第三项当做地址
.text:00DEB599 mov eax, [ebp-27F4h]
.text:00DEB59F add eax, [ebp-2A38h] ; 得到00402000
.text:00DEB5A5 add eax, [ebp-2A34h]
.text:00DEB5AB cmp [ebp-2A54h], eax
.text:00DEB5B1 jnb short loc_DEB615 ; 取出第三项当做地址
.text:00DEB5B3 mov eax, [ebp-27F4h]
.text:00DEB5B9 add eax, [ebp-2A38h]
.text:00DEB5BF mov ecx, [ebp-2A54h]
.text:00DEB5C5 sub ecx, eax ; 获取到的第一项的地址减去402000得到偏移地址
.text:00DEB5C7 mov [ebp-2A58h], ecx
.text:00DEB5CD mov al, byte_E0107C
.text:00DEB5D2 mov [ebp-55F8h], al
.text:00DEB5D8 movzx eax, byte ptr [ebp-55F8h]
.text:00DEB5DF test eax, eax
.text:00DEB5E1 jz short loc_DEB5FF ; eax等于 01600020
.text:00DEB5E3 mov eax, [ebp-2A50h]
.text:00DEB5E9 xor eax, [ebp-2A48h]
.text:00DEB5EF mov ecx, [ebp-2A30h]
.text:00DEB5F5 add ecx, [ebp-2A58h]
.text:00DEB5FB mov [ecx], eax
.text:00DEB5FD jmp short loc_DEB613
.text:00DEB5FF ; ---------------------------------------------------------------------------
.text:00DEB5FF
.text:00DEB5FF loc_DEB5FF: ; CODE XREF: sub_DEB53C+A5↑j
.text:00DEB5FF mov eax, [ebp-2A30h] ; eax等于 01600020
.text:00DEB605 add eax, [ebp-2A58h] ; 将偏移偏移再加上EAX得到要修改字节的地址
.text:00DEB60B mov ecx, [ebp-2A50h] ; 取出第二项的值当做字节码
.text:00DEB611 mov [eax], ecx ; 修改代码段jmp指令后面的地址的字节码
.text:00DEB613
.text:00DEB613 loc_DEB613: ; CODE XREF: sub_DEB53C+C1↑j
.text:00DEB613 jmp short loc_DEB623 ; 跳回继续循环修改
.text:00DEB615 ; ---------------------------------------------------------------------------
.text:00DEB615
.text:00DEB615 loc_DEB615: ; CODE XREF: sub_DEB53C+5B↑j
.text:00DEB615 ; sub_DEB53C+75↑j
.text:00DEB615 mov eax, [ebp-2A54h] ; 取出第三项当做地址
.text:00DEB61B mov ecx, [ebp-2A50h] ; 取出第四项当做字节码
.text:00DEB621 mov [eax], ecx ; 修改内存中jmp指令后面的地址的字节码
.text:00DEB623
.text:00DEB623 loc_DEB623: ; CODE XREF: sub_DEB53C:loc_DEB613↑j
.text:00DEB623 jmp sub_DEB53C ; 跳回继续循环修改
.text:00DEB628 ; ---------------------------------------------------------------------------

调试跟踪这段代码可以在运行前在00DEB53C下个硬件执行断点。这个表的每一组除了第一项,其他项都需要根据申请的内存基址计算出来的,所以我们再对这个表的起点00F97950下个硬件写入断点,重新运行程序,F9运行,程序断下,F9运行,再断下。



这时候EIP是00DEA6CC,然后执行命令dd 00F97950 ,看看数据窗口,可以看到:



可以看到第一项和第二项都有数据了后面都是零,所以断下的这段代码就是在构造这个表,让我们来看看这个表是如何构造的这又需要用到另外一个表,这时我们F7单步一直走,走到
00DEA75F ^\E9 C8FEFFFF jmp 00DEA62C

这条指令就是跳回继续循环,再F7一下,走到这边
00DEA62C 8B85 4CD7FFFF mov eax,dword ptr ss:[ebp-0x28B4]

可以给这条指令下硬件执行断点,重新载入程序,F9运行,程序断下,F7单步一下,此时EAX等于00F81008,所以表的起点是00F81008。接下来跟踪就可以从头开始看,这个表一直被填充,先看下代码拼接处执行的流程图,其中地址c和地址d之间的代码块包含被JMp指令覆盖的代码和垃圾指令。

地址A和B之间相差5个字节,所以地址B可以通过地址A求出,刚刚说到构造这个表前面还有一个表,这个表每个数组有三项,其中第一项是地址A的偏移地址,第二项是地址D的偏移地址,第三项是地址C的偏移地址,其中代码段基址固定是400000,而内存基址是不断变化的,需要申请之后获得,EIP所在的这段代码会访问申请到的内存基址,通过这个表计算出数值再填入刚才那个表。

00F81014 000029C4 A的偏移地址 - 1
00F81018 0000000D D的偏移地址
00F8101C 0000003B C的偏移地址
00F81020 000029F6 A的偏移地址 - 1
00F81024 0000003F D的偏移地址
00F81028 00000052 C的偏移地址

A的偏移地址 - 1 为什么还要减一,因为记录的不是jmp指令的偏移,而是E9 后面第一个字节的地址,方便等下修改E9的字节码,不需要再减一。

下面是对这段代码的部分关键注释。

.text:00DEA62C mov eax, [ebp-28B4h] ; 取出读取表的位置 和
.text:00DEA62C ; 表的结尾00f87944比较
.text:00DEA62C ; 到结尾就结束循环
.text:00DEA632 cmp eax, [ebp-28A0h]
.text:00DEA638 jnb loc_DEA764
.text:00DEA63E mov eax, [ebp-28B4h]
.text:00DEA644 mov eax, [eax] ; 第一项的偏移地址放到EAX
.text:00DEA646 mov [ebp-29D4h], eax ; 保存eax
.text:00DEA64C mov eax, [ebp-28B4h]
.text:00DEA652 add eax, 4
.text:00DEA655 mov [ebp-28B4h], eax
.text:00DEA65B mov eax, [ebp-28B4h]
.text:00DEA661 mov eax, [eax] ; 取出第二项的值放到EAX
.text:00DEA663 mov [ebp-29D8h], eax ; 保存
.text:00DEA669 mov eax, [ebp-28B4h]
.text:00DEA66F add eax, 4
.text:00DEA672 mov [ebp-28B4h], eax
.text:00DEA678 mov eax, [ebp-28B4h]
.text:00DEA67E mov eax, [eax] ; 取出第三项的值放到EAX
.text:00DEA680 mov [ebp-29D0h], eax
.text:00DEA686 mov eax, [ebp-28B4h]
.text:00DEA68C add eax, 4
.text:00DEA68F mov [ebp-28B4h], eax
.text:00DEA695 mov eax, lpAddress ; 关键,申请的内存基址
.text:00DEA69A add eax, [ebp-29D8h] ; 计算得到地址D
.text:00DEA6A0 mov ecx, [ebp-29D4h] ; 第一项的值放到ecx
.text:00DEA6A6 mov edx, [ebp-27F4h] ; 基址400000放到edx
.text:00DEA6AC lea ecx, [edx+ecx+4] ; 计算JMP指令后面的字节码1
.text:00DEA6B0 sub eax, ecx ; 计算JMP指令后面的字节码2
.text:00DEA6B2 mov [ebp-29CCh], eax ; 存放字节码
.text:00DEA6B8 mov eax, [ebp-27F4h] ; 基址400000放到eax
.text:00DEA6BE add eax, [ebp-29D4h] ; 计算得到地址A
.text:00DEA6C4 mov ecx, [ebp-28ACh] ; 得到表格数组的第一项目地址
.text:00DEA6CA mov [ecx], eax ; 第一项写入
.text:00DEA6CC mov eax, [ebp-28ACh]
.text:00DEA6D2 add eax, 4
.text:00DEA6D5 mov [ebp-28ACh], eax
.text:00DEA6DB mov eax, [ebp-28ACh]
.text:00DEA6E1 mov ecx, [ebp-29CCh]
.text:00DEA6E7 mov [eax], ecx ; 第二项写入
.text:00DEA6E9 mov eax, [ebp-28ACh]
.text:00DEA6EF add eax, 4
.text:00DEA6F2 mov [ebp-28ACh], eax
.text:00DEA6F8 mov eax, [ebp-29D4h] ; 得到jmp指令相对基址的偏移
.text:00DEA6FE mov ecx, [ebp-27F4h] ; 得到基址400000
.text:00DEA704 lea eax, [ecx+eax+4] ; 得到jmp指令下一句指令地址
.text:00DEA708 mov ecx, [ebp-29D0h]
.text:00DEA70E mov edx, lpAddress ; 关键,申请的内存基址
.text:00DEA714 lea ecx, [edx+ecx+4] ; 计算JMP指令后面的字节码1
.text:00DEA718 sub eax, ecx ; 计算JMP指令后面的字节码2
.text:00DEA71A mov [ebp-29CCh], eax ; 存放 结果
.text:00DEA720 mov eax, lpAddress ; 关键,申请的内存基址
.text:00DEA725 add eax, [ebp-29D0h]
.text:00DEA72B mov ecx, [ebp-28ACh]
.text:00DEA731 mov [ecx], eax ; 第三项写入
.text:00DEA733 mov eax, [ebp-28ACh]
.text:00DEA739 add eax, 4
.text:00DEA73C mov [ebp-28ACh], eax ; 存放第四项的地址
.text:00DEA742 mov eax, [ebp-28ACh]
.text:00DEA748 mov ecx, [ebp-29CCh]
.text:00DEA74E mov [eax], ecx ; 第四项写入
.text:00DEA750 mov eax, [ebp-28ACh]
.text:00DEA756 add eax, 4
.text:00DEA759 mov [ebp-28ACh], eax
.text:00DEA75F jmp loc_DEA62C ; 跳回循环
.text:00DEA764 ; ---------------------------------------------------------------------------
.text:00DEA764
.text:00DEA764 loc_DEA764: ; CODE XREF: sub_DE9ED4+764↑j
.text:00DEA764 mov eax, [ebp-28ACh]
.text:00DEA76A and dword ptr [eax], 0
.text:00DEA76D mov eax, [ebp-28ACh]
.text:00DEA773 add eax, 4
.text:00DEA776 mov [ebp-28ACh], eax
.text:00DEA77C call ds:GetTickCount
.text:00DEA782 sub eax, [ebp-28B0h]
.text:00DEA788 cmp eax, 0BB8h
.text:00DEA78D jbe short loc_DEA7C1 ; 时间反调试改为jmp,走过这条指令再改回来

上面分析的代码可能有的地方有点错误,将就着看吧,写到这边已经有点晕了,就当大家都看懂了,之前文章,是修改那个关键内存的地址为下面这个代码段。

当然这个代码段刚好放的下,如果放不下我们就要,自己加个区段了,说到这边小明又要问了,IAT不是又要加个区段给他吗,IAT的话后面来,新的区段的基址就等于这个程序最后区段的地址加上大小,算出来等于7d4000,接着继续使用OD,在00DEA62C下个硬件执行断点,重新载入程序,程序断下,找到下面这一行



其中0xE0AFB0这个地址就是存放申请的内存地址的,记住现在这个地址等下需要,我们把他修改为7d4000,然后删除所有断点,在:


00DEA78D 下个硬件执行断点,F9运行,程序断下,在数据窗口找到0xE0AFB0,右键撤销选择处修改,这么做就是修改前面的表格能够适合等下我们加的基址为7d4000的区段,这时EIP为00DEA78D。

00DEA78D /76 32 jbe short 00DEA7C1

将jbe改为jmp,f7单步走过这条代码,再选中这条代码,右键撤销选择处修改,这个时候我们查看第一次的那个表。

00F97950 0040299F
00F97954 003D165D
00F97958 007D4009 这个地址现在还不存在
00F9795C FFC2E996

需要将其修改为基址为02E10000对应的地址,因为等下我们需要用lordPE拷贝这段内存,作为基址为7d4000这个区段的数据。删除所有断点,在00DEB53C下个硬件执行断点,HE 00DEB53C,F9运行,程序断下,删除断点,这时我们需要打个补丁修改第三项的地址。

00DEB615 8B85 ACD5FFFF mov eax,dword ptr ss:[ebp-0x2A54]
00DEB61B 8B8D B0D5FFFF mov ecx,dword ptr ss:[ebp-0x2A50]

先用StrongOD申请一个内存。



将00DEB615这个地址的代码改为jmp 01690000,
在刚刚申请的内存中这样改。
 
01690000 8B85 ACD5FFFF mov eax,dword ptr ss:[ebp-0x2A54] 还原刚才被覆盖的指令
01690006 2D 00407D00 sub eax,0x7D4000 减去区段基址得到偏移
0169000B 05 0000B903 add eax,0xAAA AAA为刚才记住的内存基址
01690010 - E9 06B675FF jmp 00DEB61B 跳回执行
01690015 90 nop

接着我们删除所有断点,在00DEB628下个硬件执行断点HE 00DEB628。

00DEB623 ^\E9 14FFFFFF jmp 00DEB53C
00DEB628 6A 01 push 0x1
00DEB62A 58 pop eax ; 00E00A58

F9运行,程序断下,再将刚才打补丁的地方还原。

00DEB615 8B85 ACD5FFFF mov eax,dword ptr ss:[ebp-0x2A54]

删除所有断点,然后在代码段下F2断点,F9运行,F7一直单步走到OEP,现在用lordPE找到我们的脱壳进程右键选择区域转存,找到我们刚才记住的内存地址,右键转存,名字就叫abc,选中这个进程右键修正进程大小,再右键完全转存,再用lordPE编辑这个刚才转存的exe文件,打开区段,选中区段表中的一项,右键从磁盘载入段,选中我们刚才转存的abc文件,打开,区段载入成功,接着点保存,接着用OD载入我们修复好的iat的那个文件,打开importREC,附加到那它上,点击iat自动搜索,点击获取导入表,点击显示无效的函数,选中右键剪切指针,再点击修正转储,选择刚才转储的exe文件,双击,最后用lordPE重建PE,成功脱壳。




看雪ID:学逆向混口饭

https://bbs.kanxue.com/user-home-1004848.htm

*本文为看雪论坛精华文章,由 学逆向混口饭 原创,转载请注明来自看雪社区



# 往期推荐

1、Alt-Tab Terminator注册算法逆向

2、恶意木马历险记

3、VMP源码分析:反调试与绕过方法

4、Chrome V8 issue 1486342浅析

5、Cython逆向-语言特性分析



球分享

球点赞

球在看



点击阅读原文查看更多