首页
社区
课程
招聘
[原创]脱壳之未知加密壳
发表于: 2021-12-29 10:34 35773

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

2021-12-29 10:34
35773

①OEP
图片描述

②填充IAT地址
图片描述

③获取API地址
图片描述

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

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

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

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

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

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

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

查看RUN跟踪
图片描述

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

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

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

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

将手工操作转为脚本

将程序载入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

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

加密函数

根据推测以及对壳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函数

当计算完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类似,更改地址与部分代码即可

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

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

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

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

同样可脚本。。。

图片描述
图片描述

 
 
 
// 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已到达"
// 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已到达"
 
 
// 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已到达"
// 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已到达"
 
 
 
 
 
 
001E14DC       8BDA        MOV EBX,EDX          user32.BeginPaint
001E14DE        90             NOP
001E14DF        90             NOP
 
001E0895       891A        MOV DWORD PTR DS:[EDX],EBX
001E14DC       8BDA        MOV EBX,EDX          user32.BeginPaint
001E14DE        90             NOP
001E14DF        90             NOP
 
001E0895       891A        MOV DWORD PTR DS:[EDX],EBX
 
// 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已到达"
// 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

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

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