Windows系统调用中API的3环部分(依据分析重写ReadProcessMemory函数)

Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html

Windows系统调用中API的3环部分

一、R3环API分析的重要性

  1. Windows所提供给R3环的API,实质就是对操作系统接口的封装,其实现部分都是在R0实现的。
  2. 很多恶意程序会利用钩子来钩取这些API,从而达到截取内容,修改数据的意图。
  3. 现在我们使用olldbg对ReadProcessMemory进行跟踪分析,查看其在R3的实现,并根据我们的分析来重写一个ReadProcessMemory。
  4. 重写ReadProcessMemory之后,这就会加大恶意代码截获的难度。
  5. 当然,对于自己来说也有很多弊端,比如只能在指定的操作系统中运行(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

四、汇编代码分析解读(根据三中的序号依次解读)

  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。

五、重写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函数重写的实现(重点看汇编代码)

(执行结果)

 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 }

  

    

原文地址:https://www.cnblogs.com/onetrainee/p/11704626.html

时间: 2024-10-01 05:55:15

Windows系统调用中API的3环部分(依据分析重写ReadProcessMemory函数)的相关文章

Windows系统调用中API从3环到0环(下)

 Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html Windows系统调用中API从3环到0环(下) 如果对API在三环的部分不了解的,可以查看 Windows系统调用中的API三环部分(依据分析重写ReadProcessMemory函数 上篇:Windows系统调用中API从3环到0环(上) 这篇文章分为上下两篇,其中上篇初步讲解大体轮廓,下篇着重通过实验来探究其内部实现,最终分析两个函数(快速调用与系统中断)

Windows系统调用中的系统服务表描述符

 Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html Windows系统调用中的系统服务表描述符 在前面,我们将解过 系统服务表.可是,我们有个疑问,系统服务表存储在哪里呢? 答案就是:系统服务表 存储在 系统服务描述符表中.(其又称为 SSDT Service Descriptor Table)  一.使用PELord函数从ntoskrnl.exe文件中查看SSDT导出函数 如图,可以看出KeServiceDes

Windows系统调用中的现场保存

Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html Windows系统调用中的现场保存 我们之前介绍过三环进零环的步骤,通过中断或者快速调用来实现. 但是我们是否考虑过CPU从三环进入零环时,其三环的寄存器该如何保存. 这一篇文件就来介绍其系统调用中的(三环)现场保存的问题. 一.几个重要的结构体介绍 1. _Ktrap_frame 该结构体简单来说用于三环的寄存器保存,存储于零环,由操作系统维护 详细信息可以查看

Windows系统调用中的系统服务表

Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html Windows系统调用中的系统服务表 如果这部分不理解,可以查看 Windows内核分析索引目录依次阅读. 我们在之前讲过系统调用过程中,给予eax一个编号,操作系统通过这个编号来执行某个内核函数. 这个函数是通过操作系统的系统服务表来查找的. 现在,我们来探究一下nt!KiFastCallEntry的反汇编代码,看看其如何查看系统服务表找到要执行的函数的. 一.

windows进程中的内存结构(好多API,而且VC最聪明)

在阅读本文之前,如果你连堆栈是什么多不知道的话,请先阅读文章后面的基础知识.   接触过编程的人都知道,高级语言都能通过变量名来访问内存中的数据.那么这些变量在内存中是如何存放的呢?程序又是如何使用这些变量的呢?下面就会对此进行深入的讨论.下文中的C语言代码如没有特别声明,默认都使用VC编译的release版.   首先,来了解一下 C 语言的变量是如何在内存分部的.C 语言有全局变量(Global).本地变量(Local),静态变量(Static).寄存器变量(Regeister).每种变量都

[转载]Windows系统调用架构分析—也谈KiFastCallEntry函数地址的获取

原文地址:点击打开链接 为什么要写这篇文章 1.      因为最近在学习<软件调试>这本书,看到书中的某个调试历程中讲了Windows的系统调用的实现机制,其中讲到了从Ring3跳转到Ring0之后直接进入了KiFastCallEntry这个函数. 2.      碰巧前天又在网上看到了一篇老文章介绍xxx安全卫士对Windows系统调用的Hook,主要就是Hook到这个函数 3.      刚刚做完毕业设计,对使用中断来实现系统调用的方式记忆犹新. 以上原因导致我最近眼前总是出现系统调用这

linux下系统调用、API、系统命令,内核函数的区别与联系

1.系统调用: 应用程序和内核间的桥梁,是应用程序访问内核的入口点;但通常情况下,应用程序通过操作系统提供的API进行编程而不是使用系统调用直接编程; linux的全部系统调用加起来大约只有250个左右. 2.API: API常以c库(libc)的形式提供,c库提供了绝大部分API,每个系统调用在c库中都有对应的封装函数(通常封装函数与系统调用的名称相同).系统调用与c库函数并不是一一对应的,有些c库函数可能使用多个系统调用来实现,也有可能多个c库函数使用同一个系统调用来实现,也有些c库函数不使

玩转 Windows 10 中的 Linux 子系统(SSH服务)

在今年的 Build 2016 上,微软向全世界介绍了他们还处于 Beta 阶段的 Windows 下的 Linux 子系统Windows Subsystem for Linux(WSL),它可以让开发者们在 Windows 10 下通过 Bash shell 运行原生的 Ubuntu 用户态二进制程序.如果你参与了 Windows Insider 计划,你就可以在最新的 Windows 10 年度升级版的 Insider 构建版中体验这个功能了.Web 开发人员们不用再苦恼所用的 Window

Windows 多进程通信API总结

在一个大型的应用系统中,往往需要多个进程相互协作,进程间通信(IPC,Inter Process Communication)就显得比较重要了.在Linux系统中,有很多种IPC机制,比如说,信号(signal).管道(pipe).消息队列(message queue).信号量(semaphore)和共享内存(shared memory).套接字(socket)等,其实Windows操作系统也支持这些东西.在IBM的Developerworks发现了一篇关于Windows与Linux 之间IPC