Author: Anton Bassov
Address: http://www.codeproject.com/KB/system...protector.aspx
Author: LeoF
Time: 2010-12-15
Introduction
Recently I accidentally saw an introduction to a security product called Sanctuary, which is very interesting. it can block the running of any program, as long as the software is not included in the "allowed running Software List" of a specific machine. therefore, PC users can defend against spyware plug-ins, worms, and Trojans-even if some malware can exist on the computer, it has no chance to run, so there is no chance to damage the machine. of course, I found this feature very interesting. After thinking a little bit, I made it myself. therefore, this article describes how to hook native APIs to use programs to monitor the creation of processes within the system.
This article makes a bold assumption that the target process is created in user mode (shell function, CreateProcess (), and manual process creation as a sequence of native API calls ). although theoretically a process can be created in kernel mode, this may have a specific purpose and can be ignored without worrying about it. why ??? Try to think about it-to create a process in kernel mode, a program must load a driver, which means to execute some user-mode code first. therefore, to prevent unauthorized programs from running, we can safely narrow down the scope to control user mode Creation in the system.
Develop our strategy
First, we need to determine what we must do to monitor a Process Creation in the system.
Process Creation is a complicated task. It involves a lot of work (if you don't believe me, you can disassemble CreateProcess () and you can see it with your own eyes ). to start a process, follow these steps:
1. the executable file is opened (FILE_EXECUTE access permission)
2. the Executable image is loaded into the memory.
3. Create a Process execution Object (structure of Process Executive Object, EPROCESS, KPROCESS and PEB)
4. Allocate address space for the newly created Process
5. Create the Thread execution Object (Thread Executive Object, ETHREAD, KTHREAD, and TEB structure) of the main Thread of the process)
6. Create a master thread Stack
7. Establish the Execution context of the main thread (Execution context)
8. Notify the Win32 subsystem of the new process information
For the successful execution of any step in these steps, all the previous steps must be completed successfully (you cannot create an Executive Process Object without an executable section handle; you can't map executable section without a file handle ). therefore, if we decide to terminate any of these steps, the subsequent operations will also fail, so that the process creation will be terminated. it is easy to understand that all these steps call the corresponding native API functions. therefore, to monitor process creation, we need to hook up these API functions so that the code of starting a new process does not pass.
Which native API function do we need to hook? Although NtCreateProcess () seems to be the most obvious answer, it is indeed wrong-it is possible to create a process without using this function. for example, CreateProcess () creates a process-related kernel mode structure instead of using NtCreateProcess (). therefore, it is useless to hook NtCreateProcess.
To monitor process creation, we can hook NtCreateFile () and NtOpenFile (), or NtCreateSection ()-it is impossible to run an executable file without calling these functions. if we decide to monitor NtCreateFile () and NtOpenFile (), we have to differentiate process creation from normal file IO operations. this task is not easy. for example, if an executable file is opened with the FILE_ALL_ACCESS attribute, what should we do ??? Is it an I/O operation or is it a part of process creation ??? It is difficult to judge at this point-we need to check what the call thread will do next. Therefore, linking NtCreateFile () and NtOpenFile () is not the best choice.
It is more reasonable to hook NtCreateSection ()-if there is a ing executable file as the image (SEC_IMAGE attribute) request, we have intercepted this request to call NtCreateSection, combined with the page request with executable properties, we can determine that the process is to be started. at this time, we can make a decision. If we do not want the process to be created, let NtCreateSection () return STATUS_ACCESS_DENIED. therefore, to gain the power to fully monitor process creation on the machine, we need to hook NtCreateSection () on the system ().
Like other functions (which are translated as functions in stub), NtCreateSection () puts the service index number of the function into EAX and points EDX to the function parameter, then run the command to go to the kernel function KiDispatchService () (in windows NT/2000, run the INT 0x2E command and run the SYSENTER command in windows XP ). after checking the validity of the parameter, KiDispatchService () transfers the execution right to the actual Service implementation. In SSDT (System Service Descriptor table.pdf, the address of this Service is valid (the KeServiceDescriptorTable variable exported by ntoskrnl.exe points to this table, therefore, the kernel driver can be used ). the SSDT description can be found in the following structure:
Code:
Struct SYS_SERVICE_TABLE {
Void ** ServiceTable;
Unsigned long CounterTable;
Unsigned long ServiceLimit;
Void ** ArgumentsTable;
};
The ServiceTable member of this structure points to the array that stores the actual system service address. therefore, to hook all native API functions in the system, we need to write the address of our proxy function into the I entry of the array (I is the service index ), this array is pointed to by ServiceTable in the KeServiceDescriptorTable (foreigners do not know what to think. I have already said this sentence and used it again. Do you think the IQ of others except yourself is very low ).
It seems that we have learned how to create all the things we need to know by monitoring the process in the system. Let's start working on it!
Control Process Creation
Our solution consists of a kernel-mode driver and a user-mode application. in order to start monitoring process creation, our application transmits the service index value and the address of the Communication buffer to our driver according to NtCreateSection. the following code does this:
Code:
// Open device
Device = CreateFile ("\. \ PROTECTOR", GENERIC_READ | GENERIC_WRITE,
0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0 );
// Get index of NtCreateSection, and pass it to the driver, along with
// Address of output buffer
DWORD * addr = (DWORD *)
(1 + (DWORD) GetProcAddress (GetModuleHandle ("ntdll. dll "),
"NtCreateSection "));
ZeroMemory (outputbuff, 256 );
Controlbuff [0] = addr [0];
Controlbuff [1] = (DWORD) & outputbuff [0];
DeviceIoControl (device, 1000, controlbuff, 256, controlbuff, 256, & dw, 0 );
These codes can be explained by ourselves-we need to note that we can obtain the service index value. all slave ntdll. all dll-exported functions (stub) start with "mov eax, ServiceIndex". This applies to all windows NT versions. this is a 5-byte command. The mov eax machine code (opcode) is the first byte, and the index value is the remaining 4 bytes. therefore, to obtain the service index value corresponding to a specific native API, you need to read the four bytes from this (function) Address, in the function (stub) the offset of the first byte.
Now let's take a look at what the driver will do when it receives the IOCTL of our application:
Code:
NTSTATUS DrvDispatch (IN PDEVICE_OBJECT device, in pirp Irp)
{
UCHAR * buff = 0; ULONG a, base;
PIO_STACK_LOCATION loc = IoGetCurrentIrpStackLocation (Irp );
If (loc-> Parameters. DeviceIoControl. IoControlCode = 1000)
{
Buff = (UCHAR *) Irp-> AssociatedIrp. SystemBuffer;
// Hook service dispatch table
Memmove (& Index, buff, 4 );
A = 4 * Index + (ULONG) KeServiceDescriptorTable-> ServiceTable;
Base = (ULONG) MmMapIoSpace (MmGetPhysicalAddress (void *) a), 4,0 );
A = (ULONG) & Proxy;
_ Asm
{
Mov eax, base
Mov ebx, dword ptr [eax]
Mov RealCallee, ebx
Mov ebx,
Mov dword ptr [eax], ebx
}
MmUnmapIoSpace (base, 4 );
Memmove (& a, & buff [4], 4 );
Output = (char *) MmMapIoSpace (MmGetPhysicalAddress (void *) a), 256, 0 );
}
Irp-> IoStatus. Status = 0;
IoCompleteRequest (Irp, IO_NO_INCREMENT );
Return 0;
}
You can see that there is nothing special here-we only map the communication buffer to the kernel address space through MmMapIoSpace (), and write the address of our proxy function into SSDT (of course, we did this after saving the actual service address to the global variable RealCallee ). to overwrite the applicable SSDT