WINDOWS黑客基礎(3):注入代碼


有使用過外掛的朋友應該知道,我們在玩游戲的時候,有很多輔助功能給你使用,比如吃葯,使用物品等功能,這個時候我們就是使用注入代碼的技術,簡單的來將就是我們讓另外一個進程去執行我們想讓它執行的代碼,這中間的關鍵函數是CreateRemoteThread

HANDLE WINAPI CreateRemoteThread(
  _In_   HANDLE hProcess,
  _In_   LPSECURITY_ATTRIBUTES lpThreadAttributes,
  _In_   SIZE_T dwStackSize,
  _In_   LPTHREAD_START_ROUTINE lpStartAddress,
  _In_   LPVOID lpParameter,
  _In_   DWORD dwCreationFlags,
  _Out_  LPDWORD lpThreadId
);

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進程內開辟一塊內存,然后把我們的代碼或者數據復制進去,再執行對應的代碼就可以了,我們需要用到這幾個函數:

LPVOID WINAPI VirtualAllocEx(
  _In_      HANDLE hProcess,
  _In_opt_  LPVOID lpAddress,
  _In_      SIZE_T dwSize,
  _In_      DWORD flAllocationType,
  _In_      DWORD flProtect
);

BOOL WINAPI WriteProcessMemory(
  _In_   HANDLE hProcess,
  _In_   LPVOID lpBaseAddress,
  _In_   LPCVOID lpBuffer,
  _In_   SIZE_T nSize,
  _Out_  SIZE_T *lpNumberOfBytesWritten
);

這兩個函數跟我們平常用的函數都差不多,只是多了個進程的選項,大概步驟如下圖:

 

現在我們將實際操作一下:

下面是我們要注入的程序,在這之前,我們最好把基地址固定掉,這樣我們不會每次重新運行程序的時候函數的地址都會改變,在VS2008中,項目屬性->鏈接器->高級,把隨機基址和固定基址選擇默認值

void PrintMsg(const char *msg)
{
    printf("ThreadI D:%d Msg:%s\n",GetCurrentThreadId(),msg);
}
int main(void)
{
    printf("%d\n",GetCurrentThreadId());
    printf("Print Msg Function Address:%X\n",PrintMsg);
    system("pause");
}

假設我們的PrintMsg的地址是0x401000,現在我們需要往這個進程里面注入一段代碼,讓她可以自動調用PrintMsg這個函數

 1 static const char *msg = "INJECTION CODE SUCESS\n";
 2 static const unsigned int PARAM_SIZE = 100;
 3 static const unsigned int EXE_SIZE = 500;
 4 
 5 
 6 void InjectionCode(const char *msg)
 7 {
 8     __asm
 9     {
10         push eax
11         push msg
12         mov eax,0x401000
13         call eax            //因為在我們要注入的進程中,PrintMsg位於0x401000這個位置
14         pop eax
15         pop eax
16     }
17 }
18 int main(void)
19 {
20     HANDLE hProcess = OpenProcessByProcessNmae("main.exe"); //這個函數在上一章
21     
22     if (hProcess == INVALID_HANDLE_VALUE)
23     {
24         printf("error open process %d\n",GetLastError());
25         return 1;
26     }
27     //一定要把函數的代碼和msg寫入要注入的進程,否則會發生位置錯誤(一般是崩潰)
28     LPVOID RemoteExe = VirtualAllocEx(hProcess,NULL,EXE_SIZE,MEM_COMMIT,PAGE_EXECUTE);
29     LPVOID RemoteParam = VirtualAllocEx(hProcess,NULL,PARAM_SIZE,MEM_COMMIT,PAGE_READWRITE);
30 
31     SIZE_T WriteCount = 0;
32     int ret = 0;
33     ret = WriteProcessMemory(hProcess,RemoteParam,msg,PARAM_SIZE,&WriteCount);
34     ret = WriteProcessMemory(hProcess,RemoteExe,InjectionCode,0x13,&WriteCount);
35 
36     HANDLE hThread = CreateRemoteThread(hProcess,NULL,0,(LPTHREAD_START_ROUTINE)RemoteExe,RemoteParam,0,NULL);
37     WaitForSingleObject(hThread,INFINITE);
38 }

運行上面的程序,我們就可以在另外一個進程中創建一個線程,並且這個線程將會輸出該線程的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的地址,雖然很麻煩,特別是反匯編找代碼的那部分,但也沒辦法,這個在后面我會講


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM