Windows系統調用中API的3環部分(依據分析重寫ReadProcessMemory函數)

Windows內核分析索引目錄:https://www.cnblogs.com/onetrainee/p/11675224.htmlhtml

Windows系統調用中API的3環部分
ios

 

1、R3環API分析的重要性函數

  1. Windows所提供給R3環的API,實質就是對操做系統接口的封裝,其實現部分都是在R0實現的。
  2. 不少惡意程序會利用鉤子來鉤取這些API,從而達到截取內容,修改數據的意圖。
  3. 如今咱們使用olldbg對ReadProcessMemory進行跟蹤分析,查看其在R3的實現,並根據咱們的分析來重寫一個ReadProcessMemory。
  4. 重寫ReadProcessMemory以後,這就會加大惡意代碼截獲的難度。
  5. 固然,對於本身來講也有不少弊端,好比只能在指定的操做系統中運行(32位與64位操做系統,其運行ReadProcessMemory的執行動做是不同的,在64位運行32位程序,其中間會調用wow64cpu.dll來進行轉換)

 

2、調試代碼spa

 1 #include "pch.h"
 2 #include <iostream>
 3 #include <algorithm>
 4 #include <Windows.h>
 5 
 6 int main() {  7  getchar();  8  getchar();  9     int a[4],t; 10     printf("hello world!"); 11  getchar(); 12  getchar(); 13     // 依次往 p 指針中寫入數據,再用ReadProcessMemory讀取數據
14     for (int i = 0; i < 4; i++) { 15         WriteProcessMemory(INVALID_HANDLE_VALUE, &a[i], &i, sizeof(int),NULL); 16         
17  } 18     for (int i = 0; i < 4; i++) { 19         ReadProcessMemory(INVALID_HANDLE_VALUE, &a[i], &t, sizeof(int), NULL); 20         printf("%d\n", t); 21  } 22  getchar(); 23  getchar(); 24     
25 }

 

3、調試中的關鍵彙編代碼(系統環境:在Windows7 32位操做系統 / 調試器:olldbg)操作系統

 

 

 

 

1. 在exe 中 調用 kernel32.ReadProcessMemroy函數
  01314E3E    8BF4         mov esi,esp
  01314E40    6A 00        push 0x0
  01314E42    6A 04        push 0x4
  01314E44    8D45 DC      lea eax,dword ptr ss:[ebp-0x24]
  01314E47    50           push eax
  01314E48    8B4D C4      mov ecx,dword ptr ss:[ebp-0x3C]
  01314E4B    8D548D E8    lea edx,dword ptr ss:[ebp+ecx*4-0x18]
  01314E4F    52           push edx
  01314E50    6A FF        push -0x1
  01314E52    FF15 64B0310>call dword ptr ds:[<&KERNEL32.ReadProcessMemory>]; kernel32.ReadProcessMemory
  01314E58    3BF4         cmp esi,esp
.net

2. 在 kernel32.ReadProcessMemroy函數 中調用 jmp.&API-MS-Win-Core-Memory-L1-1-0.ReadProcessMemory> 函數
  // 該函數至關於什麼也沒作...
  7622C1CE >  8BFF             mov edi,edi
  7622C1D0    55               push ebp
  7622C1D1    8BEC             mov ebp,esp
  7622C1D3    5D               pop ebp                                                           ;
  7622C1D4  ^ E9 F45EFCFF      jmp <jmp.&API-MS-Win-Core-Memory-L1-1-0.ReadProcessMemory>
指針

3. 在 API-MS-Win-Core-Memory-L1-1-0.ReadProcessMemo 中調用 KernelBa.ReadProcessMemory 函數
  761F20CD  - FF25 0C191F7>jmp dword ptr ds:[<&API-MS-Win-Core-Memory-L1-1-0.ReadProcessMemo>; KernelBa.ReadProcessMemory
調試

4. 在KernelBa.ReadProcessMemory 調用 <&ntdll.NtReadVirtualMemory> 函數
  75DA9A0A >  8BFF         mov edi,edi
  // 這兩部分在編寫函數時就會使用
  75DA9A0C    55           push ebp
  75DA9A0D    8BEC         mov ebp,esp
  75DA9A0F    8D45 14      lea eax,dword ptr ss:[ebp+0x14]
  75DA9A12    50           push eax
  75DA9A13    FF75 14      push dword ptr ss:[ebp+0x14]
  75DA9A16    FF75 10      push dword ptr ss:[ebp+0x10]
  75DA9A19    FF75 0C      push dword ptr ss:[ebp+0xC]
  75DA9A1C    FF75 08      push dword ptr ss:[ebp+0x8]
  75DA9A1F    FF15 C411DA7>call dword ptr ds:[<&ntdll.NtReadVirtualMemory>] ; ntdll.ZwReadVirtualMemory
code

5. 在 <&ntdll.NtReadVirtualMemory> 中調用 ntdll.KiFastSystemCall 函數
  77A162F8 >  B8 15010000  mov eax,0x115  // 對應操做系統內核中某一函數的編號。
  77A162FD    BA 0003FE7F  mov edx,0x7FFE0300  // 該地方是一個函數,該函數決定了什麼方式進零環。
  77A16302    FF12         call dword ptr ds:[edx]  ; ntdll.KiFastSystemCall
htm

6. 在 ntdll.KiFastSystemCall 中 調用sysenter
  77A170B0 >  8BD4         mov edx,esp
  77A170B2    0F34         sysenter
  77A170B4 >  C3           retn

 

4、彙編代碼分析解讀(根據三中的序號依次解讀)

  1. 這部分是咱們程序中調用ReadProcessMemory後編譯器直接編譯後的彙編代碼,傳入參數與API調用
  2. 在kenel32.dll中,mov edi,edi 是用於熱補丁技術所保留的(函數開始處的MOV EDI, EDI的做用),這段代碼仔細看其實除了jmp什麼也沒幹。
  3. 轉到kernelBase.dll中實現ReadProcessMemory。
  4. 這段彙編代碼,將ReadProcessMemory中傳入的參數再次入棧,調用ntdll.ZwReadVirtualMemory函數。
  5. 這段彙編代碼看註釋,eax中存放了一個編號,其就是在內核中的ReadProcessMemory實現;在 0x7FFE0300 處存放了一個函數指針,該函數指針決定了以什麼方式進入0環(中斷/快速調用)。
  6. 在ntdll.KiFastSystemCall調用sysenter。

5、重寫ReadProcessMemory函數的思路

  咱們所看到的彙編代碼,本質就是Windows所執行的步驟,咱們依據上面的分析,徹底能夠從新寫一個該函數,只須要關鍵部分。

  1) 退而求其次

    咱們但願能夠在本身的代碼中直接使用 "sysenter",但通過編寫發現其並無提供這種指令。

    所以在"sysenter"沒法直接使用的狀況下,只能退而求其次,調用ntdll.KiFastSystemCall函數。

  2)傳遞參數,模擬call指令

    ntdll.KiFastSystemCall函數須要藉助ntdll.NtReadVirtualMemory傳遞過來的參數,而後執行call指令。

    咱們並不但願執行call指令執行,由於執行call指令意味着又上了一層。(多一層被鉤取的風險)

    咱們但願本身的代碼中直接傳遞參數,而且直接調用調用ntdll.KiFastSystemCall函數。

    所以咱們須要模擬call指令。call指令的本質就是將返回地址入棧,並跳轉。咱們不須要跳轉,只須要將返回地址入棧(四個字節 使用 sub esp,4 模擬)

  3)手動實現棧平衡

    咱們內嵌彙編代碼後,須要手動平衡棧,咱們只須要分析esp改變了多少(push、pop以及直接對esp的計算)。

    通過分析共減小了24字節,因此代碼最後應該有 add esp,24 來平衡棧。

 

6、ReadProcessMemory函數重寫的實現(重點看彙編代碼)

該代碼是使用快速調用所編寫的,若是想使用中斷實現調用內核函數,請移步這裏:Windows系統調用中API從三環到零環(下)

(執行結果)

 1 #include "pch.h"
 2 #include <iostream>
 3 #include <algorithm>
 4 #include <Windows.h>
 5 void  ReadMemory(HANDLE hProcess, PVOID pAddr, PVOID pBuffer, DWORD dwSize, DWORD  *dwSizeRet)  6 {  7 
 8  _asm  9  { 10         lea     eax, [ebp + 0x14] 11  push eax 12         push[ebp + 0x14] 13         push[ebp + 0x10] 14         push[ebp + 0xc] 15         push[ebp + 8] 16         sub esp, 4
17         mov eax, 0x115
18         mov edx, 0X7FFE0300   //sysenter不能直接調用,我間接call的
19  CALL DWORD PTR[EDX] 20         add esp, 24
21 
22  } 23 } 24 int main() 25 { 26     HANDLE hProcess = 0; 27     int t = 123; 28  DWORD pBuffer; 29     //hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0,a);
30     ReadMemory((HANDLE)-1, (PVOID)&t, &pBuffer, sizeof(int), 0); 31     printf("%X\n", pBuffer); 32     ReadProcessMemory((HANDLE)-1, &t, &pBuffer, sizeof(int), 0); 33     printf("%X\n", pBuffer); 34 
35  getchar(); 36     return 0; 37 }
相關文章
相關標籤/搜索