首页
社区
课程
招聘
学习脱壳穿山甲Armadillo 4.30a 的详细笔记
发表于: 4天前 2120

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

4天前
2120

学习脱壳穿山甲Armadillo 4.30a 的详细笔记
原文https://bbs.kanxue.com/thread-250085.htm
需要用到的工具,Xp虚拟机,ArmaFP(查看壳的版本),dillodie1.6(脱壳机),吾爱OD,IDA8.3,脱壳程序(见附件),Beyond Compare(比较工具),LordPE,improtRec。
0x01. 了解 Code Splicing
首先将脱壳程序GameJack.exe拖到打开的ArmaFP32中

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

1
2
3
4
5
6
7
8
9
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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                  ; 跳回去

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

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

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

0x03 寻找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,

1
2
3
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,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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,

0x04 修复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运行,在

1
2
3
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函数被替换了,接着我们在这一句,

1
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做对比,这边是对比后的样子,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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,

1
2
3
4
5
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一边分析,

1
2
3
4
5
6
7
8
9
10
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这个地址,

1
2
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运行,程序断在了

1
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为结尾标志,有很多个组,每组构成了一次代码拼接,一组有四项,

1
2
3
4
5
6
7
8
9
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后面的字节码的代码,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
.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单步一直走,走到

1
00DEA75F  ^\E9 C8FEFFFF     jmp 00DEA62C

这条指令就是跳回继续循环,再F7一下,走到这边

1
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所在的这段代码会访问申请到的内存基址,通过这个表计算出数值再填入刚才那个表。

1
2
3
4
5
6
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的字节码,不需要再减一。
下面是对这段代码的部分关键注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
.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

1
00DEA78D   /76 32           jbe short 00DEA7C1

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

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

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

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

先用StrongOD申请一个内存,
将00DEB615这个地址的代码改为jmp 01690000,
在刚刚申请的内存中这样改

1
2
3
4
5
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,

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

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

1
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,成功脱壳。


[课程]FART 脱壳王!加量不加价!FART作者讲授!

上传的附件:
收藏
免费 2
支持
分享
最新回复 (1)
雪    币: 67
活跃值: (726)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
很精彩
1天前
0
游客
登录 | 注册 方可回帖
返回
//