首页
论坛
课程
招聘
[原创]脱壳之未知加密壳
2021-12-29 10:34 26081

[原创]脱壳之未知加密壳

2021-12-29 10:34
26081

一、简单分析

1.大致浏览寻找OEP地址,填充IAT的地址,获取API的地址,初步编写通用脚本

①OEP
图片描述

 

②填充IAT地址
图片描述

 

③获取API地址
图片描述

 

将地址填入脚本,进行测试
图片描述
图片描述

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
// 1.定义变量
MOV dwOEP,0047148B
MOV dwGetAPI,001E1914
MOV dwWriteIAT,001E0897
 
// 2. 清除环境
BC    // 清除所有软件断点
BPHWC // 清除所有硬件断点
BPMC  // 清除内存断点
 
// 3. 设置断点
BPHWS dwOEP, "x" //当执行到此地址时产生中断.
BPHWS dwGetAPI, "x" //当执行到此地址时产生中断.
BPHWS dwWriteIAT, "x" //当执行到此地址时产生中断.
 
// 4. 循环
LOOP0:
  RUN // F9 
  CMP dwGetAPI,eip 
  JNZ CASE1 
  MOV dwTMP,eax
  JMP LOOP0
CASE1:
  CMP dwWriteIAT,eip   
  JNZ CASE2
  MOV [edx],dwTMP       //MOV DWORD PTR DS:[EDX],EAX
  JMP LOOP0
CASE2:
  CMP dwOEP,eip 
  JNZ LOOP0 
  MSG "OEP已到达"

2.再次运行脚本,发现获取API和填充IAT的地址无效了,说明代码地址发生变化。

一般来说,地址随机又两种情况,一是随机基址,二是代码所在处是在申请的内存空间中,这种情况下解决方法就是找到代码基址,然后计算偏移,根据偏移在代码处下断点。
显然这个地方地址随机是因申请了内存导致的。
图片描述

 

所以可以在VirtualAlloc处下断点,经过动态调试,发现在VirtualAlloc处断下的位置有很多出,最开始的一处栈回溯之后,代码地址是程序的模块中,推测这个地方申请的内存空间就是修复IAT代码的基地址,将之前的代码偏移,减去基址之后加上偏移,代码与之前一样,所以这个地方就是获取代码基址的地方。
图片描述
图片描述
图片描述

 

分析后故修改脚本如下:
图片描述

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
// 1.定义变量
MOV dwOEP,0047148B
MOV dwGetAPI,1914
MOV dwWriteIAT,0897
MOV dwBase,0047A37F 
 
// 2. 清除环境
BC    // 清除所有软件断点
BPHWC // 清除所有硬件断点
BPMC  // 清除内存断点
 
// 3. 设置断点
BPHWS dwOEP, "x" //当执行到此地址时产生中断.
BPHWS dwBase, "x" //当执行到此地址时产生中断.
 
// 4. 循环
LOOP0:
  RUN // F9    
  CMP dwBase,eip 
  JNZ CASE0   
  ADD dwGetAPI,eax      // 加上基地址
  ADD dwWriteIAT,eax    // 加上基地址             
  BPHWS dwGetAPI, "x"   // 下断点
  BPHWS dwWriteIAT, "x" // 下断点    
  BPHWC dwBase          // 清除硬件断点
  JMP LOOP0   
CASE0:
  CMP dwGetAPI,eip 
  JNZ CASE1 
  MOV dwTMP,eax
  JMP LOOP0
CASE1:
  CMP dwWriteIAT,eip   
  JNZ CASE2
  MOV [edx],dwTMP       //MOV DWORD PTR DS:[EDX],EAX
  JMP LOOP0
CASE2:
  CMP dwOEP,eip 
  JNZ LOOP0 
  MSG "OEP已到达"

3.其他方法

先获取以下基地址,在我们已知的填充IAT和获取API的地址下硬件执行断点
图片描述

 

程序断下后,发现EAX中保存的是API地址
图片描述

 

设置RUN跟踪,让EIP等于填充IAT的时候暂停,Ctrl+F7自动步入
图片描述

 

查看RUN跟踪
图片描述

 

重点关注7开头的数据,可能就是一个AIP地址
图片描述

 

直到这个位置EDX中仍然保存的是API地址
图片描述

 

解密思路:将API地址保存到某一个不用的寄存器中,然后在填充IAT的时候,把IAT直接填到EDX指向的内存中
图片描述
修改代码:

1
2
3
4
5
001E14DC       8BDA        MOV EBX,EDX          user32.BeginPaint
001E14DE        90             NOP
001E14DF        90             NOP
 
001E0895       891A        MOV DWORD PTR DS:[EDX],EBX

下断点运行
图片描述
图片描述
图片描述
DUMP文件并使用IMPREC修复
图片描述

 

将手工操作转为脚本

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
// 1.定义变量
MOV dwOEP,0047148B
MOV dwPatch1,14DC
MOV dwPatch2,0895
MOV dwBase,0047A37F 
 
// 2. 清除环境
BC    // 清除所有软件断点
BPHWC // 清除所有硬件断点
BPMC  // 清除内存断点
 
// 3. 设置断点
BPHWS dwOEP, "x" //当执行到此地址时产生中断.
BPHWS dwBase, "x" //当执行到此地址时产生中断.
 
// 4. 循环
LOOP0:
  RUN // F9    
  CMP dwBase,eip 
  JNZ CASE0   
  ADD dwPatch1,eax      // 加上基地址
  ADD dwPatch2,eax    // 加上基地址             
  BPHWS dwPatch1, "x"   // 下断点 
  BPHWC dwBase          // 清除硬件断点
  JMP LOOP0   
CASE0:
  CMP dwPatch1,eip 
  JNZ CASE1 
  FILL dwPatch1,4,90           //NOP 4个字节   
  ASM dwPatch1,"MOV EBX,EDX" //将当前指令修改为 MOV DWORD PTR DS:[EDI],EAX
  ASM dwPatch2,"MOV DWORD PTR DS:[EDX],EBX"   
  BPHWC dwPatch1
  JMP LOOP0
CASE1:
  CMP dwOEP,eip 
  JNZ LOOP0 
  MSG "OEP已到达"

二、单步跟踪

将程序载入OD,先查看程序入口,发现有标准的push指令(pushad/pushfd),故采用ESP定律寻找OEP
图片描述

 

使用ESP定律单步至470A036,就对ESP下断点,然后将程序运行起来
图片描述

 

程序断在popfd下面,之后一般单步几下就可以到达OEP
图片描述

 

到达OEP,根据经验可以看出这应该是VC6.0或者易语言程序,向下看可以发现第一个调用的函数CALL[XXX]中的函数地址被加密了,即IAT被加密了
图片描述

 

而解密IAT的一般方法就是在IAT函数表上下硬件写入断点。
观察OEP附件的函数调用,根据经验来说,VC6.0程序调用的第一个函数应该是GetVersion,但现在能看到的是一个随机的函数地址,像是在申请内存地址
图片描述

 

F7步入,继续单步跟踪275039地址中的代码,可以发现,原本IAT的函数地址被存放到了一个地址中,代码通过计算地址,获取到了GetVersion的函数地址,之后调用了GetVersion函数
图片描述
图片描述

 

程序的加密函数,GetVersion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
00275039      68 0A000080     PUSH 0x8000000A      ;push一个随机数,占位,到时会存储API地址
0027503E      53              PUSH EBX             ;保存寄存器环境
0027503F      57              PUSH EDI             ;保存寄存器环境 
00275040      E8 00000000     CALL 00275045
00275045      5B              POP EBX
00275046      81EB 0C104000   SUB EBX,0x40100C    ;
0027504C      81C3 24104000   ADD EBX,0x401024    ;SUB和ADD可以化解为一条指令
00275052      8BFB            MOV EDI,EBX         ; EDI=EBX=0027505D
00275054      8B3F            MOV EDI,DWORD PTR DS:[EDI] ; 从EDI中获取 API地址
00275056      897C24 08       MOV DWORD PTR SS:[ESP+0x8],EDI  ; 将API地址存入堆栈,修改的是刚才的占位地址
0027505A      5F              POP EDI             ;恢复寄存器
0027505B      5B              POP EBX             ;恢复寄存器
0027505C      C3              RETN                ;返回到api地址
0027505D      C744F6 76 00000>MOV DWORD PTR DS:[ESI+ESI*8+0x76],>    ;存储的API地址
 
有些指令可以化解,比如
00275046      81EB 0C104000   SUB EBX,0x40100C
0027504C      81C3 24104000   ADD EBX,0x401024
化解之后就是 ADD EBX,0x18

生成加密的IAT函数大概步骤如下:
1.获取原始IAT函数地址,存放在一定位置
使用LoadLibraryA/W,GetProcAddress函数
2.申请空间,构造新的IAT函数
使用VirtualAlloc申请空间,拷贝代码
3.根据原始IAT函数地址计算加密值,隐藏真实地址
计算类似GetVersion函数中的代码中的x值
sub edx,x;
add ebc,x;
4.将新IAT函数地址写入IAT
填充地址到IAT

 

加密函数

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
00274986    / EB 73           JMP SHORT 002749FB
002749FB      4C              DEC ESP
002749FC      E8 DBFFFFFF     CALL 002749DC
002749DC      8D6424 04       LEA ESP, DWORD PTR SS : [ESP + 0x4]
002749E0      4C              DEC ESP
002749E1      EB 51           JMP SHORT 00274A34
00274A34      4C              DEC ESP
00274A35      E8 D1FFFFFF     CALL 00274A0B
00274A0B      8D6424 04       LEA ESP, DWORD PTR SS : [ESP + 0x4]
00274A0F      4C              DEC ESP
00274A10    ^ EB E4           JMP SHORT 002749F6
002749F6      83EC 04         SUB ESP, 0x4
002749F9      EB 2A           JMP SHORT 00274A25
00274A25      50              PUSH EAX
00274A26    ^ EB DD           JMP SHORT 00274A05
00274A05      51              PUSH ECX
00274A06      E8 12000000     CALL 00274A1D
00274A1D      8D6424 04       LEA ESP, DWORD PTR SS : [ESP + 0x4]
00274A21      8BC8            MOV ECX, EAX
00274A23    ^ EB B3           JMP SHORT 002749D8
002749D8      03C4            ADD EAX, ESP
002749DA      EB 36           JMP SHORT 00274A12
00274A12      2BC1            SUB EAX, ECX
00274A14      E8 0F000000     CALL 00274A28
00274A28      8D6424 04       LEA ESP, DWORD PTR SS : [ESP + 0x4]
00274A2C      8958 08         MOV DWORD PTR DS : [EAX + 0x8] , EBX
00274A2F    ^ E9 6EFFFFFF     JMP 002749A2
002749A2      59              POP ECX
002749A3      E9 92000000     JMP 00274A3A
00274A3A      58              POP EAX
00274A3B      E8 85FFFFFF     CALL 002749C5
002749C5      8D6424 04       LEA ESP, DWORD PTR SS : [ESP + 0x4]
002749C9      6A 00           PUSH 0x0
002749CB    ^ EB D2           JMP SHORT 0027499F
0027499F      50              PUSH EAX
002749A0      EB 50           JMP SHORT 002749F2
002749F2      03C4            ADD EAX, ESP
002749F4    ^ EB 9B           JMP SHORT 00274991
00274991      2B0424          SUB EAX, DWORD PTR SS : [ESP]
00274994      E8 EFFFFFFF     CALL 00274988
00274988      8D6424 04       LEA ESP, DWORD PTR SS : [ESP + 0x4]
0027498C      8978 04         MOV DWORD PTR DS : [EAX + 0x4] , EDI
0027498F      EB 41           JMP SHORT 002749D2
002749D2      58              POP EAX
002749D3      E8 0B000000     CALL 002749E3
002749E3      8D6424 04       LEA ESP, DWORD PTR SS : [ESP + 0x4]
002749E7      E8 00000000     CALL 002749EC
002749EC      5B              POP EBX
002749ED      E8 C7FFFFFF     CALL 002749B9
002749B9      8D6424 04       LEA ESP, DWORD PTR SS : [ESP + 0x4]
002749BD      81EB 66104000   SUB EBX, 0x401066
002749C3      EB 54           JMP SHORT 00274A19
00274A19      8BFB            MOV EDI, EBX
00274A1B    ^ EB 8B           JMP SHORT 002749A8
002749A8      81C7 C0104000   ADD EDI, 0x4010C0
002749AE      EB 51           JMP SHORT 00274A01
00274A01      8B3F            MOV EDI, DWORD PTR DS : [EDI]
00274A03    ^ EB AB           JMP SHORT 002749B0
002749B0      897C24 08       MOV DWORD PTR SS : [ESP + 0x8] , EDI
002749B4      E9 87000000     JMP 00274A40
00274A40      5F              POP EDI
00274A41    ^ E9 53FFFFFF     JMP 00274999
00274999      5B              POP EBX
0027499A      E8 2E000000     CALL 002E49CD
002749CD      8D6424 04       LEA ESP, DWORD PTR SS : [ESP + 0x4]
002749D1      C3              RETN

根据推测以及对壳shell部分IAT的操作,我们大致可以推出,无论加密不加密IAT,壳其实都会填充IAT,只是加密IAT会填充加密之后的函数。
所以现在只要能找到加密前IAT函数地址以及填充IAT的地方,并且能够在填充IAT时将加密前的函数地址写入,那IAT就相当于完成了解密。
故我们接下来分析的两个关键点就是写入IAT的地方和加密前IAT函数地址出现的地方,对这两个关键点进行破解和解密即可。

 

综上所诉,下面就从写入IAT的地方开始分析。
首先,在原始OEP处,GetVersion函数的IAT处下写入断点,重新运行程序。
图片描述

 

程序断下,找到了填充IAT的位置
图片描述

 

一般来说,写入IAT的地方应该是一个循环,在这个循环中应该包括加载模块、获取函数地址等操作。
所以我们可以在LoadLiibraryA/W和GetProcAddress两个函数上下软件断点,在写入IAT处下一行代码下硬件执行断点。
注:壳中的代码一般都是解压、解密出来的,一般地址不可靠

 

继续单步跟踪分析,发现代码计算出了一个地址,从这个地址获取了一个4字节的数,且没有规律,一般来说,这种值就是hash值了。
图片描述

 

继续单步跟踪分析,发现其获取了Kernel32模块基地址
图片描述
继续单步跟踪分析,访问了数据目录表
图片描述

 

继续单步跟踪分析,又发现获取了导出函数字符串,结合上下文分析,推测代码是在获取导出函数字符串,求字符串的hash值,再与刚才获取的hash值进行对比。
图片描述

 

继续跟踪发现程序加载了函数字符串的每一个字节,并且进行了计算
图片描述

 

程序求函数字符串的hash函数

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
00311CB2      33D2              XOR EDX,EDX
00311CB4     /EB 1E             JMP SHORT 00311CD4
00311CD4      FC                CLD
00311CD5    ^\EB AA             JMP SHORT 00311C81
00311C81      AC                LODS BYTE PTR DS:[ESI]
00311C82      E8 13000000       CALL 00311C9A
00311C9A      8D6424 04         LEA ESP,DWORD PTR SS:[ESP+0x4]
00311C9E      84C0              TEST AL,AL
00311CA0      E8 EDFFFFFF       CALL 00311C92
00311C92      8D6424 04         LEA ESP,DWORD PTR SS:[ESP+0x4]
00311C96      74 33             JE SHORT 00311CCB
00311C98      EB 29             JMP SHORT 00311CC3
00311CC3      C1C2 03           ROL EDX,0x3
00311CC6      E8 0C000000       CALL 00311CD7
00311CD7      8D6424 04         LEA ESP,DWORD PTR SS:[ESP+0x4]
00311CDB      32D0              XOR DL,AL
00311CDD    ^ EB AF             JMP SHORT 00311C8E
00311C8E    ^\EB F1             JMP SHORT 00311C81
化简为
START:
   LODS BYTE PTR DS:[ESI]
   TEST AL,AL
   JE SHORT 00311CCB
   ROL EDX,0x3
   XOR DL,AL
   JMP START
求完之后,hash值保存在了EDX中

当计算完hash值后,会进行比较
图片描述
直接在1A28的位置下断点,运行至此,即hash相等时情况
图片描述

 

继续单步跟踪分析,找到了获取函数地址的地方
图片描述

 

继续单步跟踪分析,发现函数地址被处理,使用memcpy拷贝出了一段代码,函数地址被写入到了代码中。而新的函数地址就是memcpy拷贝的首地址,这个地址被写入到了IAT中。
图片描述
图片描述
图片描述

 

至此,我们已经知道程序在写入一个IAT函数地址时的操作过程,概括为以下步骤。
1.获取预先计算好的hash值
2.循环获取当前正在获取的模块中的导出函数名称,计算hash值,与预存的比较,如果失败继续循环获取
3.如果正确,获取导出函数的地址
4.拷贝预存的代码到缓冲区,将导出函数地址写入到缓冲区中
5.将缓冲区首地址写入IAT处,完成填充IAT的操作

 

如何解密IAT?此处从函数地址入手,如果当我们获取了原始函数地址,且在写入IAT时,寄存器中还保存的是原始函数地址,那解密IAT就会变得很容易完成。如果代码是线性执行,我们只需改一下跳转应该就可以完成了,但是现在代码混淆度比较高,比较难找到规律,虽然说只要足够耐心,更改跳转应该可以实现,仔细跟踪代码,可以发现其实函数地址最初保存在EAX中,而后保存在EDX中,之后EDX被修改为IAT地址,EAX修改为加密的地址,在这个过程中,只要我们能做到EAX最后是函数地址即可。

 

经过分析,修改两处代码即可。
第一处代码:
图片描述
这里的修改是为了将函数地址保存到EBX中,因为EBX看起来没有实际使用用处

 

第二处代码:
图片描述
这里的修改为了将函数地址保存到EAX中,因为最后填充IAT的代码使用的是EAX
图片描述
图片描述
这里也可以改为脚本,与上文方法3类似,更改地址与部分代码即可

1
2
3
4
FILL dwPatch1,4,90           //NOP 4个字节   
ASM dwPatch1,"MOV EBX,EDX"
FILL dwPatch2,2,90           //NOP 2个字节   
ASM dwPatch2,,"MOV EAX,ECX"

尝试直接修改壳代码
8D642404895401FC
在内存窗口中搜索,定位到地址

1
2
0047BB6C      8D6424 04      LEA ESP,DWORD PTR SS:[ESP+0x4]
0047BB70      895401 FC      MOV DWORD PTR DS:[ECX+EAX-0x4],EDX                ; user32.BeginPaint

计算另一个地址
地址=0047BB70-(002214DC-00220895)
图片描述

1
0047AF29      8902           MOV DWORD PTR DS:[EDX],EAX

(对0047BB70 和0047AF29 两个地址下硬件执行断点)
修改为

1
2
0047BB70 mov ebx,edx
0047AF29 MOV DWORD PTR DS:[EDX],EBX

图片描述
图片描述
图片描述

 

同样可脚本。。。

1
2
3
FILL dwPatch1,4,90           //NOP 4个字节   
ASM dwPatch1,"MOV EBX,EDX"
ASM dwPatch2,,"MOV DWORD PTR DS:[EDX],EBX"

图片描述
图片描述


【看雪培训】目录重大更新!《安卓高级研修班》2022年春季班开始招生!

最后于 2021-12-29 11:01 被HLuKT编辑 ,原因:
上传的附件:
收藏
点赞1
打赏
分享
最新回复 (3)
雪    币: 9299
活跃值: 活跃值 (32922)
能力值: (RANK:95 )
在线值:
发帖
回帖
粉丝
Editor 活跃值 2021-12-31 18:07
2
0
现在壳进化的速度好像停止似的
雪    币: 1566
活跃值: 活跃值 (964)
能力值: (RANK:250 )
在线值:
发帖
回帖
粉丝
xiaohang 活跃值 3 2022-1-9 13:58
3
0
Editor 现在壳进化的速度好像停止似的
壳的进化并没有停止,只是取向寡头化以及转移到移动端了
雪    币: 219
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
浅汐 活跃值 2022-2-23 15:53
4
0
唉,看不懂,我好菜
游客
登录 | 注册 方可回帖
返回