标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2025-3492] 作者: ShYLie 发表于: [2025-01-20]
本文共 [9] 位读者顶过
保护模式下通过段划分内存的权限以及访问的权限。 x86 和 x64都有六个段寄存器(Segment Register):
DS: Data Segment 数据段 可读可写不可执行[出自:jiwo.org] 根据intel白皮书3a的介绍,CPU额外提供了三个数据段寄存器,可以作为程序额外的段,正常情况下可能只使用到DS、CS、SS这三个段寄存器。
数据段中一般为全局变量,而局部变量一般会放入堆栈段中:
// test.cpp : 定义控制台应用程序的入口点。
此时将value定义在main函数外,视为全局变量,可以看到,编译器会将该变量放在DS段中:
当将value定义为局部变量时:
// test.cpp : 定义控制台应用程序的入口点。
此时的段会识别成SS堆栈段:
段选择子(Segment Selector) 通过段选择子(Segment Selector)来确定段的属性,默认UserMode可见部分:
对于段的属性描述,通过3.4.2描述:
段选择子总共是16位,分成三部分进行解析:Table Indicator表示从LDT或者是GDT表中查找段描述符,Index则表明表的下标。
#include <Windows.h>
可以发现DS和SS的段选择子都是0x0023,而CS的段选择子是0x001B,通过解析可以得到都是通过GDT表进行查表,通过WinDBG中的r gdtr查询,r默认输出的是通用寄存器和段寄存器:
可以通过r后跟特定寄存器查询,这里gdtr不代表真的有gdtr这个寄存器,只是通过sdtr来存储gdt表的地址。
通过dq gdtr或者dq 0x80b98800查询:
段描述符(Segment Descriptor)
可见部分的段描述符为64位QWORD
0x0023的Index为4,所以其对应的段描述符为:00cff300 0000ffff
#include <Windows.h> #include <iostream> using namespace std; #define SEG_BASE_24_31 0xff000000 #define SEG_BASE_16_23 0xff0000 #define SEG_BASE_0_15 0xffff #define SEG_LIMIT_16_19 0x000f0000 #define SEG_LIMIT_0_15 0xffff #define SEG_G 0x00800000 #define SEG_D_B 0x00400000 #define SEG_L 0x00200000 #define SEG_AVL 0x00100000 #define SEG_P 0x00008000 #define SEG_DPL 0x00006000 #define SEG_S 0x00001000 #define SEG_TYPE 0x00000f00 // Descriptor Type #define SYSTEM_TYPE 0 #define CODE_DATA_TYPE 1 // Default operation size #define BIT_16 0 #define BIT_32 1 VOID analysisSegmentDescriptorOthers(DWORD descriptor) { DWORD seg_G = (descriptor & SEG_G) >> 0x17; DWORD seg_D_B = (descriptor & SEG_D_B) >> 0x16; DWORD seg_L = (descriptor & SEG_L) >> 0x15; DWORD seg_AVL = (descriptor & SEG_AVL) >> 0x14; DWORD seg_P = (descriptor & SEG_P) >> 0xf; DWORD seg_DPL = (descriptor & SEG_DPL) >> 0x0d; DWORD seg_S = (descriptor & SEG_S) >> 0x0c; DWORD seg_TYPE = (descriptor & SEG_TYPE) >> 0x08; cout << "Granularity: " << hex << seg_G << endl; cout << "Default operation size: "; seg_D_B == BIT_32 ? cout << "32-bit segment" : cout << "16-bit segment"; cout << endl; cout << "64-bit code segment: " << hex << seg_L << endl; cout << "Available for use by system software: " << hex << seg_AVL << endl; cout << "Segment present: " << hex << seg_P << endl; cout << "Descriptor privilege level: " << hex << seg_DPL << endl; cout << "Descriptor type: "; seg_S == SYSTEM_TYPE ? cout << "system" : cout << "code or data"; cout << endl; cout << "Segment type: " << hex << seg_TYPE << endl; } VOID analysisSegmentDescriptor(DWORD64 descriptor) { DWORD base = ((descriptor >> 0x10) & SEG_BASE_0_15) ^ ((descriptor >> 0x10) & SEG_BASE_16_23 ) ^ ((descriptor >> 0x20) & SEG_BASE_24_31 ); DWORD limit = (descriptor & SEG_LIMIT_0_15) ^ ((descriptor >> 0x20) & SEG_LIMIT_16_19); cout << "Segment Limit: " << hex << limit << endl; cout << "Segment base address: " << hex << base << endl; analysisSegmentDescriptorOthers(descriptor >> 0x20); } int main() { // 0x0023 // 0000 0000 0010 0011 WORD selector = 0x0023; analysisSegmentSelector(selector); DWORD64 descriptor = 0x00cff3000000ffff; analysisSegmentDescriptor(descriptor); cout << "-------------------------------------------------" << endl; // 0x001B // 0000 0000 0001 1011 selector = 0x001B; analysisSegmentSelector(selector); descriptor = 0x00cffb000000ffff; analysisSegmentDescriptor(descriptor); return 0; } 关于段类型(Segment Type) 在3.4.5.1 Code- and Data-Segment Descriptor Types中的表Table 3-1. Code- and Data-Segment Types:
最高位区分数据或代码段:0-Data,1-Code,低三位分别表示EWA和CRA,对比前面获取到的CS和DS的段描述符,也就是在Type的位置存在区别,DS的Type为0b0011,CS的Type为0b1011/ segmentType & 0x8 ? cout << "Code " : cout << "Data "; cout << endl; cout << "Description: "; if (segmentType & 0x8) { cout << "Execute "; if (segmentType & 0x4) cout << "Conforming "; if (segmentType & 0x2) cout << "Read "; if (segmentType & 0x1) cout << "Accessed "; } else { cout << "Read "; if (segmentType & 0x4) cout << "Expand-down "; if (segmentType & 0x2) cout << "Write "; if (segmentType & 0x1) cout << "Accessed "; } cout << endl; }
![]()
这里Accessed的含义是是否已经使用过该段,例如push esp,此时会用到堆栈区域,该区域通过SS段选择子指向的段描述符进行管理,那么此时,即使将Accessed置0,内核也会将其设为1,表示该段在最后一次清零前被使用过。 验证P段的有效性,首先找到GDT表中全0的部分:Index=9的部分,此时段选择子为0x004b。 ![]()
eq复制段选择子0x0023的部分到该位置下:
此时0x004b指向的段为有效段,内联asm将ds修改为0x004b::
mov ax, 0x4b;
P位指明段是否有效:
通过eq将P位设为0:
此时执行直接就报错了,再将P设置为1: ![]()
此时不重新编译程序,直接运行:
G Flag G Flag决定limit的基础单位,若G = 0,limit的单位为byte,若G=1,limit的单位为一个页,那么此时对于:00cf7300 0000ffff,limit的值为0xfffff,一个页的大小为4096字节=4KB=0x1000 Bytes,那么此时该段的最大空间为(0xFFFFF+1) * 0x1000,这里的+1是因为0xfffff是最大索引,而不是最大长度,索引是从0开始的,所以其空间总共为0xFFFFF + 1也就是0x100000。
Determines the scaling of the segment limit field. When the granularity flag is clear, the segment D/B Flag 该标志位,取决于当前段的类型,会有不同的效果:
•Executable code segment: The flag is called the D flag and it indicates the default length for effective addresses and operands referenced by instructions in the segment. If the flag is set, 32-bit addresses and 32-bit or 8-bit operands are assumed; if it is clear, 16-bit addresses and 16-bit or 8-bit operands are assumed.The instruction prefix 66H can be used to select an operand size other than the default, and the prefix 67H can be used select an address size other than the default.
代码段下:
决定堆栈的寻址空间是16bit还是32bit
尝试修改cs:
发现是不存在mov cs, ax这样的指令的,那么眼前只能通过jmp Far来实现CS的修改:jmp far 0x004B:0x00401F1C:
此时由于0x004B的段描述符为空所以当想跨段跳转时会找不到段的信息,所以会进异常:
复制0x001B的段描述符到0x004B:
eq 807d3840+8 00cffb00`0000ffff
重新EA跨段跳转,成功将CS从0x001B改为0x004B:
注意这里修改CS之所以不会产生内核崩溃,是因为这里的CS只是对于当前进程而言,当进入内核时,会将这些段选择子的值都修复,从ring3进到ring0时也会有个上下文的转换,所以不会产生内核崩溃。
当D/B设置为0,由于此时CS是0x004B,所以指向的是修改后的段描述符,通过eq已将D/B位设置为0,
此时重新执行push eax,也只会压入2字节的栈,栈的寻址空间从32bit变为16bit
此时的esp从0x12FF8C到0x12FF8A:
在代码段是D/B位看的是D位,当该段为数据段时,那么D/B位看的是B位。
将0x004b指向的段描述符修改为:0x008ff3000000ffff
mov ax, 0x4b mov ss, ax
那么0x001b就是CS的值,先压入CS,再压入返回地址:
但是实际上也是只执行了ret,所以CS的值没有变化,仍旧是修改后的0x004B:
而由于只做了一次ret,所以esp的值指向了压入的CS的值,没有回到原来的地方。
那么此时直接运行程序,直接报ESP错误了,此时就是堆栈不平衡导致的后续程序运行出错:
既然程序默认会将0x1b压栈,就说明后续会做返回,那么此时提供了一个retf用来做段返回:
void _declspec(naked) retSegment() {
◆总结
段的有效性:
P
P = 1:
段有效
P = 0:
段无效
段的数据类型:
S
S = 1:
用户段
S = 0:
系统段
段的具体类型:
Type
Type > 7 : 代码段
Type <=7 : 数据段
S = 1
S = 0 |