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