Monitoring Processes on Windows NT from Usermode (x86 & x64)

Forum for discussion about user-mode development.
User avatar
Mut4nt
Posts: 19
Joined: Wed May 30, 2012 5:41 am
Location: Russian Federation

Sat Nov 10, 2012 6:01 pm

Well as we know on Windows NT there is no callback function ( From user mode ) to do this task,
although there are implementations that can do it.

For example, we can use the callback that Windows OS provides us to monitor all the windows that are created
then obtain their handles, get the process ID belonging to that window and compare the PID
with previously obtained list, which will have all the IDs of processes that have been created in
the system, but I think this technique is more resource-intensive than the one I will show you today.

Well, on Windows NT, explorer.exe is one of the most important processes, that is responsible for many tasks
including the initiation of all programs the user requests (not processes created by other programs, services, drivers), assigning a token, priority and so on.
Our the task is to set a hook on CreateProcessInternal function in explorer to ensure that
any process started is first passed through our hook. We can set a hook in one more Internal
function, but that is not necessary, because explorer specifically calls this Function.

Usually CreateProcessInternalW looks like this:


PUSH 624
PUSH 76084980
CALL 7607417C
MOV EAX,DWORD PTR SS:[EBP+8]
MOV DWORD PTR SS:[EBP-360],EAX
MOV EDX,DWORD PTR SS:[EBP+C]
MOV DWORD PTR SS:[EBP-334],EDX
MOV ESI,DWORD PTR SS:[EBP+10]
MOV DWORD PTR SS:[EBP-33C],ESI
MOV EAX,DWORD PTR SS:[EBP+14]
MOV DWORD PTR SS:[EBP-464],EAX
...


But this could change according to the windows version or service pack, but don't think we can just add these instructions
and hope for everything to work by itself.
Ok, because we need the first 5 bytes for our hook, we must copy these bytes to overwrite and
then execute them, luckily this instruction:

PUSH 624

has an exact size of 5 bytes, the hooking function will be this
( passing function address as the first parameter of CreateProcessInternal and the new function address as the second parameter):

Code: Select all

ULONG Hook(LPBYTE lpCreateProcessInternal,LPBYTE JMPAddress)
{
     DWORD     PrevProtect;

     if(lpCreateProcessInternal)
     {
          if(VirtualProtect((LPVOID)lpCreateProcessInternal,5,PAGE_EXECUTE_READWRITE,&PrevProtect))
          {
               // Get Old bytes
               OldBytes[0] = lpCreateProcessInternal[0];
               *(PDWORD)&OldBytes[1] = *(PDWORD)&lpCreateProcessInternal[1];
               
               // calculating relative address
               *(PDWORD)&NewBytes[1] = (DWORD)((ULONG)JMPAddress - (ULONG)lpCreateProcessInternal - 5);
               
               // Set hook
               lpCreateProcessInternal[0] =  NewBytes[0];
               *(PDWORD)&lpCreateProcessInternal[1] = *(PDWORD)&NewBytes[1];
               
               // restore protection
               VirtualProtect((LPVOID)lpCreateProcessInternal,5,PrevProtect,NULL);
               
               // get relative JMP to Function+5  ( will be optimized by the compiler )
               *(PDWORD)&OldBytes[6] = (DWORD)((ULONG)(lpCreateProcessInternal + 5) - ((ULONG)&OldBytes[5]) - 5 );

               // disable DEP
               VirtualProtect((LPVOID)OldBytes,10,PAGE_EXECUTE_READWRITE,&PrevProtect);

               // success
               return 1;
          }
     }
     
     return 0;
}
the new function will jump this:

Code: Select all

ULONG WINAPI HookCreateProcessInternal(	
                    HANDLE 	               hToken,
                    LPCWSTR 	               lpApplicationName,
                    LPWSTR 	               lpCommandLine,
                    LPSECURITY_ATTRIBUTES 	lpProcessAttributes,
                    LPSECURITY_ATTRIBUTES 	lpThreadAttributes,
                    BOOL 	               bInheritHandles,
                    DWORD 	               dwCreationFlags,
                    LPVOID 	               lpEnvironment,
                    LPCWSTR 	               lpCurrentDirectory,
                    LPSTARTUPINFOW 	     lpStartupInfo,
                    LPPROCESS_INFORMATION 	lpProcessInformation,
                    PHANDLE 	               hNewToken)
{
     ULONG                         RetStatus;
     DefCreateProcessInternal      CreateProcessInternal;

     /* We can modify the parameters */
     
     CreateProcessInternal = (DefCreateProcessInternal) &OldBytes[0];
     RetStatus = CreateProcessInternal( hToken,
                                        lpApplicationName,
                                        lpCommandLine,
                                        lpProcessAttributes,
                                        lpThreadAttributes,
                                        bInheritHandles,
                                        dwCreationFlags,
                                        lpEnvironment,
                                        lpCurrentDirectory,
                                        lpStartupInfo,
                                        lpProcessInformation,
                                        hNewToken);
     
     /* Check the success */
     if(RetStatus)
     {
          /* We can modify/show the Results */
          MessageBoxW(0,lpApplicationName,lpApplicationName,MB_OK);
     }
     else
     {
          /* Error,  */ 
          MessageBoxW(0,"ERROR","ERROR",MB_OK);
          
     }
     
     return RetStatus;
}
In this function, we simply call the stub which contains the original 5 bytes from the hooked function and a
jump to the rest of the original function and a return address to the rest of the function hook.
Then we can use any API to display a string (unicode)

WINDOWS 8


Apparently, on Windows 8 things have not changed either. Now the main module is KernelBase.dll.
Actually kernelbase and kernel32 have the definition of CreateProcessInternal but Kernel32@CreateProcessInternal
is just a wrapper because it will always jump to KernelBase@CreateProcessInternal.

Futhermore, Windows 8 doesn't call CreateProcessInternal directly, it is first calling a undocumented
function on the SHELL module. CreateProcessInternal on Windows 8 looks like this:

CreateProcessInternal:
MOV EDI,EDI
PUSH EBP
MOV EBP,ESP
PUSH 0x0FFFFFFFE
PUSH 74e824e8
...

Our program can work on windows 8 too, just change the name of the module to "kernelbase".

Now we just need to inject our code into explorer.exe, doing that we can :

[*] Block initialization of any program
[*] Obtain information about any process
[*] Inject code to every process ran by the user
[*] Use your imagination :)

For example: To block an application from being run, just change the input arguments to invalid and the process will fail.

And here's the unhook function to clean everything up:

Code: Select all

ULONG UnHook(LPBYTE lpFunctionAddress)
{
     DWORD     PrevProtect;
     
     if(VirtualProtect((LPVOID)lpFunctionAddress,5,PAGE_EXECUTE_READWRITE,&PrevProtect))
     {
          lpFunctionAddress[0] =  OldBytes[0];
          *(PDWORD)&lpFunctionAddress[1] = *(PDWORD)&OldBytes[1];
          VirtualProtect((LPVOID)lpFunctionAddress,5,PrevProtect,NULL); 
     }
     
     return 0;
}
Full source code inattached
code was tested on:
Windows XP + SP2 (x86)
Windows XP + SP3 (x86)
Windows Vista (x86)
WIndows 7 (x86)
WIndows 7 + SP1 (x86)
Windows 8 Developer preview (x86)

64-bit Version:
In attached as well.
You do not have the required permissions to view the files attached to this post.
User avatar
EP_X0FF
Global Moderator
Posts: 4905
Joined: Sun Mar 07, 2010 5:35 am
Location: Russian Federation
Contact:

Sun Nov 11, 2012 6:42 am

Not impressive at all. Basically the above post is about how to set splicing hook for one function. BTW your splicing method is buggy and will cause crash in some circumstances. Additionally it's not really viable because you need to inject your code in every process since not only Explorer can spawn new processes. And if we speak about malware - the better way is to splice NtResumeThread which always called when new process is created. And yes, instead of your method this one already used in multiple malwares like Carberp, SpyEye, backdoor bots, lots of them and will be used in future.

If we speak about monitoring then instead of this ugly solution better use built-in Windows feature called "Application Security" and used in Windows terminal services. You can create your own certification dll and use from Windows 2000 until Windows 8 without any compatibility problems like in case of your code.

Bellow is example of such certification dll code.

main.c

Code: Select all

#include <windows.h>
#include "main.h"

#pragma warning (disable: 4995)  //deprecated

EXPORT_FN NTSTATUS NTAPI CreateProcessNotify ( 
	LPCWSTR lpApplicationName, 
	ULONG uNotifyReason 
	) 
{
	switch(uNotifyReason) 
	{
	case APPCERT_IMAGE_OK_TO_RUN: 

		OutputDebugString(lpApplicationName);
		OutputDebugString(TEXT("[AppCertDll::CreateProcessNotify] APPCERT_IMAGE_OK_TO_RUN"));
		return STATUS_SUCCESS;

	case APPCERT_CREATION_ALLOWED:

		OutputDebugString(TEXT("[AppCertDll::CreateProcessNotify] APPCERT_CREATION_ALLOWED"));
		return STATUS_SUCCESS;

	case APPCERT_CREATION_DENIED:

		OutputDebugString(TEXT("[AppCertDll::CreateProcessNotify] APPCERT_CREATION_DENIED"));
		return STATUS_SUCCESS;

	default:
		return STATUS_SUCCESS;
	}
}

BOOL WINAPI DllMain(
	HINSTANCE hinstDLL,
	DWORD fdwReason,
	LPVOID lpvReserved
	)
{
	return TRUE;
}
The above callback will be called in case if someone will try to spawn new process. This method also give you safe ability to decide - allow or disallow startup by returning appropriate Status value. Use APPCERT_IMAGE_OK_TO_RUN case. Others are for notification purposes only.

main.h

Code: Select all

#define NTAPI __stdcall
#define EXPORT_FN __declspec(dllexport)

#define NT_SUCCESS(Status)               ((NTSTATUS)(Status) >= 0)
#define STATUS_UNSUCCESSFUL              ((NTSTATUS)0xC0000001L)
#define STATUS_SUCCESS					 ((NTSTATUS)0x00000000L)

#define APPCERT_IMAGE_OK_TO_RUN   0x00000001L
#define APPCERT_CREATION_ALLOWED  0x00000002L
#define APPCERT_CREATION_DENIED   0x00000003L

EXPORT_FN NTSTATUS NTAPI CreateProcessNotify ( 
    LPCWSTR lpApplicationName, 
    ULONG uReason 
    );
export.def

Code: Select all

EXPORTS
	CreateProcessNotify
Installation of this dll of course will require administrator rights, well in your case splicing in multiple processes running from diffrent accounts also will require it so not really difference.

Installation - open HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager and create key AppCertDlls. Put inside REG_EXPAND_SZ value with path to your monitoring dll. E.g. (AppSecDll=C:\windows\mydll.dll). Reboot to let Session Manager reinitialize. After reboot this feature will work. For x64 processes it is required of course x64 variant of this dll.

As a fun side of this. Mark Russinovich Autoruns will fail to detect this dll (despite the fact I have pointed him about this regkey in 2007 year, check sysinternals), because of bug inside autoruns - it is incorrectly assumes that AppCertDlls is reg value, not key as it actually is. This mistake presents in autoruns for about few years.

As for malware which uses above cerification dll method, here is example http://www.microsoft.com/security/porta ... if.gen%21H
Ring0 - the source of inspiration
User avatar
Mut4nt
Posts: 19
Joined: Wed May 30, 2012 5:41 am
Location: Russian Federation

Sun Nov 11, 2012 8:22 am

Hi EP_X0FF,

I don't know if you read my post,well, I quote itselft:
that is responsible for many tasks
including the initiation of all programs the user requests (not processes created by other programs, services, drivers), assigning a token, priority and so on.
Of course that any program can to create another processes, that's so obvious :o

Futhermore, If I hook ZwResumeThread I could get information from processes ( process name, PID, so on ) unless I use aditional function of course.
also, ZwResumeThread couldn't block the processes completly, when ZwResumeThreas is called the process which will be create is in SUSPENDED mode.

regarding my hooking method, Yeah it IS NOT a engine, which can verify the opcode intruction to know the size, type of opcode, so on. it's just a PoC, do you
know what's a PoC? it's not a malware :lol:

About your method, I don't like because it requires administrator rights as you said.
to aditional information I know another method ( always on usermode ) which can monitoring all processes that are created in the system ( including processes created by drivers,services,child process, so on ) lol

By the way, your method requires reboot, right? ... Very strong.
User avatar
EP_X0FF
Global Moderator
Posts: 4905
Joined: Sun Mar 07, 2010 5:35 am
Location: Russian Federation
Contact:

Sun Nov 11, 2012 8:34 am

Mut4nt wrote:Futhermore, If I hook ZwResumeThread I could get information from processes ( process name, PID, so on ) unless I use aditional function of course.
also, ZwResumeThread couldn't block the processes completly, when ZwResumeThreas is called the process which will be create is in SUSPENDED mode.
Modify thread context and resume -> new process will die. Is it not enough smart for you?

Your method requires multiple patching which will trigger all kind of HIPS software. Very strong.
About your method, I don't like because it requires administrator rights as you said.
Your method too - otherwise its pure bullshit, because processes from different users also can spawn other processes, surprise?
Futhermore, If I hook ZwResumeThread I could get information from processes ( process name, PID, so on ) unless I use aditional function of course.
Sure, you said this.
Ring0 - the source of inspiration
User avatar
kmd
Posts: 271
Joined: Mon Mar 15, 2010 4:09 am
Location: Russian Federation

Sun Nov 11, 2012 11:48 am

its good doing something mutant, thanks..

but how to say... no offense... if it were posted 10-12 years ago then something like it would be worth.. somehow. But in 2012 year post about inline hook?
Well as we know on Windows NT there is no callback function ( From user mode ) to do this task
thats not true, see above posts..
In this function, we simply call the stub which contains the original 5 bytes from the hooked function
this lame man, what if there hook like this?

ff25 xxxx

then you execute half of instuction and jump over in trash. You need at least length disassembler.
User avatar
R00tKit
Posts: 129
Joined: Tue Nov 16, 2010 8:23 pm
Contact:

Sun Nov 11, 2012 12:51 pm

monitor process all way Kernel-User : http://x64blog.name/tag/APC

thanks EP for AppCertDlls 8-)
@R00tkitSMM
User avatar
kmd
Posts: 271
Joined: Mon Mar 15, 2010 4:09 am
Location: Russian Federation

Sun Nov 11, 2012 2:24 pm

@EP_X0FF

regarding to your method reboot is really requered?
User avatar
EP_X0FF
Global Moderator
Posts: 4905
Joined: Sun Mar 07, 2010 5:35 am
Location: Russian Federation
Contact:

Sun Nov 11, 2012 2:38 pm

kmd wrote:@EP_X0FF

regarding to your method reboot is really requered?
Is not a necessary requirement.

Lets say:

Process A. You install monitoring dll. It wont affect already running processes like A. If process A spawns process B, process B not trigger this dll. But if process B spawns C it will trigged dll. As tested on XP when I was playing around this method in 2007. So when you do reboot, you are making sure that all processes spawned by A will trigger dll. Or you can simple re-start Explorer. So, no reboot as necessary requirement.
Ring0 - the source of inspiration
User avatar
kmd
Posts: 271
Joined: Mon Mar 15, 2010 4:09 am
Location: Russian Federation

Sun Nov 11, 2012 2:48 pm

EP_X0FF wrote:Is not a necessary requirement.

Lets say:

Process A. You install monitoring dll. It wont affect already running processes like A. If process A spawns process B, process B not trigger this dll. But if process B spawns C it will trigged dll. As tested on XP when I was playing around this method in 2007. So when you do reboot, you are making sure that all processes spawned by A will trigger dll. Or you can simple re-start Explorer. So, no reboot as necessary requirement.
so if i want to bypass this mechanism guess it enough locate and patch BasepIsProcessAllowed, right?
User avatar
EP_X0FF
Global Moderator
Posts: 4905
Joined: Sun Mar 07, 2010 5:35 am
Location: Russian Federation
Contact:

Sun Nov 11, 2012 2:54 pm

kmd wrote:
EP_X0FF wrote:Is not a necessary requirement.

Lets say:

Process A. You install monitoring dll. It wont affect already running processes like A. If process A spawns process B, process B not trigger this dll. But if process B spawns C it will trigged dll. As tested on XP when I was playing around this method in 2007. So when you do reboot, you are making sure that all processes spawned by A will trigger dll. Or you can simple re-start Explorer. So, no reboot as necessary requirement.
so if i want to bypass this mechanism guess it enough locate and patch BasepIsProcessAllowed, right?
If there already running code with enough privileges - this is not a problem. Like most of security features they won't work if overall security already been cracked. This mechanism isn't intended to block bad code that already executing. However kernel mode notifications or driver filter is of course more strong solutions, but better to stay away from solving everything at privileged level, and we speak about usermode isn't?
Ring0 - the source of inspiration
Post Reply