有使用過外掛的朋友應該知道,我們在玩游戲的時候,有很多輔助功能給你使用,比如吃藥,使用物品等功能,這個時候我們就是使用注入代碼的技術,簡單的來將就是我們讓另外一個進程去執行我們想讓它執行的代碼,這中間的關鍵函數是CreateRemoteThread
CreateRemoteThread的參數跟CreateThread的參數差不多,多出來的hProcess是我們要對其操作的進程HANDLE,在早期的WINDOWS版本CreateThread確實是使用CreateRemoteThread實現的,就是把hProcess傳入我們自己的進程HANDLE
CreateRemoteThread的功能就是在指定的進程創建一個線程,這個線程運行我們指定的函數,看起來很簡單,但是有一個問題,就是虛擬內存導致的問題
大家都知道,在WINDOWS下是使用虛擬內存來進行數據管理的,每個進程都有自己獨立的地址空間,假設進程A准備向進程B注入一段代碼,他要讓進程B執行他進程空間的函數InjectionCode(),這個函數在進程A的地址空間地址為0X3000
現在我們開始進行代碼注入,利用CreateRemoteThread我們告訴B進程,請執行虛擬內存地址為0X3000的代碼,這個時候B進程該干什麼呢??B進程收到這個命令後,他很聽話地創建了線程,然後乖乖得CALL了0X3000的內容,請注意,現在B進程CALL的是它自己內存空間內0X3000的代碼而不是A進程的,那麼現在B進程的0X3000是什麼內容??沒人知道,運氣好的話說不定真的有段代碼給你執行,運氣不好你自己也不知道會發生什麼事情,這就跟你進錯了學生公寓一樣,同樣號碼的房間,運氣好是校花的房間,運氣不好就是如花的房間
那麼我們怎麼才能讓進程去執行我們對應的代碼呢??我們只要在B進程內開辟一塊內存,然後把我們的代碼或者數據復制進去,再執行對應的代碼就可以了,我們需要用到這幾個函數:
*
這兩個函數跟我們平常用的函數都差不多,只是多了個進程的選項,大概步驟如下圖:
現在我們將實際操作一下:
下面是我們要注入的程序,在這之前,我們最好把基地址固定掉,這樣我們不會每次重新運行程序的時候函數的地址都會改變,在VS2008中,項目屬性->鏈接器->高級,把隨機基址和固定基址選擇默認值
PrintMsg( * main(
假設我們的PrintMsg的地址是0x401000,現在我們需要往這個進程裡面注入一段代碼,讓她可以自動調用PrintMsg這個函數
*msg = unsigned PARAM_SIZE = unsigned EXE_SIZE = InjectionCode( * mov eax, main( HANDLE hProcess = OpenProcessByProcessNmae( (hProcess == printf( //一定要把函數的代碼和msg寫入要注入的進程,否則會發生位置錯誤(一般是崩潰) LPVOID RemoteExe = LPVOID RemoteParam = SIZE_T WriteCount = ret = ret = WriteProcessMemory(hProcess,RemoteParam,msg,PARAM_SIZE,& ret = WriteProcessMemory(hProcess,RemoteExe,InjectionCode,,& HANDLE hThread = CreateRemoteThread(hProcess,NULL,,(LPTHREAD_START_ROUTINE)RemoteExe,RemoteParam, }
運行上面的程序,我們就可以在另外一個進程中創建一個線程,並且這個線程將會輸出該線程的ID以及我們要輸出的消息
上面的程序還有幾個要注意的:
1.資源競爭
由於是創建線程執行相應代碼所以肯定會有資源競爭的問題,以後要寫代碼一定要注意,在本例中我忽略了這個問題
2. 關於代碼的長度問題
在本例中,我們的代碼長度是0X13,但是要知道,匯編代碼的長度隨便懂一下就可能更改,可能因為一個指令,也可能因為一個參數,所以我們需要時刻注意這點,關於代碼長度怎麼測量,我是看了反匯編的代碼後計算的,這個方法比較准確,也可以大概估計下,只要能把代碼復制完整就可以,超出也沒關系,只要不超出申請的內存大小就可以
3.記得備份我們使用的寄存器
這個十分重要,一旦你更改了寄存器,如果沒有後面沒有恢復,可能會導致一系列錯誤,特別是ESP,EBP等重要的寄存器
3.注入代碼多次調用系統DLL中的函數
<<WINDOWS核心編程>>裡面說,系統的DLL都會加載到一個固定的地址,比如VirtualAllocEx,一般我們在A進程和B進程的時候,call或者jmp的地址都是一樣的,所以一般我們如果調用的是系統函數,一般我們不需要擔心,但是,昨天我想到了一個問題,比如我們進程A要命令進程B調用CreateToolhelp32Snapshot這個系統API,現在我們假設CreateToolhelp32Snapshot這個API在單獨的TLHELP32.DLL裡面(實際上這個在KERNEL32.DLL裡面,所有進程都會加載這個DLL,所以不需要擔心下面的問題,這個只是舉例),操作系統在加載DLL的時候,會統一把這個API的地址映射到虛擬內存的0XFF40100的地址,按照我們原來的想法進程B會自己跑去call 0XFF40100這個地址。但問題在於,如果我們的進程根本就沒有加載TLHELP32.DLL這個DLL,那麼進程call 0XFF40100會怎麼樣??這個就要看你這個地方是什麼代碼了,有人說操作系統會幫你加載這個DLL,但我覺得是錯的,因為操作系統要幫你加載的DLL都在PE頭裡面的導入表裡面,要嘛就是我們要顯示地去加載,否則操作系統不會知道我們的API在哪個DLL裡面
4.注入代碼多次調用我們自己編寫的函數
比如我們有IntejectionCode,裡面調用了IntejectionCode1,這個時候我們需要把IntejectionCode1也寫入對方進程裡面,不能只寫入IntejectionCode,並且,我們需要更改IntejectionCode裡面call IntejectionCode1跳轉指令,讓其跳轉到正確的位置。總之,別人的地盤別人做主,對方進程想把代碼放哪裡就放哪裡,我們無法管理(實際上virtualAllocEx是可以指定位置的,但是一般我們都盡量讓操作系統去指定),我們只能入鄉隨俗,人家讓我們去哪裡調用我們就要去哪裡調用,不然很容易導致進程崩潰
5.關於代碼的基地址
在本例中PrintMsg的基地址是固定的,是我們人為去固定基地址的,我們在開發的時候很少人會去把基地址固定掉,所以在進程運行的時候,PrintMsg這個函數的地址是會改變的,當然我們也可以算出來這段代碼在運行的時候會放在哪裡,因為PrintMsg這段代碼以二進制放在EXE文件的時候,也有一個文件偏移量,當操作系統把EXE文件加載進內存後,會根據基地址和文件偏移量,來算出PrintMsg在虛擬內存中的位置,所以我們只要能拿到進程運行時候的基地址,並且把EXE反匯編查看這段代碼的文件偏移量,也能算出每次運行的時候PrintMsg的地址,雖然很麻煩,特別是反匯編找代碼的那部分,但也沒辦法,這個在後面我會講