遠(yuǎn)程線程作為一項(xiàng)"合法"的代碼注入技術(shù),在windows上被大量使用, 它的本質(zhì)就是把一塊可執(zhí)行代碼寫入到對方進(jìn)程,然后讓其起運(yùn)行起來。
一般它的實(shí)現(xiàn)過程是這樣的, 通過VirtualAllocEx在目標(biāo)進(jìn)程分配內(nèi)存空間,然后通過WriteProcessMemory將我們的可執(zhí)行代碼寫入到目標(biāo)進(jìn)程,最后通過CreateRemoteThread讓我們的可執(zhí)行代碼在目標(biāo)進(jìn)程里運(yùn)行起來。
一般實(shí)現(xiàn)遠(yuǎn)程線程有2種方法, 一種是《windows核心編程》里介紹的,通過線程函數(shù)和LoadLibrary API函數(shù)申明的相似性, 直接在目標(biāo)進(jìn)程里調(diào)用LoadLibrary加載我們DLL,這樣我們只要在DLL_PROCESS_ATTACH里執(zhí)行我們的代碼就可以了。代碼如下, 通過
InjectLib在目標(biāo)進(jìn)程加載我們的DLL, 通過EjectLib
卸載我們的DLL:
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI InjectLibW(DWORD dwProcessId, PCWSTR pszLibFile)
{
BOOL fOk = FALSE; // Assume that the function fails
HANDLE hProcess = NULL, hThread = NULL;
PWSTR pszLibFileRemote = NULL;
__try {
// Get a handle for the target process.
hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION | // Required by Alpha
PROCESS_CREATE_THREAD | // For CreateRemoteThread
PROCESS_VM_OPERATION | // For VirtualAllocEx/VirtualFreeEx
PROCESS_VM_WRITE, // For WriteProcessMemory
FALSE, dwProcessId);
if (hProcess == NULL)
{
__leave;
}
// Calculate the number of bytes needed for the DLL's pathname
int cch = 1 + lstrlenW(pszLibFile);
int cb = cch * sizeof(WCHAR);
// Allocate space in the remote process for the pathname
pszLibFileRemote = (PWSTR)
VirtualAllocEx(hProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE);
if (pszLibFileRemote == NULL)
{
__leave;
}
// Copy the DLL's pathname to the remote process's address space
if (!WriteProcessMemory(hProcess, pszLibFileRemote,
(PVOID) pszLibFile, cb, NULL))
{
__leave;
}
// Get the real address of LoadLibraryW in Kernel32.dll
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
if (pfnThreadRtn == NULL)
{
__leave;
}
// Create a remote thread that calls LoadLibraryW(DLLPathname)
hThread = CreateRemoteThread(hProcess, NULL, 0,
pfnThreadRtn, pszLibFileRemote, 0, NULL);
if (hThread == NULL)
{
__leave;
}
// Wait for the remote thread to terminate
WaitForSingleObject(hThread, INFINITE);
fOk = TRUE; // Everything executed successfully
}
__finally { // Now, we can clean everthing up
// Free the remote memory that contained the DLL's pathname
if (pszLibFileRemote != NULL)
VirtualFreeEx(hProcess, pszLibFileRemote, 0, MEM_RELEASE);
if (hThread != NULL)
CloseHandle(hThread);
if (hProcess != NULL)
CloseHandle(hProcess);
}
return(fOk);
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI InjectLibA(DWORD dwProcessId, PCSTR pszLibFile)
{
// Allocate a (stack) buffer for the Unicode version of the pathname
PWSTR pszLibFileW = (PWSTR)
_alloca((lstrlenA(pszLibFile) + 1) * sizeof(WCHAR));
// Convert the ANSI pathname to its Unicode equivalent
wsprintfW(pszLibFileW, L"%S", pszLibFile);
// Call the Unicode version of the function to actually do the work.
return(InjectLibW(dwProcessId, pszLibFileW));
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI EjectLibW(DWORD dwProcessId, PCWSTR pszLibFile)
{
BOOL fOk = FALSE; // Assume that the function fails
HANDLE hthSnapshot = NULL;
HANDLE hProcess = NULL, hThread = NULL;
__try {
// Grab a new snapshot of the process
hthSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
if (hthSnapshot == INVALID_HANDLE_VALUE)
{
__leave;
}
// Get the HMODULE of the desired library
MODULEENTRY32W me = { sizeof(me) };
BOOL fFound = FALSE;
BOOL fMoreMods = Module32FirstW(hthSnapshot, &me);
for (; fMoreMods; fMoreMods = Module32NextW(hthSnapshot, &me)) {
fFound = (lstrcmpiW(me.szModule, pszLibFile) == 0) ||
(lstrcmpiW(me.szExePath, pszLibFile) == 0);
if (fFound) break;
}
if (!fFound)
{
__leave;
}
// Get a handle for the target process.
hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION | // Required by Alpha
PROCESS_CREATE_THREAD |
PROCESS_VM_OPERATION, // For CreateRemoteThread
FALSE, dwProcessId);
if (hProcess == NULL)
{
__leave;
}
// Get the real address of LoadLibraryW in Kernel32.dll
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary");
if (pfnThreadRtn == NULL)
{
__leave;
}
// Create a remote thread that calls LoadLibraryW(DLLPathname)
hThread = CreateRemoteThread(hProcess, NULL, 0,
pfnThreadRtn, me.modBaseAddr, 0, NULL);
if (hThread == NULL)
{
__leave;
}
// Wait for the remote thread to terminate
WaitForSingleObject(hThread, INFINITE);
fOk = TRUE; // Everything executed successfully
}
__finally { // Now we can clean everything up
if (hthSnapshot != NULL)
CloseHandle(hthSnapshot);
if (hThread != NULL)
CloseHandle(hThread);
if (hProcess != NULL)
CloseHandle(hProcess);
}
return(fOk);
}
///////////////////////////////////////////////////////////////////////////////
BOOL WINAPI EjectLibA(DWORD dwProcessId, PCSTR pszLibFile)
{
// Allocate a (stack) buffer for the Unicode version of the pathname
PWSTR pszLibFileW = (PWSTR)
_alloca((lstrlenA(pszLibFile) + 1) * sizeof(WCHAR));
// Convert the ANSI pathname to its Unicode equivalent
wsprintfW(pszLibFileW, L"%S", pszLibFile);
// Call the Unicode version of the function to actually do the work.
return(EjectLibW(dwProcessId, pszLibFileW));
}
///////////////////////////////////////////////////////////////////////////////
上面這種方法注入的代碼它的優(yōu)點(diǎn)是開發(fā)比較簡單,我們只要用C++寫一個(gè)DLL,然后調(diào)用
InjectLibW(processID, dllName)就可以了,但是因?yàn)榇a是運(yùn)行在一個(gè)DLL里,別人可以通過一些枚舉模塊的工具看到我們的DLL,所以隱蔽性不是很好。
還有一種遠(yuǎn)程線程的實(shí)現(xiàn)方法是羅云彬
《Windows環(huán)境下32位匯編語言程序設(shè)計(jì)》里介紹的, 我們不通過DLL,而是直接把可執(zhí)行代碼拷貝到目標(biāo)進(jìn)程后運(yùn)行,所以它是真正的遠(yuǎn)程線程,通過這種方法,我們的代碼和目標(biāo)進(jìn)程已經(jīng)完全融為一體,其他人根本無法察覺。
用這種方法實(shí)現(xiàn), 它的要點(diǎn)是:
(1) Kernel32.DLL加載的基址在任何進(jìn)程里都是一樣的(其實(shí)上一種LoadLibrary方法也用到了這點(diǎn)), 所以GetProcAddress,GetModuleHandleA(W), LoadLibraryA(W)這些API的地址在任何進(jìn)程里都是一樣的, 所以我們在其他進(jìn)程中用和本進(jìn)程相同的地址調(diào)用這些API。
(2) 因?yàn)樯婕暗饺肿兞康闹囟ㄎ粏栴}, 所以注入的代碼需要用匯編編寫, 并用以下匯編解決重定位問題
call @F
@@:
pop ebx
sub ebx, offset @B
下面寫了一個(gè)無DLL實(shí)現(xiàn)遠(yuǎn)程線程的測試代碼,他會(huì)在桌面Shell進(jìn)程(explorer.exe)里彈一個(gè)MessageBox,按照這個(gè)例子,我們已經(jīng)可以通過調(diào)用LoadLibraryA(W)和GetProcAddress來調(diào)用任何API了, 所以要實(shí)現(xiàn)一些復(fù)雜的功能也不是難事。
如果要避免用匯編來重定位,可以把全局變量都打包放在線程參數(shù)里,這樣單用C++就可以完全實(shí)現(xiàn)無DLL的遠(yuǎn)程線程了,當(dāng)然我們只能用Release版本(Debug版加了一些用到全局變量的調(diào)試信息, 堆棧檢測等)。
還有一個(gè)問題是遠(yuǎn)程線程很難調(diào)試, 很多時(shí)候你在目標(biāo)進(jìn)程里注入代碼后可能就只會(huì)看到一個(gè)Crash的窗口,也不知道哪里代碼有問題。如何才能單步調(diào)試呢? 我的方法是在CreateRemoteThread之前把線程的入口地址用MessageBox打印出來,然后用Windbg Attach到目標(biāo)進(jìn)程,在該地址上設(shè)置斷點(diǎn),這樣繼續(xù)運(yùn)行就可以用Windbg單步調(diào)試了(當(dāng)然只能以匯編的形式)。
遠(yuǎn)程線程測試代碼下載(Asm):
RemoteThreadTest
posted on 2012-06-20 15:38
Richard Wei 閱讀(3990)
評論(5) 編輯 收藏 引用 所屬分類:
windows desktop