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.
field | content |
---|---|
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 1001 0100 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