Monitor creation of remote threads
Author: three cents a piece
Email:Zhongts@163.com
Date: 2004.12.29
Remote thread technology is widely used in Trojans, worms, and other software. It runs code by inserting threads in other processes, which is highly concealed. For example, there are more than a dozen threads in the common assumer.exe process that run simultaneously. After a thread is inserted, no one can tell which is the inserted remote thread. This article provides a way to monitor the creation activities of remote threads and record important data such as the ID of remote threads, in this way, you can easily find out which process to which a remote thread is inserted.
The following are the remote thread creation records of icesword v1.06 and the code in this article:
As this article needs to write drivers, readers who are not familiar with driver programming can find some driver books. Here we recommend that you download the translated kmdtut on the luyun bin website. At the same time, download kmdkit because the code in this article uses this software package. After installing masm32 and kmdkit, You can compile the Code provided in this Article. If error lnk2001: unresolved external symbol is prompted during code compilation_ Psremovecreatethreadpolicyroutine @ 4Error. Copy the ntoskrnl. Lib provided in this article to the LIB/w2k folder to overwrite the original file. I also just learned to drive programming. The following is just a simple example. There are still many things to do to make it practical.
The first is to monitor the creation of threads, and then identify which ones are remote threads. To monitor thread creation, you need to use such a function pssetcreatethreadpolicyroutine. Through this function, we register a callback function. Every time a new thread is created in the system, our callback function is called. In this callback function, we can record the creation of all threads. If you want to monitor process creation, another function, pssetcreateprocesspolicyroutine, can be used to complete this function. The following is a prototype of the callback function created by the monitoring thread:
Void
(* Pcreate_thread_policy_routine )(
In handle processid,
In handle threadid,
In Boolean create
);
Processid is the process number. Here, the process number points to the process that includes the thread, rather than the process that creates the thread. Threadid is the thread Number of the thread to be created. Create indicates whether to create or destroy a thread. The following is a prototype of the callback function created by the monitoring process:
Void
(* Pcreate_process_policy_routine )(
In handle parentid,
In handle processid,
In Boolean create
);
Parentid indicates the parent process number, processid indicates the process number, and create indicates the process to be created or destroyed.
With these two functions, we can monitor the creation and destruction activities of all processes and threads. Let's take a look at the code. I listed all the major code.
DriverEntry proc uses ESI, pdriverobject: pdriver_object, pusregistrypath: punicode_string
Local Status: ntstatus
Local pdeviceobject: pdevice_object
......
MoV g_dwprocessid, 0
MoV g_bmainthread, false
Lea eax, _ processcallback
Invoke pssetcreateprocesspolicyroutine, eax, false
Lea eax, _ threadcallback
Invoke pssetcreatethreadpolicyroutine, eax
MoV status, eax
......
DriverEntry endp
The above is the code part for registering the callback function. _ processcallback and _ threadcallback are process and thread monitoring functions respectively. The callback function is registered in the start part of the driver. You also need to remove the registered callback function from the uninstall part of the driver. The Code is as follows:
_ Driverunload proc pdriverobject: pdriver_object
Lea eax, _ processcallback
Invoke pssetcreateprocesspolicyroutine, eax, true
Lea eax, _ threadcallback
Invoke psremovecreatethreadpolicyroutine, eax
Invoke iodeletesymboliclink, ADDR g_ussymboliclinkname
MoV eax, pdriverobject
Invoke iodeletedevice, (driver_object PTR [eax]). deviceobject
RET
_ Driverunload endp
Pass true to the second parameter of the pssetcreateprocesspolicyroutine function to move the registered process callback function. To transfer the registered thread callback function, you need to call the psremovecreatethreadpolicyroutine function. This function is an undisclosed function provided after Windows XP and cannot be verified because it does not have a Windows 2000 system at hand, you can see if your Windows 2000 system has this function. Because this is an undisclosed function, it cannot be called directly and needs to be imported into the database. The method for generating import/export data is also very simple. You can use the inc2l tool in the masm32 software package. You can refer to the method for generating import/export data by masm32. This is why the ntoskrnl. Lib file needs to be overwritten.
To monitor a remote thread, you only need to register a thread callback function. To determine whether it is a remote thread, you must determine whether it is a remote thread based on the process that creates the thread and the process that contains the thread. Therefore, we also need to register a process callback function.
_ Processcallback proc uses ESI, parentid: DWORD, processid: DWORD, bcreate: DWORD
. If bcreate
MoV eax, processid
MoV g_dwprocessid, eax
MoV g_bmainthread, true
. Endif
RET
_ Processcallback endp
This is the process callback function. If a new process is created, set g_bmainthread to true and save the process ID to g_dwprocessid. Because when a new process is created, its main thread is not created by itself, but created by its parent process. Here, the parent process and its own process are definitely not the same process, but the primary thread created at this time is not a remote thread. The above code records the creation of the process, and the thread created next is not a remote thread.
_ Threadcallback proc uses ebx esi edi, processid: DWORD, threadid: DWORD, bcreate: DWORD
Local lpparenteprocess, lpeprocess
Local dwparentpid, dwparenttid
CMP g_bmainthread, true
Je exit_0
CMP bcreate, 0
Je exit_0
CMP processid, 4
Je exit_0
Invoke psgetcurrentprocessid
MoV dwparentpid, eax
CMP eax, processid
Je exit_0
Invoke psgetcurrentthreadid
MoV dwparenttid, eax
Invoke pslookupprocessbyprocessid, dwparentpid, ADDR lpparenteprocess
CMP eax, STATUS_SUCCESS
JNE exit_0
Invoke pslookupprocessbyprocessid, processid, ADDR lpeprocess
CMP eax, STATUS_SUCCESS
JNE exit_0
MoV ESI, lpparenteprocess
Add ESI, g_dwoffset
MoV EDI, lpeprocess
Add EDI, g_dwoffset
Invoke dbuplint, $ cta0 ("Caller: Name = % s pid = % d tid = % d/t the caller: name = % s pid = % d tid = % d/N "),/
ESI, dwparentpid, dwparenttid, EDI, processid, threadid
Exit_0:
MoV g_bmainthread, false
RET
_ Threadcallback endp
This code is a thread callback function and also our core code. First, determine whether the master thread of a process is created, if not, continue to judge. Is it creating a thread? If yes, continue to judge. Is the process a system process? If yes, ignore it. This is because every time a folder is opened and the folder named assumer.exe is switched, a remote thread is created in the system process, so we ignore it. You can comment out the two sentences and see the output of the program to understand them.
The callback function has only two parameters. One is processid, which indicates the process number containing the thread, and the other is threadid, which indicates the created thread number. Therefore, we also need to find the process number for creating the thread to compare whether the process of creating the thread and the process containing the thread are the same process and determine whether it is a remote thread. A problem is mentioned here. When a process creates a thread, the system calls our thread callback function in the context of the process, therefore, we can use the psgetcurrentprocessid function to obtain the process number. Get the thread number through psgetcurrentthreadid. Note that this thread is not the thread to be created, but the thread that contains the code for creating the thread.
The Code then calls an undisclosed function pslookupprocessbyprocessid to obtain the eprocess structure of a process. The eprocess structure is carried by w2kundoc in kmdkit. INC has a detailed description, saving the process name in the imagefilename Member of the eprocess structure. Because different imagefilename members in the system have different offset positions, a global variable g_dwoffset is used in different code of the system to save the offset. The following code determines the system:
Invoke psgetversion, null, ADDR g_dwsystemminorversion, null, null
. If g_dwsystemminorversion = 0
MoV g_dwoffset, 1fch
. Elseif g_dwsystemminorversion = 1
MoV g_dwoffset, 174 h
. Elseif g_dwsystemminorversion = 2
MoV g_dwoffset, 154 H
. Endif
After all the work is done, the collected information is output, which can be achieved through the dbuplint function.
You can use the kmdmanager tool that comes with kmdkit to register/run the remotethreadmonitor. SYS file generated by the code in this article, and then view the code output using dbgview or SoftICE. Knowing the thread Number of the remote thread, you can use tools such as process explorer to kill the remote thread.
References:
(1) Write a process/thread monitor using sinister
Http://www.xfocus.net/articles/200303/495.html
(2) DDK
(3) kmdkit
Http://www.freewebs.com/four-f/
(4) kmdtut Chinese Translation
Http://asm.yeah.net
The author of the following article is unknown.
/* Sometimes we want to dynamically monitor the creation and destruction of any process/thread in the system. To achieve
For this purpose, I have read the DDK manual and found the pssetcreateprocesspolicyroutine () provided by it (),
Pssetcreatethreadpolicyroutine (), and other functions can implement this function. These two functions can
Register a callbalck function with the system to monitor processes, threads, and other operations. The original function is as follows:
Ntstatus
Pssetcreateprocesspolicyroutine (
In pcreate_process_policy_routine policyroutine,
In Boolean remove
);
Void
(* Pcreate_process_policy_routine )(
In handle parentid,
In handle processid,
In Boolean create
);
Ntstatus
Pssetcreatethreadpolicyroutine (
In pcreate_thread_policy_routine policyroutine
);
Void
(* Pcreate_thread_policy_routine )(
In handle processid,
In handle threadid,
In Boolean create
);
The original form shows that the callback function only provides the process ID/thread ID. Not provided
Process name. Then we need to further obtain the process name through the process ID. This requires an undisclosed
Function pslookupprocessbyprocessid (). The original function is as follows:
Ntstatus pslookupprocpolicyprocessid (
In ulong ulprocid,
Out peprocess * peprocess
);
The eprocess structure of function output is also an undisclosed kernel process structure. Many people call it kpeb.
The offset 0x1fc in the eprocess structure points to the offset of the current process name. (Although this structure can be found in
Directly used in the driver. However, its structure has not been published. Many experts on the Internet have already provided its structure. Yes
If you are interested, you can search by yourself or get it from ifs DDK. The structure is too long, so you will not post it here)
With this structure, we can get the process name. The NT System also provides a function for dynamic monitoring.
Load the image according to the process. This function can obtain the DLL name and full path called when the process is planted.
Some image information. It provides us with more detailed process loading information and better help.
The original function is as follows:
Ntstatus
Pssetloadimagenotifyroutine (
In pload_image_policy_routine policyroutine
);
Void
(* Pload_image_policy_routine )(
In punicode_string fullimagename,
In handle processid, // Where image is mapped
In pimage_info imageinfo
);
Typedef struct _ image_info {
Union {
Ulong properties;
Struct {
Ulong imageaddressingmode: 8; // Code addressing mode
Ulong systemmodeimage: 1; // system mode image
Ulong imagemappedtoallpids: 1; // mapped in all processes
Ulong Reserved: 22;
};
};
Pvoid imagebase;
Ulong imageselector;
Ulong imagesize;
Ulong imagesectionnumber;
} Image_info, * pimage_info;
With the functions and structures provided above, we can implement a process/thread monitor. The following section
The Code demonstrates how to implement this function.
*/
/*************************************** **************************
File Name: wssprocmon. c
Description: Process/thread monitor.
By sinister
Last modification date: 2002-11-02
**************************************** *************************/
# Include "ntddk. H"
# Include "windef. H"
# Include "string. H"
# Define sysname "system"
// # Define processnameoffset 0x1fc
Ulong getprocessnameoffset ();
Void unload (in pdriver_object driverobject );
Static ntstatus mydrvdispatch (in pdevice_object deviceobject, in pirp );
Ntstatus pslookupprocessbyprocessid (in ulong ulprocid, out peprocess * peprocess );
Void processcreatemon (in handle hparentid, in handle PID, in Boolean bcreate );
Void threadcreatemon (in handle PID, in handle tid, in Boolean bcreate );
Void imagecreatemon (in punicode_string fullimagename, in handle processid, in pimage_info imageinfo );
// Driver entry
Ulong processnameoffset = 0;
Ntstatus DriverEntry (in pdriver_object driverobject, in punicode_string registrypath)
{
Unicode_string namestring, linkstring;
Pdevice_object deviceobject;
Ntstatus status;
Int I;
// Create a device
Rtlinitunicodestring (& namestring, l "// device // wssprocmon ");
Status = iocreatedevice (driverobject,
0,
& Namestring,
File_device_unknown,
0,
True,
& Deviceobject
);
If (! Nt_success (Status ))
Return status;
Rtlinitunicodestring (& linkstring, l "// dosdevices // wssprocmon ");
Status = iocreatesymboliclink (& linkstring, & namestring );
If (! Nt_success (Status ))
{
Iodeletedevice (driverobject-> deviceobject );
Return status;
}
Processnameoffset = getprocessnameoffset ();
/* Status = pssetloadimagenotifyroutine (imagecreatemon );
If (! Nt_success (Status ))
{
Dbuplint ("pssetloadimagenotifyroutine ()/n ");
Return status;
}*/
Status = pssetcreatethreadpolicyroutine (threadcreatemon );
If (! Nt_success (Status ))
{
Dbuplint ("pssetcreatethreadpolicyroutine ()/n ");
Return status;
}
Status = pssetcreateprocesspolicyroutine (processcreatemon, false );
If (! Nt_success (Status ))
{
Dbuplint ("pssetcreateprocesspolicyroutine ()/n ");
Return status;
}
For (I = 0; I <irp_mj_maximum_function; I ++)
{
Driverobject-> majorfunction [I] = mydrvdispatch;
}
Driverobject-> driverunload = unload;
Return STATUS_SUCCESS;
}
Void unload (in pdriver_object driverobject)
{
Unicode_string linkstring;
Psremovecreatethreadpolicyroutine (threadcreatemon );
Pssetcreateprocesspolicyroutine (processcreatemon, true );
Rtlinitunicodestring (& linkstring, l "// dosdevices // wssprocmon ");
Iodeletesymboliclink (& linkstring );
Iodeletedevice (driverobject-> deviceobject );
}
// Process device object operations
Static ntstatus mydrvdispatch (in pdevice_object deviceobject, in pirp IRP)
{
IRP-> iostatus. Status = STATUS_SUCCESS;
IRP-> iostatus. Information = 0l;
Iocompleterequest (IRP, 0 );
Return IRP-> iostatus. status;
}
Handle g_dwprocessid;
Bool g_bmainthread;
Void processcreatemon (in handle hparentid, in handle PID, in Boolean bcreate)
{
Peprocess eprocess;
Ulong ulcurrentprocessid;
Lptstr lpcurproc;
Ntstatus status;
# Ifdef _ amd64 _
Ulong processid = handletoulong (PID );
Status = pslookupprocessbyprocessid (processid, & eprocess );
# Else
Handle processid = PID;
Status = pslookupprocessbyprocessid (ulong) PID, & eprocess );
# Endif
If (! Nt_success (Status ))
{
Dbuplint ("pslookupprocessbyprocessid ()/n ");
Return;
}
If (bcreate)
{
// G_dwprocessid = processid;
G_bmainthread = true;
Lpcurproc = (lptstr) eprocess;
Lpcurproc = lpcurproc + processnameoffset;
Dbuplint ("create process = process name: % s, process parentid: % d, process ID: % d, process address % x:/N ",
Lpcurproc,
Hparentid,
PID,
Eprocess );
}
Else
{
Dbuplint ("terminated = process ID: % d/N", pid );
}
}
Void threadcreatemon (in handle PID, in handle tid, in Boolean bcreate)
{
Peprocess eprocess, parenteprocess;
Lptstr lpcurproc, lpparnentproc;
Ntstatus status;
# Ifdef _ amd64 _
Ulong system = 4;
Ulong dwparentpid = handletoulong (psgetcurrentprocessid (); // process that creates the thread
Ulong processid = handletoulong (PID );
Status = pslookupprocessbyprocessid (processid, & eprocess );
Status = pslookupprocessbyprocessid (dwparentpid, & parenteprocess );
# Else
Handle System = (handle) 4; // under XP, in Win2k is 8
Handle dwparentpid = psgetcurrentprocessid (); // process used to create the thread
Handle processid = PID; // processid indicates the process number. Here, the process number points to the process that includes the thread, rather than the process that creates the thread.
Status = pslookupprocessbyprocessid (ulong) processid, & eprocess );
Status = pslookupprocessbyprocessid (ulong) dwparentpid, & parenteprocess );
# Endif
If (! Nt_success (Status ))
{
Dbuplint ("pslookupprocessbyprocessid ()/n ");
Return;
}
If (bcreate)
{
If (g_bmainthread = true) & (processid! = System) & (processid! = Dwparentpid ))
{
Handle dwparenttid = psgetcurrentthreadid ();
Lpcurproc = (lptstr) eprocess;
Lpparnentproc = (lptstr) parenteprocess;
Lpcurproc ++ = processnameoffset;
Lpparnentproc + = processnameoffset;
Dbuplint ("Caller: Name = % s pid = % d tid = % d/T/tcalled: Name = % s pid = % d tid = % d/N ",/
Lpparnentproc, dwparentpid, dwparenttid, lpcurproc, processid, tid );
G_bmainthread = false;
}
Lpcurproc = (lptstr) eprocess;
Lpcurproc = lpcurproc + processnameoffset;
Dbuplint ("create thread = process name: % s process ID: % d, thread ID: % d/N", lpcurproc, PID, tid );
}
Else
{
Dbuplint ("terminated = thread ID: % d/N", tid );
}
}
// Void imagecreatemon (in punicode_string fullimagename, in handle processid, in pimage_info imageinfo)
//
//{
// Dbuplint ("fullimagename: % s, process ID: % d/N", fullimagename-> buffer, processid );
// Dbuplint ("imagebase: % x, imagesize: % d/N", imageinfo-> imagebase, imageinfo-> imagesize );
//}
Ulong getprocessnameoffset ()
{
Peprocess curproc;
Int I;
Curproc = psgetcurrentprocess ();
//
// Scan for 12kb, hopping the kpeb never grows that big!
//
For (I = 0; I <3 * page_size; I ++ ){
If (! Strncmp (sysname, (pchar) curproc + I, strlen (sysname ))){
Return I;
}
}
//
// Name not found-oh, well
//
Return 0;
}
According to your change to C code, and supports WINXP-64 .......