要實現線程的遠程注入必須使用Windows提供的CreateRemoteThread函數來創建一個遠程線程
該函數的原型如下:
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
參數說明:
hProcess:目標進程的句柄
lpThreadAttributes:指向線程的安全描述結構體的指針,一般設置爲NULL,表示使用默認的安全級別
dwStackSize:線程堆棧大小,一般設置爲0,表示使用默認的大小,一般爲1M
lpStartAddress:線程函數的地址
lpParameter:線程參數
dwCreationFlags:線程的創建方式
CREATE_SUSPENDED 線程以掛起方式創建
lpThreadId:輸出參數,記錄創建的遠程線程的ID
CreateRemoteThread函數介紹完畢,其他詳細信息參考MSDN中關於該函數的詳細說明!
既然知道了使用這個函數來創建一個遠程線程,接下來我們就來定義線程函數體,和普通的線程函數的
定義相同,遠程線程的線程函數必須定義程類的靜態成員函數或者全局函數
例如:
DWORD __stdcall threadProc(LPVOID lParam)
{
//我們在這裏先將該線程函數定義爲空函數
return 0;
}
在這裏我們先將線程函數體定義爲空,因爲要作爲遠程注入的線程,線程函數體的編寫方式和普通線程函數
稍有不同。
然後將線程代碼拷貝到目標進程地址空間中(該地址必須是頁面屬性爲PAGE_EXECUTE_READWRITE的頁面)或者
其他宿主進程能執行地方(如:共享內存映射區)。在這裏我們選擇宿主進程。在拷貝線程體的時候我們需要
使用VirtualAllocEx函數在宿主進程中申請一塊存儲區域,然後再通過WriteProcessMemory函數將線程代碼寫
入宿主進程中。
要取得宿主進程的ID可以有很多種方法可以使用Psapi.h中的函數,也可以使用Toolhelp函數,在這裏提供一種
使用Toolhelp實現的函數,函數如下
//根據進程名稱得到進程的ID,如果有多個實例在同時運行的話,只返回第一個枚舉到的進程ID
DWORD processNameToId(LPCTSTR lpszProcessName)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnapshot, &pe)) {
MessageBox(NULL,
"The frist entry of the process list has not been copyied to the buffer",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
while (Process32Next(hSnapshot, &pe)) {
if (!strcmp(lpszProcessName, pe.szExeFile)) {
return pe.th32ProcessID;
}
}
return 0;
}
以上步驟完成之後就可以使用CreateRemoteThread創建遠程線程了!示例代碼如下
#include <windows.h>
#include <TlHelp32.h>
#include <iostream>
//要插入宿主進程中的線程函數
DWORD __stdcall threadProc(LPVOID lParam)
{
return 0;
}
int main(int argc, char* argv[])
{
const DWORD dwThreadSize = 4096;
DWORD dwWriteBytes;
std::cout << "Please input the name of target process" << std::endl;
char szExeName[MAX_PATH] = { 0 };
//等待輸入宿主進程名稱
std::cin >> szExeName;
//得到指定名稱進程的進程ID,如果有多個進程實例,則得到第一個進程ID
DWORD dwProcessId = processNameToId(szExeName);
HANDLE hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId)
void* pRemoteThread = VirtualAllocEx(hTargetProcess, 0,
dwThreadSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
//把線程體寫入宿主進程中
if (!WriteProcessMemory(hTargetProcess,
pRemoteThread, &threadProc, dwThreadSize, 0)) {
MessageBox(NULL, "Write data to target process failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//在宿主進程中創建線程
HANDLE hRemoteThread = CreateRemoteThread(
hTargetProcess, NULL, 0, (DWORD (__stdcall *)(void *))pRemoteThread,
NULL, 0, &dwWriteBytes);
if (!hRemoteThread) {
MessageBox(NULL, "Create remote thread failed !", "Notice", MB_ICONSTOP);
return -1;
}
return 0;
}
當上面的代碼運行的時候會在宿主進程中創建一條由程序員定義的線程,只不過現在這個線程函數體爲空
什麼都不做。
下面我們來編寫具體的線程函數體的內容,在這裏我們只是簡單的顯示一個消息對話框MessageBox
修改之後的線程函數體如下:
DWORD __stdcall threadProc(LPVOID lParam)
{
MessageBox(NULL, "hello", "hello", MB_OK);
return 0;
}
線程體修改完畢之後我們運行程序,將線程注入到宿主進程之中。不過此時會產生一個非法訪問的錯誤。原
因就是線程體中的MessageBox(NULL, "hello", "hello", MB_OK);函數的第二和第三個參數所指向的字符串
是存在於當前進程的地址空間中,宿主進程中的線程訪問該字符串"hello"就會出現訪問內存非法的錯誤。
解決的方法就是將該字符串的內容也拷貝到宿主進程的地址空間中,而且連同MessageBox函數在User32.dll
中的地址也拷貝到宿主進程之中。
要將字符串和MessageBox函數的入口地址拷貝到宿主進程中我們首先定義下面這個RemoteParam結構體,用來
存放MessageBox函數的入口地址和MessageBox顯示的字符串的內容,該結構的定義如下:
//線程參數
typedef struct _RemoteParam {
char szMsg[12]; //MessageBox函數顯示的字符串
DWORD dwMessageBox;//MessageBox函數的入口地址
} RemoteParam, * PRemoteParam;
RemoteParam remoteData;
ZeroMemory(&remoteData, sizeof(RemoteParam));
HINSTANCE hUser32 = LoadLibrary("User32.dll");
remoteData.dwMessageBox = (DWORD)GetProcAddress(hUser32, "MessageBoxA");
strcat(remoteData.szMsg, "Hello/0");
//在宿主進程中分配存儲空間
RemoteParam* pRemoteParam = (RemoteParam*)VirtualAllocEx(
hTargetProcess , 0, sizeof(RemoteParam), MEM_COMMIT, PAGE_READWRITE);
if (!pRemoteParam) {
MessageBox(NULL, "Alloc memory failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//將字符串和MessageBox函數的入口地址寫入宿主進程
if (!WriteProcessMemory(hTargetProcess ,
pRemoteParam, &remoteData, sizeof(remoteData), 0)) {
MessageBox(NULL, "Write data to target process failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//創建遠程線程
HANDLE hRemoteThread = CreateRemoteThread(
hTargetProcess, NULL, 0, (DWORD (__stdcall *)(void *))pRemoteThread,
pRemoteParam, 0, &dwWriteBytes);
另外還需要注意的一點是,在打開進程的時候有些系統進程是無法用OpenProcess函數
打開的,這個時候就需要提升進程的訪問權限,進而來達到訪問系統進程的目的,在這裏
我提供了一個提升進程訪問權限的函數enableDebugPriv(),該函數的定義如下:
//提升進程訪問權限
bool enableDebugPriv()
{
HANDLE hToken;
LUID sedebugnameValue;
TOKEN_PRIVILEGES tkp;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
return false;
}
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue)) {
CloseHandle(hToken);
return false;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL)) {
CloseHandle(hToken);
return false;
}
return true;
}
至此創建遠程線程的工作全部結束,下面就給出完整的代碼:
#pragma once
#include "stdafx.h"
#include <windows.h>
#include <TlHelp32.h>
#include <iostream>
//線程參數結構體定義
typedef struct _RemoteParam {
char szMsg[12]; //MessageBox函數中顯示的字符提示
DWORD dwMessageBox;//MessageBox函數的入口地址
} RemoteParam, * PRemoteParam;
//定義MessageBox類型的函數指針
typedef int (__stdcall * PFN_MESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, DWORD);
//線程函數定義
DWORD __stdcall threadProc(LPVOID lParam)
{
RemoteParam* pRP = (RemoteParam*)lParam;
PFN_MESSAGEBOX pfnMessageBox;
pfnMessageBox = (PFN_MESSAGEBOX)pRP->dwMessageBox;
pfnMessageBox(NULL, pRP->szMsg, pRP->szMsg, 0);
return 0;
}
//提升進程訪問權限
bool enableDebugPriv()
{
HANDLE hToken;
LUID sedebugnameValue;
TOKEN_PRIVILEGES tkp;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
return false;
}
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue)) {
CloseHandle(hToken);
return false;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL)) {
CloseHandle(hToken);
return false;
}
return true;
}
//根據進程名稱得到進程ID,如果有多個運行實例的話,返回第一個枚舉到的進程的ID
DWORD processNameToId(LPCTSTR lpszProcessName)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnapshot, &pe)) {
MessageBox(NULL,
"The frist entry of the process list has not been copyied to the buffer",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
while (Process32Next(hSnapshot, &pe)) {
if (!strcmp(lpszProcessName, pe.szExeFile)) {
return pe.th32ProcessID;
}
}
return 0;
}
int main(int argc, char* argv[])
{
//定義線程體的大小
const DWORD dwThreadSize = 4096;
DWORD dwWriteBytes;
//提升進程訪問權限
enableDebugPriv();
//等待輸入進程名稱,注意大小寫匹配
std::cout << "Please input the name of target process !" << std::endl;
char szExeName[MAX_PATH] = { 0 };
std::cin >> szExeName;
DWORD dwProcessId = processNameToId(szExeName);
if (dwProcessId == 0) {
MessageBox(NULL, "The target process have not been found !",
"Notice", MB_ICONINFORMATION | MB_OK);
return -1;
}
//根據進程ID得到進程句柄
HANDLE hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (!hTargetProcess) {
MessageBox(NULL, "Open target process failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//在宿主進程中爲線程體開闢一塊存儲區域
//在這裏需要注意MEM_COMMIT | MEM_RESERVE內存非配類型以及PAGE_EXECUTE_READWRITE內存保護類型
//其具體含義請參考MSDN中關於VirtualAllocEx函數的說明。
void* pRemoteThread = VirtualAllocEx(hTargetProcess, 0,
dwThreadSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!pRemoteThread) {
MessageBox(NULL, "Alloc memory in target process failed !",
"notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//將線程體拷貝到宿主進程中
if (!WriteProcessMemory(hTargetProcess,
pRemoteThread, &threadProc, dwThreadSize, 0)) {
MessageBox(NULL, "Write data to target process failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//定義線程參數結構體變量
RemoteParam remoteData;
ZeroMemory(&remoteData, sizeof(RemoteParam));
//填充結構體變量中的成員
HINSTANCE hUser32 = LoadLibrary("User32.dll");
remoteData.dwMessageBox = (DWORD)GetProcAddress(hUser32, "MessageBoxA");
strcat(remoteData.szMsg, "Hello/0");
//爲線程參數在宿主進程中開闢存儲區域
RemoteParam* pRemoteParam = (RemoteParam*)VirtualAllocEx(
hTargetProcess , 0, sizeof(RemoteParam), MEM_COMMIT, PAGE_READWRITE);
if (!pRemoteParam) {
MessageBox(NULL, "Alloc memory failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//將線程參數拷貝到宿主進程地址空間中
if (!WriteProcessMemory(hTargetProcess ,
pRemoteParam, &remoteData, sizeof(remoteData), 0)) {
MessageBox(NULL, "Write data to target process failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//在宿主進程中創建線程
HANDLE hRemoteThread = CreateRemoteThread(
hTargetProcess, NULL, 0, (DWORD (__stdcall *)(void *))pRemoteThread,
pRemoteParam, 0, &dwWriteBytes);
if (!hRemoteThread) {
MessageBox(NULL, "Create remote thread failed !", "Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
CloseHandle(hRemoteThread);
return 0;
}