beibeijia 2019-10-19
Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html
Windows系统调用中API的3环部分
一、R3环API分析的重要性
二、调试代码
#include "pch.h" #include <iostream> #include <algorithm> #include <Windows.h> int main() { getchar(); getchar(); int a[4],t; printf("hello world!"); getchar(); getchar(); // 依次往 p 指针中写入数据,再用ReadProcessMemory读取数据 for (int i = 0; i < 4; i++) { WriteProcessMemory(INVALID_HANDLE_VALUE, &a[i], &i, sizeof(int),NULL); } for (int i = 0; i < 4; i++) { ReadProcessMemory(INVALID_HANDLE_VALUE, &a[i], &t, sizeof(int), NULL); printf("%d\n", t); } getchar(); getchar(); }
三、调试中的关键汇编代码(系统环境:在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函数的思路
我们所看到的汇编代码,本质就是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函数重写的实现(重点看汇编代码)
(执行结果)
#include "pch.h" #include <iostream> #include <algorithm> #include <Windows.h> void ReadMemory(HANDLE hProcess, PVOID pAddr, PVOID pBuffer, DWORD dwSize, DWORD *dwSizeRet) { _asm { lea eax, [ebp + 0x14] push eax push[ebp + 0x14] push[ebp + 0x10] push[ebp + 0xc] push[ebp + 8] sub esp, 4 mov eax, 0x115 mov edx, 0X7FFE0300 //sysenter不能直接调用,我间接call的 CALL DWORD PTR[EDX] add esp, 24 } } int main() { HANDLE hProcess = 0; int t = 123; DWORD pBuffer; //hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0,a); ReadMemory((HANDLE)-1, (PVOID)&t, &pBuffer, sizeof(int), 0); printf("%X\n", pBuffer); ReadProcessMemory((HANDLE)-1, &t, &pBuffer, sizeof(int), 0); printf("%X\n", pBuffer); getchar(); return 0; }