- 问题发现
- 劫持方法
- 劫持思路
- 可能问题
- 劫持实现
- 导出函数列表
- 函数上下文
- 获取真函数的地址
- eax值的保存
- 完整代码
问题发现
dll劫持是一种常见的攻击方法,但是也可以用在不知道程序源码的情况下调试dll的函数。之前在滴水教程的视频中注意到一个问题,视频作者演示了一个劫持messagebox函数,打印输出参数的过程,当时学生提问是否存在一种通用的方法可以劫持所有的函数,当时视频作者回答是无法做到。最近有些空闲想到了这个问题,觉得从模块化的角度来讲,应该存在一种可以劫持所有dll函数的方法。所以在此尝试,尝试后发现了一种伪造dll的通用方法,使用这中方法,可以通过脚本的方式,实现任意一个dll的伪造。
劫持方法
劫持思路
本文采取的方式为新建一个dll,新建的dll满足一下特点:
1.导出原dll所有的函数
2. dll的名称和原dll相同
3. 所有原dll的导出函数都有对应的实现
这样在应用程序中就无法分辨自己加载的dll是原本合法的还是伪造的。当然,此方法仅限于不检查签名的dll。
可能问题
1.函数的返回类型未知
个人见解:从汇编的角度讲,函数的返回类型在汇编语言的运行过程中是未知的,返回值大都存储在eax中,主程序则根据设定的不同,去使用函数返回的eax的值。因此,我们如果在自己的函数中,让真正的函数去执行,那么返回值就无所谓了。
2.函数的参数未知
个人见解:从汇编的角度讲,函数的参数列表只是在调用的时候,为调用者提供一个压栈的顺序。在函数调用的过程中,函数调用者负责压栈参数,函数的执行者只需要依据调用约定不同,做到保持堆栈平衡和按顺序使用参数就可以。所以,只要我们在我们的函数最后调用真正的函数,那么就不会影响到原本程序的堆栈空间和执行流程。
劫持实现
导出函数列表
在实验中,我使用了lordpe这个应用的procs.dll。该函数的导出函数列表为:

因此,我们的dll应该导出并实现十个对应的函数。
函数上下文
既然本方法是一个通用的方法,那么自然只需要实现一个通用的代码流程即可。考虑到调用者调用dll中的程序时,堆栈的分布情况如下所示:
返回地址
参数1
参数2
…
参数n
因此我们在调用真正的方法之前,必须要将堆栈等信息恢复到这个状态。
所以此处我们选择pushad、pushfd、popfd、popad来对环境进行保存和恢复。
获取真函数的地址
并且,我们需要获取到原本函数的位置,用来跳转,因此我们需要使用getprocaddress函数以及对应的函数名称。通过对dll的导出表进行,遍历,获取名称十分的简单。我们可以定一个全局变量,用于存放每一个函数的名称。如下:
char funname1[] = "GetModuleHandleEx";
DWORD (DWORD)GetProcAddress(LoadLibraryA("kernel32.dll"), "GetProcAddress");//获取GetProcAddress函数的地址
hm = LoadLibraryA("PROCSold.DLL");//加载原dll,写在dllmain中
eax值的保存
由于要使用popad,那么运算得到的eax的值就会同样被覆盖,所以我们需要将eax的值传送出去。
这里我们通过将eax的值mov到pushad的时候eax被压栈的地方即可,我们在popad命令执行之前,增加语句
mov[esp+28],eax;
这样eax的值就被保存起来了。
完整代码
代码已上传至github,有兴趣可以访问我们github。上传的代码只限于提供一个劫持的实验,具体应用则需要编写一个脚本,这里就不提供脚本了。
https://github.com/guanginuestc/DLL-hijacking