首页
社区
课程
招聘
[翻译]通过DLL重定向实现API劫持
发表于: 2018-1-1 11:52 8918

[翻译]通过DLL重定向实现API劫持

2018-1-1 11:52
8918

译自exploit-database-papers中的API Interception via DLL Redirection,详见原文。鉴于译者水平有限,不免有错误存在,如是,望读者斧正。本文同步于我的博客.

在Windows系统中,所有的应用程序都必须经由API函数来实现和内核的通信;同样地,尽管在最简单的Windows应用程序中这些函数也十分危险。因此,拦截、监视以及修改一个应用程序API调用的技术通常叫做API Hooking,非常有效的给予其所调用进程的完全控制。从多方面考虑这将非常有用,包括调试、逆向工程以及hacking。

实际上有多种方法可以用于实现我们的目的,被文章仅仅实验DLL重定向。选择这一方法出于多种原因考虑:

本文将使用下列软件。你当然也可以选择自己偏好的任意工具,然而,请记住它们的具体使用方法和实现:

假定读者有牢固的Win32 C/C++编程、汇编语言以及以上所提到应用程序使用的基础(当然不包括linkout.pl)。对于API Hooking其他的方法有一个基础的理解也很有帮助。

可执行程序的导入API函数源于DLL文件,DLL重定向允许我们去告知一个程序所需要加载的DLL被放在另一个目录,而不在使用原始的那些。通过这种方法我们可以创建一个与原始文件同名的DLL,导出和原始DLL相同的函数,但是每个函数都可以包含我们想放置的代码。DLL重定向有两种方法,第一种方法有时被称为"dot local"重定向:

应用程序可能依赖于一个特定的DLL版本,当其他应用程序安装了旧或新的相同DLL时,该应用程序可能会运行失败。有两种方法来确保应用程序使用了正确的DLL:DLL重定向以及端对端组件。开发者和管理员应该对存在的应用程序使用DLL重定向技术,因为他无需修改应用程序。

换句话说,.local DLL重定向为开发者提供了强制一个应用程序使用特殊的不同版本DLL的能力。例如,oldapp.exe仅仅在一个旧版本的user32.dll上正常运行,你可以告诉它在自己的工作目录中加载旧版本的user32.dll来工作,而不是将这个旧版本的user32.dll放置在system32目录。其他的应用程序仍然会加载system32下的.local文件(该文件是个简单的空文件,名字是在目标应用程序的名字后加一个.local扩展;这里就是oldapp.exe.local),这个.local文件连同旧版本user32.dll都要放在oldapp.exe所在的目录下。

然而,仍然存在一些限制。最重要的,根据MSDN的说法,具体的DLL文件(称为Known DLL's)在Windows XP中不能够重定向(Win2k没有此限制)。Known DLL's的列表可以在KHEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs中找到;其中也包含了广为人知的kernel32.dll,user32.dll,gdi32.dll。然而以我个人经验来看,这并不是真的——似乎在Windows XP中,一个应用程序要么可以重定向所有的DLL,要么都不行。同样地,如果目标是一个Windows XP上运行的应用程序,.local重定向这种方法并不值得信赖,这种方法应该仅仅用于Windows 2000。

第二种方法,也就是我们即将要使用的方法,使用了manifest文件来达成同样的结果。manifest文件和.local文件使用相同的命名转换方式(例如,oldapp.exe.manifest),但却并不为空。他们必须包含具体的XML格式信息。此外,manifest文件也仅在Windows XP和Vista系统上被支持(Win2K不行),但相对于.local重定向来说它们更为可靠,并允许我们重定向任何DLL文件。(注意:我仅在Windows XP上验证过;Windows Vista上可能会有些限制和改动)

上面提及的任一方法做重定向都相当简单,像我们后面即将看到的那样,完整的DLL重定向实现比这复杂一些。现在,我们来看一些基础:获取程序来加载当前工作目录下的DLL文件。

程序仅在被通知需要重定向时才会使用DLL重定向,通知的方法也非常简单。对.local重定向来说,program_name.exe.local文件的创建会引起应用程序在系统目录中查找DLL文件之前优先在当前工作目录中查找。这非常简单,但也正如上文提及的,在现代系统上并不可靠。

manifest文件则相对复杂,有一些必要的XML信息必须要保存在manifest文件中。下面是一个manifest文件的样本:

我们放弃在manifest文件格式上的讨论,这和本文的主题并不直接相关(更多信息参考4f6K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6A6L8X3c8G2N6%4y4K6k6r3E0Q4x3X3g2E0M7$3c8F1i4K6u0W2L8h3W2U0M7X3!0K6L8$3k6@1i4K6u0W2j5$3!0E0i4K6u0r3k6h3&6Q4x3X3c8Y4j5W2)9J5c8X3I4A6j5Y4u0S2M7Y4W2Q4x3V1k6E0M7K6M7$3y4U0b7#2y4q4)9J5k6h3q4K6M7s2S2Q4c8f1k6Q4b7V1y4Q4z5o6W2Q4c8e0y4Q4z5o6m8Q4z5o6u0Q4c8e0N6Q4z5o6c8Q4b7U0k6Q4c8e0S2Q4z5o6m8Q4z5p5y4Q4c8f1k6Q4b7V1y4Q4z5p5y4Q4c8e0k6Q4b7U0y4Q4b7e0S2Q4c8e0k6Q4z5o6c8Q4z5p5j5`.<file>一节,这一节中我们声明了名字属性,设置它和user32.dll相同。这告诉应用程序manifest文件中指定的user32.dll需要在当前目录下加载。一旦该文件被创建,用和.local文件同样的命名方法保存名字(program_name.exe.manifest),同时也要把它放在目标程序所在的目录下。

我们已经清楚了DLL重定向如何工作,但是像大多数事情那样,实现上还有一点点复杂:当我们重定向DLL时,我们需要让所有的函数都能够查找到。比如说我们想要拦截IE的所有MessageBox调用。MessageBox在user32.dll中,所以我们需要创建一个叫user32.dll且包含MessageBox函数的文件,同时创建一个iexplore.exe.manifest文件并把二者都放在iexplore.exe所在目录下。现在当IE导入API函数时,会加载我们的user32.dll,此后当IE调用MessageBox时,就会执行我们的函数。

问题在于MessageBox并不是IE从user32.dll导入的唯一函数。IE有着几百个从user32.dll中导入的函数,如果其中的一个找不到那么IE就会加载失败。另一方面我们也不想要重写所有的user32 DLL函数,我们可以简单的把其他的函数都转向原始user32.dll文件中的函数。

首先,我们需要用dumpbinGUI工具来查看user32.dll导出的所有函数(右击user32.dll,找到dumpbinGUI->EXPORTS)。你可能会看到类似这样的东西:

这里我没用这个花哨的GUI,直接dumpbin就行了:

现在选择所有列出的函数(ActivateKeyboardLayout开始到wvsprintfW截止),拷贝到一个user32.txt文件中,稍后我们会使用。所有的这些函数都需要从我们的DLL中导出并且定向到原始user32.dll中一致的函数。可以使用链接器导向语法来实现:

该语句使得链接器增加一个可导出的MessageBox函数到我们的DLL导出表中,并且这个导出的函数简单的被定位到user33.dll中的MessageBox函数。注意到使用了user33这个名字而不是user32。这是因为如果我们将其命名为user32.dll,我们就需要指定成user32.MessageBox,这会递归的指向本体。因此,我们拷贝原始user32.dll文件到IE所在目录下并重命名为user33.dll来防止递归。

User32不包含任何内容,除了一些按顺序的DLL导出函数。这些函数需要一些额外信息。一个函数的序号代表了出现在DLL文件中的位置;换句话说,一个序号为243的函数是DLL导出的第243个函数。为了保证我们的DLL按顺序导出函数,使得函数指向原始DLL中的正确序号,我们用这样的语法:

他告诉链接器导出一个序号为243的ord243函数,指向了shlwapi32中的第243个,这里没有包含导出的名称。这里唯一的差别在于"@243"表示链接器导出函数使用的是243序列值,而"NONAME"则告诉链接器不要通过函数名称进行导出。ord243是个随机名称,当不使用名字导出时,我们写啥名字其实都不影响。

简明扼要的说,我们现在有3个文件,多放在iexplore.exe所在目录中:

我们已经清楚了需要干些什么,也清楚该如何做,是时候展现真正的技术了。user32.dll有一大堆的函数,如果手工一个一个创建链接器语句实在是浪费生命。因此,我们使用一个脚本来完成这项工作,该脚本需要用到上文提到的user32.txt,它就是linkout.pl。脚本使用方法非常简单:仅需要指定保存函数列表的文本文件(user32.txt)、想要导向的DLL名称(user33)以及输出文件名(缺省使用out.txt)作为参数。

linkout.pl创建的out.txt文件长成这样:

现在我们需要的链接器导向语句全部生成好了,拷贝它们到DLL项目的CPP源文件中,编译成user32.dll。打开VS IDE创建一个新的Win32 C++ DLL Project,命名为user32。你可以删除user32.cpp中的所有预生成代码(除了#include "stdafx.h"),粘贴out.txt中所有代码到user32.cpp中,编译整个项目。

拷贝生成的user32.dll到IE所在目录,同时也拷贝原始的user32.dll文件(记得改成user33.dll)。最后,使用上文提供的模板创建一个iexplore.exe.manifest文件(IE识别.local重定向,所以你如果信任的话也可以创建一个.local文件)。启动IE,它应该会运行良好。为了测试IE加载的确实是我们的新user32.dll而不是system32下的,可以简单的重命名user33.dll成user34.dll,然后再次运行IE。此时IE应该会因下列错误而失败:

这确定了我们的DLL,使用的GetShellWindow函数是user33.GetShellWindow。

到此我们的DLL除了转到user33.dll以外什么也没做。我们的终极目标是修改一些API调用。让我们看看还需要哪些额外的步骤去拦截修改API函数,以使用windows计算器为例。注意到计算器不识别.local重定向,所以必须要使用manifest文件。

创建一个calc.exe的拷贝,使用OD打开。因为我们已经有了一个user32 DLL,让我们找找在user32.dll中被调用到的API函数。第一个遇到的调用是GetProcessDefaultLayout。

再次打开user32 DLL工程,注释掉GetProcessDefaultLayout一行。增加下面的代码:

在函数中,我们先pop出栈上的返回地址到retaddr变量中;我们可以通过LoadLibrary和GetProcAddress来找到真正的GetProcessDefaultLayout地址。此后,我们创建一个消息框,提供我们以虚拟的确认以表示我们成功的拦截了API调用。最后,我们调用真实的GetProcessDefaultLayout函数(call far dword ptr function),将返回地址压入到栈上并返回。我们也可以修改传递给GetProcessDefaultLayout的参数以及它的返回值。

为了导出我们的新函数,我们需要一个DEF文件。DEF文件可以用于创建一个导出函数的列表,并且可以定义导出的函数名。在工程目录下创建一个user32.def文件并写入下面的内容:

这回告诉链接器去导出myGetProcessDefaultLayout函数作为GetProcessDefaultLayout。把DEF文件加入到项目中(项目属性->配置属性->链接器->输入->模块定义文件)并编译新的user32项目。

拷贝创建的user32.dll,user33.dll和calc.exe.manifest到calc.exe所在的目录。运行计算器,你会看到这样一个消息框:

DLL重定向很有用,然而,试图拦截一些关键DLL如user32,kernel32中的函数调用会导致应用程序的不稳定。尽管所有的函数都转向了原始的函数,但也可能引起一些非期望结果的情形。其他的方法,特别是IAT补丁,允许你仅仅重定向单一调用,这无疑降低了风险。结合DLL重定向和IAT补丁,你可以实现最佳的效果:重定向关键DLL中的单一函数,与此同时仍然消除了外部应用程序的需求或项目标应用程序IAT做DLL注入。

例如,你可以在应用程序执行体中找到一个早期调用的函数,放置在相关的隐藏DLL中,执行DLL重定向来拦截调用。此后你可以在传递控制权给应用程序之前,实现IAT补丁所需要的代码。

DLL重定向在Windows中可以是一个很有用的工具用于控制用户空间应用程序。它允许你控制任意Windows平台下可用的API函数,因此就允许你控制已存在的程序代码(或插入新的代码到进程)而无需修改应用程序代码本身(在磁盘或内存中)。这种能力也带来了安全隐患,对于用户和软件公司来说,它可能被用于危害用户的系统,也可以绕过一些审计保护技术(time-trials,CRC校验等)。

测试相关代码已上传到我的github

 
 
 

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 1
支持
分享
最新回复 (10)
雪    币: 775
活跃值: (2357)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
2
mark  感谢分享
2018-1-1 12:15
0
雪    币: 2046
活跃值: (265)
能力值: ( LV7,RANK:104 )
在线值:
发帖
回帖
粉丝
3
DLL重顶向的目标就是更改API参数    实现自己的调用功能么?    那如果想让程序运行新增的功能,实现侧面来讲可以么?
2018-1-1 12:44
0
雪    币: 28
活跃值: (633)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
额~
2018-1-1 17:10
0
雪    币: 5697
活跃值: (984)
能力值: ( LV9,RANK:400 )
在线值:
发帖
回帖
粉丝
5
ilyzqe DLL重顶向的目标就是更改API参数 实现自己的调用功能么? 那如果想让程序运行新增的功能,实现侧面来讲可以么?
也可以理解成一种API  Hook,不只是更改参数、结果,执行体可以插入任意代码,比如一段shellcode弹个reverse_tcp,:D
2018-1-1 19:26
0
雪    币: 300
活跃值: (2772)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
mark
2018-1-1 20:25
0
雪    币: 28
活跃值: (633)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
OKOK~有些软件可以 有些不能!
2018-1-1 21:17
0
雪    币: 355
活跃值: (281)
能力值: ( LV11,RANK:190 )
在线值:
发帖
回帖
粉丝
8
感谢分享
2018-1-1 23:28
0
雪    币: 912
活跃值: (2599)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
mark
2018-1-2 00:18
0
雪    币: 2
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
mark
2018-4-13 15:43
0
雪    币: 8197
活跃值: (3302)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
11
aheadlib?
2018-4-13 16:58
0
游客
登录 | 注册 方可回帖
返回