This article is a logical continuation of the Simple SST Unhooker article. This article is written as an answer to the article Driver to Hide Processes and Files. Second Edition: Splicing by Serg Bratus.
I will try to oppose the splicing method to remove all the hooks, which setting is described in his article.
Leader of Network Security Team
Table of Contents
3. Import table
6. How to build
What is the best way of dealing with splicing in the context of struggle with hidden processes? Obviously, the best way is to verify the whole ntoskernel image entirely.
The verification of the loaded (original) image with a file is provided in the previous article. But I analyzed only a part of ntoskernel - sdt / sst - there. It is possible to expand the functionality of the previous driver so that it passes through all the sections and verifies them, as the windbg !chkimg extension does:
"The !chkimg extension detects corruption in the images of executable files by comparing them to the copy on a symbol store or other file repository." (for more information, see http://msdn.microsoft.com/en-us/library/ff562217(v=vs.85).aspx).
In fact, we need to write something similar. We can use memory mapped files, just like I did in the previous article to compare the loaded ntoskernel with the file. The easiest way is to take the old driver as a basis and add the necessary functionality to it. As far as the executing ntoskernel.exe system is a standard PE file, the verification algorithm will repeat some actions of the PE loader.
PE loader works section by section as follows:
"It's important to note that PE files are not just mapped into memory as a single memory-mapped file. Instead, the Windows loader looks at the PE file and decides what portions of the file to map in. This mapping is consistent in that higher offsets in the file correspond to higher memory addresses when mapped into memory. The offset of an item in the disk file may differ from its offset once loaded into memory. However, all the information is present to allow you to make the translation from disk offset to memory offset (see Figure 1)." (for more information, see http://msdn.microsoft.com/en-us/magazine/cc301805.aspx)
That’s why we have to verify the file section by section too.
PE format is well described in the article mentioned above, so I will not describe it entirely. I will describe it only from a practical point of view.
The PE file section is described by such structure:
The next figure illustrates the way of using its fields:
Figure 1. PE header. Section on the disk and in the memory
As it is shown in figure 1, the virtual addresses describe the section after loading, and the physical (raw) addresses describe the section on the disk. We have to know how to translate virtual addresses into physical ones to compare the section on the disk and in the memory.
You can do this as follows:
We will use this function in future because it is virtual addresses that are used in all PE tables.
If we just map the file and try to compare it with the loaded image, relocations are the first problem we meet.
Here is the thing: the code, which is saved on the disk, stores all the absolute addresses as relative to the
ImageBase value from the
OptionalHeader of the PE file.
For example, the function from ntoskernel, which is just mapped in the memory, can look as follows:
00050a71 8bff mov edi,edi 00050a73 55 push ebp 00050a74 8bec mov ebp,esp 00050a76 51 push ecx 00050a77 6a01 push 0x1 00050a79 8d450c lea eax,[ebp+0xc] 00050a7c 50 push eax 00050a7d ff7508 push dword ptr [ebp+0x8] 00050a80 b910794500 mov ecx,0x457910 // here 00050a85 6a03 push 0x3 00050a87 6a65 push 0x65 00050a89 e8ef210c00 call 00112c7d 00050a8e 59 pop ecx 00050a8f 5d pop ebp 00050a90 c3 ret
The absolute address is moved to
ECX in this function:
00050a80 b910794500 mov ecx,0x457910 // *** and it is relative to ImageBase
We can view the value of the ImageBase image by the
lm + dh commands:
kd> lm start end module name 82602000 82a12000 nt (pdb symbols) kd> !dh 82602000 File Type: EXECUTABLE IMAGE FILE HEADER VALUES 14C machine (i386) 16 number of sections 4A5BC007 time date stamp Tue Jul 14 02:15:19 2009 … skipped OPTIONAL HEADER VALUES 10B magic # 9.00 linker version 343000 size of code C0000 size of initialized data 2800 size of uninitialized data 11D4D8 address of entry point 1000 base of code ----- new ----- 00400000 image base /// IT IS! 1000 section alignment 200 file alignment 1 subsystem (Native) 6.01 operating system version 6.01 image version 6.01 subsystem version 410000 size of image 800 size of headers
That is, image base is equal to 0x400000. If
ntoskernel always loads at this address, the offset information is needless. But as far as ntoskernel usually loads to high addresses at some
moduleAddress address, NT loader uses the information from the relocation table to transform relative offsets into the absolute ones.
The relocation table is stored in a special section of the PE file. It is a chain of records, each of which is described by the header
Each table entry has its own size, which is defined in the
SizeOfBlock field. Also it has a dynamic array
TypeOffsetSizeOfBlock. Each element of the
TypeOffsetSizeOfBlock array describes one absolute offset in the file.
MSDN describes this structure as follows:
"Immediately following the
IMAGE_BASE_RELOCATION structure is a variable number of
WORD values. The number of
WORDs can be deduced from the
SizeOfBlock field. Each
WORD consists of two parts. The top 4 bits indicate the type of relocation, as given by the
IMAGE_REL_BASED_xxx values in WINNT.H. The bottom 12 bits are an offset, relative to the
VirtualAddress field, where the relocation should be applied.” (for more information, see http://msdn.microsoft.com/en-us/magazine/cc301808.aspx).
If we have such entry in the table:
VirtualAddress = 10000 SizeOfBlock = sizeof(IMAGE_BASE_RELOCATION)+4,
This means that two
WORDs, which describe the offsets, follow it. Let it be such values:
offset1 = 3100 offset2 = 3200
It means that the entry describes two absolute addresses, which are located at the 10100 and 10200 virtual addresses. Based on the type of offsets (in this case, it is
IMAGE_REL_BASED_HIGHLOW(3)), loader will perform such actions to process the record:
We can visualize these actions as shown of the picture below:
Figure 2. Module before and after relocation processing.
We have to apply the relocations to our memory mapped image of ntoskernel in the same way as NT loader. It is important not to forget to translate virtual addresses from the table to the raw ones using the
ConvertVAToRaw function, which is described above.
There is an interesting moment. If we use the same diff, which is used by the loader of the original image, we will get such image:
Figure 3. Our module after processing the relocations.
In this case, we will be able to compare sections even using the
Function that adjusts all the relocations will look as follows:
ProcessRelocationEntry is as follows:
After execution of the
FixRelocs function, our loaded module will look like as in Figure 3.
We have only one task, except of the relocations. The task is to process the import tables.
Ntoskrnl import anything? Yes, it uses some modules. For example, in my Windows 7, they are as follows:
"PSHED.dll" "HAL.dll" "BOOTVID.dll" "KDCOM.dll" "CLFS.SYS" "CI.dll"
Obviously, this list can be different on different Windows versions.
The import table is well described in the Injective Code inside Import Table article (for more information, see http://www.codeproject.com/KB/system/inject2it.aspx) and in other sources. That’s why I will not describe it here.
Let’s concentrate on the algorithm of import and export linking. The task on this step is to link the
ntoskernel import table with the corresponding exported functions of other loaded modules.
This is the algorithm in a Nassi-Shneiderman diagram form (see http://en.wikipedia.org/wiki/Nassi%E2%80%93Shneiderman_diagram):
Figure 4. Algorithm of imported functions search
And this is its implementation:
LinkThunk function task is to fill the
u1.Function address value field for the thunk, with which it is called:
Finally, after this step, we can compare our loaded module with the original one in the very simple way:
In the sources, this function is slightly improved to find 1-byte differences.
The API was implemented for checking
Ntoskrnl integrity using all stuff described above:
It can be simply used:
For example, the code that cancels all changes of the NT executive system looks as follows:
I must say that a very interesting detail appeared here.
Program still shows the one byte difference on the clean system!
Figure 5. The result of unhooker.exe stat work
Is it a bug?
No, it is not. The windbg
u (Unassemble) command clearly shows that the difference really exists.
RtlPrefetchMemoryNonTemporal function from the loaded ntoskernel:
kd> u 0x82603000+FB9A*4 nt!RtlPrefetchMemoryNonTemporal: 82641e68 90 nop 82641e69 a1b4aa7282 mov eax,[nt!KePrefetchNTAGranularity (8272aab4)] 82641e6e 0f184100 prefetchnta byte ptr [ecx] 82641e72 03c8 add ecx,eax 82641e74 2bd0 sub edx,eax 82641e76 77f6 ja nt!RtlPrefetchMemoryNonTemporal+0x6 (82641e6e) 82641e78 c3 ret 82641e79 90 nop
This is it in the file:
kd> u 0x00050800+FB9A*4 0008f668 c3 ret 0008f669 a1b4aa7282 mov eax,[nt!KePrefetchNTAGranularity (8272aab4)] 0008f66e 0f184100 prefetchnta byte ptr [ecx] 0008f672 03c8 add ecx,eax 0008f674 2bd0 sub edx,eax 0008f676 77f6 ja 0008f66e 0008f678 c3 ret 0008f679 90 nop
As we can see, two functions differ only in the first byte. Why did this byte change?
After some research I found out, that the NT boot loader (http://en.wikipedia.org/wiki/NTLDR) transfers its knowledge about the CPU processor properties to the executing system in such way. It performs something like this:
if the processor has all necessary characteristics for this function execution.
Using this information, we have to change the final version of the
ScanAllModule procedure. Now, it just skips a 1-byte change if it is in the beginning of the
m_pRtlPrefetchMemoryNonTemporal variable contains the name of the function:
This solution is not good enough for production code, and it would be better to think about something more universal, but it is quite appropriate for this article.
Now, we will show the work of the developed driver.
Here are the results of its work on a clean system:
Figure 6. The results of unhooker.exe stat work on a clean system.
And now it’s time to fight with the the driver from the Hide Processes and Files. Second Edition: Splicing article! Let’s deploy it and hide all processes named calc.exe:
Figure 7. Result of Splicing Driver work – hidden calc.exe processes
To demonstrate all possibilities, I added all the functionality to the old driver and updated the unhooker.exe console utility. Its syntax did not change from the last article: utility can be started without parameters; in this case, it shows information about its abilities:
- “stat” command shows statistics about SST hooking and kernel patching;
- “unhook” command cleans ntoskrnl.
Let’s try to diagnose the system with the help of the unhook stat:
Figure 8. Resalts of the unhooker stat work in the infected system.
As we can see, together with the information about the SST changing, the information about the changed module is also returned. Let’s try to remove all the hooks:
Figure 9. Result of the unhooker unhook work on the infected system.
Hurrah! The calc.exe processes are visible again and it means that we succeeded.
Steps are the same as in the previous article.
Thank you for your attention!