DLL教程 - 如何编写动态链接库

本dll教程是自己在实际编程中总结的,作为笔记记录,对应的代码工程下载地址Dll编程demo
若没有积分,可以从github上clone仓库 :git@github.com:FFFlyFish/LibraryTest.git

一、 概述

DLL(Dynamic Linkable Library)它提供一些可以直接使用的变量,类和函数。在经历了“无库—静态链接库—动态链接库”的历程后,dll使用十分广泛。本文主要介绍dll,顺便介绍静态库(.lib)的一些知识。

1、静态链接库和动态链接库的异同点

静态链接库和动态链接库都是共享代码,如果采用静态链链接库(.lib),lib中的指令最终都会编译到链接该静态库的exe(或dll)文件中,发布软件时,只需要发布exe(或dll)文件,不需要.lib文件。但是若使用动态链接库(. dll),dll中的指令不会编译到exe文件中,而是在exe文件执行期间,动态的加载和卸载独立的dll文件,需要和exe文件一起发布。
静态链接库和动态链接库另一个区别是静态链接库不能再包含其他动态链接库或静态链接库,而动态链接库不受此限制,动态链接库中可以再包含其他的动态链接库和静态链接库。

2、Dll的一些概念

(1)只要遵循约定的dll接口规范和调用方式,用各种语言编写的dll可以相互调用。
(2)dll的分类有三种,Non-MFC DLL(非MFC DLL),MFC Regular DLL(MFC规则DLL)和MFC Extension DLL(MFC拓展DLL)。非MFC DLL不采用MFC类库规则,其导出函数为标准C接口,能被非MFC或MFC编写的应用程序调用;MFC规则DLL包含一个CWinApp的类,但其无消息循环;MFC拓展DLL采用MFC的动态链接库版本创建,它只能被用MFC类库编写的应用程序调用。

二、 静态链接库

在介绍动态链接库前先要介绍静态链接库的知识,静态链接库的后缀是.lib。下面的一个例程将介绍如何生成.lib文件和如何调用.Lib

1、如何生成一个.lib文件

1)、建立一个空解决方案,方案名称为StaticLibrary。
这里写图片描述
2)、添加一个win32项目,类型为Static library,工程名称为StaticLib,空项目。
选择win32 project项目类型
这里写图片描述
选择Static library, 取消Precompiled header
这里写图片描述
3)、在该项目中添加lib.h和Lib.cpp文件,两个文件代码如下图所示。
这里写图片描述
lib.h文件代码如下:

#ifndef __LIB_H__
#define __LIB_H__

int add(int a,int b);

#endif

Lib.cpp文件中提供一个函数,实现两个整数的相加,返回两数的和。代码如下:

#include "lib.h"

int add(int a,int b)
{
	return a+b;
}

4)、库工程不能单独运行,需要右键点击生成
这里写图片描述
生成成功后,将在解决方案目录的debug文件夹中生成一个StaticLib.lib文件,该文件就是静态库文件。
这里写图片描述

2、如何调用.lib文件

1)、在解决方案里再添加一个项目,项目名称为StaticLibCall,类型为Win32,控制台程序,选择空项目。
这里写图片描述
选择Console application 和Empty project
这里写图片描述
2)、在项目源文件文件中添加main.cpp文件。
这里写图片描述
main.cpp文件中添加如下代码:

#include <stdio.h>
#include "lib.h"
#pragma comment (lib,"StaticLib.lib")//指定与静态库一起连接

int main()
{
	printf("2+3=%d",add(2,3));
}

3)、将刚刚生成的StaticLib.lib文件和lib.h两个文件复制到该项目的目录下。(一般使用静态库时必须提供这两个文件,.h文件提供函数的预定义,而.lib提供函数的实现)
这里写图片描述
4)、生成的exe文件是可以独立运行的运行程序,lib文件中的函数实现被链接到exe文件中,lib不再需要了。运行结果如下
这里写图片描述

三、 动态链接库

本教程只介绍一种DLL(非MFC DLL)的创建与调用方法,本Dll实现的功能与第2节介绍的静态库实现的功能一样。

1、如何生成一个dll文件

1)、创建dll工程的步骤和上面介绍的建立lib的步骤一样,仅仅在选择类型时需要选择dll。建立工程后,添加DLib.h和DLib.cpp文件
这里写图片描述
在项目中添加DLib.h和DLib.cpp文件
这里写图片描述
2)、DLib.h和DLib.cpp代码如下所示:
DLib.h文件

#ifndef __DLIB_H__
#define __DLIB_H__

extern "C" int __declspec(dllexport) add(int a,int b); 


#endif

DLib.cpp文件

#include "Dlib.h"

int add(int a,int b)
{
	return a+b;
}

分析该代码,该工程的.cpp文件中代码和第2节的.cpp中代码完全一样。而.h文件不一样,该工程的.h文件中对add函数添加extern “C” 是告诉编译器该函数采用C调用方式进行编译,而__declspec(impoet)是声明函数add为dll的导出函数,dll的函数分两种:
(a) DLL导出函数,可供调用dll的应用程序调用
(b) DLL内部函数,只能在DLL程序使用,调用DLL的应用程序无法调用
3)、右键–生成;生成成功后,将在debug文件夹中生成一个DynamicLib.dll文件,同时,在此路径下也生成DynamicLib.lib文件,该lib文件不同于第一节中的静态库文件,此lib文件只是dll文件中导出函数的声明和定位信息,并不包含函数的实现(而第一节中的静态库文件,包含了函数的实现),因此此lib文件只是在调用对应dll库的工程编译时使用,不需要随exe发布。
这里写图片描述

2、如何调用.dll文件

dll文件的调用方式有两种,一种是动态调用,一种是静态调用。
动态调用是由编程者调用系统API函数加载和卸载dll,程序员可以决定dll文件何时加载,何时卸载,加载哪个dll文件,将dll文件的使用权完全交给程序员。
静态调用是由编译系统完成对dll文件的加载和应用程序结束时完成对dll的卸载,当调用某dll的应用程序结束时,则windows系统对该dll的应用记录减1,直到使用该dll的所有应用程序都结束,即对该dll应用记录为0,操作系统会卸载该dll,静态调用方法简单,但不如动态调用适用。
(1)、动态调用dll
1)、新建控制台项目,添加main.cpp文件,将刚刚生成的dynamicLib.dll文件拷贝到项目目录下,main.cpp代码如下

#include "stdio.h"
#include "windows.h"

typedef int (*lpAddFun)(int ,int );//宏定义函数指针类型

int main()
{
	HINSTANCE hDll;//DLL 句柄
	lpAddFun addFun;//函数指针
	hDll = LoadLibrary(L"DynamicLib.dll");//动态获取dll文件的路径
	if (hDll!=NULL)
	{
		addFun =(lpAddFun)GetProcAddress(hDll,"add");//根据函数名在dll文件中获取该函数的地址	
		if (addFun!=NULL)
		{
			int result =addFun(2,3);
			printf("2+3=%d",result);
		}

	FreeLibrary(hDll);
	}
	return 0;
}

运行结果如下:
这里写图片描述
main.cpp代码分析:
语句typedef int (*lpAddFun)(int ,int )定义了一个与add函数接收参数类型和返回值均相同的函数指针类型,随后在main函数中定义了lpAddFun的实例addFun;
在函数main中定义了一个DLL HISTANCE句柄实例hDll,通过Win32API函数LoadLibrary动态加载DLL模块并将DLL模块句柄赋给hDll;
在main函数中通过Win32API函数GetProcAddress得到所加载的DLL模块中函数add的地址并赋值给addFun,经由函数指针addFun进行了对该DLL中add函数的调用;
在完成对dll的调用后,在main函数中通过Win32API函数FreeLibrary释放已加载的DLL模块。

通过以上的分析可知:
(a) 动态调用只需要dll文件即可,不需要对应的.h头文件和.lib文件,一般情况下,只要有dll,就可以调用此dll中的导出函数。
(b) 在调用dll中的函数时,需要知道导出函数的函数签名,若拥有dll对应的头文件,可以参照头文件即可,若没有头文件,使用特定工具也可以得到dll中导出函数的函数签名
(c) DLL需要已某种特定的方式声明导出函数(或变量,类)
(d) 应用程序需要以特定的方式调用DLL的淡出函数(或变量,类)

(2)、静态调用dll
1)、新建一个工程,命名为DllStaticCall,添加main.cpp文件,并将刚刚生成的DynamicLib.dll和DynamicLib.lib两个文件拷贝到工程目录下。
这里写图片描述
这里写图片描述
main.cpp代码如下

#include "stdio.h"

//.lib文件中仅仅是关于其对应DLL文件中函数的定位信息
#pragma comment(lib,"DynamicLib.lib")

extern "C" int __declspec(dllimport) add(int a,int b);

int main()
{
	int result =add(2,3);
    printf("2+3=%d",result);
	scanf("%d");
	return 0;
}

从上述代码可以看出,静态调用需要完成两个动作:
a)#pragma comment(lib,“DynamicLib.lib”)是告诉编译器与该dll相对应的.lib文件所在的路径和文件名。
在生成dll文件时,链接器会自动为其生成一个对应的.lib文件,该文件包含了dll导出函数的符号名和序号(并没有实际的代码)。在应用程序中,.lib文件将作为dll的替代文件参与编译,编译完成后,.lib文件就不需要了。
b)extern “C” int __declspec(dllimport) add(int a,int b)是声明导入函数,这需要与dll工程中.h文件中的函数声明一致。
c)可以将dll工程中的头文件包含在工程中,这样上述代码中就不需要写extern “C” int __declspec(dllimport) add(int a,int b)声明了,但若没有提供对应的头文件,只有采用本文这种方式声明函数。

静态调用不需要使用Win32API函数来加载和卸载Dll以及获取Dll中导出函数的地址,这是因为当通过静态链接方式编译生成程序时,编译器会将.lib文件中导出函数的函数符号链接到生成的exe文件中,.lib文件中包含的与之对应的dll文件的文件名也被编译存储在exe文件内部,当应用程序运行过程中需要加载dll文件时,windows将根据这些信息查找并加载dll,然后通过符号名实现对dll函数的动态链接,这样,exe将能直接通过函数名调用dll 的输出函数,就像调用程序内部的其他函数一样。

四、 动态链接库的def文件

上节介绍的dll导出函数是前面添加__declspec(impoet)语句,声明该函数为dll的导出函数,还有另一种方式声明函数为导出函数–通过def文件

1、如何使用def文件

(1)新建解决方案,添加两个项目,DllLib是生成dll文件的项目,Dllcall是调用该dll的项目。
这里写图片描述
(2)在dllLib中添加lib.cpp和dlllib.def两个文件(不需要.h头文件),代码如下
Lib.cpp代码:声明两个函数,加法和减法

int __stdcall Add(int numa, int numb)
{
	return (numa + numb);
}

int __stdcall Sub(int numa, int numb)
{
	return (numa - numb);
}

dllLib.def代码如下:

LIBRARY DllLib
EXPORTS
Add @ 1
Sub @ 2

.def文件的规则为:
(1)LIBRARY语句说明该.def文件相对于的dll(不需要后缀dll)
(2)EXPORTS语句后面列出要到处的函数名称,可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n,在进行函数调用时,可以根据这个编号调用该函数(参见下面的调用过程)
(3).def文件中的注释由每行开始处的分号(;)指定,且注释不能与语句共需一行。
由此可以看出,例子中的.def文件的含义是生成名为DllLib的动态链接库,导出其中的add和sub函数,并且指定add函数序号为1,sub序号为2。

2、调用dll

调用的方法与上面介绍的一样,本例使用动态调用。将刚刚生成的dll拷贝到项目目录下。
main.cpp代码如下:

#include <stdio.h>
#include <windows.h>

typedef int (__stdcall *FUN)(int, int);
HINSTANCE hInstance;

FUN   fun;

int main()
{
	hInstance = LoadLibrary(L"DllLib.dll");
	if(hInstance!=NULL)
	{
		fun = (FUN)GetProcAddress(hInstance, "Add");
		//当在Def文件中指定函数序号时,可以通过序号导出,否则只能通过函数名称导出
		//fun = (FUN)GetProcAddress(hInstance, MAKEINTRESOURCE(1));
		if (fun!=NULL)
		{
				printf("1+2=%d",fun(1, 2));
		}
	}
	FreeLibrary(hInstance);
	return 0;
}

注:def文件中定义了函数序号,在动态加载dll时,可以根据这个序号加载函数,这样做的好处时,当dll工程的导出函数的函数名有变化,而功能没有变化时,只要def中定义的函数序号没有变化,则调用dll的代码不需要任何改变。
运行结果如下:
这里写图片描述

五、 DllMain函数

Windows在加载dll时候,需要一个入口函数,就如同控制台需要main函数,win32程序需要WinMain函数一样,在前面的例子中dll并没有提供DllMain函数,应用程序也能成功的调用dll,这是因为Windows在找不到DllMain函数时,系统会从其他运行库中引用一个不做任何操作的缺省DllMain函数版本,并不是意味着dll可以放弃DllMain函数,
根据编写规范,Windows必须查找并执行dll里面的DllMain函数作为加载dll的依据,它使得dll能够驻留在内存里,DllMain函数不属于导出函数,而是dll的内部函数,这意味着不能直接在应用程序中引用DllMain函数,DllMain函数是自动被调用的。DLLMain函数的作用是可以做一下初始化操作,相当于类的构造函数,关于DLLMain函数和导出类,在这里不做过多的讲解,后续需要时,再深入研究。

1、为dll添加DllMain函数

(1)新建解决方案,添加两个工程。一个是生成dll,一个是调用dll。
这里写图片描述
Lib.h代码

#ifndef __LIB_H__
#define __LIB_H__
	extern "C" int __declspec(dllexport) add(int a,int b); 
#endif

Lib.cpp代码

#include "lib.h"
#include "stdio.h"
#include "windows.h"

BOOL APIENTRY DllMain(HANDLE hModule,
	DWORD ul_reason_for_call,
	LPVOID lpReserved)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		printf("\nprocess attach of dll");
		break;
	case DLL_THREAD_ATTACH:
		printf("\nthread attach of dll");
		break;
	case DLL_THREAD_DETACH:
		printf("\nprocess detach of dll");
		break;
	case DLL_PROCESS_DETACH:
		printf("\nprocess detach of dll");
		break;
	}
	return TRUE;
}

int add(int a,int b)
{
	return a+b;
}

Main.cpp代码

#include "stdio.h"
#include "windows.h"

typedef int (*lpAddFun)(int ,int );//宏定义函数指针类型

int main()
{
	HINSTANCE hDll;//DLL 句柄
	lpAddFun addFun;//函数指针
	hDll = LoadLibrary(L"DllLib.dll");//动态获取dll文件的路径
	if (hDll!=NULL)
	{
		addFun =(lpAddFun)GetProcAddress(hDll,"add");
		if (addFun!=NULL)
		{
			int result =addFun(2,3);
			printf("\ncall add in dll %d",result);
		}

		FreeLibrary(hDll);
	}
	return 0;
}

六、 导出类

建立工程如下:

这里写图片描述

工程中的几个类代码如下:

point.h

#ifndef __POINT_H__
#define __POINT_H__

#ifdef DLL_FILE
class _declspec (dllexport) point //导出类point
#else 
class _declspec(dllimport) point //导入类point
#endif
{
public: 
	float y;
	float x;
	point();
	point(float x_coordinate,float y_coordinate);
};

#endif

point.cpp

#ifndef DLL_FILE
#define DLL_FILE
#endif
#include"point.h"
//类point的缺省构造函数
point::point()
	  {
		  x=0.0;
		  y=0.0;
	  }
point::point(float x_coordinate,float y_coordinate)
	  {
		  x=x_coordinate ;
		  y=y_coordinate;
	  }

circle.h

#ifndef __CIRCLE_H__
#define __CIRCLE_H__
#include "point.h"
#ifdef DLL_FILE
class _declspec (dllexport) circle //导出类circle
#else 
class _declspec(dllimport) circle //导入类circle
#endif
{
public: 
	void SetCenter(const point crePoint);
	void SetRadius(float r);
	float GetGirth();
	float GetArea();
	circle();
private:
	float radius;
	point center;
};

#endif

circle.cpp

#ifndef DLL_FILE
#define DLL_FILE
#endif
#include"circle.h"
#define  PI 3.1415926
//类circle的缺省构造函数
circle::circle()
{
	center =point(0,0);
	radius =0;
}
//得到圆的面积
float circle::GetArea()
{
	return PI*radius*radius;
}
//得到圆从周长
float circle::GetGirth()
{
	return 2*PI*radius;
}
//设置圆心坐标
void circle::SetCenter(const point crePoint)
{
	center =crePoint;
}
//设置圆的半径
void circle::SetRadius(float r)
{
	radius	 =r;
}

将circle.h,point.h和生成的.lib文件放在同级目录
这里写图片描述

main.cpp

#include "circle.h"
#include "stdio.h"

//.lib文件中仅仅是关于其对应DLL文件中函数的定位信息
#pragma comment(lib,"DllExportClass.lib")

int main()
{
    circle c;
	point p(2.0,2.0);
	c.SetCenter(p);
	c.SetRadius(1.0);
	printf("area:%f,girth:%f",c.GetArea(),c.GetGirth());
	scanf("%d");
	return 0;
}


结果为
这里写图片描述

  • 45
    点赞
  • 171
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
最简单的dll并不比c的helloworld难,只要一个DllMain函数即可,包含objbase.h头文件(支持COM技术的一个头文件)。若你觉得这个头文件名字难记,那么用windows.H也可以。源代码如下:dll_nolib.cpp #include #include BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) { HANDLE g_hModule; switch(dwReason) { case DLL_PROCESS_ATTACH: cout<<"Dll is attached!"<<endl; g_hModule = (HINSTANCE)hModule; break; case DLL_PROCESS_DETACH: cout<<"Dll is detached!"<<endl; g_hModule=NULL; break; } return true; } 其中DllMain是每个dll的入口函数,如同c的main函数一样。DllMain带有三个参数,hModule表示本dll的实例句柄(听不懂就不理它,过windows程序的自然懂),dwReason表示dll当前所处的状态,例如DLL_PROCESS_ATTACH表示dll刚刚被加载到一个进程中,DLL_PROCESS_DETACH表示dll刚刚从一个进程中卸载。当然还有表示加载到线程中和从线程中卸载的状态,这里省略。最后一个参数是一个保留参数(目前和dll的一些状态相关,但是很少使用)。 从上面的程序可以看出,当dll被加载到一个进程中时,dll打印"Dll is attached!"语句;当dll从进程中卸载时,打印"Dll is detached!"语句。 编译dll需要以下两条命令: cl /c dll_nolib.cpp 这条命令会将cpp编译为obj文件,若不使用/c参数则cl还会试图继续将obj链接为exe,但是这里是一个dll,没有main函数,因此会报错。不要紧,继续使用链接命令。 Link /dll dll_nolib.obj 这条命令会生成dll_nolib.dll。 注意,因为编译命令比较简单,所以本文不讨论nmake,有兴趣的可以使用nmake,或者个bat批处理来编译链接dll。 加载DLL(显式调用)
远程注入DLL方法有很多种,也是很多木马病毒所使用的隐藏进程的方法,因为通过程序加载的DLL在进程管理器是没有显示的.这里介绍一种用 CreateRemoteThread 远程建立线程的方式注入DLL. 首先,我们要提升自己的权限,因为远程注入必不可免的要访问到目标进程的内存空间,如果没有足够的系统权限,将无法作任何事.下面是这个函数是用来提升我们想要的权限用的. function EnableDebugPriv: Boolean; var hToken: THandle; tp: TTokenPrivileges; rl: Cardinal; begin Result := false; //打开进程令牌环 OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken); //获得进程本地唯一ID if LookupPrivilegeValue(nil, 'SeDebugPrivilege', tp.Privileges[0].Luid) then begin tp.PrivilegeCount := 1; tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; //调整权限 Result := AdjustTokenPrivileges(hToken, false, tp, SizeOf(tp), nil, rl); end; end; 关于 OpenProcessToken() 和 AdjustTokenPrivileges() 两个 API 的简单介绍: OpenProcessToken():获得进程访问令牌的句柄. function OpenProcessToken( ProcessHandle: THandle; //要修改访问权限的进程句柄 DesiredAccess: DWORD; //指定你要进行的操作型 var TokenHandle: THandle//返回的访问令牌指针 ): BOOL; AdjustTokenPrivileges() :调整进程的权限. function AdjustTokenPrivileges( TokenHandle: THandle; // 访问令牌的句柄 DisableAllPrivileges: BOOL; // 决定是进行权限修改还是除能(Disable)所有权限 const NewState: TTokenPrivileges; { 指明要修改的权限,是一个指向TOKEN_PRIVILEGES结构的指针,该结构包含一个数组, 数据组的每个项指明了权限的型和要进行的操作; } BufferLength: DWORD; //结构PreviousState的长度,如果PreviousState为空,该参数应为 0 var PreviousState: TTokenPrivileges; // 指向TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息 var ReturnLength: DWORD //实际PreviousState结构返回的大小 ) : BOOL; 远程注入DLL其实是通过 CreateRemoteThread 建立一个远程线程调用 LoadLibrary 函数来加载我们指定的DLL,可是如何能让远程线程知道我要加载DLL呢,要知道在Win32系统下,每个进程都拥有自己的4G虚拟地址空间,各个进程之间都是相互独立的。所我们需要在远程进程的内存空间里申请一块内存空间,入我们的需要注入的 DLL 的路径. 需要用到的 API 函数有: OpenProcess():打开目标进程,得到目标进程的操作权限,详细参看MSDN function OpenProcess( dwDesiredAccess: DWORD; // 希望获得的访问权限 bInheritHandle: BOOL; // 指明是否希望所获得的句柄可以继承 dwProcessId: DWORD // 要访问的进程ID ): THandle; VirtualAllocEx():用于在目标进程内存空间中申请内存空间以DLL的文件名 function VirtualAllocEx( hProcess: THandle; // 申请内存所在的进程句柄 lpAddress: Pointer; // 保留页面的内存地址;一般用nil自动分配 dwSize, // 欲分配的内存大小,字节单位;注意实际分 配的内存大小是页内存大小的整数倍 flAllocationType: DWORD; flProtect: DWORD ): Pointer; WriteProcessMemory():往申请到的空间中DLL的文件名 function WriteProcessMemory( hProcess: THandle; //要入内存数据的目标进程句柄 const lpBaseAddress: Pointer; //要入的目标进程的内存指针, 需以 VirtualAllocEx() 来申请 lpBuffer: Pointer; //要入的数据 nSize: DWORD; //入数据的大小 var lpNumberOfBytesWritten: DWORD //实际入的大小 ): BOOL; 然后就可以调用 CreateRemoteThread 建立远程线程调用 LoadLibrary 函数来加载我们指定的DLL. CreateRemoteThread() //在一个远程进程中建立线程 function CreateRemoteThread( hProcess: THandle; //远程进程的句柄 lpThreadAttributes: Pointer; //线程安全描述字,指向SECURITY_ATTRIBUTES结构的指针 dwStackSize: DWORD; //线程栈大小,以字节表示 lpStartAddress: TFNThreadStartRoutine; // 一个TFNThreadStartRoutine型的指针,指向在远程进程中执行的函数地址 lpParameter: Pointer; //传入参数的指针 dwCreationFlags: DWORD; //创建线程的其它标志 var lpThreadId: DWORD //线程身份标志,如果为0, 则不返回 ): THandle; 整个远程注入DLL的具体实现代码如下: function InjectDll(const DllFullPath: string; const dwRemoteProcessId: Cardinal): Boolean; var hRemoteProcess, hRemoteThread: THandle; pszLibFileRemote: Pointer; pszLibAFilename: PwideChar; pfnStartAddr: TFNThreadStartRoutine; memSize, WriteSize, lpThreadId: Cardinal; begin Result := false; // 调整权限,使程序可以访问其他进程的内存空间 if EnableDebugPriv then begin //打开远程线程 PROCESS_ALL_ACCESS 参数表示打开所有的权限 hRemoteProcess := OpenProcess(PROCESS_ALL_ACCESS, false, dwRemoteProcessId); try // 为注入的dll文件路径分配内存大小,由于为WideChar,故要乘2 GetMem(pszLibAFilename, Length(DllFullPath) * 2 + 1); // 之所以要转换成 WideChar, 是因为当DLL位于有中文字符的路径下时不会出错 StringToWideChar(DllFullPath, pszLibAFilename, Length(DllFullPath) * 2 + 1); // 计算 pszLibAFilename 的长度,注意,是以字节为单元的长度 memSize := (1 + lstrlenW(pszLibAFilename)) * SizeOf(WCHAR); //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名空间 pszLibFileRemote := VirtualAllocEx(hRemoteProcess, nil, memSize, MEM_COMMIT, PAGE_READWRITE); if Assigned(pszLibFileRemote) then begin //使用WriteProcessMemory函数将DLL的路径名入到远程进程的内存空间 if WriteProcessMemory(hRemoteProcess, pszLibFileRemote, pszLibAFilename, memSize, WriteSize) and (WriteSize = memSize) then begin lpThreadId := 0; // 计算LoadLibraryW的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'LoadLibraryW'); // 启动远程线程LoadLbraryW,通过远程线程调用创建新的线程 hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, pszLibFileRemote, 0, lpThreadId); // 如果执行成功返回 True; if (hRemoteThread 0) then Result := true; // 释放句柄 CloseHandle(hRemoteThread); end; end; finally // 释放句柄 CloseHandle(hRemoteProcess); end; end; end; 接下来要说的是如何卸载注入目标进程中的DLL,其实原理和注入DLL是完全相同的,只是远程调用调用的函数不同而已,这里要调用的是FreeLibrary,代码如下: function UnInjectDll(const DllFullPath: string; const dwRemoteProcessId: Cardinal): Boolean; // 进程注入和取消注入其实都差不多,只是运行的函数不同而已 var hRemoteProcess, hRemoteThread: THandle; pszLibFileRemote: PChar; pszLibAFilename: PwideChar; pfnStartAddr: TFNThreadStartRoutine; memSize, WriteSize, lpThreadId, dwHandle: Cardinal; begin Result := false; // 调整权限,使程序可以访问其他进程的内存空间 if EnableDebugPriv then begin //打开远程线程 PROCESS_ALL_ACCESS 参数表示打开所有的权限 hRemoteProcess := OpenProcess(PROCESS_ALL_ACCESS, false, dwRemoteProcessId); try // 为注入的dll文件路径分配内存大小,由于为WideChar,故要乘2 GetMem(pszLibAFilename, Length(DllFullPath) * 2 + 1); // 之所以要转换成 WideChar, 是因为当DLL位于有中文字符的路径下时不会出错 StringToWideChar(DllFullPath, pszLibAFilename, Length(DllFullPath) * 2 + 1); // 计算 pszLibAFilename 的长度,注意,是以字节为单元的长度 memSize := (1 + lstrlenW(pszLibAFilename)) * SizeOf(WCHAR); //使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名空间 pszLibFileRemote := VirtualAllocEx(hRemoteProcess, nil, memSize, MEM_COMMIT, PAGE_READWRITE); if Assigned(pszLibFileRemote) then begin //使用WriteProcessMemory函数将DLL的路径名入到远程进程的内存空间 if WriteProcessMemory(hRemoteProcess, pszLibFileRemote, pszLibAFilename, memSize, WriteSize) and (WriteSize = memSize) then begin // 计算GetModuleHandleW的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'GetModuleHandleW'); //使目标进程调用GetModuleHandleW,获得DLL在目标进程中的句柄 hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, pszLibFileRemote, 0, lpThreadId); // 等待GetModuleHandle运行完毕 WaitForSingleObject(hRemoteThread, INFINITE); // 获得GetModuleHandle的返回值,存在dwHandle变量中 GetExitCodeThread(hRemoteThread, dwHandle); // 计算FreeLibrary的入口地址 pfnStartAddr := GetProcAddress(LoadLibrary('Kernel32.dll'), 'FreeLibrary'); // 使目标进程调用FreeLibrary,卸载DLL hRemoteThread := CreateRemoteThread(hRemoteProcess, nil, 0, pfnStartAddr, Pointer(dwHandle), 0, lpThreadId); // 等待FreeLibrary卸载完毕 WaitForSingleObject(hRemoteThread, INFINITE); // 如果执行成功返回 True; if hRemoteProcess 0 then Result := true; // 释放目标进程中申请的空间 VirtualFreeEx(hRemoteProcess, pszLibFileRemote, Length(DllFullPath) + 1, MEM_DECOMMIT); // 释放句柄 CloseHandle(hRemoteThread); end; end; finally // 释放句柄 CloseHandle(hRemoteProcess); end; end; end;

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值