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 }
相关文章
相关标签/搜索