给Source Insight做个外挂系列之二--将本地代码注入到Source Insight进程
?作者:星軌(oRbIt)
?E_Mail?:inte2000@163.com? ?
????上一篇文章介紹了如何發(fā)現(xiàn)正在運(yùn)行的“Source Insight”窗口,本篇將介紹“TabSiPlus”是如何進(jìn)行代碼注入的。Windows 9x以后的Windows操作系統(tǒng)都對(duì)進(jìn)程空間進(jìn)行了嚴(yán)格的保護(hù),進(jìn)程之間不能想16位的Windows那樣通過一個(gè)確定的內(nèi)存地址互相訪問數(shù)據(jù),甚至是代碼,這就意味著要實(shí)現(xiàn)一個(gè)軟件的擴(kuò)展功能就必須將自己的代碼“注入”到該軟件的進(jìn)程空間中,當(dāng)然,一個(gè)進(jìn)程也可以利用Windows的消息傳遞機(jī)制或mailslot、pipe之類的進(jìn)程通信方式影響另一個(gè)進(jìn)程,但是,并不是所有的程序都這么“好心”,會(huì)留這些接口給別人操作,所以將自定義的代碼注入到另一個(gè)進(jìn)程中運(yùn)行就成了不是方法的方法。
??? 將代碼注入到其它進(jìn)程的方法很多。。。。。(此處省略XXXXX字)。
【#$%$%^&,拜托,把磚頭放下,“此處省略XXXXX字”并不是為了嘩眾取寵,只是因?yàn)殛P(guān)于代碼注入的方法太多了,隨便搜索一下就是一大堆,不說多如牛毛,也是多如羊毛狗毛的,無論我再怎么精心構(gòu)造,遣詞排句,也拜托不了抄襲的“厄運(yùn)”,所以干脆不寫了,如果想了解這些技術(shù)的可以參考Codeproject上的一篇文章:Three Ways to Inject Your Code into Another Process】
Windows為可執(zhí)行文件加載動(dòng)態(tài)鏈接庫的時(shí)候并不為動(dòng)態(tài)鏈接庫創(chuàng)建單獨(dú)的進(jìn)程空間,而是將其復(fù)制一份,映射到加載它的可執(zhí)行文件的進(jìn)程空間中,也就是說,一個(gè)動(dòng)態(tài)鏈接庫如果被兩個(gè)可執(zhí)行文件同時(shí)使用,那么這個(gè)動(dòng)態(tài)鏈接庫就有兩份拷貝,它們之間的數(shù)據(jù)互相不會(huì)有影響。這種加載方式就是實(shí)現(xiàn)代碼注入的基礎(chǔ):將自定義的代碼放在動(dòng)態(tài)鏈接庫中讓被掛程序加載,這樣自定義的代碼就進(jìn)入了被掛程序的進(jìn)程空間,就可以訪問屬于被掛程序的進(jìn)程空間中的資源,包括內(nèi)存、句柄和內(nèi)核對(duì)象,當(dāng)然還有更重要的,就是函數(shù)導(dǎo)入表,這個(gè)函數(shù)導(dǎo)入表是注入的程序能否正常運(yùn)行的關(guān)鍵。
??? 現(xiàn)在問題的關(guān)鍵就是如何讓被掛程序加載我們的定制動(dòng)態(tài)鏈接庫,這是實(shí)現(xiàn)外掛的關(guān)鍵之處,各種代碼注入的方法不同之處就是這一步的實(shí)現(xiàn)方法,我們從這些多如羊毛狗毛的方法中挑選了一種方法,那就是使用CreateRemoteThread() API將定制的動(dòng)態(tài)鏈接庫加載到“Source Insight”的進(jìn)程空間中。需要說明一點(diǎn),Windows 95/98/Me 是不支持這個(gè)API的,所以這種方法只能用于基于NT技術(shù)構(gòu)建的Windows(話又說回來了,現(xiàn)在使用這些老的操作系統(tǒng)的人已經(jīng)不多了,不支持需要理由嗎?不需要嗎?需要嗎?.....Q$#^#%#^$%&%^*)。CreateRemoteThread()的用法和CreateThread()相似,只是CreateThread()只能啟動(dòng)進(jìn)程內(nèi)的線程,而CreateRemoteThread()可以在另一個(gè)進(jìn)程中啟動(dòng)一個(gè)線程。使用CreateRemoteThread()需要兩個(gè)條件,一是線程函數(shù)的起始地址,當(dāng)然這個(gè)函數(shù)地址當(dāng)然是在被掛進(jìn)程的進(jìn)程空間中,另一個(gè)條件是線程函數(shù)參數(shù),CreateRemoteThread()需要的線程函數(shù)類型和CreateThread()一樣,有一個(gè)無類型指針參數(shù),通常線程的創(chuàng)建者利用這個(gè)指針向線程傳遞一個(gè)struct的指針,線程所需的參數(shù)都在這個(gè)struct中,當(dāng)然,這個(gè)struct也必須位于被掛進(jìn)程的進(jìn)程空間中。很顯然,被掛進(jìn)程中不會(huì)存在這樣一個(gè)”乖巧”的函數(shù)等著我們的CreateRemoteThread()使用,更不會(huì)“巧到”還有這樣一個(gè)符合要求的struct,所以,這些條件都要自己創(chuàng)造。
??? 幸運(yùn)的是,Windows提供了讓我們創(chuàng)造條件的一切手段,來看看:VirtualAllocEx()和VirtualFreeEx()這兩個(gè)API用于在指定的進(jìn)程中分配和釋放一塊內(nèi)存,這個(gè)指定的進(jìn)程可以是另一個(gè)進(jìn)程;WriteProcessMemory()和ReadProcessMemory()這兩個(gè)API可以寫和讀一個(gè)指定進(jìn)程中的內(nèi)存,當(dāng)然這個(gè)指定的進(jìn)程也可以是另一個(gè)進(jìn)程。現(xiàn)在有了能夠在其它進(jìn)程中操作內(nèi)存的手段,可以讀還可以寫,可是問題的關(guān)鍵是讀什么、寫什么?其實(shí)要寫入另一個(gè)進(jìn)程的東西有兩部分,一是線程函數(shù)的代碼,另一個(gè)是線程函數(shù)的參數(shù)。線程參數(shù)就是一塊內(nèi)存數(shù)據(jù),沒有什么詭秘的地方,關(guān)鍵是對(duì)線程函數(shù)代碼,這塊代碼是另一個(gè)編譯器產(chǎn)生的,它的映射地址和內(nèi)存訪問地址都是相對(duì)于本進(jìn)程中的虛地址計(jì)算的偏移,所以要將這個(gè)函數(shù)的代碼復(fù)制到另一個(gè)進(jìn)程中并運(yùn)行,還需要處理很多細(xì)節(jié)。首先是這個(gè)函數(shù)必須遠(yuǎn)離任何運(yùn)行庫函數(shù)和API,很簡單,一旦使用了這些庫函數(shù)和API,你就需要在代碼復(fù)制到另一個(gè)進(jìn)程后根據(jù)另一個(gè)進(jìn)程的函數(shù)導(dǎo)入表,手工計(jì)算修改這些庫函數(shù)和API的調(diào)用地址,這和病毒的手法是一致的,我可不想這么麻煩,所以最好不使用它們。可是,不能使用庫函數(shù)和API,還有什么辦法能夠讓這個(gè)線程函數(shù)起到加載我們定制的動(dòng)態(tài)鏈接庫?答案是:沒有。很顯然,如果不調(diào)用運(yùn)行庫函數(shù)或API,標(biāo)準(zhǔn)的C/C++語言是沒有加載動(dòng)態(tài)鏈接庫的語法的,所以還必須使用(至少)windows的API。
??? 在Windows平臺(tái)上加載一個(gè)動(dòng)態(tài)鏈接庫并運(yùn)行其中一個(gè)導(dǎo)出函數(shù)需要三個(gè)步驟:1.加載動(dòng)態(tài)鏈接庫;2.定位到導(dǎo)出函數(shù)的地址,運(yùn)行函數(shù);3.釋放動(dòng)態(tài)鏈接庫。這些操作涉及三個(gè)API:LoadLibraryA(W),GetProcAddressA(W)和FreeLibrary,這三個(gè)API都位于kernel32.dll中,幾乎所有的windows應(yīng)用程序都要使用kernel32.dll,當(dāng)然也有一些程序確實(shí)是不使用這個(gè)動(dòng)態(tài)鏈接庫的,不過這樣的程序通常也沒有掛的必要。Windows平臺(tái)上這三個(gè)API的地址在每個(gè)進(jìn)程中的位置是固定的,這是本文介紹的加載方法的關(guān)鍵依賴,就是在本地進(jìn)程中將這三個(gè)API的地址得到,然后作為參數(shù)傳遞給在被掛進(jìn)程中的啟動(dòng)的線程函數(shù),這樣這個(gè)線程函數(shù)就可以使用這三個(gè)API(直接通過函數(shù)地址調(diào)用)加載我們定制的動(dòng)態(tài)鏈接庫。
??? 現(xiàn)在總結(jié)一下我們的方法,首先使用OpenProcess()得到被掛進(jìn)程的進(jìn)程句柄,然后調(diào)用VirtualAllocEx()在被掛進(jìn)程中分配兩塊內(nèi)存,一塊用于填寫被CreateRemoteThread()調(diào)用的啟動(dòng)線程的代碼,一塊用于存放需要傳遞給遠(yuǎn)程線程函數(shù)的參數(shù),用于傳遞參數(shù)的這一塊內(nèi)存大小比較容易確定,由參數(shù)大小決定,主要包括三個(gè)API的地址,還有定制動(dòng)態(tài)鏈接庫的文件名(全路徑),本文介紹的啟動(dòng)方法是在定制動(dòng)態(tài)庫中中設(shè)置一個(gè)導(dǎo)出函數(shù),通過調(diào)用這個(gè)導(dǎo)出函數(shù)完成外掛程序的初始化,所以還要包括這個(gè)導(dǎo)出函數(shù)的名字。這個(gè)導(dǎo)出函數(shù)的名字當(dāng)然是事先就定下的,那為什么還要?jiǎng)討B(tài)傳遞呢?直接使用就行了,比如:
ParaBlock->pGetProcessAddress(hMudule,"InitFunc");
如果你真是這樣想的,那就請(qǐng)?jiān)诳聪挛闹巴瓿梢韵聞?dòng)作:面向一堵墻站好,然后低下頭,用5米/秒的速度前進(jìn),直到停下為止........................
.......................
.......................
做完了?現(xiàn)在看看為什么要這樣做:因?yàn)?#34;InitFunc"會(huì)被編譯器放到本地進(jìn)程的靜態(tài)數(shù)據(jù)段中,這句代碼實(shí)際操作的是一個(gè)靜態(tài)數(shù)據(jù)段中的地址,而被掛進(jìn)程中是沒有這個(gè)靜態(tài)數(shù)據(jù)的,當(dāng)你將代碼復(fù)制到被掛進(jìn)程中后,照著這個(gè)地址調(diào)用,輕則得到一個(gè)無用的數(shù)據(jù)導(dǎo)致ParaBlock->pGetProcessAddress調(diào)用失敗,重則訪問非法地址導(dǎo)致被掛程序意外中止(比如被掛程序沒有捕獲這個(gè)異常),現(xiàn)在明白了嗎?這能使你腦袋開竅,就這樣。這個(gè)遠(yuǎn)程線程函數(shù)不能引用任何外部變量,所有外部變量都要通過VirtualAllocEx()在被掛進(jìn)程中分配一塊內(nèi)存,然后復(fù)制過去。
??? 關(guān)于參數(shù)的內(nèi)存比較好處理,關(guān)鍵之處是復(fù)制線程函數(shù)代碼,總結(jié)前面的描述,這個(gè)線程函數(shù)不能內(nèi)部調(diào)用運(yùn)行庫函數(shù)和API,不能使用外部變量,除此之外,對(duì)這個(gè)函數(shù)還有其它的要求,比如不能使用異常機(jī)制(包括Windows的結(jié)構(gòu)化異常處理),不能使用跳轉(zhuǎn)函數(shù)jump和longjump,甚至是不能使用CASE語句(沒有理論依據(jù),但是很多黑客都承認(rèn)這一點(diǎn),大概和不同的編譯器生成代碼方式不同有關(guān)),這很容易理解,因?yàn)橐WC編譯器生成的函數(shù)代碼是連續(xù)存放的,便于復(fù)制。這個(gè)線程函數(shù)還不能太大,畢竟,想要成為一個(gè)受人尊敬的外掛就要處處為被掛程序著想,盡量節(jié)省內(nèi)存。所要分配的內(nèi)存大小由實(shí)際的函數(shù)代碼大小決定,可以使用一個(gè)估計(jì)的足夠大的數(shù)值,比如TabSiPlus使用1024字節(jié),因?yàn)門abSiPlus的線程函數(shù)很小,1K字節(jié)足夠了,如果為了追求完美,可以使用相鄰函數(shù)地址相減(通過函數(shù)名完成)的方法計(jì)算出函數(shù)代碼塊的精確大小,不過,沒有編譯器能夠保證生成機(jī)器碼的時(shí)候按照C/C++代碼的順序,它們只是盡力而為。
??? 現(xiàn)在做最后的總結(jié),這種方法需要在被掛進(jìn)程中分配兩塊內(nèi)存,首先是分配遠(yuǎn)程線程參數(shù),以下是參數(shù)結(jié)構(gòu)的定義:
typedef HMODULE (WINAPI *PLoadLibraryW)(LPCWSTR);
typedef BOOL??? (WINAPI *PFreeLibrary)(HMODULE);
typedef FARPROC (WINAPI *PGetProcAddress)(HMODULE, char*);
struct RemoteThreadPara
{
?DWORD????LastError;
?PLoadLibraryW??fnLoadLibraryW;
?PFreeLibrary??fnFreeLibrary;
?PGetProcAddress??fnGetProcAddress;
?WCHAR????lpModulePath[MAX_PATH];?// the DLL path
?CHAR????lpFunctionName[256];??// the called function
??? //...還可以有其它參數(shù)
};
下面是參數(shù)處理代碼:
?RemoteThreadPara *c = 0;
?DWORD rc = (DWORD)-1;
?HMODULE hKernel32 = 0;
?RemoteThreadPara localCopy;
?// allocate memory for parameter block
?c = (RemoteThreadPara*) VirtualAllocEx( hProcess, 0, sizeof(RemoteThreadPara), MEM_COMMIT, PAGE_READWRITE );
?
?// 先填充一個(gè)本地結(jié)構(gòu)
#ifdef _UNICODE
?lstrcpyW( localCopy.lpModulePath, lpDllPath );
#else
?wsprintfW( localCopy.lpModulePath, L"%hs", lpDllPath );//lpDllPath是定制動(dòng)態(tài)庫的全路徑名
#endif
?if ( lpFunctionName == NULL )
??localCopy.lpFunctionName[0] = '/0';
?else
??lstrcpynA( localCopy.lpFunctionName, lpFunctionName, SIZEOF_ARRAY(localCopy.lpFunctionName) );//lpFunctionName是啟動(dòng)函數(shù)名稱
?
?// kernel32.dll
?hKernel32 = GetModuleHandle( _T("kernel32.dll") );
?
?// get the addresses for the functions, what we will use in the remote thread
?localCopy.fnLoadLibraryW = (PLoadLibraryW)GetProcAddress( hKernel32, "LoadLibraryW" );
?localCopy.fnFreeLibrary = (PFreeLibrary)GetProcAddress( hKernel32, "FreeLibrary" );
?localCopy.fnGetProcAddress = (PGetProcAddress)GetProcAddress( hKernel32, "GetProcAddress" );
??? WriteProcessMemory( hProcess, c, &localCopy, sizeof localCopy, 0 );//現(xiàn)在明白為什么需要一個(gè)本地結(jié)構(gòu)了吧?因?yàn)橹挥羞@個(gè)函數(shù)可以操作c的內(nèi)存
下面是函數(shù)處理代碼:
?// allocate memory for injected code
?p = VirtualAllocEx( hProcess, 0, 1024, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
?// copy function there, we will execute this piece of code
?WriteProcessMemory( hProcess, p, RemoteDllThread, 1024, 0 );
下面就是關(guān)鍵的RemoteDllThread線程函數(shù)代碼:
DWORD __stdcall RemoteDllThread( LPVOID pPara)
{
??? RemoteThreadPara *prBlock = (RemoteThreadPara *)pPara;
?HMODULE hModule = NULL;
?// load the requested dll
?if ( prBlock->bLoadLibrary )
?{
??hModule = (HMODULE)(*prBlock->fnLoadLibraryW)( prBlock->lpModulePath );
?}
?// call function
?if ( prBlock->lpFunctionName[0] != 0 )
?{
??//execute a function if we have a function name
??PFN pfn = (PFN)(*prBlock->fnGetProcAddress)( hModule, prBlock->lpFunctionName );
??// execute the function, and get the result
??if ( pfn != NULL )
??{
???DWORD ret = 0;
??????????? ret = (*pfn)();//調(diào)用外掛動(dòng)態(tài)鏈接庫的啟動(dòng)函數(shù)
??}
?}
?// free library
?if ( pfn->bFreeLibrary )
?{
??pfn->fnFreeLibrary( hModule );
?}
??? //.........其它處理代碼
?return 0;
}
PFN是定制動(dòng)態(tài)鏈接庫啟動(dòng)函數(shù)的原型:
typedef DWORD (WINAPI *PFN)();
現(xiàn)在萬事具備,只欠東風(fēng)啦,就是調(diào)用CreateRemoteThread()啟動(dòng)遠(yuǎn)程線程,裝載我們的定制動(dòng)態(tài)鏈接庫了:
ht = CreateRemoteThread( hProcess, 0, 0, (DWORD (__stdcall *)( void *)) p, c, 0, &ThreadId );
這一篇就到這里,下一篇文章將介紹如何構(gòu)建那個(gè)提供定制功能的定制動(dòng)態(tài)鏈接庫。
Source Insignt文件標(biāo)簽外掛:TabSiPlus的下載地址:
點(diǎn)擊下載
版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的给Source Insight做个外挂系列之二--将本地代码注入到Source Insight进程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 给Source Insight做个外挂系
- 下一篇: 给Source Insight做个外挂系