System call interface
===========================
С смотрел на снег весь день ... падающий ...
Всегда вниз. Падающий весь день.
И тогда я закричал "это жизнь?"
(c) by my dream bride
The system call is an interface that threaded by the user mode to the kernel mode. Naturally, if the system's security and reliability, the implementation mechanism of research system calls is very beneficial. The system-service implementation error is the system security vulnerability, because the thread in any user mode can use this error to access the kernel mode.
Therefore, the thread in user mode needs to call the system service and transfer to the kernel mode. The call to the system service is performed by interrupting 2 eh. User Mode Module NTDLL.DLL will call many functions in the steering kernel. For example, the code form of the export function NTQuerySection is as follows:
7f67cdc public _ntQuerysection @ 20
7F67CDC _NTQUERYSECTION @ 20 Proc Near
7F67CDC
7f67cdc arg_0 = byte PTR 4
7f67cdc
7F67CDC MOV EAX, 77H; NTQuerysection
7F67CE1 Lea EDX, [ESP ARG_0]
7F67CE5 INT 2EH
7F67CE7 RETN 14H
7f67ce7 _ntQuerysection @ 20 ENDP
In fact, all other calls to kernel services in NTDLL.DLL are like this. As can be seen from the code, when the interrupt is interrupt 2EH, the EAX register is the function number of the service, the EDX register is the address of the parameters in the stack. Now let's see part of the code of the _KISystemService (interrupt 2EH handler) in ntoskrnl.exe. Interesting is the following paragraph:
[Skipped]
8013cb20 _kiendunexpectedRange Proc Near
8013CB20 CMP ECX, 10h; if Call to Win32k.sys
8013CB23 JNZ Short Kss_LimitError
8013CB25 PUSH EDX
8013CB26 PUSH EBX
8013cb27 call _psconverttoguithread @ 0
8013cb2c or eax, EAX
8013CB2E POP EAX
8013CB2F POP EDX
[Skipped]
8013CBD0; S u b R o U t I n e
8013CBD0
8013cbd0 public _kiSystemService
8013CBD0 _KISYSTEMSERVICE PROC NEAR; DATA XREF: INIT: 801C7A50 O
[Skipped] 8013CBD8 MOV EBX, 30H
8013CBDD DB 66H
8013CBDD MOV FS, BX; Set FS To 30 (Processor Contol Region)
8013cbe0 push dword Ptr DS: 0FFDFF000H
8013cbe6 MOV DWORD PTR DS: 0FFDFFFFH, 0FFFFFFFH
8013CBF0 MOV ESI, DS: 0FFDFF124H; Current Kernel Thread Pointer
8013CBF6 Push DWORD PTR [ESI 137H]; Previous Mode: kernel / user
[Skipped]
8013cc29 _kivalstemServiceRepeat:
8013cc29 MOV EDI, EAX; Function Number
8013cc2b SHR EDI, 8
8013cc2e and edi, 30h
8013cc31 MOV ECX, EDI
8013cc33 Add Edi, [ESI 0DCH]; Got Service Tables Address
8013cc39 MOV EBX, EAX
8013cc3b and Eax, 0FFFH
8013cc40 CMP EAX, [EDI 8]; Num of Services
8013cc43 jnb _kiendynexpectedRange
[Skipped]
8013CC6E MOV ESI, EDX; Parameters Addres
8013cc70 MOV EBX, [EDI 0CH]; Table with Sizes
8013cc73 xor ECX, ECX
8013cc75 MOV CL, [EAX EBX]; SIZE OF Parameters
8013cc78 MOV EDI, [EDI]; HANDLER's TABLE
8013cc7a MOV EBX, [EDI EAX * 4]; Got Function Address
8013CC7D SUB ESP, ECX; Clear Stack
8013cc7f SHR ECX, 2
8013cc82 MOV EDI, ESP
8013cc84 CMP ESI, DS: _MMUSERPROBEADDRESS; 7FFF0000
8013cc8a jnb kss80
8013cc90 KisystemServiceCopyarguments:
8013cc90 repe movsd; Copy To Ring0 Stack
8013cc92 kssdoit:
8013cc92 Call EBX
8013cc94 KSS60:
8013cc94 MOV ESP, EBP
8013cc96 KSS70:
8013cc96 MOV ECX, DS: 0FFDFF124H
8013cc9c MOV EDX, [EBP 3CH]
8013cc9f MOV [ECX 128H], EDX
8013cc9f _kiSystemService Endp8013cc9f
8013cca5 _kiserviceexit proc Near
[Skipped]
8013 CE34 KSS80:
8013 CE34 TEST BYTE PTR [EBP 6CH], 1; KERNEL / User
8013 Ce38 JZ KisystemServiceCopyarguments
8013CE3E MOV EAX, 0C0000005H; Status_Access_violation
8013 CE43 JMP KSS60
Thus, if the call is generated in RING0, the handler checks whether the parameter is located in the user address area (see 8013cc84). After that, the handler check is passed to the parameter (copy the parameters to the Ring0 stack, starting with the label KisystemServiceCopyarguments). If there is no error, CALL EBX is performed in advance from the address selected from the service address table. Then, noticed two interesting places. The first is that all core threads can acquire the address of the service address table (refer to the code at 8013cbf0 and 8013cc33). The second interesting place is that the service table can have four (for each thread). The call to the code _KISystemServiceRepeAT code depends on the value of bit 0x3000, and selects one from four descriptors of the data table address. The descriptor occupies 16 bytes and continuously. These four descriptors generally referred to the service descriptor table. For each thread, there is a pointer to the service descriptor table in the kernel thread structure. This pointer can be taken from the 0DCH offset of the thread structure (Windows NT 4.0). The address of the thread structure can be obtained from the offset 124h of PCRB in the kernel mode. (MOV EAX, FS: [124H]). Each thread has a pointer to the service descriptor table, in fact, pointers in all threads point to one of two descriptors. These two tables are located in Ntoskernel.exe, namely KeserviceDescriptable, and KeserviceDescriptAbleshadow. The format of the descriptor in the table is as follows:
Typedef struct _serviceDescriptor {
DWORD * ServiceTable; / * Pointer to the service address table * /
DWORD RESERVED; / * Using * / in Checked Build
DWORD serviceLIMIT; / * Number of services in the table * /
Byte * argumenttable; / * Pointer to the parameter table size in the service stack * /
/ * Actually equal (4 * parameter number) * /
ServiceDescriptor;
When system initialization (kiinitsystem), the descriptor 0 of Table KeserviceDescriptorTable and KeserVicedescriptorshadow is initialized to the following (pseudo code):
KeserviceDescriptable [0] .serviceetable = kiserventetable;
KeserviceDescriptable [0] .servicelimit = kiserviceLimit;
KeserviceDescriptable [0] .AgumentTable = KiargumentTable;
Memcpy (& KeserviceDescriptAbleshadow [0], & KeserviceDescriptable [0], 0x10); the rest of the descriptor is 0. KiserventAble is the offset table of the function in ntoskrnl.exe. KiargumentTable multiplied 4 (the size of the parameter stack Frame). KiserviceLimit is the number of services in the Kiserventetable table. The descriptor 0 of the KeserviceDescriptAbleshaDOW table is a copy of the created descriptor. Therefore, the descriptor 0 is filled in the end of the kernel, and the basic service of the kernel is described. This descriptor is the same for all threads. What is the rest of the descriptor? The kernel function keaddsystemServentable is called when driving Win32k.sys initialization. Its pseudo code is as follows:
Bool KeaddsystemServentAble
PVOID * ServiceTable,
Ulong reserved,
Ulong Limit,
Byte * arguments,
Ulong Numofdesc)
{
IF (NumofDesc> 3) Return 0;
Descriptor = & kerviceDescriptorTable [NumofDesc * 16];
IF (Descriptor-> ServiceTable) Return 0;
ShadowDescriptor = & keserviceDescriptAbleshadow [NumofDesc * 16];
IF (shadowdescriptor-> service) return 0;
Shadowdescriptor-> service;
Shadowdescriptor-> reserved = reserved;
Shadowdescriptor-> service;
Shadowdescriptor-> argumenttable = arguments;
IF (NumofDesc! = 1) {
Descriptor-> service = serviceable;
Descriptor-> reserved = reserved;
Descriptor-> ServiceLimit = Limit;
Descriptor-> argumenttable = arguments;
}
Return 1;
}
The function is simple, but many information can be obtained. This function fills one of the four descriptors. In general, it may populate the shadow table, and it is also possible to fill the primary table (if the descriptor is 0, it is not used). But there is a special place very interesting - if a descriptor 1 is added, the descriptor will only be added to the Shadow Table. When you initialize Win32k.sys, you just add a descriptor 1. At this point, the remaining descriptors are not used. We know that in order to improve efficiency, User and GDI functions of Win32 subsystems are implemented in the kernel in Windows NT V4.0. Win32k is a kernel mode driver that implements Win32 functions, and the descriptor 1 describes these services. Now, let's take a look, these tables provide anything for threads. The function keinitializetHread has two lines:
80119344 MOV ESI, [EBP LPTHREAD]
[Skipped]
80119394 MOV DWORD PTR [ESI 0DCH], OFFSET _KESERVICEDESCRIPTORTABLE There are two rows, but in the PSCONVERTTOGUITHREAD function:
80192919 MOV ECX, [EBP LPSERVICEDESCRIPTORTABLE]; Thread Struct 0DCH
[Skipped]
80192926 MOV DWORD PTR [ECX], Offset _KServicedescriptAbleshadow
If you call a Win32k.sys service, but the descriptor 1 for the current thread is not initialized, the function PSConvertTOGUITHREAD is called in the 2E interrupt handler. The service descriptor table has two - primary tables and shadow tables. There is only one non-zero descriptor offset from 0 in the primary table, which describes the basic system service. In addition to the descriptor 0 in the shadow table, there is a descriptor 1 of Win32k.sys initialization, which describes GDI and USER services. For the GUI thread, the offset 0DCH of the thread structure is the address of the shadow table, for other threads, the address of the home table. If the thread requests Win32k.sys service, it is to be a GUI thread. After studying the structure of the service table and the use of the descriptor 1, it can be seen that the Win32 subsystem is very close to the kernel. The particularity of the descriptor 1 is that it is embedded in the kernel code. The KeaddSystemServentAble function is an unapproved function, which is very simple and can be called in the new service driver. We noticed that IIS used two descriptors. So it is best to add your own services in the third descriptor.
The special of Windows NT calls is a large number of pointers in user mode. Almost every kernel function begins with the correctness of the correctness of the pointer area parameters. Therefore, all user address spaces are coincident with kernel space, and while working in user mode, the kernel is isolated from page protection, while ink kernel mode, incorrect user pointers can address to the kernel area. If you look at the boundaries of selectors 10 and 23, you can see them are the same (0xfffffffff). The boundaries of the selector 23 (selector of the user mode) should be equal to the kernel spatial start address minus 1 (0x7ffffff). For example, it is this in Linux. If you try to modify this limit value in the debugger, Windows NT will come out of BSOD. Why is this this? The answer is incredible: When the thread is executed in the kernel, the selection sub-23 is used. On the one hand, it is very convenient - driveing using a user pointer like using a normal pointer. On the other hand, this will also cause potential errors. I have already said that under Linux, the user and kernel space do not worg up. When using the user pointer in the kernel, you need to call a function such as a COPY_FROM_USER () (for I386, these functions are only some regular copies from different segments). Code). This incapacity is forcing kernel procedures to control and minimizes the use of user pointers.
The space overlapping the Windows NT has caused many errors in the initial version of the system. These errors are very concealed - to know that Win32 often uses services, which requires the correct parameters to the kernel.
Changes in system call interfaces in Windows 2K kernel
============================================================================================================================================================================================================= ========= // The main content here is in an article I have seen. :( I didn't want to plagiarize the work of others, but I really forgot who this is the article and where to see it.
The core of Windows 2K can transfer to kernel mode via the Sysenter / Sysexit directive in addition to the interrupt 2EH system call interface. These instructions are only in the Pentium II processor. The SYSENTER's handler is located in the KisystemService in the kernel and calls KifastCallenTry. The look of the KifastCallenTry begins as follows:
MOV ESP, SS: [0xffdff040]
MOV ESP, [ESP 4]; Set Ring-0 Stack
Push 0x23; Simulated Ring3 Stack
Push EDX; pointer to Ring3 stack parameters
Sub DWORD PTR [ESP], 4; in the stack of Ring3
Pushfd
OR DWORD PTR [ESP], 0x200; Simulated Ring3 flag register
Push 0x1b; Ring3 CS selector
Push ECX; RING3 EIP
;.. Fill in KetrapFrame
; .. The following sections are the same as the system call.
Obviously, for the same part as the system call, the handler fully transparently implemented the system call - the above code simulates the stack when the call is interrupted. In addition, you can now use the FAST System Call mechanism to perform system calls.
MOV EAX, NTCALLCODE; system call number
Lea Edx, [ESP 4]; parameters in the stack
Lea ECX, SYSEXIT_POINT; return point
Sysenter
SYSEXIT_POINT:
All of this seems to be called from NTDLL.DLL by interrupt 2E. Other interfaces are similar to this. The last part of the system call is as follows:
Test KefeatureBits, 0x1000; Support for Fast System Call
JZ RETURNFROMINTERRUPT; non - IRet
TEST DWORD PTR [ESP 4], 1; Call from Ring3?
JZ RETURNFROMINTERRUPT; non - IRet
TEST DWORD PTR [ESP 8], 0x20000; Call from V86?
Jnz returnfrominterrupt; is - IRet
POP EDX; Returned EIP
Add ESP, 8; Recycling Simulation Interrupt Stack
POP ECX; RING3 ESP 3
STI
Sysexit
ReturnFromInterrupt:
IRETD
This - kernel supports two system call interfaces. However, NTDLL.DLL is the same as Windows NT 4.0, which includes encapsulation for system calls. In this way, Windows NT cannot use the interface in the FAST System Call. It seems that the next version of NTDLL.DLL will contain two interfaces. Or provide two different NTDLL.DLLs before and after PII. -------------------------------------------------- -------------------------
(c) GLOOMY AKA Peter Kosyh, Melancholy Coding'2001
http://gLoomy.cjb.net
Mailto: GL00MY@mail.ru