Core Rootkit Technology-use nt! _ MDL (memory descriptor linked list) breaks through the SSDT (System Service Descriptor Table) read-only access restriction Part I, _ mdlssdt

Source: Internet
Author: User
Tags microsoft c ssdt

Core Rootkit Technology-use nt! _ MDL (memory descriptor linked list) breaks through the SSDT (System Service Descriptor Table) read-only access restriction Part I, _ mdlssdt

--------------------------------------------------------

 

A basic requirement for rootkit and malware development is to hook the system service Descriptor Table (SSDT) of the Windows Kernel

 

Replace specific system service functions with our own malicious routines. Of course, to ensure the normal operation of the system, we need to use a function pointer to save the original

System Service, and the function pointer is called in the logic of our malicious routine, and then hook, otherwise the corrupted kernel code and data structure will cause

A BugCheck (commonly known as a blue screen ).

 

 

 

Although 64-bit Windows introduces technologies like PatchGuard, it monitors critical kernel data in real time, including but not limited to SSDT, IDT, GDT... Wait,

 

It ensures its integrity. However, it is common to modify SSDT on 32 systems. Therefore, this article introduces this.

OS generally sets SSDT as read-only access in the system initialization phase, which is also designed to avoid accidental changes to the driver and other kernel components. Therefore, our first task is

Is to try to bypass this read-only attribute.

 

 

 

Before that, review several data structures related to SSDT and explain the process of locating SSDT.

 

 

We know that in the _ KTHREAD structure of each thread, a wildcard pointer (PVOID or void *) called ServiceTable is at the offset of 0xbc bytes *),

This field points to a global data structure called KeServiceDescriptorTable, Which is SSDT. The first field in SSDT is a pointer, pointing

The global data structure is KiServiceTable, which is an array in which each member is a function pointer and holds the corresponding system service routine entry address.

Sometimes, some concepts used to describe the kernel in words are too abstract and word-poor. Let's take a look. It vividly shows the above relationship:

 

 

 

 

 

Based on our ideas, we first try to get the _ KTHREAD structure of the current running thread, and then we can gradually locate the KiServiceTable, which is our final

Hook object!

Since ServiceTable is a pointer, it holds the address of the other pointer KeServiceDescriptorTable

(That is, "pointer to pointer", I will not distinguish "hold" from "point to" in the future), while KiServiceTable is a function pointer array;

In the Rootkit source code, they can be represented by three global variables (declared outside the driver's entry point DriverEntry (). For example, I used

The variable name of "self-Comments" is easy to understand. Besides, I keep the asterisks after the reserved words of the type to avoid confusion with the "uncited" Operation (So asterisks are an overloaded operation.

Character ):

--------------------------------------------------------

 

For kernel-mode driver developers, it is obviously not easy to implement a routine to obtain the _ KTHREAD structure of the current running thread. Fortunately, the document

The converted PsGetCurrentThread () routine can complete this task.

(In fact, the disassembly code of PsGetCurrentThread () Just shows that this is very simple. The following code only

Just move the content at the fs: [00000124 h] address to the eax register as the return value, and the logic of the KeGetCurrentThread () is similar to it! )

 

 1 kd> u PsGetCurrentThread 2  3 nt!PsGetCurrentThread: 4 83c6cd19 64a124010000    mov     eax,dword ptr fs:[00000124h] 5 83c6cd1f c3              ret 6 83c6cd20 90              nop 7 83c6cd21 90              nop 8 83c6cd22 90              nop 9 83c6cd23 90              nop10 83c6cd24 90              nop11 nt!KeReadStateMutant:12 83c6cd25 8bff            mov     edi,edi13 14 15 kd> u KeGetCurrentThread16 17 nt!PsGetCurrentThread:18 83c6cd19 64a124010000    mov     eax,dword ptr fs:[00000124h]19 83c6cd1f c3              ret20 83c6cd20 90              nop21 83c6cd21 90              nop22 83c6cd22 90              nop23 83c6cd23 90              nop24 83c6cd24 90              nop

 

 

 

 

 

Normally, fs registers are used to store "segment delimiters", and "segment delimiters" are used to index A "segment descriptor" in GDT. The latter has a "segment base address"

Attribute, that is, the KPCR (Kernel Processor Control Region, Kernel Processor Control area) structure (nt! _ KPCR) Start address; nt! _ KPCR

Offset 0x120 bytes is an nt! _ KPRCB structure. The "CurrentThread" field offset between 0 and 4 bytes is a _ KTHREAD structure.

This field is updated during thread switching. This is a powerful Design Concept hidden behind the simplicity of fs: [00000124 h!

 

 

Note: PsGetCurrentThread () returns a pointer to the _ ETHREAD structure (that is, "PETHREAD". As you can see, Microsoft prefers the pointer concept.

Play the "header Text P" game), and the first field Tcb in the _ ETHREAD structure is a _ KTHREAD instance-this means that we do not need to calculate additional

Offset. You only need to consider the ServiceTable offset 0xbc, for example:

 

 

 

However, we need to execute the addition operation on this pointer and move it to the ServiceTable field. Therefore, we cannot declare a PETHREAD variable to store it.

The return value of PsGetCurrentThread (), because "pointer plus value n" adds the address currently held by the pointer (n * the data type size referred to by the pointer)

Byte -- Expression

1 PETHREAD ethread_ptr += 0xbc;

 

Actually, the starting address is added.0xbc * sizeof (ETHREAD)Bytes, far exceeding our expectation ....

What should we do? Easy to do, declareByte typePointer to save the return value of PsGetCurrentThread (), and forcibly convert the returned value to the same one!

In this way, the expression

1 BYTE* byte_ptr += 0xbc;

 

Add the starting address0xbc * sizeof (BYTE)Bytes, in line with our expectations.

Note: This requires you to add related type definitions, such:

 

 

This indicates that BYTE is equivalent to the unsigned bytes type (also equal to Microsoft's own UCHAR), and the size is single BYTE; DWORD is equivalent to the unsigned long integer, And the size is both

Four bytes -- we use a DWORD variable to store the address of the array KiServiceTable.

 

--------------------------------------------------------

 

The next step is to locate the KiServiceTable process through a series of pointer transformation and cited operations, which once again highlights the position of pointers in C Programming, whether it is an application

Program or kernel ..... After a value assignment, the global variable OS _ki_service_table holds the KiServiceTable address. Note:

In addition to the macro definition of the Offset, all operations are completed in our driver entry routine DriverEntry (), and to support dynamic uninstallation, I registered

Unload () callback. Later, you will see the internal implementation of Unload ()-roughly canceling the write permission ing to KiServiceTable during uninstallation.

--------------------------------------------------------------------------------

 

To verify the accuracy of the KiServiceTable process, I added the following print output statement. Note that the output of dbuplint () must be on the debugged Machine

View DbgView.exe or directly output it to windbg.exe/kd.exe on the debugging machine:

 

 

------------------------------------------------------------------------------

Combined, verify in the debugger-the "dd" command can display the content at the given virtual memory address in double words (four bytes); the "dps" command can follow the letter

The number symbol displays the routine address starting with the given memory address. It is designed for function pointer arrays (for example, KiServiceTable), for example:

 

----------------------------------------------------------

Now, KiServiceTable can be accessed in read-only mode through the global variable OS _ki_service_table. Before we hook it, try to change it to writable.

Let's take a look at what will happen when I try to write to the read-only KiServiceTable, as shown in

The first four bytes held (that is, the system service nt! NtAcceptConnectPort) is filled with four ASCII characters "":

 

------------------------------------------------------------

 

Note that the first parameter of RtlFillMemory () is a pointer pointing to the memory block to be filled. The last two parameters are the fill length and data respectively.

The variable OS _ki_service_table is of the DWORD type, So I forced it into a matched pointer and passed it as a real parameter .... Re-build the driver,

Load it in a virtual machine that runs in debug mode, as shown in figure. Assume that the name of the compiled rootkit is

UseMdlMappingSSDT. sys,

The figure shows a fatal system error with the code 0x000000BE. The four parameters with error information are enclosed in parentheses and will be used for troubleshooting.

In fact, this is a BugCheck. When an error check occurs, if the target system is connected to the debugger on the host, the debugger is disconnected. Otherwise, the target system

The KeBugCheckEx () routine will be executed, the latter will shield all interrupt events on the processor core, and then switch the display to the low-resolution VGA graphics mode,

Draw a blue background and display the shutdown code corresponding to the "check result" to the user. This is the origin of the "blue screen.

 

----------------------------------------------------------

In this scenario, we get a stop code 0x000000BE and use it as a keyword string to search for the MSDN document. The description is as follows:

 

 

 

--------------------------------------------------------

 

 

The official explanation is clear: 0x000000BE (ATTEMPTED_WRITE_TO_READONLY_MEMORY) is caused by the driver trying to read-only

 

The first parameter is the virtual address to be written, and the second parameter is the PTE (page table item) that describes the virtual page where the virtual address is located-physical page)

Content; the following two parameters are reserved for future extension usage, so they are truncated by me. Based on the previous figure, we know that the virtual address to be written is

0x83CAFF7C: The PTE content of the physical page mapped to it is 0x03CAF121. The following two parameters can be ignored currently.

As shown in, 0x83CAFF7C is the starting address of the KiServiceTable. The identifier that describes its PTE decoding has an "R" attribute, indicating

Read-Only; stack tracing information is displayed at the BugCheck time. The exception handler MmAccessFault () in the kernel is used to handle errors related to memory access,

It is a front-end parsing routine. If exceptions or errors can be handled, it is distributed to the actual processing function. Otherwise, it calls the KeBugCheck * () series functions,

This family of functions determines whether the debugger exists or not-either call KiBugCheckDebugBreak () to break into the debugger; or execute the operations described above

Process to draw a blue screen:

 

 

 

------------------------------------------------------------

 

So far, it is determined that the BugCheck is caused by calling RtlFillMemory () in the driver to write the read-only kernel memory. Another more powerful debugger extension command

"! Analyze-v can output detailed information, including the BugCheck "field" command address and register status, as shown in.

The command address is 0x9ff990b4. This command transfers the current value of the eax register (0x41414141, that is, four ASCII characters passed in by RtlFillMemory ().

"A") write to the memory address held by the ecx register, and try to put nt! Replace the endpoint address of NtAcceptConnectPort () with 0x41414141.

Give the corresponding line number in the driver source code-that is, the RtlFillMemory () call of line 137th:

 

----------------------------------------------------------

 

As you can see, Microsoft C/C ++ compilers (cl.exe) Put RtlFillMemory () in its callers, in other words, despite the public documentation describing its

Return Value, parameter .... The specific implementation is determined by the compiler --For performance optimization, RtlFillMemory () is directly implemented as a simple data movement.

The related parameters are transmitted by registers. There is no additional overhead for creating or destroying stack frames due to function calls!

So far, although we have located the system service pointer table from _ KTHREAD through a series of steps, we have been unable to hook the System Service letter by conventional means.

Because it is read-only.

 

In the next article, I will discuss how to use MDL (Memory Descriptor List, Memory Descriptor linked List) to bypass this restriction and read and write as you like.

KiServiceTable!

 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.