protected mode-segment selector

Table of contents

CPU classification concept

Segment selector structure

GDT and GDTR

Find segment descriptor by segment selector

Quickly locate the offset of the segment descriptor in the GDT table


CPU classification concept

        The concept of CPU classification means that the CPU assigns different privilege levels to different programs and operating system processes. The privilege levels include 0~3, also known as "kernel mode" and "user mode". as the picture shows:

  • Privilege level 0 (Ring 0): also known as the kernel state, is the highest privilege level of the CPU, and only the operating system kernel and some device drivers can run under this privilege level. Under the privilege level 0, the program can access all resources of the system, including hardware, memory, etc., and can execute all CPU instructions.

  • Privilege level 1 (Ring 1) and privilege level 2 (Ring 2) exist, but Windows does not use them.

  • Privilege level 3 (Ring 3): Also known as user mode, it is the privilege level run by ordinary applications. Under these privilege levels, programs can only access authorized system resources and cannot execute some sensitive CPU instructions. Usually, the operating system will limit the permissions of user programs to ensure the security and stability of the system.

        The operating system kernel usually runs under R0 and can directly access the CPU, memory and other system resources, thereby realizing the control of the entire system. User programs run under R3 and can only access authorized resources, but cannot directly access system resources such as CPU and memory. When a user program needs to access system resources, it needs to request access permission from the operating system through system calls, etc., and the operating system will decide whether to authorize the access according to the user program's permissions and security policies.

        The classification concept of CPU is realized by the cooperation of operating system and CPU, which can protect the security and stability of the system and prevent malicious programs from causing damage to the system.

Segment selector structure

        As mentioned in the previous chapter, the segment selector is a 16-bit segment identifier, which is the visible part of the segment register. It does not point directly to a segment, but to the segment descriptor that defines the segment. A segment selector has the following structure:

        RPL: Request Privilege Level (RPL-Requestor Privilege Level) is used to determine the privilege level of the selector, that is, the privilege level used to indicate the code or data that requests access to the segment, and the privilege level in the segment descriptor (DPL- Descriptor Privilege Level) are used together to determine access rights. RPL is a 2-bit value that can take any one of 0, 1, 2, and 3, where 0 represents the highest privilege level and 3 represents the lowest privilege level.

        When the CPU executes an instruction to access memory, it will first obtain the corresponding segment descriptor through the segment selector, and then judge whether it has permission to access the segment according to the DPL in the segment descriptor and the RPL in the segment selector. Among them, DPL is the privilege level in the segment descriptor, indicating the access rights of the segment, and the value range is also 0, 1, 2, 3, where 0 represents the highest privilege level and 3 represents the lowest privilege level. RPL indicates the privilege level of the code or data currently accessing the segment. If the RPL is greater than the DPL (the privilege level of the RPL is lower than the privilege level of the DPL), the segment has no right to access, otherwise it can be accessed.

        If a code segment has a DPL of 0, when a code with a privilege level of 3 requests access to that segment, its RPL is 3, which is greater than the DPL of the segment descriptor, and thus does not have access to the segment.

        TI: Table Indicator is a bit in the segment selector, which is used to indicate whether the table pointed to by the segment selector is a GDT table (Global Descriptor Table, Global Descriptor Table) or an LDT table (Local Descriptor Table, Local Descriptor Table) . When TI is 0, the table pointed to by the selector is a GDT table; when TI is 1, the table pointed to by the selector is an LDT table. In practical applications, most operating systems use GDT tables to manage memory segments, and rarely use LDT tables.

        Index: is an index value used to indicate the position of the segment descriptor in the GDT table or LDT table. It is a 13-bit value, so the value range is between 0 and 8191.

        When the CPU executes an instruction to access memory, it will determine the table pointed to by the selector according to the segment index and TI bit in the segment selector, and then obtain the corresponding segment descriptor according to the segment index. The CPU will multiply the index value by 8 (the number of bytes in the segment descriptor), and then add the base address of the GDT or LDT (the base address is in the GDTR or LDTR register).

GDT and GDTR

        GDT Global Descriptor Table, full name Global Descriptor Table, is used to store various segment descriptors, including code segment, data segment, stack segment, etc. Each segment descriptor specifies the segment's base address, size, access rights, and other information. In protected mode, GDT is used to protect and isolate memory. It defines all available segments in the system and assigns a unique segment selector to each segment.

        GDTR, full name Global Descriptor Table Register, is a 48-bit register used to store the address and length of GDT. Specifically, the lower 16 bits of the GDTR register store the length limit of the GDT table, that is, the number of bytes occupied by the GDT. The upper 32 bits store the base address of the GDT table, that is, the starting address of the GDT in memory. In protected mode, the CPU uses the address stored in the GDTR register to find the GDT and fetch the required segment descriptor from it. Its structure is expressed in C language as follows:

struct gdtr {
    unsigned short limit;   // GDT的大小
    unsigned int base;      // GDT的起始地址
};

        Many people think that the GDTR register structure should be base first, and then limit, which means that the lower 32 bits store the base address of the GDT table, and the upper 16 bits store the length limit of the GDT table. But the author flipped through the Intel white paper and found the opposite, as shown in the figure:

        In the x86 architecture, the LGDT and SGDT instructions can be used to load and save the value in the GDTR register . The LGDT instruction is used to load the 48-bit GDT address and limit into the GDTR register, so that the CPU can access the GDT. The SGDT instruction is used to store the value in the GDTR register into a specified memory location so that the GDT can be restored when needed. 

Find segment descriptor by segment selector

  1. Use OD to attach any program in the virtual machine, and check the visible part of the segment register, which is the segment selector. Taking the ds segment as an example, the segment selector is 0x0023, as shown in the figure:

  2. Split 0x0023 according to the structure of the segment selector, and the following information can be obtained:
    0x0023 0000 0000 0010 0011 => 0 0000 0000 0100 0 11
    RPL 11 => 3 => Ring 3 level
    OF 0 => 0 => query GDT table
    Index 0 0000 0000 0100 => 4 => index is 4
  3. Use windbg to run the command to view the base address and length limit of the GDT table in the GDTR register:

  4. Access the GDT table and view the segment descriptor with index 4:

Quickly locate the offset of the segment descriptor in the GDT table

0x0023 >> 3 = 4 Remove the RPL and TI bits to get Index
4 * 8 = 0x20 Each segment descriptor occupies 8 bytes, so multiply by 8

The above operation can be transformed into the following form:

0x0023 >> 3 * 8  = 0x0023 >> 3 << 3  = 0x0023 & 0xFFF8

That is to say, you only need to use the last number of the segment selector to perform an AND operation with 8, and then splicing the previous number is the offset.

0x23: 3 & 8 = 0, the offset of the number 2 before the splicing is 0x20
0x2B: B & 8 = 8, the offset of the number 2 before the splicing is 0x28 0x28
: 8 & 8 = 8, the offset of the number 2 before the splicing is still 0x28 The segment selector just requests different permissions

Guess you like

Origin blog.csdn.net/weixin_43074760/article/details/131748771