动态Dll注入和卸载(Dynamic DLL Injection And Unloading)

Dynamic DLL Injection And Unloading

提到注入技术,第一想到的可能是游戏外挂(我第一次听到注入也是在外挂。。。),病毒等等,但注入也会被应用到插件开发,应用拓展,修复bug上

远程线程注入

  • OpenProcess 用待注入进程的id拿到句柄

  • VirtualAllocEx 注入进程内申请远程一块虚拟空间

  • WriteProcessMemory 往这块虚拟内存写入dll名称

  • GetModuleHandle 获取kernel32.dll句柄

    常见的系统模块kernel32.dll user32.dll gdi32.dll advapi32.dll shell32.dll等加载到各个进程虚拟地址的偏移是相同的(这个原因也就导致我们32位注入器只能注入32位进程,64位只能注入64位进程),这一点让我好像明白了windows dll为什么采用直接修改内存代码的基址重定位,不怕浪费内存吗?,这样看如果地址一样,直接在内存中映射一份就可以多进程共享了,(不过在实验(xp sp3里)中发现,或者说是多个本来映射到同一块,某个进程改时复制一块

    aslr是每次启动系统时,系统模块加载基址会变

    感觉这种机制也是为什么linux的服务端为什么会windows安全很多的原因之一

  • GetProcAddress从kernel32中获取LoadLibraryW函数地址

  • CreateRemoteThread向远程进程开启远程线程来调用LoadLibraryW注入到进程,进程执行DllMain

hack.dll

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
HMODULE g_hMod;
DWORD WINAPI ThreadProc(LPVOID lParam) {
TCHAR Path[MAX_PATH];
if (!GetModuleFileName(g_hMod, Path, MAX_PATH))
return false;
MessageBox(NULL,L"grxer",Path,MB_OK);
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
g_hMod = hModule;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
OutputDebugString(L"hack.dll injection\n");
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
break;
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

Inject.exe

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <winnt.h>
#include <assert.h>
BOOL Inject(DWORD dwPID, LPCTSTR szDllPath) {
HANDLE hProcess;
HANDLE hThread;
HMODULE hMod = NULL;
DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
LPVOID pRemoteBuf = NULL;
LPTHREAD_START_ROUTINE pThreadProc;
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) {
_tprintf(L"open fail %d", GetLastError());
return FALSE;
}
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
_tprintf(L"%p", pRemoteBuf);
if (pRemoteBuf == 0) {
_tprintf(L"%d\n", GetLastError());
return FALSE;
}
WriteProcessMemory(hProcess, pRemoteBuf, (LPCVOID)szDllPath, dwBufSize, NULL);
hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
int _tmain(int argc, TCHAR* argv[]) {
if (argc != 3) {
_tprintf(L"USAGE : %s <pid> <dll_path>\n", argv[0]);
return 1;
}
_tprintf(L"%s,%s", argv[1], argv[2]);
if (!Inject((DWORD)_tstol(argv[1]), argv[2])) {
_tprintf(L"InjectDll(\"%s\") failed!!!\n", argv[2]);
return 1;
}
_tprintf(L"InjectDll(\"%s\") success!!!\n", argv[2]);
return 0;
}

如果注入另一帐户拥有的进程内存需要先获得SeDebug权限,需要我们注入器在管理员权限下做提权操作

SE_DEBUG_NAME TEXT (“SeDebugPrivilege”) 调试和调整另一帐户拥有的进程内存所必需的。 用户权限:调试程序。
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) {
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;

if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken)) {
_tprintf(L"OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}

if (!LookupPrivilegeValue(NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid)) // receives LUID of privilege
{
_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;

// Enable the privilege or disable all privileges.
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;
}
if (!SetPrivilege(SE_DEBUG_NAME, TRUE))
return 1;

全局消息钩子注入

image-20230526000213620

用SetWindowsHookEx把钩子挂入钩链

同样是32位程序只能给32位程序挂钩子

KeyHook.dll

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <tchar.h>
#define DEF_PROCESS_NAME L"notepad.exe"
HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_hInstance = hModule;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
TCHAR szPath[MAX_PATH] = { 0 };
TCHAR* p = NULL;

if (nCode >= 0) {
// bit 31 : 0 => press, 1 => release
if (!(lParam & 0x80000000)) {
GetModuleFileName(NULL, szPath, MAX_PATH);
p = _tcsrchr(szPath, '\\');
MessageBox(NULL, szPath, szPath, MB_OK);
// 比较当前进程名称,若为notepad,则消息不会传递给应用程序
if (!_tcsicmp(p + 1, DEF_PROCESS_NAME))
return 1;
}
}
// 若非notepad,则传给下一个程序
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void HookStart()//这个函数要从本DLL导出。我要给别人用。
{
MessageBox(NULL,L"hook start",L"hook start",MB_OK);
g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);//g_hInstance:dll文件句柄
}

__declspec(dllexport) void HookStop()//这个函数要从本DLL导出。我要给别人用。
{
if (g_hHook) {
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
#ifdef __cplusplus
}
#endif

MainHook.cpp

#include <stdio.h>
#include <conio.h>
#include <windows.h>
#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"
typedef void (*PFN_HOOKSTART)();
typedef void (*PFN_HOOKSTOP)();
int main() {
HMODULE hDll = NULL;
PFN_HOOKSTART HookStart = NULL;
PFN_HOOKSTOP HookStop = NULL;
hDll = LoadLibraryA(DEF_DLL_NAME);
if (hDll == NULL) {
printf("LoadLibrary(%s) failed!!! [%d]", DEF_DLL_NAME, GetLastError());
return 1;
}
// 获取导出函数地址
HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
printf("HookStart(%p)\n ", HookStart);
HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);
printf("HookStop(%p)\n ", HookStop);
HookStart();
printf("mortypress 'q' to quit!\n");
while (_getch() != 'q');
// 停止勾取
HookStop();

// 卸载KeyHook.dll
FreeLibrary(hDll);

}

注册表注入

下面路径的AppInit_DLLs设置为要注入的DLL的路径并且将LoadAppInit_DLLs的值改成1,当程序重启的时候,所有加载user32.dll的进程都会根据AppInit_Dlls中的DLL路径加载指定的DLL

计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows

可以在dll里加个判断只注入想注入的进程

if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )//检索包含指定模块的文件的完全限定路径。 模块必须由当前进程加载。
break;

if( !(p = _tcsrchr(szPath, '\\')) )// strrchr函数查找 str 中的 c(已转换为 char)末次出现位置。 搜索包括终止 NULL 字符
break;

if( _tcsicmp(p+1, "想注入进程名字") )
break;

DLL卸载

  • CreateToolhelp32Snapshot去拍一个TH32CS_SNAPALL参数的系统快照
  • Process32First和NEXT通过PROCESSENTRY32结构体的szExeFile来比较找到notepad
  • CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)进程的所有模块快照找到dll加载基址 即dll句柄
  • 后面和加载一样,创建远程线程FreeLibrary掉库
#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#define DEF_PROC_NAME (L"notepad.exe")
#define DEF_DLL_NAME (L"hack.dll")
DWORD FindProcessID(LPCTSTR szProcessName) {
DWORD dwPID = 0xFFFFFFFF;
HANDLE hSnapShot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe;

// 获取系统快照
pe.dwSize = sizeof(PROCESSENTRY32);
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);

// 查找进程
Process32First(hSnapShot, &pe);
do {
if (!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile)) {
dwPID = pe.th32ProcessID;
break;
}
} while (Process32Next(hSnapShot, &pe));

CloseHandle(hSnapShot);

return dwPID;
}
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName) {
BOOL bMore = FALSE, bFound = FALSE;
HANDLE hSnapshot, hProcess, hThread;
HMODULE hModule = NULL;
MODULEENTRY32 me = { sizeof(me) };
LPTHREAD_START_ROUTINE pThreadProc;

// dwPID = notepad 进程id
// 使用TH32CS_SNAPMODULE 参数,获取加载到notepad 进程dll的名称
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);//句柄

bMore = Module32First(hSnapshot, &me);
for (; bMore; bMore = Module32Next(hSnapshot, &me))//循环比较地址
{
if (!_tcsicmp((LPCTSTR)me.szModule, szDllName) ||
!_tcsicmp((LPCTSTR)me.szExePath, szDllName)) {
bFound = TRUE;
break;
}
}

if (!bFound) {
CloseHandle(hSnapshot);
return FALSE;
}

if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))//获取目标进程的句柄
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}

hModule = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");//获取FreeLibrary的api地址
hThread = CreateRemoteThread(hProcess, NULL, 0,
pThreadProc, me.modBaseAddr,
0, NULL); //卸载dll
WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);
CloseHandle(hSnapshot);

return TRUE;
}
int _tmain()
{
DWORD dwPID = 0xFFFFFFFF;
dwPID = FindProcessID(DEF_PROC_NAME);//查找notepad的id
if (dwPID == 0xFFFFFFFF) {
_tprintf(L"There is no <%s> process!\n", DEF_PROC_NAME);
return 1;
}

_tprintf(L"PID of \"%s\" is %d\n", DEF_PROC_NAME, dwPID);

if (EjectDll(dwPID, DEF_DLL_NAME))
_tprintf(L"EjectDll(%d, \"%s\") success!!!\n", dwPID, DEF_DLL_NAME);
else
_tprintf(L"EjectDll(%d, \"%s\") failed!!!\n", dwPID, DEF_DLL_NAME);

return 0;
}