Windows內核分析索引目錄:https://www.cnblogs.com/onetrainee/p/11675224.html
Windows系統調用中API的3環部分
一、R3環API分析的重要性
- Windows所提供給R3環的API,實質就是對操作系統接口的封裝,其實現部分都是在R0實現的。
- 很多惡意程序會利用鈎子來鈎取這些API,從而達到截取內容,修改數據的意圖。
- 現在我們使用olldbg對ReadProcessMemory進行跟蹤分析,查看其在R3的實現,並根據我們的分析來重寫一個ReadProcessMemory。
- 重寫ReadProcessMemory之后,這就會加大惡意代碼截獲的難度。
- 當然,對於自己來說也有很多弊端,比如只能在指定的操作系統中運行(32位與64位操作系統,其運行ReadProcessMemory的執行動作是不一樣的,在64位運行32位程序,其中間會調用wow64cpu.dll來進行轉換)
二、調試代碼
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 }
三、調試中的關鍵匯編代碼(系統環境:在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
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
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
6. 在 ntdll.KiFastSystemCall 中 調用sysenter
77A170B0 > 8BD4 mov edx,esp
77A170B2 0F34 sysenter
77A170B4 > C3 retn
四、匯編代碼分析解讀(根據三中的序號依次解讀)
- 這部分是我們程序中調用ReadProcessMemory后編譯器直接編譯后的匯編代碼,傳入參數與API調用
- 在kenel32.dll中,mov edi,edi 是用於熱補丁技術所保留的(函數開始處的MOV EDI, EDI的作用),這段代碼仔細看其實除了jmp什么也沒干。
- 轉到kernelBase.dll中實現ReadProcessMemory。
- 這段匯編代碼,將ReadProcessMemory中傳入的參數再次入棧,調用ntdll.ZwReadVirtualMemory函數。
- 這段匯編代碼看注釋,eax中存放了一個編號,其就是在內核中的ReadProcessMemory實現;在 0x7FFE0300 處存放了一個函數指針,該函數指針決定了以什么方式進入0環(中斷/快速調用)。
- 在ntdll.KiFastSystemCall調用sysenter。
五、重寫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 來平衡棧。
六、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 }