揭开虚拟Api的面纱[曝光]
本帖最后由 ps520 于 2010-2-22 14:44 编辑虚拟Api,这个功能应该是绝大多数保护壳都带的功能,这个功能的用途是让我们在调试时无法下断Api。
比如一个MessageBoxA的窗口,以前我们可以Bp MessageBoxA,通过回溯来找到调用,从而达到快速找到切入点。
现在我们下断虚拟MessageBox的软件,却发现OD无法断下了。这是为什么呢?老妹你说捏~
下面我们就来解密这个传说中的虚拟Api吧~
-------------------------------------------------------------------------------
虚拟Api的实质是什么?
虚拟Api应该说有两种形式(我自己编写的和碰到的。如果有漏洞请指正)。
第一种:程序载入系统DLL(如User32.dll)至内存,然后获得函数地址并调用。
第二种:程序通过汇编或底层模拟,自己实现Api的功能。(这个比较考功底,我曾经看到过y3哥哥用驱动实现过Api的功能。)
这两种的效果都是一样的,都可以将Bp 函数名挡住。
相比之下第2种效率比较高,因为代码都是自己写的,所以不用加载系统DLL,内存占用小。
但是我们常见到的却是第1种,因为第一种相比之下更简单,更具有效率。
毕竟系统Api不是每个都可以模拟的,每个都可以轻松模拟的。
----------------------------------------------------------------------------------
虚拟Api的实现
我原来以为虚拟Api实现了就可以写壳了,其实还有很多没处理的。
比如需要修改原来的输入表,让程序调用API时自动转向自己的代码。
由于技术限制,我只能根据我的经验来给大家讲解了(我是菜鸟我怕谁~!)
第一种方法的虚拟Api通常是这样做的:
1.获取系统Dll(一般保护壳会自带那个DLL或者从system32读入)
2.加载系统DLL
3.当系统需要某个函数时,通过枚举DLL的函数名取得其函数入口地址
4.使用APICallWindowProcA调用该命令或者用汇编实现call
这里我们需要解释这些点:
1.其实在编程里,这个过程一般不叫虚拟Api,这个过程就是大家所熟悉的内存加载Dll
或者叫从内存执行DLL。百度一下可以获得大量资料,各种语言都有。
2.用户调用Api的参数获取。
保护壳是通过修改输入表,将输入表的函数地址修改为自己的模拟指针来实现的。
在此过程之前,所有参数信息都已经入栈,所以可以直接写。
3.模拟Api函数的形式。
请注意,必须与原Api相同,参数、返回值类型都应该完全相同。
有的Api参数是通用型,可以设置为各类(比如FindWindow这个Api,参数可以为整数和文本两种。)
但编程时我们就需要明确Api到底是哪个类型,所以你需要通过调试来获取数据类型。
4.虚拟系统Api时不能卸载
这个问题不知道是我自己写的代码有问题还是什么,据朋友说是因为看似函数已经执行完毕,实际上DLL里
的某些代码还在执行,所以卸载会导致程序崩溃。这个问题大家可以去测试下,我反正从来就没卸载过。
反复加载、卸载对软件性能也是很有影响滴~~~~~~~
---------------------------------------------------------------------------
具体的实现过程:
A:加载.
1.读入系统DLL,比如将它赋值给Dll_Data这个变量
2.使用lstrcpynA获取Dll_Data的内存地址
3.取Dll_Data的长度
4.检查Dll是否合法(也就是是否为DLL格式)
;继续来看~
5.获取ImageSize
6.申请一块大小为ImagfeSize的内存,地址赋值给pMemoryAddress
7.复制Dll数据,填充IAT
8.修改这块内存的属性,将其改为读写。
9.返回pImageBase
总结:
这段函数的意思,总结成一句话就是:
将Dll数据载入自己内存,并取得这块内存的base地址。应该算是模拟LoadLibrary这个函数吧。
为什么要取这块地址的base地址呢?
下面你就会知道了。
B.模拟调用函数
模拟调用函数必须知道2个东西。
第一,函数入口地址
第二,参数
由于我们是模拟写壳的过程,在此之前数据已经入栈了
所以不需要考虑参数问题
接下来就是考虑取函数入口地址了。
我们有两种取法,不知道各位喜欢哪种。
第一种是直接在我们加载的那个Dll里,通过函数名匹配来取。(这个比较麻烦啊~~~因为里面还嵌个循环比对)
第二种是直接取系统函数的地址,然后取RVA,然后+上之前的base地址。
我们讲下第2个方法吧~
以下内容摘自与师兄yangjt的对话:
HANDLE hModule=GetModuleHandle(LPSTR lpModuleName);
取入口用GetModuleHandle
那个GetProcAddress(hModule,"Name")
返回的就是hModule 里Name函数的VA取入口用GetModuleHandle
那个GetProcAddress(hModule,"Name")
返回的就是hModule 里Name函数的VA
所以我们可以LoadLibrary
再GetModuleHandle复制给A
接着GetProcAddress(hModule,"Name"),复制给B
用B-A,就可以得到函数的RVA
RVA+我们的base
就可以得到函数在咱自己程序里的地址了。
(这个过程描述得令我痛苦,各位看官多加点分啊)
剩下的就是Call这个函数执行功能了。
这里我们又有2种方法:
a.用API调用
b.用汇编实现
我推荐用汇编实现,效果更好,并且不给拦截。
置入代码 ({ 201, 88, 135, 4, 36, 255, 224 })
这样就可以完成函数的模拟过程了。
--------------------------------------------------------------
补充笔记:
师兄说,这样的方法兼容不是很好。
所以大家可以参考下HC哥哥的这篇文章:
http://www.unpack.cn/viewthread.php?tid=33864&highlight=
师兄又解释了这个的原理:
说白了就是把要用的Dll复制一份
复制到进程的其他地址空间
然后调用的时候转到复制的内存
完全复制的话好处就是怎么换系统问题都不大
你要使自己写就存在兼容问题
---------------------------------------------------------------
感谢:
感谢LCG、UpK两大组织的大力支持
特别感谢:
啊cr
yangjt
以及所有帮助过我的人
感谢:
吴涛
Dtcser
李光
凌晨孤星
等易语言元老
感谢CCTV的支持 这文章写得基本没人会认真看啊 路过,学习了·· 我太菜了,目前还看不懂~~~ 惭愧,一样都看不懂~ 感谢~~
学习了~~
感谢LZ分享 {:1_908:}貌似看懂了
页:
[1]