Windows kernel basics-5-call gate (32 bit call gate)

Windows kernel basics-5-call gate (32 bit call gate)

A key function of a call gate is to raise rights. A call gate is actually a segment.

Call gate:

 

 

This is the structure of the segment descriptor. The s field inside is used to mark whether it is a code segment, a data segment or a system segment. The previous analysis talked about the case of S==1, that is, the case of Code or data. This time, the call gate is the case when s==0.

When s==0, the content of type is as follows:

 

 

Then the call gate is actually a segment register when the type is 12.

32-Bit Call Gate. A call gate in 32-bit case.

It is a segment under a segment descriptor with S = = 0 & & type = = 12.

Segment descriptor of calling gate:

 

 

Using call gate to lift weight

We can add a call gate by constructing segment selector and segment descriptor, that is, add a segment for our own use.

Because a segment register simply has a starting address and can be used as the content of a segment. Those who have written code in assembly must know that if a segment is constructed:

sample PROC
    ret
sample ENDP

 

Then we can construct a system segment to execute our code. The segment register is only the default of the system, which is convenient for us to use. Isn't it finished if we jump to use it through segment:eip.

Segment registers, such as ss (stack segment), cannot integrate the contents of stack segments through segment selectors and segment descriptors.

So here we add our own segment directly, which is the so-called call gate here.

Resolve call gate descriptor:

We need to configure a caller by ourselves. We certainly need to know how to configure the segment selector. Here we also need to know the segment descriptor:

 

 

This is quite different from the segment descriptor of the previous segment register, except for some differences.

Like the previous segment descriptor, the upper is the high 32-bit address and the lower is the low 32-bit address.

fieldcontent
offset in segment The address of the function to jump, or the address to jump
segment selector Segment selector, the segment selector to be changed (the key to raising the right)
Param Count Number of function parameters
High 5-7 Fixed three zeros
Type The system segment can only be 1100 (decimal 12)
High 12 address It is the S field of the segment descriptor. The system call must be 0
DPL It must be assigned to 3 so that ring3 can.
p Like the segment descriptor, it indicates whether the segment is valid. It is invalid when P is 0 and valid when 1.

Construction segment descriptor:

The segment selector segment descriptor field can be added through WinDbg to view the value of the cs segment register during Windows dual machine debugging, because the system is starting at this time, which must be in the case of r0:

 

 

Therefore, the segment selector here is constructed as: 0008 (because the segment selector is two bytes and 16 bits)

Then, according to the previous analysis, our current structure is as follows:

High 32 bits:
    0-3:
    0(hexadecimal) 
    4-7:
    0(hexadecimal)
    8-11:
     C(Hex)
    12-15:
     E(hexadecimal)
     16-31: 
        High address of function address:xxxx
Low 32:
    0-15:
        Low address of function address: xxxx
    16-31:
        0008
Collection:
    xxxxEC000008xxxx

 

At present, it is the function address that needs to be solved. This function address is actually the starting address of our code, and then deal with the content of this function.

We can write a function, but we need to change it to a fixed base address so that the function address will not change. The release version needs to be adopted, because the debug version has a jmp, and then the optimization and random base address have to be modified:

 

 

 

 

 

#include<iostream>
#include<Windows.h>
using namespace std;
​
void _declspec(naked) test()
{
    _asm
    {
        //Here we access an address that can only be accessed by 0 ring
        //In this way, you can know whether you have obtained the permission of 0 ring
        push eax
        mov eax,0x80b95040
        mov eax,[eax]
        pop eax
        ret
    }
}
​
int main()
{
    printf("%x\n", test);//Address of the output function
    system("pause");
    return 0;
}

 

Here my function address is 00401080.

Therefore, the complete segment descriptor value is:

    0040EC0000081080

 

Add segment descriptor to gdt table:

 

 

This section describes the gdt table at offset 9.

Use call gate

We have already configured the segment descriptors of the previous callers.

Now you need to use the call gate. You need to learn two instructions:

call far    ;Cross segment call long call
jmp far     ;Long jump

 

The call gate can only use call far, but jmp far cannot, because jmp far cannot skip the level.

Then there is a segment selector:

 

 

RPL:
    00//Use 0 ring
TI:
    0
Index
    10010100 1011

 

The final segment selector is 0x48

The address of call far should be 0x48:xxxx

However, it cannot be written like this in the inline assembly. It can only be written like this:

    BYTE code[] = {0,0,0,0,0x48,0};
    //0,0,0,0 yes eip,Then 0 x0048 It is a segment selector and adopts the small end byte order
    //0000 is used here because it will not be used here
    //Because the address of the function has been specified in the segment descriptor, it will jump automatically, so you can write anything
    _asm
    {
        call far fwrod ptr code
    }

 

Full code:

#include<iostream>
#include<Windows.h>
using namespace std;
​
void _declspec(naked) test()
{
    _asm
    {
        //Here we access an address that can only be accessed by 0 ring
        //In this way, you can know whether you have obtained the permission of 0 ring
        push eax
        mov eax,0x80b95040
        mov eax,[eax]
        pop eax
        ret
    }
}
​
int main()
{
    //Jump
    BYTE code[] = {0,0,0,0,0x48,0};
    //Segment selection sub is 0000
    _asm
    {
        call far fwrod ptr code
    }
    system("pause");
    return 0;
}

 

Then take it to the virtual machine and run it:

 

 

 

 

There is a direct system error. WinDbg captures and displays the blue screen, but at least one thing is certain, that is, we must have run to the kernel to execute. Otherwise, how can it lead to system errors? The application layer code of r3 will certainly not lead to system problems.

It's normal for problems to occur. Hit several breakpoints and observe:

#include<iostream>
#include<Windows.h>
using namespace std;
​
void _declspec(naked) test()
{
    _asm
    {
        //Here we access an address that can only be accessed by 0 ring
        //In this way, you can know whether you have obtained the permission of 0 ring
        int 3
        push eax
        mov eax, 0x80b95040
        mov eax, [eax]
        pop eax
        ret
    }
}
​
int main()
{
    //Jump
    BYTE code[6] = {0,0,0,0,0x48,0};
    //Segment selection is 0 x0048
    __asm
    {
        call far fword ptr code
    }
    printf("%x\n", test);
    system("pause");
    return 0;
}

 

 

 

Through one-step debugging of assembly code, it is found that the reason for this ret, call far or jmp far, needs to use retf.

Change to retf:

#include<iostream>
#include<Windows.h>
using namespace std;
​
void _declspec(naked) test()
{
    _asm
    {
        //Here we access an address that can only be accessed by 0 ring
        //In this way, you can know whether you have obtained the permission of 0 ring
        int 3
        push eax
        mov eax, 0x80b95040
        mov eax, [eax]
        pop eax
        retf
    }
}
​
int main()
{
    //Jump
    BYTE code[6] = {0,0,0,0,0x48,0};
    //Segment selection is 0 x0048
    __asm
    {
        call far fword ptr code
    }
    printf("%x\n", test);
    system("pause");
    return 0;
}

 

After that, the code in our function is executed normally, but it is still a blue screen. Through my observation, it is a problem of segment register. Here, we can judge this problem by looking at the problems before and after entering through the observation of od.

So I don't want this int 3 breakpoint directly here. Maybe the execution of int 3 breakpoints under r0 and r3 is different. Put:

#include<iostream>
#include<Windows.h>
using namespace std;
​
void _declspec(naked) test()
{
    _asm
    {
        //Here we access an address that can only be accessed by 0 ring
        //In this way, you can know whether you have obtained the permission of 0 ring
        push eax
        mov eax, 0x80b93040
        mov eax, [eax]
        pop eax
        retf
    }
}
​
int main()
{
    //Jump
    BYTE code[6] = {0,0,0,0,0x48,0};
    //Segment selection is 0 x0048
    __asm
    {
        call far fword ptr code
    }
    printf("%x\n", test);
    system("pause");
    return 0;
}

 

In this way, our program can be executed successfully:

 

 

Summary

Segment is a very important concept used for memory segmentation. A lot of information about a segment description is stored in the segment descriptor. Because there are many segments, there is a segment descriptor table. Then, a segment selector points to which segment descriptor in the table is obtained, and the segment register is a register used by the CPU for convenience, What is used to save is the segment selector. So here we use a system call segment to execute our instructions, which is allowed by the internal operation of the operating system and the content of intel Architecture. It is more complex. Here, we can understand that this is enough. If you want to go deep, you can read the references behind this blog.

 

 

 

reference: (3 messages) segment selector segment register farmwang column - CSDN blog segment register

reference: (3 messages) segment selector tasty CSDN blog segment selector

 

Posted on Mon, 01 Nov 2021 14:23:32 -0400 by thebusinesslad