Process creation via hook control

Source: Internet
Author: User

First, Introduction

Recently, I learned about a very interesting security product called Sanctuary. It prevents any programs from running-These programs are not displayed in the list of software-programs in that table are allowed to run on a particular machine. As a result, PC users are protected from a variety of plug-in spyware, worms, and Trojan horses-even if they are able to access his/her computer, they have no chance to execute and therefore have no chance of damaging the machine. Of course, I think this feature is quite interesting and, after a little thought, I have a realization of my own. Therefore, this article describes how to monitor the creation of a process and control it at the system level by hooking up the native API.

This article boldly assumes that the target process was created in a user mode (Shell function, CreateProcess (), manual process creation with a series of native API calls, and so on). Although theoretically, a process can be started in kernel mode, but in practice, such a possibility is negligible, so we do not have to worry about this. Why? Think logically-in order to start a process in kernel mode, the user must mount a driver, which in turn implies the execution of some user-mode code. Therefore, in order to prevent the execution of unauthorized programs, we can safely restrict the creation of processes that we control ourselves at the system level in user mode.

Second, Defining Policies

Let us first make it clear that the purpose of this is to monitor and control process creation at the system level.

Process creation is quite a complex thing-it contains quite a bit of work (if you don't believe me, you can disassemble CreateProcess (), so you'll see it yourself). In order to start a process, you can use the following steps:

1. The executable file must be opened in File_execute access mode.

2. The executable image must be loaded into RAM.

3. Process Execution Objects (eprocess,kprocess and PEB structures) must be established.

4. The new process must be assigned an address space.

5. The Thread execution objects (Ethread,kthread and tebstructures) that must be established for the main threads of the process.

6. The main thread must be allocated a stack.

7. The execution context of the main thread of the process must be established.

8. The WIN32 subsystem must be notified about the creation of the new process.

To ensure the success of any of these steps, all of its preceding steps must be successful (you cannot create an executable process object without a handle to the executable area; You cannot map an executable area without a file handle, and so on). Therefore, if we decide to quit any of these steps, all subsequent steps will fail so that the entire process creation will fail. All of the above steps can be implemented by invoking some native API functions, which is understandable. So, in order to monitor and control process creation, all we have to do is hook these API functions-they can't bypass the code to be executed to create a new process.

What native API functions should we hook up with? Although Ntcreateprocess () seems to be the most obvious answer to the question, the answer is wrong-there may be no need to call this function or create a new process. For example, CreateProcess () can create a process-dependent kernel-mode structure instead of calling Ntcreateprocess (). So hooking up with ntcreateprocess () has done us no good.

In order to monitor the creation of the process, we must either Hook ntcreatefile () and ntopenfile () or hook up with ntcreatesection ()-It is absolutely impossible to run any executables without calling these APIs. If we decide to monitor calls to NtCreateFile () and Ntopenfile (), then we have to differentiate between open process creation and regular file IO operations. The task is not always so easy. For example, what should we do if some executable files are being opened in file_all_access access mode? Is this only an IO operation or is it part of a process creation? At this point, it's hard to judge-we need to understand what the calling thread is going to do next. Therefore, hooking ntcreatefile () and Ntopenfile () is not the best possible option.

Hooking Ntcreatesection () is more reasonable-if we want to intercept a call to Ntcreatesection (), the request is made as an image (Sec_image property) mapping executable (Sec_image property), At the same time, request the page protection that is allowed to execute; then we can be sure that the process will be started. At this point, we are able to make a decision, and let Ntcreatesection () return status_access_denied if we do not want to create the process. Therefore, in order to fully control the process creation on the target machine, all we have to do is to hook ntcreatesection () at the system level.

Like any other agent in Ntdll.dll, ntcreatesection () loads the EAX with the service index, causes EdX to point to the function arguments, and passes execution to the Kidispatchservice () kernel-mode routines (this is through windows The int 0x2e instruction in nt/2000 or the sysenter instruction under Windows XP). After verifying the function parameters, the Kidispatchservice () Pass execution to the actual implementation part of the service-its address can be used in the Service Description table (pointers to this table are output by Ntoskrnl.exe as a keservicedescriptortable variable, so it is available for kernel-mode drivers). The service Description table is described by the following structure:

struct Sys_service_table {
void **servicetable;
unsigned long countertable;
unsigned long servicelimit;
void **argumentstable;
};


The Servicetable field in this structure points to an array-it has all the addresses of functions that implement system services. So, in order to hook up to any native API function at the system level, all we have to do is write the address of our proxy function to the first entry of the array to which the keservicedescriptortable servicetable field is pointing (i is the service index).
At this point, it looks like we've seen everything that is being created at the system level to monitor and control the process. Now let's start the actual work.

Third, 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 will pass the service index (corresponding to Ntcreatesection ()) and the address of the swap buffer to our driver. This is done by the following code:

Turn on the device
Device=createfile ("////.//protector", generic_read| Generic_write,
0,0,open_existing, file_attribute_system,0);
Get the index of the ntcreatesection and pass it along with the address of the output buffer to the device
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);


This code is obvious-the only thing to be aware of is how we get the service index. All agents from Ntdll.dll start with a line of code MOV Eax,serviceindex-it can be applied to any version and flavor of Windows NT. This is a 5-byte long instruction, with the mov eax opcode as the first byte, the service index as 4 bytes left. So, in order to get a service index corresponding to some of the special native API functions, all you have to do is read 4 bytes from that address, which is located at a distance of 1 bytes from this agent.

Now let's look at what our driver does when it receives the IOCTL from our application:

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-up Service Scheduling 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,a
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;
}


As you can see, there's nothing special here-we just map the swap buffer to the kernel via Mmmapiospace () and write the address of our proxy function to the service table (of course, We are doing this after saving the address of the actual service execution to the global variable Realcallee. To rewrite the appropriate entry for the service table, we mapped the destination address via Mmmapiospace (). Why are we doing this? Anyway, we can already access the service table, right? The problem is that the service table may reside in a read-only memory. Therefore, we have to check if we have permission to write to the target space, and if we do not have this permission, we must change the page protection before overwriting the service table. Don't you think this has been too much work? So we only use Mmmapiospace () to map our target addresses so that we don't have to worry about any page protection issues-from now on, we assume that we have permission to write to the target page. Now let's take a look at our proxy functions:

This function is used to determine if we should allow the ntcreatesection () call to succeed
ULONG __stdcall Check (Pulong Arg)
{
HANDLE hand=0; Pfile_object file=0;
Pobject_handle_information info; ULONG A;char*buff;
Ansi_string str; Large_integer Li;li. quadpart=-10000;
Check the flag. If the requested access method is not Page_execute,
It doesn't matter.
if ((arg[4]&0xf0) ==0) return 1;
if ((arg[5]&0x01000000) ==0) return 1;
To get the file name via a handle
Hand= (HANDLE) arg[6];
Obreferenceobjectbyhandle (Hand,0,0,kernelmode,&file,&info);
if (!file) return 1;
Rtlunicodestringtoansistring (&str,&file->filename,1);
A=str. Length;buff=str. Buffer;
while (1)
{
if (buff[a]== '. ') {a++;break;}
a--;
}
Obdereferenceobject (file);
If it's not enforceable, it doesn't matter.
Returns 1
if (_stricmp (&buff[a], "EXE")) {rtlfreeansistring (&STR); return 1;}
Now, we're going to ask the user for their choice.
Writes the file name to the buffer and waits until the user displays the response
(The first DWORD of 1 means we can continue)
Synchronize access to the buffer
KeWaitForSingleObject (&event,executive,kernelmode,0,0);
Set the first two DWORD of the buffer to 0,
Copy the string into the buffer and loop it down until the user puts each
The DWORD is set to 1.
The value of the second DWORD indicates the user's response
strcpy (&output[8],buff);
Rtlfreeansistring (&STR);
A=1;
Memmove (&output[0],&a,4);
while (1)
{
Kedelayexecutionthread (Kernelmode,0,&li);
Memmove (&a,&output[0],4);
if (!a) break;
}
Memmove (&a,&output[4],4);
KeSetEvent (&event,0,0);
return A;
}
Save execution context only and call check ()
_declspec (Naked) Proxy ()
{
_asm{
Save the execution context and call check ()
-The value returned by check () later depends on
If the return value is 1, the actual call continues.
Otherwise, return status_access_denied
Pushfd
Pushad
MOV Ebx,esp
Add ebx,40
Push EBX
Call Check
CMP eax,1
Jne Block
Continue the actual call
Popad
Popfd
JMP Realcallee
Back to Status_access_denied
Block:popad
mov ebx, DWORD ptr[esp+8]
MOV DWORD ptr[ebx],0
MOV eax,0xc0000022l
Popfd
RET 32
}
}


Proxy () holds registers and flags, presses a pointer to the service parameter into the stack and calls check (). The other depends on the value returned by check (). If check () returns True (that is, we want to continue the request), then Proxy () restores the register and flag and gives control to the service implementation section. Otherwise, Proxy () writes the status_access_denied to EAX, resumes the ESP and returns-from the caller's point of view, as if the call to Ntcreatesection () failed-in the wrong state Status_access_ Denied returns. How does the check () function make a decision? Once it receives a pointer parameter that points to the service parameter, it can check for these parameters. First, it checks flags and properties-if a part is not required as an executable image map, or if the required page protection does not allow execution, then we can determine that the ntcreatesection () call has nothing to do with process creation. In this case, check () returns true directly. Otherwise, it will check the extension of the potential file-after all, the Sec_image property and the page protection that is allowed to execute may be required to map a DLL file. If the potential file is not an. exe file, check () returns TRUE. Otherwise, it gives the user pattern code a chance to make a decision. Therefore, it writes only the file name and path to the swap buffer, and it loops through the query until it gets a response.

Before we open our driver, our application creates a thread that runs the following function:

void Thread ()
{
DWORD a,x; Char msgbuff[512];
while (1)
{
Memmove (&a,&outputbuff[0],4);
If there is nothing, Sleep () 10 milliseconds and then check
if (!a) {Sleep]; continue;}
Looks like our permission was questioned.
If the suspected file already exists in the blank list,
A positive response is given.
Char*name= (char*) &outputbuff[8];
for (x=0;x<stringcount;x++)
{
if (!stricmp (Name,strings[x])) {A=1;goto skip;}
}
Require the user to allow the program to run
strcpy (Msgbuff, "Do-Want to run");
strcat (Msgbuff,&outputbuff[8]);
If the user's response is positive, then add the program to the blank list
if (Idyes==messagebox (0, Msgbuff, "WARNING", mb_yesno| mb_iconquestion|0x00200000l))
{a=1; strings[stringcount]=_strdup (name); stringcount++;}
else a=0;
Writes the response to the buffer, and then the driver retrieves it
Skip:memmove (&outputbuff[4],&a,4);
Tell the driver to continue
a=0;
Memmove (&outputbuff[0],&a,4);
}
}


This piece of code is obvious-our thread queries the swap buffer every 10 milliseconds. If it finds that our driver has sent its request to the buffer, it checks the file name and path in the list of programs that are allowed to run on this computer. If a match is found, it gives an OK response directly. Otherwise, it displays a message window asking the user whether to allow the problematic program to execute. If the response is positive, we add the problematic program to the list of software that is allowed to run on this computer. Finally, we write the user response to the buffer, i.e., pass it to our driver. As a result, the user has full control over the creation of the process on its PC-as long as our program is running, there is absolutely no way to start any process on that PC without the user's permission.

As you can see, we let kernel-style code wait for the user to respond. Is this a smart move? To answer this question, you have to ask yourself if you are blocking any critical system resources-everything depends on the specific situation. In our case, everything happens at the irqlpassive_level level and does not contain the processing of the IRPs, and the thread that must wait for the user to respond is not very important. So, in our case, everything works fine. However, this example is written for demonstration purposes only. In order to actually use it, it is important to rewrite our application in a way that automatically starts the service. In this case, I recommend that we remove the LocalSystem account and, in the case where ntcreatesection () is invoked in the context of a thread with LocalSystem account privileges, you can continue the actual service implementation without performing any checks-anyway, The LocalSystem account runs only those executable programs that are specified in the registry. Therefore, such a release will not be compromised with our security.

   Four, Conclusion

Finally, I must point out that hooking the native API is clearly one of the most powerful programming techniques available. This article shows you an example of the ability to do this by hooking the native API-as you can see, we've managed to prevent unauthorized programs from executing-this can be done by hooking up a single native API function. You can further extend this approach and gain complete control over hardware devices, file IO operations, network traffic, and so on. However, our current solution is not intended to be used by kernel-mode API callers-once kernel-mode code is allowed to invoke the output of Ntoskrnl.exe directly, these calls do not need to go through the system service sender.

The source code in this article was successfully tested on several machines running Windows XP SP2. Although I haven't tested it under any other environment, I'm sure it should work everywhere-anyway, it never uses any system-specific structures. In order to run this example, all you have to do is place the Protector.exe and Protector.sys into the same directory and run the Protector.exe. Until the Protector.exe application window is closed, you will be prompted every time you try to run any executable program.

http://blog.csdn.net/jiangxinyu/article/details/5269851

Process creation via hook control

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.