Windows 系统调用学习笔记

news/2025/2/28 13:59:31/文章来源:https://www.cnblogs.com/Here-is-SG/p/18743105

依然是 x86 的,照着 lzyddf 师傅的 blog 和 OneTrainee师傅的blog 学的

Windows API

Application Programming Interface,简称 API 函数。Windows API 是微软为 Windows 操作系统提供的一组函数、数据结构、常量和协议,允许开发者与操作系统进行交互。通过 Windows API,开发者可以创建和管理窗口、处理用户输入、执行文件操作、管理内存、进行网络通信等。

Windows 的 API 主要是存放在 C:\WINDOWS\system32 下面所有的 dll。列举几个重要的系统自带的 dll:

  1. Kernel32.dll:最核心的功能模块,比如管理内存、进程和线程相关的函数等
  2. User32.dll:是 Windows 用户界面相关应用程序接口,如创建窗口和发送消息等
  3. GDI32.dll:全称是 Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数。比如要显示一个程序窗口,就调用了其中的函数来画这个窗口
  4. Ntdll.dll:大多数 API 都会通过这个 DLL 进入内核(0环)

分析 ReadProcessMemory

如果你用的 Windows XP,那么这个函数在 kernel32.dll。但是如果你是 Windows 7 或更高的系统,那么 kernel32.dll 就只是一个转发层,实际实现位于 KernelBase.dll 内。不止是 ReadProcessMemory,很多函数的实现都被重构到了 KernelBase.dll 内。这里我们看见的函数都是 Windows 11 里面的实现。

Windows 系统调用学习笔记 1

Windows 系统调用学习笔记 2

逻辑十分简单。BaseSetLastNTError 函数实现如下,这个函数的作用就是将 NTSTATUS 错误码 转换为 Win32 错误码,并将其设置为当前线程的最后一个错误码。简单理解就是把错误码传给用户层程序的东西。

__int64 __fastcall BaseSetLastNTError(NTSTATUS a1)
{ULONG v1; // ebxv1 = RtlNtStatusToDosError(a1);RtlSetLastWin32Error(v1);return v1;
}

那我们发现,ReadProcessMemory 好像啥也没干,其实就调用了一下 NtReadVirtualMemory 函数然后进行了返回值和错误处理的设置。

分析 NtReadVirtualMemory

查一下 KernelBase.dll 的导入表可以发现 NtReadVirtualMemory 是 ntdll 导入的。然后这个函数直接在 Functions 里面还搜不到,得在 Exports 里面搜

养成先看汇编的好习惯(

Windows 系统调用学习笔记 3

我们会发现这个函数名字叫 ZwReadVirtualMemory。最上面两行可以看见它有两个导出符号,NtReadVirtualMemory 导出序号 547 和 ZwReadVirtualMemory 导出序号 2193

然后我们来分析一下这个函数。首先将第一个参数(rcx)保存到 r10,因为 syscall 会使用 rcx 寄存器,然后将系统调用号 0x3F 存入 eax。接着 test 这里是检查是否使用快速系统调用,如果不使用快速系统调用就跳转到 int 2Eh,使用 int 2Eh 进入内核,否则就直接 syscall 进入内核。

具体解释一下。0x3F 是 NtReadVirtualMemory 的系统调用号。test byte ptr ds:7FFE0308h, 1这里,检查地址 0x7FFE0308 处的字节的最低位是否为 1。这个地址是 Windows 的 系统调用分发表(KiSystemCall64)的一部分,用于决定使用哪种方式进入内核模式。最低位为 0 就是 syscall,最低位为 1 就是 int 2Eh。syscall 是 x64 架构下的快速系统调用指令,int 2Eh 是传统的调用方法。

那么逻辑差不多就是这样:用户模式代码调用 ZwReadVirtualMemory,ZwReadVirtualMemory 准备系统调用号 0x3F 和参数,然后通过 syscall 或 int 2Eh 进入内核模式。内核模式的 KiSystemCall64 根据 eax 中的系统调用号 0x3F,调用对应的内核函数 NtReadVirtualMemory。内核函数执行完毕后,返回到用户模式的 ZwReadVirtualMemory,ZwReadVirtualMemory 再返回到调用者。

所以,真正读取进程内存的函数在 0 环实现,我们所用的函数只是系统提供给我们的函数接口。那么我们其实可以把 kernel32.dll 和 KernelBase.dll 都理解为一个分发器,或者说 Ring 3 到 Ring 0 的桥梁。

3 环进 0 环

我们看到,ReadProcessMemory 函数最终进入 0 环的方法是通过 int 2E 或者 syscall 快速系统调用,这里我们就来研究 3 环进 0 环。

在操作系统中,内存被划分为用户空间和内核空间,CPU 的运行状态也因此分为用户态和内核态。用户态下,CPU 只能访问用户空间的内存;而内核态下,CPU 可以访问整个内存空间,包括内核空间,并能够执行特权指令。因此,CPU 进入内核态意味着其运行状态从用户态切换到内核态,获得了更高的权限。

CPU 从内核态切换到用户态相对简单,因为内核态可以执行特权指令。然而,用户态无法直接执行特权指令,所以只能通过其它方式来切换。在保护模式的笔记里面我们有学到过可以通过中断和异常的方式切到内核态,但是这两种方法 CPU 都是被动地进入的内核态。还有一种由程序主动触发的方法就是自陷(Trap)。自陷是一种主动行为,通常用于实现系统调用。执行自陷指令后,CPU 会切换到内核态,并跳转到内核中预定义的处理程序。int 2Eh 和 syscall 都是经典的自陷指令。

从 3 环进 0 环需要这些寄存器的改变:

  1. CS 的权限由 3 变为 0,意味着需要新的 CS
  2. SS 与 CS 的权限永远一致,需要新的 SS
  3. 权限发生切换的时候,堆栈也一定会改变,需要新的 ESP
  4. 进 0 环后的代码位置,需要 EIP

test byte ptr ds:7FFE0308h, 1这里,检查地址 0x7FFE0308 处的字节的最低位是否为 1。这个地址是 Windows 的 系统调用分发表(KiSystemCall64)的一部分,用于决定使用哪种方式进入内核模式。最低位为 0 就是 syscall,最低位为 1 就是 int 2Eh。

我们系统是 64 位的,上述过程也是 64 位的东西。在 x86 中,检查的是地址 0x7FFE0300,这个地址其实是结构体_KUSER_SHARED_DATA的一个成员。Windows 在在 User 层和 KerNel 层分别定义了一个_KUSER_SHARED_DATA 结构区域,用于 User 层和 Kernel 层共享某些数据。它们使用同一段页,只是映射位置不同。虽然同一页,但 User 只读,Kernel 层可写。它们使用固定的地址值映射,_KUSER_SHARED_DATA 结构在User为 0x7FFE0000,在 Kernel 层为 0xFFDF0000。

+0x300 SystemCall : Uint4B。该成员保存着系统调用的函数入口,如果当前CPU支持快速调用,则存储着ntdll.dll!KiFastSystemCall()函数地址;如果不支持快速调用,则存储着ntdll.dll!KiIntSystemCall()函数地址。

int 0x2E 进 0 环

来看ntdll.dll!KiIntSystemCall()函数

  .text:77F070C0.text : 77F070C0                 public KiIntSystemCall.text : 77F070C0 KiIntSystemCall proc near; DATA XREF : .text : off_77EF61B8↑o.text : 77F070C0.text : 77F070C0 arg_4 = byte ptr  8.text : 77F070C0            // 之前调用该函数时 mov eax, 0x115, 向 eax 传入一个函数号.text : 77F070C0                 lea     edx, [esp + arg_4] // 当前参数的指针存储在 edx 中.text : 77F070C4                 int     2Eh; // 通过中断门的形式进入到内核中.text:77F070C4; DS:SI->counted CR - terminated command string.text : 77F070C6                 retn.text : 77F070C6 KiIntSystemCall endp

注意在执行 KiIntSystemCall 函数前,编号已被写入 eax。其在触发 int 2Eh 中断前用到两个寄存器,一个是内核中调用函数的函数号,另外一个就是传入参数的指针。

流程大概是这样:

在系统启动初期,Windows 会初始化 IDT,并将 int 2Eh 的中断服务例程注册为 KiSystemService 函数。因此,当用户模式代码执行 int 2Eh 指令时,CPU 会通过 IDT 找到 KiSystemService 函数,并将执行权交给它。

KiSystemService 位于内核空间,在切换执行权之前,CPU 会检查源位置(用户态代码段)和目标位置(内核态代码段)的权限,确保 CPL 正确。如果权限检查失败,CPU 会触发一般保护异常(#GP)。每个线程在内核态执行时都必须使用独立的内核栈,大小通常为 8KB 或 12KB。CPU 会将当前线程的 ESP 和上下文(EFLAGS、CS、EIP 等)保存到内核栈中,然后切换到内核栈。

KiSystemService 函数会根据系统调用号从系统服务分发表 SSDT 中查找对应的服务函数地址和参数描述,随后会将用户态传递的参数从用户栈复制到当前线程的内核栈中。准备完毕后,KiSystemService 调用内核中真正的服务函数来执行请求的操作。服务函数在内核态完成操作后,将结果返回给 KiSystemService。KiSystemService 将操作结果从内核栈复制回用户栈。最后,KiSystemService 通过 IRET 指令将执行权交回给用户模式的 ntdll.dll,继续执行 int 2Eh 后面的指令。

快速系统调用进 0 环

上面那坨流程看着就慢,有一堆内存访问和查表、检查权限的操作。所以就有了快速调用。

来看ntdll.dll!KiFastSystemCall()函数

  .text:77F070B0                 public KiFastSystemCall.text:77F070B0 KiFastSystemCall proc near              ; DATA XREF: .text:off_77EF61B8↑o.text:77F070B0            // 之前调用该函数时 mov eax, 0x115,向eax传入一个函数号.text:77F070B0                 mov     edx, esp // 将当前堆栈放入edx,用它来存储参数.text:77F070B2                 sysenter.text:77F070B2 KiFastSystemCall endp

这里的核心指令是 sysenter。在执行 sysenter 指令之前,操作系统必须指定 0 环的 CS 段、SS 段、EIP 以及 ESP。其中,CS 段、EIP 以及 ESP 来自 MSR 寄存器。这里列举三个 MSR 寄存器最重要的值。SS 不能直接从 MSR 寄存器获得,但 SS = IA32_SYSENTER_CS+8。

Windows 系统调用学习笔记 4

保存现场

首先了解一下 Trap Frame 结构体。

无论是通过中断门进入 0 环,还是通过快速调用进入 0 环,进入 0 环前(3 环)的所有寄存器都会存到这个结构体中。这个结构体本身处于 0 环,由 windows 操作系统进行维护。当程序通过中断门从 3 环进入 0 环时,ESP 指向 TrapFrame+0x64 的位置,当程序通过快速调用从 3 环进入 0 环时,ESP 指向 TrapFrame+0x78 的位置。

Windows 系统调用学习笔记 5

在保护模式下,最后四个成员(0x7C~0x88)并没有被使用,因此无需考虑;只有在虚拟 8086 模式下,才会用到。当中断门执行时,3 环的 SS、ESP、EFLAGS、CS、EIP 会被存储到结构体的 0x68~0x78 中,而执行快速调用时不会

上一节我们讲 int 2Eh 的流程的时候提到了一个叫内核栈的东西,Trap Frame 可以理解为内核栈的一个子集,就是专门用来保存寄存器的。

TrapFrame 结构体的其它成员通过 KiSystemService 和 KiFastCallEntry 进行赋值。不管是 KiSystemService 还是 KiFastCallEntry,最终都要执行一部分相同的代码,分为两个函数是因为进入 0 环时,堆栈里的值不一样,走同一条函数会出问题。

SST&SSDT

系统服务表(System Service Table,SST)共有两张,第一张表后紧接第二张表,里的函数都是来自内核文件导出的函数。它并不包含内核文件导出的所有函数,而是 3 环最常用的内核函数。系统服务表位于 _KTHREAD +00xE0

typedef struct _SERVICE_DESCRIPTOR_TABLE
{PULONG ServiceTableBase;			// 指针,指向函数地址,每个成员占4字节PULONG ServiceCounterTableBase;		// 当前系统服务表被调用的次数ULONG  NumberOfService;				// 服务函数的总数PUCHAR ParamTableBase;				// 服务函数的参数总长度,以字节为单位,每个成员占一个字节// 如:服务函数有两个参数,每个参数占四字节,那么对应参数总长度为8// 函数地址成员 与 参数总长度成员 一一对应
} SSDTEntry, *PSSDTEntry;

Windows 系统调用学习笔记 6

系统服务描述符表(System Services Descriptor Table,SSDT)的每个成员叫做系统服务表。SSDT 的第一个成员是导出的,声明一下即可使用,第二个成员是未导出的,需要通过其它方式查找。
在 Windows 中,SSDT 的第三个成员和第四个成员未被使用

总结

还算是比较好理解的。参考的两位师傅的 blog 有一些在 xp 环境下用 windbg 做的实验就只是看了一下,没有在这里写下来,但是感觉还是很有用的。后续打算先 2 周内把毕设速通一下,做到只剩个论文没写,然后准备一下中期答辩,之后就是看进程和线程的内容和驱动开发的东西以及玩怪猎荒野,再然后就是研究一下 cs2 的 external hack,看能做到什么程度吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/891147.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

作业一:自我介绍+软工五问

作业一这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/SoftwareEngineeringClassof2023这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/SoftwareEngineeringClassof2023/homework/13325这个作业的目标 学习使用github和博客园自我介绍、兴趣爱好 我叫梁鑫…

deepseek---官方API接入

最近deepseek又开放充值了,而且还大降价,果断接入: 1、首先就是去充值,然后获取key 2、打开接口文档-找到合适自己的语言接口 3、直接复制代码就能运行<?php$curl = curl_init();curl_setopt_array($curl, array(CURLOPT_URL => https://api.deepseek.com/chat/comp…

香港服务器选择指南:高防 vs 站群 vs GPU场景解析

一、引言 背景:香港作为亚太地区数据中心枢纽,凭借国际带宽、网络自由、法律完善等优势,成为企业全球化布局的首选节点。 目标:解析香港服务器在不同业务场景下的选择逻辑,帮助企业根据需求匹配高防、站群或GPU服务器。 二、香港服务器的核心优势 | 网络自由性:| 国际带…

大模型常见文件格式safetensors vs. gguf

safetensorsHeader: 文件的元数据(大小、版本) Meta data: 列表,每个元素表示文件里存的张量的类型、形状、偏移量 Tensor data: 列表元素对应的张量数据ggufgguf不依赖外部的配置文件,它可以把配置文件、词表、tokenizer、template等存入gguf中(如果一个模型有多个gguf文…

2025年最值得入手的CRM系统!这5款真的好用不踩坑

挑CRM真让人头大!有的太复杂,上手难 有的功能太少,啥都干不了 有的收费离谱,小团队根本用不起别担心!今天就给大家盘点5款2025年最值得入手的CRM系统,每一款都是真实用户口碑不错的,不管你是创业小团队,还是大公司,这里面肯定有一款适合你。 ​​ 一、简道云CRM 如果你…

web开发 辅助学习管理系统开发日记 day4

/**法法法!!!忘记保存发布了,写了一天的草稿直接没了,我晕死,今天补档昨天的吧。 */今天开始开发员工管理模块了,也得知了3月14有校招的消息,看来要加快进度了(╯▔皿▔)╯ Q1:今天调了一个时间最长的bug,在写分页查询的时候我将三层架构全部写完之后,运行程序下面报…

Web前端入门第3问:前端需要学习哪些技术?

Web前端开发技术学习路径基础知识必备 HTML+CSS+JavaScript ,就目前来看,这三板斧是入门前端开发的门槛,无论如何都是逃不掉了。进阶知识必须会一门主流的前端框架,比如:React/Vue/Angular/Svelte,就国内的从业环境来看,Vue占有绝对优势,后续也会写一些 vue 相关的文章…

Web前端入门第1问:英语是否很重要?有哪些前置条件?

HELLO,这里是大熊学习前端开发的入门笔记。 本系列笔记基于 windows 系统。 在入门之前,是否有这样的疑问: 程序员的英语是否很牛?毕竟程序员的代码像天书一样,比如这样:答案是否定的。 英语并不是编程的前置条件,不要被看似天书的代码吓到,程序代码都存在一定的语法结…

红外成像工具 非接触式热成像仪 高精度温度测量与多功能应用

红外成像工具 非接触式热成像仪 高精度温度测量与多功能应用IFD-x是一款非接触式热成像仪器,采用红外阵列高精度温度传感器和先进的软件算法。该设备能够对视场范围内的物体进行红外成像,成像分辨率达到512*384像素,温度灵敏度为0.1℃,绝对精度为1.5℃,刷新频率最高可达64…

Educational Codeforces Round 175 (Rated for Div. 2) 比赛记录

Educational Codeforces Round 175 (Rated for Div. 2) 比赛记录 比赛连接 手速场,上蓝场,但是有点唐,C 想错了写了半个多小时,想到正解不到 \(10\) 分钟就写出来了,看到 D 后悔没先做 D 了,过于简单了。 赛时切掉了 A - D,也算是成功渡劫上蓝了! 过题记录:A. FizzBuzz…

numpy知识点

1.点乘 .dot() 2.转置 .T 3.求逆矩阵np.linalg.inv() 4.拼接(返回变化后,但是并不对原来对象更改) np.concatenate((要拼接的ndarray对象),axis=按哪个维度拼接) axis=0 -->行增加 axis=1-->列增加 对于如果只有一个维度的ndarray,那么只能增加列,一直只有一行import…

销售必读!避免急躁,用这3个技巧轻松拿下客户

做销售,内心要急迫,但表面上不能着急。一旦着急,就会陷入被动,甚至在商业竞争中输得一败涂地。 不急于介绍产品,循序渐进 有些销售人员见到客户后,就像倒豆子一样,噼里啪啦地介绍产品功能、材质工艺,甚至是各种奇特设计,滔滔不绝。但他们从没想过,客户真的喜欢听这些…