IAT劫持在加壳中的运用
本帖最后由 charryyu_ych 于 2017-12-25 16:21 编辑来吾爱有一段时间了,看过一些前辈们的文章,受益良多。今天也准备分享一点干货,以示回馈。
关于加脱壳、PE、IAT的基本概念这里就不重复了,有需要的可以看看其他高手分析的文章。
程序的一大特点就是具有确定性。只要两套程序在某个时间点具有相同状态,在同样的外部输入下,它们将会得到同样的运行结果。而这就是加壳脱壳的基本前提。我们知道,脱壳的基本流程是:dump、寻找oep、修复IAT(严格来说是IT)。理论上讲,在oep处脱壳后修复了IT的程序,与带壳达到oep的程序基本具有相同的状态,因而能够向后正常运行。但注意是基本相同,也就是说一旦存在一点不同,脱壳就是不彻底的,还需要做额外的修补。
我们来仔细回顾一下脱壳的过程。假定在第n次运行带壳程序到oep时脱壳,我们将此时的程序状态命名为State。以后第m次启动脱壳后的程序到达oep的状态命名为State。思考:State与State是否都能正常运行?它们有什么区别?
State:第n次运行原始程序时在oep的程序状态,壳程序参与了初始化,一定能正常运行。
State:第n次dump的静态程序状态+第m次动态加载到达oep之后的状态,壳程序未参与初始化,可能无法正常运行。
区别1:壳程序是否参与了oep之前的初始化。
区别2:不同次启动时的各个dll的基地址不同。
为什么说State可能无法正常运行呢?假定其中包含的State的部分静态信息是需要被动态修复的,那么它就无法运行。而这个就可以被某些高级加壳程序所利用。举个例子:
壳程序在到达oep之前,劫持对IAT中某dll API函数dllX!API_X的jmp 到壳程序段的私有程序funcKe。但是在funcKe中重新生成要jmp的目标地址ptrTarget。此时ptrTarget1= 。在此时简单脱壳的话, jmp 将永远被保持为被劫持状态,并且指向固定的虚拟地址ptrTarget1。脱壳之后,当重新运行时,dllX的基地址发生了变化,由于壳程序不再参与初始化,原来的ptrTarget1不再有效了。当程序调用 dllX!API_X时,就会进入funcKe,然后使用未修复的ptrTarget1,程序崩溃......
我第一次遇到这个case是在手动为某流行游戏脱壳的时候发现的。出于商业忌讳,名字我就不说了,但是我相信懂的人有共鸣。当我脱壳后运行游戏时直接crash,起初还怀疑是否脱壳的过程操作不当。后来调试一下脱壳后的程序,发现部分jmp 被劫持到了壳程序段才恍然大悟。那段壳程序由几百行混淆汇编代码构成,当时为了确定问题,不得不从最后一行反向阅读汇编代码,分析栈数据的来源。壳程序的一种典型做法是:
原始IAT中转 jmp ----->call funcKe
funcKe:
address call address+1
address+1 pop eax //利用栈上的ret拿到了call eip+1这句代码所处的虚拟地址address
.........
address+x sub eax,n //经过中间运算得到API地址
push eax //将API地址放到栈顶
ret //利用ret指令实现对API的调用。
领会了这个要领之后,对抗思路也就很清晰了。先按照常规流程进行脱壳,脱壳之后再进行相应处理。
处理方式1:恢复被劫持代码。将中转jmp的call funcKe恢复为jmp 或者在funcKe的首地址处理也可以。
处理方式2:dll固定基地址技术。记住脱壳时的每个exe、dll的基地址,只要保证每次程序启动时仍然使用相同的基地址即可。本文只是讲壳相关的,该技术不在此论述。
很高兴能与大家一起学习交流,写得不好的地方还望大家见谅,欢迎批评指正。 转载的嘛?貌似看过有一模一样的 Poner 发表于 2017-12-25 16:53
转载的嘛?貌似看过有一模一样的
在看雪上发过,自己亲手写的。 谢谢分享 谢谢分享 不错 通俗易懂 谢谢大家的支持。
补充一下,大多数情况下脱壳后就可以进行静态分析,但仍需要动态调试的配合,因此需要让它能正常运行。如果为了方便,修复少数几个IAT劫持就可以让程序跑起来了。这是因为一般而言,部分主要的dll加载地址基本都是在固定位置的。但是要注意一点:kernel32.dll、user32.dll、gdi32.dll等主要的dll跟系统的启动有关。因此如果要偷懒,就尽量完成分析之后再重启电脑。否则你会发现有一大堆的kernel32 API需要修复,到那时比起修复的工作量,还不如重新脱壳。 谢谢分享{:1_918:} 谢谢分享!
页:
[1]