文章目录

  • 前言
  • 逆向分析ReadProcessMemory
    • NtReadVirtualMemory
    • _KUSER_SHARED_DATA
    • SystemCall
      • 通过int 0x2E中断门进入零环
      • 通过sysenter快速调用进入零环
  • 总结
  • 总结

前言

在三环操作系统提供了各种API,这些API实际上只是一个暴露在三环的接口,真正的功能实现部分,最终都是要进到零环。

逆向分析ReadProcessMemory

###ReadProcessMemory

以ReadProcessMemory这个函数为例,来看一下三环的API的执行流程大致是什么样的。用IDA打开kernel32.dll,找到ReadProcessMemory函数。

ReadProcessMemory在内部调用了NtReadVirtualMemory函数,这个函数来自于kernel32.dll的导入表。接下来我们在导入表中找到这个函数

从导入表中可以看到,NtReadVirtualMemory这个函数来自于ntdll。接着我们用IDA打开ntdll.dll。并找到NtReadVirtualMemory函数。

NtReadVirtualMemory

NtReadVirtualMemory这个函数只有下面的几行代码

.text:7C92E2BB                 mov     eax, 0BAh       ; NtReadVirtualMemory
.text:7C92E2C0                 mov     edx, 7FFE0300h
.text:7C92E2C5                 call    dword ptr [edx]
.text:7C92E2C7                 retn    14h

这里call了一个[edx],那么接下来我们就要去找[edx]指向的是哪个函数,而edx的内容则取决于7FFE0300h这个地址里面是什么。

而想要了解7FFE0300h这个地址里的内容,需要先了解一个结构体->_KUSER_SHARED_DATA。

_KUSER_SHARED_DATA

在用户层和内核层分别定义了一个_KUSER_SHARED_DATA结构区域,用于在用户层和内核层共享某些数据。它们使用固定的地址值映射,_KUSER_SHARED_DATA结构区域在User和Kernel层地址分别为:

  • User层地址为:0x7ffe0000
  • Kernel层地址为:0xffdf0000

User层和Kernel层映射同一个物理页。虽然它们指向的是同一个物理页,但在User层是只读的,在Kernel层是可写的。

直接在windbg里查看一下这两个地址的内容,首先挂载到任意一个进程

PROCESS 88049c68  SessionId: 0  Cid: 0930    Peb: 7ffd9000  ParentCid: 05b8DirBase: 7f4b64c0  ObjectTable: a7725ab8  HandleCount: 14034.Image: OEM8.exekd> .process 88049c68
Implicit process is now 88049c68
WARNING: .cache forcedecodeuser is not enabled

接着查看这两个地址的内容

kd> dd 0x7ffe0000
7ffe0000  00000000 0f99a027 5283733f 00000000
7ffe0010  00000000 22ad355a 01d5b7d1 01d5b7d1
7ffe0020  f1dcc000 ffffffbc ffffffbc 014c014c
7ffe0030  003a0043 0057005c 006e0069 006f0064
7ffe0040  00730077 00000000 00000000 00000000
7ffe0050  00000000 00000000 00000000 00000000
7ffe0060  00000000 00000000 00000000 00000000
7ffe0070  00000000 00000000 00000000 00000000
kd> dd 0xffdf0000
ffdf0000  00000000 0f99a027 5283733f 00000000
ffdf0010  00000000 22ad355a 01d5b7d1 01d5b7d1
ffdf0020  f1dcc000 ffffffbc ffffffbc 014c014c
ffdf0030  003a0043 0057005c 006e0069 006f0064
ffdf0040  00730077 00000000 00000000 00000000
ffdf0050  00000000 00000000 00000000 00000000
ffdf0060  00000000 00000000 00000000 00000000
ffdf0070  00000000 00000000 00000000 00000000

两块地址空间的内容完全相同。接着再查看一下两个地址的属性

kd> !vtop 7f4b64c0 0x7ffe0000
X86VtoP: Virt 000000007ffe0000, pagedir 000000007f4b64c0
X86VtoP: PAE PDPE 000000007f4b64c8 - 000000004fe09801
X86VtoP: PAE PDE 000000004fe09ff8 - 000000004fa07867
X86VtoP: PAE PTE 000000004fa07f00 - 80000000001e2025
X86VtoP: PAE Mapped phys 00000000001e2000
Virtual address 7ffe0000 translates to physical address 1e2000.
kd> !vtop 7f4b64c0 0xffdf0000
X86VtoP: Virt 00000000ffdf0000, pagedir 000000007f4b64c0
X86VtoP: PAE PDPE 000000007f4b64d8 - 000000004c00b801
X86VtoP: PAE PDE 000000004c00bff0 - 000000000018a063
X86VtoP: PAE PTE 000000000018af80 - 00000000001e2163
X86VtoP: PAE Mapped phys 00000000001e2000
Virtual address ffdf0000 translates to physical address 1e2000.

0x7ffe0000这个三环的地址PTE属性是只读的,而0xffdf0000这个零环的地址的PTE属性是可读可写的。

SystemCall

接下来回到NtReadVirtualMemory这个函数

.text:7C92E2BB                 mov     eax, 0BAh       ; NtReadVirtualMemory
.text:7C92E2C0                 mov     edx, 7FFE0300h
.text:7C92E2C5                 call    dword ptr [edx]
.text:7C92E2C7                 retn    14h

现在我们已经知道了0x7ffe0000这个内存是一块共享的内存区域,接下来看一下偏移0x300的位置也就是7FFE0300这个地址的值是什么。

kd> dt _KUSER_SHARED_DATA 0x7ffe0000
nt!_KUSER_SHARED_DATA+0x000 TickCountLowDeprecated : 0+0x004 TickCountMultiplier : 0xf99a027+0x008 InterruptTime    : _KSYSTEM_TIME+0x014 SystemTime       : _KSYSTEM_TIME+0x020 TimeZoneBias     : _KSYSTEM_TIME+0x02c ImageNumberLow   : 0x14c+0x02e ImageNumberHigh  : 0x14c+0x030 NtSystemRoot     : [260]  "C:\Windows"+0x238 MaxStackTraceDepth : 0+0x23c CryptoExponent   : 0+0x240 TimeZoneId       : 0+0x244 LargePageMinimum : 0x200000+0x248 Reserved2        : [7] 0+0x264 NtProductType    : 1 ( NtProductWinNt )+0x268 ProductTypeIsValid : 0x1 ''+0x26c NtMajorVersion   : 6+0x270 NtMinorVersion   : 1+0x274 ProcessorFeatures : [64]  ""+0x2b4 Reserved1        : 0x7ffeffff+0x2b8 Reserved3        : 0x80000000+0x2bc TimeSlip         : 0+0x2c0 AlternativeArchitecture : 0 ( StandardDesign )+0x2c4 AltArchitecturePad : [1] 0+0x2c8 SystemExpirationDate : _LARGE_INTEGER 0x0+0x2d0 SuiteMask        : 0x310+0x2d4 KdDebuggerEnabled : 0x3 ''+0x2d5 NXSupportPolicy  : 0x2 ''+0x2d8 ActiveConsoleId  : 1+0x2dc DismountCount    : 0+0x2e0 ComPlusPackage   : 0xffffffff+0x2e4 LastSystemRITEventTickCount : 0+0x2e8 NumberOfPhysicalPages : 0x7ff7e+0x2ec SafeBootMode     : 0 ''+0x2ed TscQpcData       : 0 ''+0x2ed TscQpcEnabled    : 0y0+0x2ed TscQpcSpareFlag  : 0y0+0x2ed TscQpcShift      : 0y000000 (0)+0x2ee TscQpcPad        : [2]  ""+0x2f0 SharedDataFlags  : 0xc+0x2f0 DbgErrorPortPresent : 0y0+0x2f0 DbgElevationEnabled : 0y0+0x2f0 DbgVirtEnabled   : 0y1+0x2f0 DbgInstallerDetectEnabled : 0y1+0x2f0 DbgSystemDllRelocated : 0y0+0x2f0 DbgDynProcessorEnabled : 0y0+0x2f0 DbgSEHValidationEnabled : 0y0+0x2f0 SpareBits        : 0y0000000000000000000000000 (0)+0x2f4 DataFlagsPad     : [1] 0+0x2f8 TestRetInstruction : 0xc3+0x300 SystemCall       : 0x776c70b0+0x304 SystemCallReturn : 0x776c70b4+0x308 SystemCallPad    : [3] 0+0x320 TickCount        : _KSYSTEM_TIME+0x320 TickCountQuad    : 0x22a9+0x320 ReservedTickCountOverlay : [3] 0x22a9+0x32c TickCountPad     : [1] 0+0x330 Cookie           : 0xe0c0696a+0x334 CookiePad        : [1] 0+0x338 ConsoleSessionForegroundProcessId : 0n1600+0x340 Wow64SharedInformation : [16] 0+0x380 UserModeGlobalLogger : [16] 0+0x3a0 ImageFileExecutionOptions : 0+0x3a4 LangGenerationCount : 1+0x3a8 Reserved5        : 0+0x3b0 InterruptTimeBias : 0+0x3b8 TscQpcBias       : 0+0x3c0 ActiveProcessorCount : 1+0x3c4 ActiveGroupCount : 1+0x3c6 Reserved4        : 0+0x3c8 AitSamplingValue : 0+0x3cc AppCompatFlag    : 1+0x3d0 SystemDllNativeRelocation : 0xff7c0000+0x3d8 SystemDllWowRelocation : 0+0x3dc XStatePad        : [1] 0+0x3e0 XState           : _XSTATE_CONFIGURATION

找到+300的位置

 +0x300 SystemCall       : 0x776c70b0

这个地方是一个SystemCall,查看一下对应的反汇编代码,看看NtReadVirtualMemory函数的call dword ptr [edx]具体是做了什么。

kd> u 0x776c70b0
ntdll!KiFastSystemCall
776c70b0 8bd4            mov     edx,esp
776c70b2 0f34            sysenter
776c70b4 c3              ret

这个函数叫KiFastSystemCall,实际上就只有三行代码,首先把esp保存到edx,目的是为了在零环能够方便的找到三环的堆栈。接着用sysenter指令进到零环,最后通过ret指令返回。

然而并不是所有的CPU都支持sysenter快速调用指令。这就要了解一下另外一个问题?0x7ffe0300到底存储的是什么?

###两种从三环进零环的方式

操作系统在启动的时候,需要初始化_KUSER_SHARED_DATA这个结构体,其中最重要的就是初始化0x300这个位置。操作系统要往这里面写一个函数,这个函数决定了所有的三环的API进入零环的方式。

操作系统在写入之前会通过cpuid这个指令来检查当前的CPU是否支持快速调用,如果支持的话,就往0x300这个位置写入KiFastSystemCall。如果不支持,则写入KiIntSystemCall。

我们可以在IDA中看到KiIntSystemCall的函数内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uUumiE8A-1576921324496)(assets/1576917420769.png)]

.text:7C92EBA5                 lea     edx, [esp+arg_4]
.text:7C92EBA9                 int     2Eh
.text:7C92EBAB                 retn

KiIntSystemCall就只有三行代码,利用int 0x2E这条指令通过中断门的方式进入零环。

也就是说Windows使用了两种从三环进零环的方式。一种是中断门,一种是sysenter快速调用。

通过int 0x2E中断门进入零环

如果是通过中断门的方式进入到零环的话,最终EIP会指向哪呢?这就要查看IDT表了。

首先查看idt表的基址

kd> r idtr
idtr=80b95400

接着查看IDT表项0x2E的位置的段描述符

kd> dq 80b95400+0x2E*8
80b95570  83e8ee00`00083fee 83e88e00`000876b0
80b95580  83e88e00`000836b0 83e88e00`000836ba
80b95590  83e88e00`000836c4 83e88e00`000836ce
80b955a0  83e88e00`000836d8 83e88e00`000836e2
80b955b0  83e88e00`000836ec 83e28e00`00089104
80b955c0  83e88e00`00083700 83e88e00`0008370a
80b955d0  83e88e00`00083714 83e88e00`0008371e
80b955e0  83e88e00`00083728 83e88e00`00083732

通过拆分83e8ee00`00083fee这个中断门描述符可以得出CS段选择子为0008,EIP为83e83fee。也就是说API通过中断门的方式最终会跳转到0x83e83fee。接着查看一下这个地址的反汇编

kd> u 83e83fee
nt!KiSystemService:
83e83fee 6a00            push    0
83e83ff0 55              push    ebp
83e83ff1 53              push    ebx
83e83ff2 56              push    esi
83e83ff3 57              push    edi
83e83ff4 0fa0            push    fs
83e83ff6 bb30000000      mov     ebx,30h
83e83ffb 668ee3          mov     fs,bx

KiSystemService函数的地址是8开头的,而且模块是nt不再是ntdll。到这里,API已经完成了从三环进入零环的过程。

通过sysenter快速调用进入零环

想要从三环进入到零环首先必须要提权,提权需要切换CS SS EIP ESP。如果通过中断门进入零环,门描述符里保存有CS和EIP,而SS和ESP来自于TSS。

在了解sysenter指令之前,要先了解一个寄存器,叫MSR。操作系统并没有公开这个寄存器的内部细节。但是我们可以知道这个寄存器的部分含义:

  • 0x174保存的是CS
  • 0x175保存的是ESP
  • 0x176保存的是EIP

如果想查看msr寄存器174就可以使用下面的指令

kd> rdmsr 174
msr[174] = 00000000`00000008

sysenter快速调用指令完成的事情就是从msr寄存器里拿到174 175和176的值,覆盖原来寄存器的值。int 0x2E和sysenter两种进入零环的方式的本质都是切换寄存器。

还有一个问题在于,通过msr寄存器只能拿到三个值,分别是CS ESP和EIP,那么SS来自于哪呢?这个SS的值实际上是写死的。举个例子来说,如果提权之后的CS的值为8,那么SS=CS+8=0x10。(具体细节请参考Intel白皮书第二卷 搜索sysenter)

总结

API从三环进到零环过程如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vBdovwgd-1576921324505)(assets/API三环进零环的过程.png)]

sysenter快速调用指令完成的事情就是从msr寄存器里拿到174 175和176的值,覆盖原来寄存器的值。int 0x2E和sysenter两种进入零环的方式的本质都是切换寄存器。

还有一个问题在于,通过msr寄存器只能拿到三个值,分别是CS ESP和EIP,那么SS来自于哪呢?这个SS的值实际上是写死的。举个例子来说,如果提权之后的CS的值为8,那么SS=CS+8=0x10。(具体细节请参考Intel白皮书第二卷 搜索sysenter)

总结

API从三环进到零环过程如图:

系统调用001 API从三环进零环的过程相关推荐

  1. 深入理解Linux系统调用与API(0.9)

    学习方法论 写作原则 标题括号中的数字代表完成度与完善度 0.0-1.0 代表完成度,1.1-1.5 代表完善度 0.0 :还没开始写 0.1 :写了一个简介 0.3 :写了一小部分内容 0.5 :写 ...

  2. 程序员的自我修养--链接、装载与库笔记:系统调用与API

    系统调用(System Call)是应用程序(运行库也是应用程序的一部分)与操作系统内核之间的接口,它决定了应用程序是如何与内核打交道的.无论程序是直接进行系统调用,还是通过运行库,最终还是会到达系统 ...

  3. 用大白话解析函数调用,系统调用和API之间的关系

    一.官方的解释(大概了解一下) 函数调用: 函数调用是计算机编或运行时,使用某个函数来完成相关命令. 系统调用: 系统调用是用户在程序中使用"访管指令"调用由操作系统提供的子功能集 ...

  4. 程序员自我修养阅读笔记——系统调用与API

    1 系统调用 1.1 系统调用简介   由操作系统实现提供的所有系统调用所构成的集合即程序接口或应用编程接口(Application Programming Interface,API).是应用程序同 ...

  5. 程序员自我修养》系统调用与API

    什么是系统调用 在现代的操作系统里,程序运行的时候,本身是没有权利访问多少系统资源的.由于系统有限的资源有可能被多个不同的应用程序同时访问,因此,如果不加以保护,那么各个应用程序难免产生冲突.所以现代 ...

  6. Socket 系统调用深入研究(TCP协议的整个通信过程)

    说明 本文主要参考的原文:Know your TCP system call sequences socket api可以参考我的博客:socket API 介绍 TCP DEMO:tcp demo ...

  7. Web APi之控制器选择Action方法过程(九)

    前言 前面我们叙述了关于控制器创建的详细过程,在前面完成了对控制器的激活之后,就是根据控制器信息来查找匹配的Action方法,这就是本节要讲的内容.当请求过来时首先经过宿主处理管道然后进入Web AP ...

  8. 从open系统调用的源码看文件的打开过程

    open系统调用:创建file结构体,(指针)放入进程打开文件表,返回表下标(文件描述符) 转自:http://blog.csdn.net/qiang81020/archive/2010/06/20/ ...

  9. 网络与IO知识扫盲(三):从系统调用的角度,剖析 Socket 的连接过程、BIO 的连接过程

    Socket的连接过程.TCP的一些参数 前置知识 用到的命令 netstat -natp 查看网络连接和占用的端口 tcpdump -nn -i eth0 port 9090 开监听抓取数据包 ls ...

最新文章

  1. 简单两步就能将 Laravel Log 信息发到其他平台上
  2. 废旧纸箱做机器人图片_网购后的快递纸箱被你扔掉了吗?
  3. 通用Login功能自动化测试
  4. redis List的用途及常用命令
  5. POJ2828线段树 插队(单点更新)
  6. python pp模块_Python模块--Pexpect
  7. java sorted_Java记录 -59- SortedSet
  8. [初级]Java中的switch对整型、字符型、字符串的具体实现细节
  9. 动手学CV-目标检测入门教程:基本概念
  10. c语言dfs算法全排列代码,c语言dfs解决全排列问题
  11. ACM题目————食物链
  12. Java之序列化和反序列化
  13. swapLexOrder
  14. Zookeeper如何保证数据一致性
  15. java青蛙跳井_数学运算归纳
  16. 微软关闭了两种攻击途径:Office 宏、RDP 暴力破解
  17. 高速公路匝道口事故何时了?
  18. Chromium内核的浏览器Browsers查看Chromium的版本 : navigator.userAgent
  19. Keil暗色模式配置文件
  20. 先验概率跟后验概率(通俗易懂)

热门文章

  1. Py之glob: glob库文件名模式匹配+返回所有匹配的文件路径列表库的简介、使用方法之详细攻略
  2. 成功解决pypmml.base.PmmlError: (‘MalformedInputException‘, ‘Input length = 1‘)
  3. ML之xgboost:利用xgboost算法(sklearn+3Split+调参曲线+EarlyStop)训练mushroom蘑菇数据集(22+1,6513+1611)来预测蘑菇是否毒性(二分类预测)
  4. 成功解决OpenCV Error: Assertion failed (scn == 3 || scn == 4) in cv::cvtColor
  5. bzoj3714:[PA2014]Kuglarz
  6. 在VMware运行Linux下,密码错误的原因
  7. advanced installer重新打包教程
  8. 【BZOJ2115】[Wc2011] Xor 高斯消元求线性基+DFS
  9. linux下的动态链接库和静态链接库到底是个什么鬼?(一)静态链接库的编译与使用...
  10. FreeRTOS 中断优先级嵌套错误引发HardFault异常解决