0x00前言
RT,最近正在学习DLL注入。尝试写篇总结
0x01正文
什么是远程线程注入?
远程线程注入是指一个进程在另一个进程中创建线程的技术。
前置的一些知识
CreateToolhelp32Snapshot函数
https://learn.microsoft.com/zh-cn/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot
获取指定进程的快照,以及这些进程使用的堆、模块和线程。(也就是说,我们可以利用这个函数来获取进程的PID)
HANDLE CreateToolhelp32Snapshot(
[in] DWORD dwFlags,
[in] DWORD th32ProcessID
);
PROCESSENTRY32结构体
https://learn.microsoft.com/zh-cn/windows/win32/api/tlhelp32/ns-tlhelp32-processentry32
用来存放快照进程信息的一个结构体。(存放进程信息和调用成员输出进程信息)用来Process32First指向第一个进程信息,并将进程信息抽取到PROCESSENTRY32中。用Process32Next指向下一条进程信息。
引用所需包含的头文件:#include"tlhelp32.h"
typedef struct tagPROCESSENTRY32 {
DWORD dwSize;
DWORD cntUsage;
DWORD th32ProcessID;
ULONG_PTR th32DefaultHeapID;
DWORD th32ModuleID;
DWORD cntThreads;
DWORD th32ParentProcessID;
LONG pcPriClassBase;
DWORD dwFlags;
CHAR szExeFile[MAX_PATH];
} PROCESSENTRY32;
Process32First函数
https://learn.microsoft.com/zh-cn/windows/win32/api/tlhelp32/nf-tlhelp32-process32first
检索有关系统快照中遇到的第一个进程的信息。
BOOL Process32First(
[in] HANDLE hSnapshot,
[in, out] LPPROCESSENTRY32 lppe
);
Process32Next函数
https://learn.microsoft.com/zh-cn/windows/win32/api/tlhelp32/nf-tlhelp32-process32next
Process32Next是一个进程获取函数,当我们利用函数CreateToolhelp32Snapshot()获得当前运行进程的快照后,我们可以利用Process32Next函数来获得下一个进程的句柄。
BOOL Process32Next(
[in] HANDLE hSnapshot,
[out] LPPROCESSENTRY32 lppe
);
OpenProcess函数
https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
打开现有的本地进程
HANDLE OpenProcess(
[in] DWORD dwDesiredAccess,
[in] BOOL bInheritHandle,
[in] DWORD dwProcessId
);
VirtualAllocEx函数
https://learn.microsoft.com/zh-cn/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
在指定进程的虚拟地址空间内保留、提交、或更改内存的状态
LPVOID VirtualAllocEx(
[in] HANDLE hProcess,
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect
);
WriteProcessMemory函数
https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory
在指定的进程中将数据写入内存区域,要写入的整个区域必须可访问,否则操作失败
BOOL WriteProcessMemory(
[in] HANDLE hProcess,
[in] LPVOID lpBaseAddress,
[in] LPCVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesWritten
);
GetProcAddress函数
https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress
从指定的动态链接库(DLL)检索导出函数(也称为过程)或变量的地址。
FARPROC GetProcAddress(
[in] HMODULE hModule,
[in] LPCSTR lpProcName
);
CreateRemoteThread函数
https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethread
在另一个进程的虚拟地址空间中创建运行的线程
HANDLE CreateRemoteThread(
[in] HANDLE hProcess,
[in] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in] LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out] LPDWORD lpThreadId
);
LoadLibrary函数(有A系/W系(分别代表了ascii码和Unicode码))
https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw
将指定的模块加载到调用进程的地址空间中。指定的 模块可能导致加载其他模块。
HMODULE LoadLibraryW(
[in] LPCWSTR lpLibFileName
);
如何获取PID
DWORD GetProcess(LPCTSTR lpProcessName) { //获取进程的句柄
DWORD processId=0;
PROCESSENTRY32 p32;
HANDLE processAll = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);//获取到系统全部进程
if (processAll == INVALID_HANDLE_VALUE) //CreateToolhelp32Snapshot函数失败返回值
{
printf("CreateToolhelp32Snapshot,error:%d",GetLastError());
return 0;
}
p32.dwSize = sizeof(PROCESSENTRY32);// 所有快照进程信息的大小
Process32First(processAll, &p32);//从进程中获取倒
do {
if (!lstrcmp(p32.szExeFile , lpProcessName)) {//szExeFile为进程的可执行文件的名称
processId = p32.th32ProcessID;
break;
}
} while (Process32Next(processAll, &p32));//获取下一个进程句柄
CloseHandle(processAll); //关闭句柄
return processId;
}
远程线程注入实现原理
dll远程线程注入的核心是CreateRemoteThread函数,利用该函数可以在个进程空间中创建一个线程。从CreateRemoteThread需要的传递的参数中可知,我们需要目标进程空间的多线程函数地址,以及多线程参数。也就是说我们可以把LoadLibrary函数的地址给作为多线程函数的地址(LoadLibrary函数是用来动态加载DLL的),然后将一个DLL的地址作为多线程的参数。这样就可以成功在目标的空间中利用CreateRemoteThread创建一个多线程。
但是由于Windows引入了基址随机化ASLR安全机制,每次开机or在不同的系统中,系统DLL的加载基址都不一样,也就是说DLL的导出函数地址也都不一样。不过,像(kernel32,ntdll)的加载基地址在系统启动后是固定不变的,也就是说在任何一个程序调用它们的地址都一样,导出函数地址也一致,所以自己程序中的LoadLibrary函数与其他程序的LoadLibrary函数地址也一致,所以我们可以直接用GetProcAddress函数去获取LoadLibrary的地址。
所以说,我们可以先用VirtualAllocEx函数在对方的进程中申请一块内存,然后用WriteProcessMemory函数将指定DLL写入到目标的进程空间中,然后利用GetProcAddress函数去获取LoadLibrary的地址,最后利用CreateRemoteThread函数创建线程并注入进目标的进程当中,最后等待线程结束后释放DLL空间并关闭线程。这样就实现了远程线程注入DLL。
实现远程线程的代码
DWORD CreatRemoteThreadInjectDll(DWORD pid, LPCTSTR DllName) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);//打开注入进程获取进程句柄
if (hProcess == NULL) {
printf("OpenProcess error\n");
return FALSE;
}
DWORD size = (lstrlen(DllName) + 1) * sizeof(TCHAR);
LPVOID pAllocMemory = VirtualAllocEx(hProcess, NULL, size, MEM_COMMIT, PAGE_READWRITE);//为注入的进程申请内存
if (pAllocMemory == NULL) {
printf("VirtualAllocEx error\n");
return FALSE;
}
BOOL WPM = WriteProcessMemory(hProcess, pAllocMemory, DllName, size, NULL); //写入内存
if (WPM == 0) {
printf("WPM error!\n");
return FALSE;
}
FARPROC pThread = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibrary");//获取LoadLibrary的地址
if (pThread == NULL) {
printf("GetProcAddress error \n");
return FALSE;
}
//创建远程注入线程
HANDLE hRemoteThred = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pThread, pAllocMemory, 0, NULL);
if (hRemoteThred == NULL) {
printf("CreateRemoteThread error \n");
return FALSE;
}
WaitForSingleObject(hRemoteThred, -1);//等待线程结束
VirtualFreeEx(hProcess, pAllocMemory, size, MEM_DECOMMIT);//释放DLL空间
CloseHandle(hProcess);
return true;
}
dll代码
简单写来个弹窗dll
实现效果&&完整代码
#include<stdio.h>
#include <Windows.h>
#include <string.h>
#include <TlHelp32.h>
DWORD GetProcess(LPCTSTR lpProcessName) { //获取进程的句柄
DWORD processId=0;
PROCESSENTRY32 p32;
HANDLE processAll = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);//获取到系统全部进程
if (processAll == INVALID_HANDLE_VALUE) //CreateToolhelp32Snapshot函数失败返回值
{
printf("CreateToolhelp32Snapshot,error:%d",GetLastError());
return 0;
}
p32.dwSize = sizeof(PROCESSENTRY32);// 所有快照进程信息的大小
Process32First(processAll, &p32);//从进程中获取倒
do {
if (!lstrcmp(p32.szExeFile , lpProcessName)) {//szExeFile为进程的可执行文件的名称
processId = p32.th32ProcessID;
break;
}
} while (Process32Next(processAll, &p32));//获取下一个进程句柄
CloseHandle(processAll); //关闭句柄
return processId;
}
DWORD CreatRemoteThreadInjectDll(DWORD pid, LPCTSTR DllName) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);//打开注入进程获取进程句柄
if (hProcess == NULL) {
printf("OpenProcess error\n");
return FALSE;
}
DWORD size = (lstrlen(DllName) + 1) * sizeof(TCHAR);
LPVOID pAllocMemory = VirtualAllocEx(hProcess, NULL, size, MEM_COMMIT, PAGE_READWRITE);//为注入的进程申请内存
if (pAllocMemory == NULL) {
printf("VirtualAllocEx error\n");
return FALSE;
}
BOOL WPM = WriteProcessMemory(hProcess, pAllocMemory, DllName, size, NULL); //写入内存
if (pAllocMemory == 0) {
printf("WPM error!\n");
return FALSE;
}
FARPROC pThread = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibrary");//获取LoadLibrary的地址
if (pThread == NULL) {
printf("GetProcAddress error \n");
return FALSE;
}
//创建远程注入线程
HANDLE hRemoteThred = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pThread, pAllocMemory, 0, NULL);
if (hRemoteThred == NULL) {
printf("CreateRemoteThread error \n");
return FALSE;
}
WaitForSingleObject(hRemoteThred, -1);//等待线程结束
VirtualFreeEx(hProcess, pAllocMemory, size, MEM_DECOMMIT);//释放DLL空间
CloseHandle(hProcess);
return true;
}
int main(int argc, TCHAR* argv[]){
DWORD PID = GetProcess(L"notepad.exe");
//printf("notepad 的进程为:%d",PID);
CreatRemoteThreadInjectDll(PID, L"dll地址");
}