脱壳之未知加密壳
本帖最后由 Nattevak 于 2021-12-23 21:30 编辑一、简单分析
1.大致浏览寻找OEP地址,填充IAT的地址,获取API的地址,初步编写通用脚本
①OEP
②填充IAT地址
③获取API地址
将地址填入脚本,进行测试
// 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 ,dwTMP //MOV DWORD PTR DS:,EAX
JMP LOOP0
CASE2:
CMP dwOEP,eip
JNZ LOOP0
MSG "OEP已到达"
2.再次运行脚本,发现获取API和填充IAT的地址无效了,说明代码地址发生变化。
一般来说,地址随机又两种情况,一是随机基址,二是代码所在处是在申请的内存空间中,这种情况下解决方法就是找到代码基址,然后计算偏移,根据偏移在代码处下断点。
显然这个地方地址随机是因申请了内存导致的。
所以可以在VirtualAlloc处下断点,经过动态调试,发现在VirtualAlloc处断下的位置有很多出,最开始的一处栈回溯之后,代码地址是程序的模块中,推测这个地方申请的内存空间就是修复IAT代码的基地址,将之前的代码偏移,减去基址之后加上偏移,代码与之前一样,所以这个地方就是获取代码基址的地方。
分析后故修改脚本如下:
// 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 ,dwTMP //MOV DWORD PTR DS:,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指向的内存中
修改代码:
001E14DC 8BDA MOV EBX,EDX user32.BeginPaint
001E14DE 90 NOP
001E14DF 90 NOP
001E0895 891A MOV DWORD PTR DS:,EBX
下断点运行
DUMP文件并使用IMPREC修复
将手工操作转为脚本
// 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:,EAX
ASM dwPatch2,"MOV DWORD PTR DS:,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中的函数地址被加密了,即IAT被加密了
而解密IAT的一般方法就是在IAT函数表上下硬件写入断点。
观察OEP附件的函数调用,根据经验来说,VC6.0程序调用的第一个函数应该是GetVersion,但现在能看到的是一个随机的函数地址,像是在申请内存地址
F7步入,继续单步跟踪275039地址中的代码,可以发现,原本IAT的函数地址被存放到了一个地址中,代码通过计算地址,获取到了GetVersion的函数地址,之后调用了GetVersion函数
程序的加密函数,GetVersion
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中获取 API地址
00275056 897C24 08 MOV DWORD PTR SS:,EDI; 将API地址存入堆栈,修改的是刚才的占位地址
0027505A 5F POP EDI ;恢复寄存器
0027505B 5B POP EBX ;恢复寄存器
0027505C C3 RETN ;返回到api地址
0027505D C744F6 76 00000>MOV DWORD PTR DS:,> ;存储的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
加密函数
00274986 / EB 73 JMP SHORT 002749FB
002749FB 4C DEC ESP
002749FC E8 DBFFFFFF CALL 002749DC
002749DC 8D6424 04 LEA ESP, DWORD PTR SS :
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 :
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 :
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 :
00274A2C 8958 08 MOV DWORD PTR DS : , 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 :
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 :
00274994 E8 EFFFFFFF CALL 00274988
00274988 8D6424 04 LEA ESP, DWORD PTR SS :
0027498C 8978 04 MOV DWORD PTR DS : , EDI
0027498F EB 41 JMP SHORT 002749D2
002749D2 58 POP EAX
002749D3 E8 0B000000 CALL 002749E3
002749E3 8D6424 04 LEA ESP, DWORD PTR SS :
002749E7 E8 00000000 CALL 002749EC
002749EC 5B POP EBX
002749ED E8 C7FFFFFF CALL 002749B9
002749B9 8D6424 04 LEA ESP, DWORD PTR SS :
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 :
00274A03 ^ EB AB JMP SHORT 002749B0
002749B0 897C24 08 MOV DWORD PTR SS : , 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 :
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函数
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:
00311C82 E8 13000000 CALL 00311C9A
00311C9A 8D6424 04 LEA ESP,DWORD PTR SS:
00311C9E 84C0 TEST AL,AL
00311CA0 E8 EDFFFFFF CALL 00311C92
00311C92 8D6424 04 LEA ESP,DWORD PTR SS:
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:
00311CDB 32D0 XOR DL,AL
00311CDD ^ EB AF JMP SHORT 00311C8E
00311C8E ^\EB F1 JMP SHORT 00311C81
化简为
START:
LODS BYTE PTR DS:
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类似,更改地址与部分代码即可
FILL dwPatch1,4,90 //NOP 4个字节
ASM dwPatch1,"MOV EBX,EDX"
FILL dwPatch2,2,90 //NOP 2个字节
ASM dwPatch2,,"MOV EAX,ECX"
尝试直接修改壳代码
8D642404895401FC
在内存窗口中搜索,定位到地址
0047BB6C 8D6424 04 LEA ESP,DWORD PTR SS:
0047BB70 895401 FC MOV DWORD PTR DS:,EDX ; user32.BeginPaint
计算另一个地址
地址=0047BB70-(002214DC-00220895)
0047AF29 8902 MOV DWORD PTR DS:,EAX
(对0047BB70 和0047AF29 两个地址下硬件执行断点)
修改为
0047BB70 mov ebx,edx
0047AF29 MOV DWORD PTR DS:,EBX
同样可脚本。。。
FILL dwPatch1,4,90 //NOP 4个字节
ASM dwPatch1,"MOV EBX,EDX"
ASM dwPatch2,,"MOV DWORD PTR DS:,EBX"
未知加密壳的分析至此结束。
谢谢大佬分享 这帖子有点技术含量啊,基本上把脱加密壳的步骤都来了一遍。。。 支持一下! 谢谢分享 非常详细,消化需要时间,感谢大佬! 很详细 不错
非常详细,感谢大佬! 看看学习 下载失败,可否修复
页:
[1]
2