遠程執行緒注入
第一節 前置知識
- 提起遠程執行緒注入,大家有可能會理解為我在廣西,你在北京,我注入你的執行緒。其實並不是這個樣子。
- 系統在每次運行一個exe 程式的時候系統會默認分配一個4G 的地址空間,給這個exe 程式。
- 然而,我們的系統有16G、32G等等。那豈不是只能運行幾個exe 程式了?
- 其實我們在給exe 程式分配地址空間的時候,是一個虛擬地址空間。
- 通過映射的方式,映射到我們的真實機上。
- 每個exe 程式之間是不能互相訪問的。比如QQ 和 微信不能互相訪問。如果可以訪問,就會非常不安全。
第二節 注入初始
DLL文件,是動態鏈接庫,我們執行程式的時候最後調用的都是這個DLL文件。
每一個進程中都會有多個執行緒。
遠程執行緒注入,簡單來說,就是指我們通過一個工具或者其他的方法,在一個指定的進程中,開闢一個執行緒的記憶體地址空間,然後用來執行載入我們的DLL文件,或者是我們想做的事情。
第三節 注入原理
- 我們的程式在執行的過程中,都會創建一個進程、執行緒,然後通過進程執行緒載入DLL文件,從而執行我們想要的功能。
- 在我們的系統程式中, 有兩個DLL程式就是Kernel32.dll 和user32.dll 。這兩個DLL 是在大部分程式上都會調用的DLL。
- 其中Kernel32.dll 文件中有一個LoadLibraryW函數。這個函數是應用程式調用動態鏈接庫時的函數。
- 為什麼使用這兩個函數呢?
- 因為同一個DLL,在不同的進程中,不一定被映射(載入)到同一個記憶體地址下
有的載入的是同一個DLL文件的A函數
有的載入的是同一個DLL文件的B函數 - 但是Kernel32.dll 和user32.dll 是例外的,他們總是被映射到進程的記憶體首選地址。
- 因此在所有使用這個DLL文件的進程中,這兩個DLL的記憶體地址是相同的
第四節 注入思路
思路一:
- 獲取一個目標執行緒的句柄
- 在我們的進程中得到LoadLibrary 函數的地址,因為載入時這個DLL文件的記憶體地址相同,所以這個地址也是目標進程中的地址
- 傳入我們想要注入進去的DLL的地址
- 開啟一個執行緒(開闢一塊記憶體地址空間)
- 讓這個執行緒,在我們想要注入的目標進程中工作,這個執行緒的作用就是使用LoadLibrary 這個函數載入我們想注入的DLL
思路二:
- 提升進程的許可權:因為我們要將程式注入到別的進程中,所有我們的許可權一定要夠,比如說我們的系統有system用戶和administrator用戶等
- 查看我們獲得到的特權資訊是什麼
- 調節進程許可權
- 查找窗口,就是獲得指定程式的進程,可以理解為就是獲取窗口句柄
- 根據窗口句柄獲取進程的PID(Process ID)
- 根據PID獲取進程句柄。由於PID只是一個進程的序號,不夠強大,所以我們需要獲取一個更加強大的控制進程的東西,叫做進程句柄。這個進程句柄可以控制exe的關閉、暫停、執行等等行為。
- 根據進程句柄在指定的進程中申請一塊記憶體地址空間。拿到進程句柄後,就可以對exe進行操作了。由於我們想要學習的進程注入,所以演示進程注入
- DLL的路徑寫入到遠程進程中
- 在遠程進程中開闢一個執行緒
第五節 項目實戰
好了,經過上面的學習,我們對遠程執行緒注入有了一個基本的了解。雖然有了了解,但是還是對遠程執行緒注入的過程、原理以及使用有一些模糊,不太理解。(個人學習過程中的感悟)
我們需要配合一些遠程執行緒注入的實戰來進一步了解
項目一:
要求:編寫一個程式,在程式中,指定注入的DLL的文件的路徑以及被注入進程的資訊。當我們點擊注入後,就可以將DLL程式注入到進程中,從而執行我們注入的DLL文件
成果展示:
程式碼展示:
- 步驟一獲取我們想要獲取許可權的鎖
//1. 提升進程的許可權
// 因為我們要將我們寫的程式注入到別的進程之中,所以我們的許可權一定要夠
// 比如作業系統有system\administrator用戶
/*
函數簡介:
OpenProcessToken:我們想要提升許可權,首先要進入到提升許可權的空間中,相當於那一把鑰匙,打開提升許可權的盒子。
GetCurrentProcess:獲取我的進程。得到我的自己的進程的鎖。
TOKEN_ALL_ACCESS:打開所有的許可權。打開讀寫等等所有許可權
*/
HANDLE hToken;//將獲取到的「鑰匙」保存到這個變數。
// || OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,&hToken);
//如果返回的結果是FALSE,就是獲取失敗,提示獲取鑰匙失敗
if (FALSE == OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)){
MessageBox(L"打開進程,訪問令牌失敗");
return;
}
//==================執行到這裡就說明是拿到了鎖==================
- 查看進程裡面的特權資訊
//2. 查看進程裡面的特權資訊
/*
LookupPrivilegeValue:查看盒子,去看一下盒子裡面的資訊
參數一:系統特權名字
NULL:查看本機系統
參數二:主要看什麼特權
SE_DEBUG_NAME:調試許可權
參數三:將系統的許可權賦值給變數
luid
*/
//將獲取到的許可權,賦值給一個變數
LUID luid;
// || LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid);
//判斷是否獲取成功
if (FALSE == LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)){
MessageBox(L"查看進程裡面的特權資訊失敗。");
return;
}
//==================執行到這裡就說明是獲取到了許可權==================
- 調節進程的許可權
//3. 調節進程許可權
/*
AdjustTokenPrivileges:
參數一:拿著鑰匙hToken,第一步獲取到的
參數二:是否禁用所有的特權。我們使用的是不禁用
*/
//定義一個新的特權,用來接收函數調節後的許可權。
TOKEN_PRIVILEGES tkp;
tkp.PrivilegeCount = 1;//特權數組的個數為一個
tkp.Privileges[0].Attributes + SE_PRIVILEGE_ENABLED;//因為只有一個元素,所有數組就是0
tkp.Privileges[0].Luid = luid; //將獲得到的許可權賦值給這個特權。
if (FALSE == AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL)){
MessageBox(L"調節進程許可權失敗");
return;
}
//==================執行到這裡就說明是已經調節了許可權==================
- 查找窗口(就是獲取指定應用程式的進程)
//4. 查找窗口(就是獲取指定應用程式的進程)
/*
FindWindow:
參數一:Notepad,進程的類,一個應用程式的類是一樣的
參數二:就是標題
*/
//如果找到了,這個變數就有值了
HWND hNotepader = ::FindWindow(L"Notepad",L"新建文本文檔.txt - 記事本");
//判斷這個窗口是否打開
if (hNotepader == NULL){
MessageBox(L"沒有打開記事本");
return;
}
//==================執行到這裡就說明是已經獲取到了應用程式的窗口==================
- 獲取進程PID(Process ID進程ID)
//5. 獲取進程PID(Process ID進程ID)
/*
GetWindowThreadProcessId:函數就是根據窗口句柄,獲取PID
參數一:傳入一個窗口的句柄
參數二:傳入接收穫取到的PID的變數
*/
DWORD dwPID = 0;
GetWindowThreadProcessId(hNotepader,&dwPID);
if (dwPID == 0){
MessageBox(L"獲取進程PID失敗");
return;
}
//==================執行到這裡就說明是已經獲取到了進程PID==================
- 根據PID(進程的序號)獲取進程句柄
//由於PID只是一個進程的序號,不夠強大,所以我們需要獲取一個更加強大的控制進程的東西,叫做進程句柄
//這個進程句柄可以控制exe的關閉、暫停、執行等等行為
//6. 根據PID(進程的序號)獲取進程句柄
/*
OpenProcess()函數:打開一個進程
參數一:以所有(最大)的許可權打開,就是我們可以執行任何許可權
參數二:是否可以繼承父進程的環境變數,一些屬性,FALSE是不繼承
參數三:根據指定的PID打開一個進程
RETURN:
打開成功會返回一個進程句柄
*/
//記事本的進程句柄
HANDLE hNotepad = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
//判斷記事本的進程句柄是否成功
if (hNotepad == NULL){
MessageBox(L"打開進程失敗");
}
//==================執行到這裡就說明是已經獲取到了進程句柄==================
- 因為每個進程中,有4G的虛擬地址空間,在遠程進程中申請一小塊記憶體空間
//拿到進程句柄後,就可以對exe進行操作了。
//由於我們想要學習的進程注入,所以演示進程注入
//7. 因為每個進程中,有4G的虛擬地址空間,在遠程進程中申請一小塊記憶體空間
/*
VirtualAllocEx()函數:專門在遠程進程中進行記憶體申請的
參數一:指定在哪個進程中申請(根據進程句柄)
參數二:指定申請的位置,NULL是指不指定那一塊,隨便給一塊地址就可以
參數三:指定申請的大小,0x1000:4096個位元組
參數四:申請一塊物理地址,物理存儲器用來存儲虛擬記憶體。
參數五:讓這個空間可讀可寫可執行。就是我們可以進行操作
RETURN:
返回一個地址空間
*/
LPVOID lpAddr = VirtualAllocEx(hNotepad, NULL, 0x0100, MEM_COMMIT,PAGE_EXECUTE_READWRITE);
//判斷申請的空間是否成功
if (lpAddr == NULL){
MessageBox(L"在遠程進程中申請記憶體是否成功");
}
//==================執行到這裡就說明是已經獲取到了遠程進程的記憶體空間==================
- 將DLL路徑寫入遠程進程中
//8. DLL的路徑寫入到遠程進程中
/*
WriteProcessMemory()函數:
參數一:寫入到指定的進程中
參數二:寫入到指定的申請的地址空間中
參數三:將指定的DLL文件的路徑寫入進去
參數四:指定的DLL文件大小,多少位元組
參數五:實際寫入到多少個位元組,NULL不關注,只關注當前就可以
RETURN:
失敗返回FALSE
成功放回TRUE
*/
//指定我們需要注入的DLL的文件路徑
TCHAR szDLLPath[] = L"C:\\Users\\lenovo\\Desktop\\123.dll";
if (FALSE == WriteProcessMemory(hNotepad, lpAddr, szDLLPath, sizeof(szDLLPath), NULL)){
MessageBox(L"在遠程進程中寫入數據失敗");
return;
}
/*
GetModuleHandle()函數:能夠獲得我們Kernel32.dll的句柄
參數一:獲取指定名字的句柄
GetProcAddress()函數:返回一個指定函數的地址
參數一:是一個獲取到的dll
參數二:是一個指定的函數
Kernel32.dll是一個核心的動態庫,所有的exe進程都載入了這個動態鏈接函數
記事本也載入了Kernel32.dll
但是所有的dll動態鏈接函數在動態鏈接庫中都是只有一份
不同的exe程式調用的dll文件都是只有一份
也就是說所有的exe都載入了Kernel32.dll文件
既然是共享的,那麼dll裡面的函數也是共享的
*/
//GetModuleHandle(L"Kernel32.dll");
//GetProcAddress(GetModuleHandle(L"Kernel32.dll"),"LoadLibraryA");//窄字元
//可以理解為返回一個函數指針
PTHREAD_START_ROUTINE pfnStartAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "LoadLibraryW");//寬字元
//LPTHREAD_START_ROUTINE *pfnStartAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "LoadLibraryW");//寬字元
if (pfnStartAddr == NULL){
MessageBox(L"返回指針失敗");
return;
}
//==================執行到這裡就說明是已經獲取到了指定DLL文件的指定函數的指針==================
- 再遠程執行緒中開闢一個執行緒
//9. 在遠程進程中開闢一個執行緒
/*
CreateRemoteThread()函數:我們打開註冊器,讓它在記事本中自己開一個執行緒
參數一:在指定的進程中開執行緒
參數二:執行緒的安全屬性為NULL
參數三:堆棧大小默認為0
參數四:遠程執行緒執行哪個函數( 執行緒入口函數的起始地址)
參數五:傳進來我們申請的地址空間
參數六:什麼時候啟動,0為馬上啟動
參數七:執行緒ID,NULL就可以
RETURN:
返回一個遠程執行緒句柄
*/
HANDLE hRemote = CreateRemoteThread(hNotepad, NULL, 0, (LPTHREAD_START_ROUTINE)pfnStartAddr, lpAddr, 0, NULL);
WaitForSingleObject(hRemote, INFINITE);
//判斷創建遠程執行緒是否成功
if (hRemote == NULL){
MessageBox(L"創建遠程執行緒失敗");
return;
}
第六節 結語
本次部落格只是演示了一個項目用於讓沒有基礎的同學,理解遠程執行緒注入的基礎,更加高深的利用手法,需要自行琢磨
本次的項目靈感來自於頓開教育里奇老師。