基础知识
DLL注入技术,一般来讲是向一个正在运行的进程插入/注入代码的过程。我们注入的代码以动态链接库(DLL)的形式存在。DLL文件在运行时将按需加载(类似于UNIX系统中的共享库(share object,扩展名为.so))。然而实际上,我们可以以其他的多种形式注入代码(正如恶意软件中所常见的,任意PE文件,shellcode代码/程序集等)
DLL入口函数标准
必须使用标准调用约定声明 DLL 入口点函数。 如果未正确声明 DLL 入口点,则不会加载 DLL,系统会显示一条消息,指示必须使用 WINAPI 声明 DLL 入口点。 在函数正文中,可以处理以下调用 DLL 入口点的方案的任何组合:
- 进程加载 DLL (DLL_PROCESS_ATTACH) 。
- 当前进程创建新的线程 (DLL_THREAD_ATTACH) 。
- 线程通常 (DLL_THREAD_DETACH) 退出。
- 进程卸载 DLL (DLL_PROCESS_DETACH) 。
数据类型
在C++中,HHOOK__是一种结构类型,用于定义钩子句柄。HHOOK类型被定义为指向此结构类型的指针。钩子是一种机制,允许您在消息队列或事件循环到达应用程序之前拦截消息、事件或数据。它们可用于监视系统事件,例如按键和鼠标单击,并在到达其预期目的地之前修改或丢弃它们。 HWND是线程相关的,你可以通过HWND找到该窗口所属进程和线程 Handle 是代表系统的内核对象,如文件句柄,线程句柄,进程句柄。 系统对内核对象以链表的形式进行管理,载入到内存中的每一个内 核对象都有一个线性地址,同时相对系统来说,在串列中有一个索引位置,这个索引位置就是内核对象的handle。 HINSTANCE的本质是模块基地址,他仅仅在同一进程中才有意义,跨进程的HINSTANCE是没有意义 HMODULE 是代表应用程序载入的模块,win32系统下通常是被载入模块的线性地址。 HINSTANCE 在win32下与HMODULE是相同的东西(只有在16位windows上,二者有所不同).
全局钩子注入
钩子编程(hooking),也称作“挂钩”,是计算机程序设计术语,指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)。一般钩子分局部钩子与全局钩子,局部钩子一般用于某个线程,而全局钩子一般通过dll文件实现相应的钩子函数。
SetWindowsHookExW 函数 (winuser.h)
将应用程序定义的挂钩过程安装到挂钩链中。 你将安装挂钩过程来监视系统的某些类型的事件。 这些事件与特定线程或调用线程位于同一桌面中的所有线程相关联。
HHOOK SetWindowsHookExW(
[in] int idHook,
[in] HOOKPROC lpfn,
[in] HINSTANCE hmod,
[in] DWORD dwThreadId
);
idHook
值 | 含义 |
---|---|
WH_CALLWNDPROC\4 | 安装一个挂钩过程,用于监视系统将消息发送到目标窗口过程之前的消息。 有关详细信息,请参阅 CallWndProc 挂钩过程。 |
WH_CALLWNDPROCRET\12 | 安装一个挂钩过程,用于在目标窗口过程处理消息后监视消息。 有关详细信息,请参阅 CallWndRetProc 挂钩过程。 |
WH_CBT\5 | 安装一个挂钩过程,该挂钩过程接收对 CBT 应用程序有用的通知。 有关详细信息,请参阅 CBTProc 挂钩过程。 |
WH_DEBUG\9 | 安装用于调试其他挂钩过程的挂钩过程。 有关详细信息,请参阅 DebugProc 挂钩过程。 |
WH_FOREGROUNDIDLE\11 | 安装将在应用程序的前景线程即将处于空闲状态时调用的挂钩过程。 此挂钩可用于在空闲时间执行低优先级任务。 有关详细信息,请参阅 ForegroundIdleProc 挂钩过程。 |
WH_GETMESSAGE\3 | 安装一个挂钩过程,用于监视发布到消息队列的消息。 有关详细信息,请参阅 GetMsgProc 挂钩过程。 |
WH_JOURNALPLAYBACK\1 | 安装一个挂钩过程,用于发布以前由 WH_JOURNALRECORD 挂钩过程记录的消息。 有关详细信息,请参阅 JournalPlaybackProc 挂钩过程。警告:从 Windows 11 开始,不支持日记挂钩 API,将在将来的版本中删除。 因此,强烈建议改为调用 SendInput TextInput API。 |
WH_JOURNALRECORD\0 | 安装一个挂钩过程,用于记录发布到系统消息队列的输入消息。 此挂钩可用于录制宏。 有关详细信息,请参阅 JournalRecordProc 挂钩过程。警告:从 Windows 11 开始,不支持日记挂钩 API,将在将来的版本中删除。 因此,强烈建议改为调用 SendInput TextInput API。 |
WH_KEYBOARD\2 | 安装监视击键消息的挂钩过程。 有关详细信息,请参阅 KeyboardProc 挂钩过程。 |
WH_KEYBOARD_LL\13 | 安装用于监视低级别键盘输入事件的挂钩过程。 有关详细信息,请参阅 LowLevelKeyboardProc 挂钩过程。 |
WH_MOUSE\7 | 安装监视鼠标消息的挂钩过程。 有关详细信息,请参阅 MouseProc 挂钩过程。 |
WH_MOUSE_LL\14 | 安装用于监视低级别鼠标输入事件的挂钩过程。 有关详细信息,请参阅 LowLevelMouseProc 挂钩过程。 |
WH_MSGFILTER\-1 | 安装一个挂钩过程,用于监视对话框、消息框、菜单或滚动条中输入事件生成的消息。 有关详细信息,请参阅 MessageProc 挂钩过程。 |
WH_SHELL\10 | 安装一个挂钩过程,该挂钩过程接收对 shell 应用程序有用的通知。 有关详细信息,请参阅 ShellProc 挂钩过程。 |
WH_SYSMSGFILTER\6 | 安装一个挂钩过程,用于监视对话框、消息框、菜单或滚动条中输入事件生成的消息。 挂钩过程监视与调用线程位于同一桌面中的所有应用程序的消息。 有关详细信息,请参阅 SysMsgProc 挂钩过程。 |
lpfn
指向挂钩过程的指针。 如果 dwThreadId 参数为零或指定由其他进程创建的线程的标识符, 则 lpfn 参数必须指向 DLL 中的挂钩过程。 否则, lpfn 可以指向与当前进程关联的代码中的挂钩过程。
hmod
包含 lpfn 参数指向的挂钩过程的 DLL 的句柄。 如果 dwThreadId 参数指定由当前进程创建的线程,并且挂钩过程位于与当前进程关联的代码中,则必须将 hMod 参数设置为 NULL。
dwThreadId
要与之关联的挂钩过程的线程的标识符。 对于桌面应用,如果此参数为零,则挂钩过程与调用线程在同一桌面上运行的所有现有线程相关联。 对于 Windows 应用商店应用,请参阅“备注”部分。
返回
如果函数成功,则返回值是挂钩过程的句柄,类型为HHOOK。 如果函数失败,则返回值为 NULL。
例子
HHOOK WINAPI SetWindowsHookEx(
__in int idHook, \\钩子类型
__in HOOKPROC lpfn, \\回调函数地址
__in HINSTANCE hMod, \\实例句柄
__in DWORD dwThreadId); \\线程ID
)
如果创建的是全局钩子,那么钩子函数必须在一个DLL中。这是因为进程的地址空间是独立的,发生对应事件的进程不能调用其他进程地址空间的钩子函数。如果钩子函数的实现代码在DLL中,则在对应事件发生时,系统会把这个DLL加较到发生事的进程地址空间中,使它能够调用钩子函数进行处理。
在操作系统中安装全局钩子后,只要进程接收到可以发出钩子的消息,全局钩子的DLL文件就会由操作系统自动或强行地加载到该进程中。因此,设置全局钩子可以达到DLL注入的目的。创建一个全局钩子后,在对应事件发生的时候,系统就会把 DLL加载到发生事件的进程中,这样,便实现了DLL注入。
为了能够让DLL注入到所有的进程中,程序设置WH_GETMESSAGE消息的全局钩子。因为WH_GETMESSAGE类型的钩子会监视消息队列,并且 Windows系统是基于消息驱动的,所以所有进程都会有自己的一个消息队列,都会加载 WH_GETMESSAGE类型的全局钩子DLL。
// 设置全局钩子
BOOL SetHook()
{
g_Hook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllMoudle, 0);
if (g_Hook == NULL)
{
return FALSE;
}
return TRUE;
}
第二个参数是回调函数,那么我们还需要写一个回调函数的实现,这里就需要用到CallNextHookEx这个api,主要是第一个参数,这里传入钩子的句柄的话,就会把当前钩子传递给下一个钩子,若参数传入0则对钩子进行拦截
// 钩子回调函数
LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
return ::CallNextHookEx(g_Hook, code, wParam, lParam);
}
使用到UnhookWindowsHookEx这个api来卸载钩子
// 卸载钩子
BOOL UnsetHook()
{
if (g_Hook)
{
::UnhookWindowsHookEx(g_Hook);
}
}
进程通信的方法有很多,比如自定义消息、管道、dll共享节、共享内存等等,这里就用共享内存来实现进程通信
// 共享内存
#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS"
远程线程注入
原理
使用CreateRemoteThread这个API,首先使用CreateToolhelp32Snapshot拍摄快照获取pid,然后使用Openprocess打开进程,使用VirtualAllocEx远程申请空间,使用WriteProcessMemory写入数据,再用GetProcAddress获取LoadLibraryW的地址(由于Windows引入了基址随机化ASLR安全机制,所以导致每次开机启动时系统DLL加载基址都不一样,有些系统dll(kernel,ntdll)的加载地址,允许每次启动基址可以改变,但是启动之后必须固定,也就是说两个不同进程在相互的虚拟内存中,这样的系统dll地址总是一样的),在注入进程中创建线程(CreateRemoteThread)
例子
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, L"success!", L"Congratulation", MB_OK);
case DLL_THREAD_ATTACH:
MessageBox(NULL, L"success!", L"Congratulation", MB_OK);
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
// RemoteThreadInject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <windows.h>
#include <TlHelp32.h>
#include "tchar.h"
char string_inject[] = "F:\\C++\\Inject\\Inject\\Debug\\Inject.dll";
//通过进程快照获取PID
DWORD _GetProcessPID(LPCTSTR lpProcessName)
{
DWORD Ret = 0;
PROCESSENTRY32 p32;
HANDLE lpSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (lpSnapshot == INVALID_HANDLE_VALUE)
{
printf("获取进程快照失败,请重试! Error:%d", ::GetLastError());
return Ret;
}
p32.dwSize = sizeof(PROCESSENTRY32);
::Process32First(lpSnapshot, &p32);
do {
if (!lstrcmp(p32.szExeFile, lpProcessName))
{
Ret = p32.th32ProcessID;
break;
}
} while (::Process32Next(lpSnapshot, &p32));
::CloseHandle(lpSnapshot);
return Ret;
}
//打开一个进程并为其创建一个线程
DWORD _RemoteThreadInject(DWORD _Pid, LPCWSTR DllName)
{
//打开进程
HANDLE hprocess;
HANDLE hThread;
DWORD _Size = 0;
BOOL Write = 0;
LPVOID pAllocMemory = NULL;
DWORD DllAddr = 0;
FARPROC pThread;
hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);
//Size = sizeof(string_inject);
_Size = (_tcslen(DllName) + 1) * sizeof(TCHAR);
//远程申请空间
pAllocMemory = ::VirtualAllocEx(hprocess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE);
if (pAllocMemory == NULL)
{
printf("VirtualAllocEx - Error!");
return FALSE;
}
// 写入内存
Write = ::WriteProcessMemory(hprocess, pAllocMemory, DllName, _Size, NULL);
if (Write == FALSE)
{
printf("WriteProcessMemory - Error!");
return FALSE;
}
//获取LoadLibrary的地址
pThread = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;
//在另一个进程中创建线程
hThread = ::CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory, 0, NULL);
if (hThread == NULL)
{
printf("CreateRemoteThread - Error!");
return FALSE;1
}
//等待线程函数结束,获得退出码
WaitForSingleObject(hThread, -1);
GetExitCodeThread(hThread, &DllAddr);
//释放DLL空间
VirtualFreeEx(hprocess, pAllocMemory, _Size, MEM_DECOMMIT);
//关闭线程句柄
::CloseHandle(hprocess);
return TRUE;
}
int main()
{
DWORD PID = _GetProcessPID(L"test.exe");
_RemoteThreadInject(PID, L"F:\\C++\\Inject\\Inject\\Debug\\Inject.dll");
}
反射dll注入
学习案例:https://github.com/stephenfewer/ReflectiveDLLInjection 本项目由注入器inject和反射dll reflective_dll组成
代码分析
#endif
do
{
// Usage: inject.exe [pid] [dll_file]
if( argc == 1 )
dwProcessId = GetCurrentProcessId();
else
dwProcessId = atoi( argv[1] );
if( argc >= 3 )
cpDllFile = argv[2];
hFile = CreateFileA( cpDllFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if( hFile == INVALID_HANDLE_VALUE )
BREAK_WITH_ERROR( "Failed to open the DLL file" );
dwLength = GetFileSize( hFile, NULL );
if( dwLength == INVALID_FILE_SIZE || dwLength == 0 )
BREAK_WITH_ERROR( "Failed to get the DLL file size" );
lpBuffer = HeapAlloc( GetProcessHeap(), 0, dwLength );
if( !lpBuffer )
BREAK_WITH_ERROR( "Failed to get the DLL file size" );
if( ReadFile( hFile, lpBuffer, dwLength, &dwBytesRead, NULL ) == FALSE )
BREAK_WITH_ERROR( "Failed to alloc a buffer!" );
if( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )//打开目标进程的访问令牌
{
priv.PrivilegeCount = 1;
priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if( LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid ) )
AdjustTokenPrivileges( hToken, FALSE, &priv, 0, NULL, NULL );
CloseHandle( hToken );
}
hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, dwProcessId );
if( !hProcess )
BREAK_WITH_ERROR( "Failed to open the target process" );
hModule = LoadRemoteLibraryR( hProcess, lpBuffer, dwLength, NULL );
if( !hModule )
BREAK_WITH_ERROR( "Failed to inject the DLL" );
printf( "[+] Injected the '%s' DLL into process %d.", cpDllFile, dwProcessId );
WaitForSingleObject( hModule, -1 );
} while( 0 );
使用CreateFileA()打开cpDllFile变量内储存的dll文件,并将该流存放在hFile中,使用GetFileSize()读取dll文件长度存放在变量dwLength中,使用HeapAlloc()分配一块内存用lpBuffer指向它,ReadFile()读取dll文件并把文件写入到lpBuffer指向的内存块。使用OpenProcess()打开目标进程,并用hProcess指向,使用LoadRemoteLibraryR()对目标进程进行注入
HANDLE WINAPI LoadRemoteLibraryR( HANDLE hProcess, LPVOID lpBuffer, DWORD dwLength, LPVOID lpParameter )
{
BOOL bSuccess = FALSE;
LPVOID lpRemoteLibraryBuffer = NULL;
LPTHREAD_START_ROUTINE lpReflectiveLoader = NULL;
HANDLE hThread = NULL;
DWORD dwReflectiveLoaderOffset = 0;
DWORD dwThreadId = 0;
__try
{
do
{
if( !hProcess || !lpBuffer || !dwLength )
break;
// check if the library has a ReflectiveLoader...
dwReflectiveLoaderOffset = GetReflectiveLoaderOffset( lpBuffer );
if( !dwReflectiveLoaderOffset )
break;
// alloc memory (RWX) in the host process for the image...
lpRemoteLibraryBuffer = VirtualAllocEx( hProcess, NULL, dwLength, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE );
if( !lpRemoteLibraryBuffer )
break;
// write the image into the host process...
if( !WriteProcessMemory( hProcess, lpRemoteLibraryBuffer, lpBuffer, dwLength, NULL ) )
break;
// add the offset to ReflectiveLoader() to the remote library address...
lpReflectiveLoader = (LPTHREAD_START_ROUTINE)( (ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset );
// create a remote thread in the host process to call the ReflectiveLoader!
hThread = CreateRemoteThread( hProcess, NULL, 1024*1024, lpReflectiveLoader, lpParameter, (DWORD)NULL, &dwThreadId );
} while( 0 );
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
hThread = NULL;
}
return hThread;
GetReflectiveLoaderOffset()计算写入内存的dll文件中的ReflectiveLoader函数的位置,顺便检查它是否存在来判断使用的dll文件是否是一个反射dll文件,lpRemoteLibraryBuffer指向使用VirtualAllocEx()在目标进程内划出的虚拟空间,WriteProcessMemory()向该空间写入之前存储在内存中的dll文件,最终使用CreateRemoteThread()来在远程进程中调用ReflectiveLoader函数完成dll注入。