要實現線程的遠程注入必須使用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函數在宿主進程中申請一Block Storage地區,然後再通過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;
}
//在宿主進程中為線程體開闢一Block Storage地區
//在這裡需要注意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;
}