一、 前言:
遠程線程技術指的是通過在其他進程中創(chuàng)建新線程的方法進入該進程的內存地址空間,從而獲得對該進程的控制權的方法。
在進程中可以通過CreateThread函數(shù)創(chuàng)建線程,被創(chuàng)建的新線程與主線程共享地址空間以及其他的資源。同樣,通過CreateRemoteThread函數(shù)可以在其他進程內創(chuàng)建新線程,新創(chuàng)建的的遠程線程可以共享遠程進程的地址空間。
所以通過在遠程進程中創(chuàng)建新的方法,就可以進入到遠程進程的內存地址空間,也就擁有了和那個遠程進程相當?shù)臋嘞?,可以在遠程進程中執(zhí)行代碼,從而達到遠程進程控制、進程隱藏的目的。
二 、基本原理:
2.1. A P I 函數(shù)
其中VirtualAllocEx和CreateRemoteThread兩個API函數(shù)只能NT內核下用。實現(xiàn)的基本過程如下:
(1)通過OpenProcess函數(shù)打開進程PID為ProcessID的遠程進程:
hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, ProcessId);
(2)通過VirtualAllocEx函數(shù)在剛打開的進程中申請IMageSize個字節(jié)的內存:
InjectPoint = (LPBYTE)VirtualAllocEx(hProcess, 0, ImageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE) ;
(3)通過WriteProcessMemory函數(shù)將NewModule開始的IMageSize個字節(jié)的代碼寫入已經(jīng)申請的內存中:
WriteProcessMemory(hProcess, InjectPoint, NewModule, ImageSize, NULL) ;
(4)通過CreateRemoteThread函數(shù)啟動剛寫入的代碼:
CreateRemoteThread(hProcess, NULL, 0, RemoteEntryPoint, Param, 0, NULL);
2.2. 重定位和函數(shù)導入的問題
在遠程進程執(zhí)行的代碼中最重要的是要解決代碼的重定位和API函數(shù)導入的問題,對于參考文獻1的方法是通過在遠程進程中執(zhí)行LoadLibrary函數(shù)將DLL文件載入,所以系統(tǒng)會自動完成代碼的重定位和API函數(shù)導入工作:而參考文獻2中提到的方法是通過匯編語言進行編程來解決,相對來講會使編程難度增大,但隱藏性是最好的。在這里,探討一種新的方法既能使用高級語言進行編程又有完美的隱藏效果的方法。
首先要解決的是重定位問題,這就要用到pE文件中的重定位表,利用重定位表將即將寫入遠程進程的代碼按照在遠程進程申請到的內存地址通過RelocCode函數(shù)進行重定位,然后將重定位好的代碼寫入遠程進程的內存空間。這些寫入的代碼必然要用到API函數(shù),所以在遠程線程代碼調用第一個API之前,要替系統(tǒng)來完成裝入API函數(shù)對應的DLL文件,ing填好相關API函數(shù)入口地址的工作,這就要用到PE文件的輸入表提供的信息,輸入表記錄了一個win32程序需要加載的所有DLL文件名及從中引入的API函數(shù)名,這就可以用LoadLIbrary函數(shù)注入需要用的DLL文件,在通過GETProcAddress函數(shù)獲得相應的API函數(shù)地址,這里是通過LoadAPI函數(shù)來完成這些工作。下面程序中的LoadAPI函數(shù)作為遠程線程的入口,其用到的API函數(shù)LoadLibrary和 GETProcAddress的入口地址有時如何確定的呢?事實上幾乎所有的Windows進程都會裝入“kernel32.dll”,而這兩個函數(shù)就定位于“kernel32.dll”中,而且所有裝入“kernel32.dll”的進程都會把它裝入到同一個虛擬內存地址,即在本地進程中使用到“kernel32.dll”中的API函數(shù)和遠程進程中對應的API函數(shù)地址是一樣的,所以在遠程進程代碼中可以想本地進程一樣調用LoadLibrary加載DLL文件,然后用GETProcAddress獲得輸入函數(shù)的入口地址并寫入響應的數(shù)據(jù)結構中,完成API函數(shù)的導入。
三、編程實現(xiàn):
001.
//為了簡化代碼,下面程序中去掉了對出錯處理的代碼,實際應用中應該考慮程序運行時可能的出錯:
002.
#include "stdafx.h"
003.
static
PIMAGE_NT_HEADERS nt_header;
004.
#define IMAGESIZE (nt_header->OptionalHeader.SizeOfImage)
005.
#define EXPORT_TABEL (nt_header->OptionalHeader.DataDirectory[0].VirtualAddress)
006.
#define RELOC_TABEL (nt_header->OptionalHeader.DataDirectory[5].VirtualAddress)
007.
static
void
RelocCode (
PBYTE
Image,
LPBYTE
InjectBase)
// 完成代碼的重定位
008.
{
009.
DWORD
Rva = 0, RvaCount = 0, RelocOffset=0;
010.
WORD
*Offset = NULL;
011.
LPBYTE
RelocTable = Image + RELOC_TABEL;
//重定位表位置
012.
PIMAGE_BASE_RELOCATION basereloc= (PIMAGE_BASE_RELOCATION) RelocTable;
013.
RelocOffset= (
DWORD
)InjectBase - nt_header ->OptionalHeader.ImageBase;
//重定位表偏移
014.
while
(basereloc ->VirtualAddress != NULL)
// 遍歷重定位表,修正需要重定位的代碼
015.
{
016.
Offset = (
WORD
*)(RelocTable +
sizeof
(IMAGE_BASE_RELOCATION));
017.
RvaCount = (basereloc ->SizeOfBlock -
sizeof
(IMAGE_BASE_RELOCATION)) / 2;
018.
for
(
DWORD
i=0; iVirtualAddress + (
DWORD
)Image;
019.
*(
DWORD
*)Rva += RelocOffset;
//RVA加上修正量進行修正
020.
}
021.
RelocTable += basereloc ->SizeOfBlock;
//指向下一頁重定位信息處
022.
basereloc = (PIMAGE_BASE_RELOCATION) RelocTable;
023.
}
024.
}
025.
int
LoadAPI(
LPBYTE
InjectBase)
// 用于完成API函數(shù)的導入,參數(shù)為要插入代碼處地址
026.
{
027.
PIMAGE_DOS_HEADER dos_h = (PIMAGE_DOS_HEADER) InjectBase;
028.
PIMAGE_NT_HEADERS nt_h = (PIMAGE_NT_HEADERS)(InjectBase + dos_h->e_lfanew);
029.
PIMAGE_IMPORT_DESCRIPTOR import_d = (PIMAGE_IMPORT_DESCRIPTOR)
030.
(InjectBase + nt_h->OptionalHeader.DataDirectory[1].VirtualAddress);
031.
for
( ; import_d->OriginalFirstThunk != 0; import_d++)
//遍歷導入表
032.
{
033.
HMODULE
hDll = LoadLibrary((
LPCSTR
)(InjectBase + import_d->Name));
034.
//上面能直接引用LoadLibrary是由于本地和遠程進程中該函數(shù)地址都是相同的
035.
if
(hDll == NULL)
036.
return
0;
037.
PIMAGE_THUNK_DATA Origin = (PIMAGE_THUNK_DATA)(InjectBase +import_d->OriginalFirstThunk);
038.
PIMAGE_THUNK_DATA First = (PIMAGE_THUNK_DATA)(InjectBase + import_d->FirstThunk);
039.
LPCSTR
Name = NULL;
040.
PIMAGE_IMPORT_BY_NAME Import_name = NULL;
041.
for
(; Origin->u1.Ordinal != 0; Origin++, First++)
042.
{
043.
if
(Origin->u1.Ordinal & IMAGE_ORDINAL_FLAG)
044.
Name = (
LPCSTR
)IMAGE_ORDINAL(Origin->u1.Ordinal);
045.
else
046.
{
047.
Import_name = (PIMAGE_IMPORT_BY_NAME)(InjectBase + (
DWORD
)( Origin->u1.AddressOfData));
048.
Name = (
LPCSTR
)Import_name->Name;
049.
}
050.
First->u1.Function = (
DWORD
* )GetProcAddress(hDll, Name);
051.
//上面能直接引用GetProcAddress是由于本地和遠程進程中該函數(shù)地址都是相同的
052.
if
(First->u1.Function == NULL)
053.
return
0;
054.
}
055.
}
056.
return
1;
057.
}
058.
DWORD
RemoteThread_Main(
HINSTANCE
hInstance)
//遠程要執(zhí)行的代碼,在這里只演示MessageBox
059.
{
060.
::MessageBox (0,
"遠程線程插入成功!"
,
"遠程線程"
,0);
061.
return
1;
062.
}
063.
DWORD
ThreadEntry(
LPBYTE
ImageBase)
/*** 遠程線程入口 ***/
064.
{
065.
if
(LoadAPI(ImageBase))
//先完成API函數(shù)的導入工作
066.
RemoteThread_Main ((
HINSTANCE
)ImageBase);
//執(zhí)行函數(shù)RemoteThread_Main中的代碼
067.
return
1;
068.
}
069.
static
DWORD
GetTargetProcessId()
//獲取遠程進程 PID
070.
{
071.
DWORD
ProcessId = 0;
072.
HWND
hWnd = FindWindow(
"Progman"
,
"Program Manager"
);
//獲取資源管理器進程PID
073.
if
(hWnd != NULL)
074.
GetWindowThreadProcessId(hWnd, &ProcessId);
075.
return
ProcessId;
076.
}
077.
int
APIENTRY WinMain(
HINSTANCE
hInstance,
HINSTANCE
hPrevInstance,
LPSTR
lpCmdLine,
int
nCmdShow)
078.
{
079.
LPBYTE
InjectPoint = NULL, Module = NULL, NewModule = NULL,Param =NULL;
080.
HANDLE
hProcess = NULL, hThread = NULL;
081.
DWORD
ProcessId = 0, ImageSize = 0;
082.
LPTHREAD_START_ROUTINE RemoteEntryPoint = NULL;
083.
Module = (unsigned
char
*)GetModuleHandle(NULL);
//獲取自身句柄
084.
nt_header = (PIMAGE_NT_HEADERS)(Module + ((PIMAGE_DOS_HEADER)Module)->e_lfanew);
085.
ImageSize = IMAGESIZE ;
//得到內存映象大小
086.
//通過VirtualAllocEx申請存放自身代碼的內存空間
087.
NewModule =(
LPBYTE
)VirtualAlloc(NULL, ImageSize, MEM_COMMIT | MEM_RESERVE,
088.
PAGE_EXECUTE_READWRITE);
089.
memcpy
(NewModule, Module, ImageSize) ;
//將自身復制到NewModule處
090.
nt_header = (PIMAGE_NT_HEADERS)(NewModule + ((PIMAGE_DOS_HEADER)NewModule)->e_lfanew);
091.
if
( ProcessId = GetTargetProcessId() )
//獲取遠程進程 PID
092.
if
( hProcess = OpenProcess(PROCESS_ALL_ACCESS,
false
, ProcessId))
//打開進程
093.
//通過VirtualAllocEx在打開的遠程進程中申請內存空間
094.
if
( InjectPoint = (
LPBYTE
)VirtualAllocEx(hProcess, 0, ImageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE) )
095.
{
096.
RelocCode (NewModule, InjectPoint);
//重定位NewModule處的代碼
097.
RemoteEntryPoint = (LPTHREAD_START_ROUTINE)( InjectPoint +(
DWORD
) &ThreadEntry - Module );
//得到ThreadEntry在遠程進程中的地址
098.
Param = InjectPoint;
//將插入點地址作為參數(shù)傳遞給線程函數(shù)
099.
//將重定位好的代碼通過WriteProcessMemory寫入遠程進程的內存空間中
100.
if
(WriteProcessMemory(hProcess, InjectPoint, NewModule, ImageSize, NULL))
101.
//通過CreateRemoteThread啟動剛寫入的代碼,參數(shù)為Param
102.
CreateRemoteThread(hProcess, NULL, 0, RemoteEntryPoint, Param, 0, NULL);
103.
}
104.
return
0;
105.
}
四 、總結
代碼可以把它當做一個模板,只要是在RemoteThread_Main函數(shù)中加入實現(xiàn)各種功能的代碼即可把它隱藏到其他進程中去運行。遠程線程技術除了隱藏進程外,還有其他一些用途,如用于殺毒軟件進行內存殺毒等。
以上代碼均在WindowsXP專業(yè)版和Windows2000下正常運行,注意在編譯程序時候應保留重定位表的信息,即在“Project”菜單中選“SETting”,然后選Link選項卡,將Link incrementally選上在進行編譯,否則會出錯。