Madshi, elimination of kernel driver?
Madshi, elimination of kernel driver?
Hello Mathias,
I have a question about system and session-wide DLL injection via your kernel driver that's been plaguing me for a couple of years, with all due respect as mch works wonderfully for me.
Why are you not simply injecting into the csrss process and hooking csrsrv.dll!CsrCreateProcess() for process creation notifications instead of using a driver where you need to worry about signing for your users, on their part? You are provided a valid hProcess and hThread as well as their respective identifiers and even nt session information. If you allow copying a function to a native process (smss) you can even hook process creations for it and then reinject the same core csrss hooking module into any future csrss processes created in other sessions (i.e> XP fast user switching, Vista system process session isolation etc.) easily.
I mentioned this because, as you know, csrss is the process supervisor but the smss process is the parent process to it respectively (governing any/all sessions), naturally if you hook the native create process function in the smss process you will catch all session specific csrss child processes. It is just as effective as PsSetCreateProcessNotifyRoutine() in ring0 for catching ALL processes before initialization but does not require kernel mode access and driver signing in x64 OS environments.
Even asynchronous procedure calls can be used for injection at this point because all threads are created in an alertable state as they are suspended before initially running, once they run/execute they initialize their APC queue and functions queued are executed first, ensuring that you do not miss any process entrypoint API calls. I have written a demo which does exactly what I am referring to and I achieve the exact same thing as your driver all from usermode! I can do the exact same in kernel mode as you do with your driver but again this requires a lot of work on your part for 64-bit operating systems and can trouble users when driver signing is concerned. Also, installed kernel event notifications are limited to 8 for a specific type and if the queue is filled your driver callback installation fails. Who hooks native process creation functions for this purpose? I've seen none thus far yet what I have detailed makes complete sense both conceptually and logically implementation-wise.
P.S> Last I tested, csrss' csrsrv.dll!CsrCreateProcess() shared the exact same function prototype from Windows NT 3.51 / Windows 2k -> Windows 7. If you also want process exit/termination events you can optionally hook csrsrv.dll!CsrDestroyProcess() but this only catches parent processes terminating along with their respective exit status codes, enumerating child processes is easy however as I am sure you know via chaining parent->child process links. Nonetheless, process termination is not important really... just creation since modules have to be injected prior to its main function or entrypoint being called.
--Iconic
I have a question about system and session-wide DLL injection via your kernel driver that's been plaguing me for a couple of years, with all due respect as mch works wonderfully for me.
Why are you not simply injecting into the csrss process and hooking csrsrv.dll!CsrCreateProcess() for process creation notifications instead of using a driver where you need to worry about signing for your users, on their part? You are provided a valid hProcess and hThread as well as their respective identifiers and even nt session information. If you allow copying a function to a native process (smss) you can even hook process creations for it and then reinject the same core csrss hooking module into any future csrss processes created in other sessions (i.e> XP fast user switching, Vista system process session isolation etc.) easily.
I mentioned this because, as you know, csrss is the process supervisor but the smss process is the parent process to it respectively (governing any/all sessions), naturally if you hook the native create process function in the smss process you will catch all session specific csrss child processes. It is just as effective as PsSetCreateProcessNotifyRoutine() in ring0 for catching ALL processes before initialization but does not require kernel mode access and driver signing in x64 OS environments.
Even asynchronous procedure calls can be used for injection at this point because all threads are created in an alertable state as they are suspended before initially running, once they run/execute they initialize their APC queue and functions queued are executed first, ensuring that you do not miss any process entrypoint API calls. I have written a demo which does exactly what I am referring to and I achieve the exact same thing as your driver all from usermode! I can do the exact same in kernel mode as you do with your driver but again this requires a lot of work on your part for 64-bit operating systems and can trouble users when driver signing is concerned. Also, installed kernel event notifications are limited to 8 for a specific type and if the queue is filled your driver callback installation fails. Who hooks native process creation functions for this purpose? I've seen none thus far yet what I have detailed makes complete sense both conceptually and logically implementation-wise.
P.S> Last I tested, csrss' csrsrv.dll!CsrCreateProcess() shared the exact same function prototype from Windows NT 3.51 / Windows 2k -> Windows 7. If you also want process exit/termination events you can optionally hook csrsrv.dll!CsrDestroyProcess() but this only catches parent processes terminating along with their respective exit status codes, enumerating child processes is easy however as I am sure you know via chaining parent->child process links. Nonetheless, process termination is not important really... just creation since modules have to be injected prior to its main function or entrypoint being called.
--Iconic
Re: Madshi, elimination of kernel driver?
madCodeHook 1.x used a user-mode-only approach. Basically madCodeHook hooked NtCreateProcess(Ex) in all processes and "spread itself" into all newly created processes. It worked great on my PC. But I got reports from some of my customers that after a while this all stopped working sometimes on some machines, for whatever reason.iconic wrote:Why are you not simply injecting into the csrss process and hooking csrsrv.dll!CsrCreateProcess() for process creation notifications instead of using a driver where you need to worry about signing for your users, on their part? You are provided a valid hProcess and hThread as well as their respective identifiers and even nt session information.
There a number of reasons why I prefer the kernel mode approach over hooking e.g. CsrCreateProcess():
(1) Hooking CsrCreateProcess() would probably work, but it would be easy enough for malware to create a process without CsrCreateProcess() being used. The malware would just have to patch the (Nt)CreateProcess code to not notify csrss.
(2) When doing all the work through csrss, all injection is done in user land. Some security products complain about doing user mode injection. Well, madCodeHook needs to inject the hook dll into already running processes, too, so there will be complaints, anyway. But the injection from kernel mode doesn't bother security products as much.
(3) The kernel mode approach is tried and proven. It's very stable and reliable. Why should I replace that with a different solution which might be less stable? Even if it works on the dev PC, the problem is that madCodeHook must run on every single Windows PC in the world without issues, or else my customers will complain. So I need to use the most reliable method available, just to be safe.
(4) Just imagine another hooking library would also hook SMSS and csrss, using your suggested method. There might be conflicts and madCodeHook might lose and injection would stop working.
(5) The driver signing requirement has the advantage of shutting out most malware programmers. So the danger of madCodeHook 3.0 being misused by malware programmers is *much* lower than it was for madCodeHook 2.0. If malware programmers do find a way to get a valid signing certificate, they also make their software easily detectable. And their certificate can be revoked.
(6) The madCodeHook 3.0 kernel driver solution works in that way that basically every of my customer has his own driver, which is totally separate and indepdendent of any other madCodeHook customer. This is extra safety. Basically it should be very difficult (or maybe even impossible) for malware programmers to misuse the madCodeHook kernel mode driver of a legit software for their bad purpose. If I used your csrss user mode solution, it would be much more difficult to separate different madCodeHook customers from each other.
I understand that the driver signing requirement is a problem for some people. But to be honest, for me its also a good protection against malware misuse. And for "real companies" getting a signing certificate is not much of a problem. Many of my customers already had a certificate, anyway. Ok, some of them had a certificate that can only sign dll and exe files, but not drivers. That's the most annoying thing.
BTW, the limitation to 8 notifications is long gone. E.g. in Vista it's been raised to 64.
Hi Mathias,
From your response I understand but keep in mind that preventing malware was not my reason for posting because the idea would have to be targeted specifically, as would even your driver. Ease of use for the end-user was my reason for posting without needing a driver or a signed certificate.
Let's face it, if real malware was present, even a kernel mode driver callback is far from untouchable if things got ugly. If things are stable and "proven" then I agree, keep things the same as before for the sake of stability. I never suggested that you replace it entirely, perhaps a "backup" mode for system-wide injection would have been my idea from ring3 is all because not every developer, especially for personal use, own a valid certificate for driver signing.
--Iconic
From your response I understand but keep in mind that preventing malware was not my reason for posting because the idea would have to be targeted specifically, as would even your driver. Ease of use for the end-user was my reason for posting without needing a driver or a signed certificate.
Let's face it, if real malware was present, even a kernel mode driver callback is far from untouchable if things got ugly. If things are stable and "proven" then I agree, keep things the same as before for the sake of stability. I never suggested that you replace it entirely, perhaps a "backup" mode for system-wide injection would have been my idea from ring3 is all because not every developer, especially for personal use, own a valid certificate for driver signing.
--Iconic
But there are different degrees of security. Your suggested solution would be rather easy to bypass for any malware. You'd not even need admin rights for that cause the malware would only have to patch ntdll.dll in its own process.iconic wrote:Let's face it, if real malware was present, even a kernel mode driver callback is far from untouchable if things got ugly.
But my main concern these days is that I want/need to be safe from further *misuse* of madCodeHook for malware development. And the whole madCodeHook 3.0 concept of requiring driver signing should help a lot with that...
I usually don't even sell madCodeHook licenses for "personal use", anymore. I mostly sell only to companies these days. Because with "personal use" developers there's almost no way for me to be sure whether the developer will be misusing for malware writing or not. Even with companies I'm double checking every new customer thouroughly.iconic wrote:If things are stable and "proven" then I agree, keep things the same as before for the sake of stability. I never suggested that you replace it entirely, perhaps a "backup" mode for system-wide injection would have been my idea from ring3 is all because not every developer, especially for personal use, own a valid certificate for driver signing.
-
- Posts: 109
- Joined: Thu Dec 30, 2004 9:59 pm
- Location: UK
I completely agree with Madshi on this, sorry iconic... i think its much better having the kernel method. The signing of the driver is a pain for debugging, but a simple batch script makes it easy enough.
The signing also means its a lot easier to identify malware... how many malware sign their files?
I like that the driver and dll's are signed by me.. this way customers do not need to ask "is this file yours? .. our AV keeps moaning about it" - now they know its for our products and ignores it or excludes it from scanning.
Again, the Kernel method is proven and it works. Changing to user-land to 'help' a few people who cant signt the drivers would simply open a whole can of worms for malware programmers again.
The signing also means its a lot easier to identify malware... how many malware sign their files?
I like that the driver and dll's are signed by me.. this way customers do not need to ask "is this file yours? .. our AV keeps moaning about it" - now they know its for our products and ignores it or excludes it from scanning.
Again, the Kernel method is proven and it works. Changing to user-land to 'help' a few people who cant signt the drivers would simply open a whole can of worms for malware programmers again.
Re: Madshi, elimination of kernel driver?
Dear Iconic,
I found your idea of injecting into csrss process and hooking csrsrv.dll!CsrCreateProcess() very exciting. You said "I have written a demo which ...."
Would it be possible to share this demo code with me. I would appreciate this very much.
Regards
Nikola
I found your idea of injecting into csrss process and hooking csrsrv.dll!CsrCreateProcess() very exciting. You said "I have written a demo which ...."
Would it be possible to share this demo code with me. I would appreciate this very much.
Regards
Nikola
iconic wrote:Hello Mathias,
I have a question about system and session-wide DLL injection via your kernel driver that's been plaguing me for a couple of years, with all due respect as mch works wonderfully for me.
Why are you not simply injecting into the csrss process and hooking csrsrv.dll!CsrCreateProcess() for process creation notifications instead of using a driver where you need to worry about signing for your users, on their part? You are provided a valid hProcess and hThread as well as their respective identifiers and even nt session information. If you allow copying a function to a native process (smss) you can even hook process creations for it and then reinject the same core csrss hooking module into any future csrss processes created in other sessions (i.e> XP fast user switching, Vista system process session isolation etc.) easily.
I mentioned this because, as you know, csrss is the process supervisor but the smss process is the parent process to it respectively (governing any/all sessions), naturally if you hook the native create process function in the smss process you will catch all session specific csrss child processes. It is just as effective as PsSetCreateProcessNotifyRoutine() in ring0 for catching ALL processes before initialization but does not require kernel mode access and driver signing in x64 OS environments.
Even asynchronous procedure calls can be used for injection at this point because all threads are created in an alertable state as they are suspended before initially running, once they run/execute they initialize their APC queue and functions queued are executed first, ensuring that you do not miss any process entrypoint API calls. I have written a demo which does exactly what I am referring to and I achieve the exact same thing as your driver all from usermode! I can do the exact same in kernel mode as you do with your driver but again this requires a lot of work on your part for 64-bit operating systems and can trouble users when driver signing is concerned. Also, installed kernel event notifications are limited to 8 for a specific type and if the queue is filled your driver callback installation fails. Who hooks native process creation functions for this purpose? I've seen none thus far yet what I have detailed makes complete sense both conceptually and logically implementation-wise.
P.S> Last I tested, csrss' csrsrv.dll!CsrCreateProcess() shared the exact same function prototype from Windows NT 3.51 / Windows 2k -> Windows 7. If you also want process exit/termination events you can optionally hook csrsrv.dll!CsrDestroyProcess() but this only catches parent processes terminating along with their respective exit status codes, enumerating child processes is easy however as I am sure you know via chaining parent->child process links. Nonetheless, process termination is not important really... just creation since modules have to be injected prior to its main function or entrypoint being called.
--Iconic
Re: Madshi, elimination of kernel driver?
Hello ngidalov,
That post is over a year old and I unfortunately don't keep proof-of-concepts (PoCs) laying around for too long on my hard drive but I do have a csrss folder containing the csrsrv.dll!CsrCreateProcess() hook code on one of my thumbdrives. I can clean it up and post it here if that helps you at all. Let me know.
--Iconic
That post is over a year old and I unfortunately don't keep proof-of-concepts (PoCs) laying around for too long on my hard drive but I do have a csrss folder containing the csrsrv.dll!CsrCreateProcess() hook code on one of my thumbdrives. I can clean it up and post it here if that helps you at all. Let me know.
--Iconic
Re: Madshi, elimination of kernel driver?
Hello iconic.
Thank you for your reply.
Yes, I'm very interested in hooking csrsrv.dll!CsrCreateProcess() and I would be very happy to see the example code
Waiting for your next post,
Regards,
Nikola
Thank you for your reply.
Yes, I'm very interested in hooking csrsrv.dll!CsrCreateProcess() and I would be very happy to see the example code
Waiting for your next post,
Regards,
Nikola
Re: Madshi, elimination of kernel driver?
I apologize for the delay but I've been very busy lately.
I am pasting my process watcher proof-of-concept code here for anyone to learn from. I chose to hook csrsrv.dll!CsrCreateProcess() inside the csrss process to accomplish this task. Keep in mind, you would need to modify this demo to inject the same hook DLL into csrss processes in other sessions, each session has its own csrss process. Vista session isolation, Windows XP fast user switching etc.
// Hook DLL code
// Executable Control Code
Project source and binaries can be downloaded here http://bugczech.fu8.com/csrss_process_watcher.rar
Csrss in the current session is injected with the hook dll, the dll hooks CsrCreateProcess in order to catch newly created processes. Inside the hook callback we gather the process id and filenames for both the parent process and the to-be-spawned process. We ask the executable whether to allow or deny the process creation through madCodeHook's IPC functions. If allowed, the filename is stored in a listbox so the user isn't prompted again for that respective process. If denied, we queue an asynchronous procedure call (APC) to the target process' main thread which in turn executes NtTerminateProcess on itself which terminates the target process before its entrypoint is ever called. I chose to do this for a few reasons, returning anything other than STATUS_SUCCESS / NTSTATUS(0) will cause the OS to display error dialogs when denying a process. By allowing the process to be created we aren't breaking anything and we still can reliably and cleanly destroy the process with an APC before it ever gets a chance to execute its entrypoint.
Also, since newly created processes are caught at such an early stage standard Win32 APIs for obtaining filenames will fail i.e> psapi.dll!GetModuleFileNameExA/W(). This is because the process address space isn't completely initialized yet along with other internal structures such as the process environment block (PEB) so I use the section-backed memory to determine the filename. In order to query the correct module filename we need to first determine the imagebase / load address of the main executable. Luckily, the PEB field for the process ImageBase is filled in correctly so this is all we need.
If anybody has any questions I can always address them, I think that I have explained the project's inner-workings as best as possible. Keeping it in layman terms isn't always an easy thing to do when you're dealing with native APIs and undocumented structures.
--Iconic
I am pasting my process watcher proof-of-concept code here for anyone to learn from. I chose to hook csrsrv.dll!CsrCreateProcess() inside the csrss process to accomplish this task. Keep in mind, you would need to modify this demo to inject the same hook DLL into csrss processes in other sessions, each session has its own csrss process. Vista session isolation, Windows XP fast user switching etc.
// Hook DLL code
Code: Select all
library csrcp_hook;
{$IMAGEBASE $5a800000}
uses
Windows, madCodeHook;
const
IPC = 'csrcp';
var
NtTerminateProcess: Pointer = nil;
function NtQueryInformationProcess(hProcess: ULONG;
ProcessInformationClass: ULONG;
ProcessInformation: Pointer;
ProcessInformationLength: ULONG;
ReturnLength: PULONG): Integer; stdcall; external 'ntdll.dll' name 'NtQueryInformationProcess';
function NtQueryVirtualMemory(hProcess: ULONG;
BaseAddress: Pointer;
MemoryInformationClass: ULONG;
MemoryInformation: Pointer;
MemoryInformationLength: ULONG;
ReturnLength: PULONG): Integer; stdcall; external 'ntdll.dll' name 'NtQueryVirtualMemory';
function NtQueueApcThread(hThread: ULONG;
ApcRoutine: Pointer;
ApcContext: Pointer;
Arg1: Pointer;
Arg2: Pointer): Integer; stdcall; external 'ntdll.dll' name 'NtQueueApcThread';
type
PCLIENT_ID = ^CLIENT_ID;
CLIENT_ID = packed record
ProcessId: ULONG;
ThreadId: ULONG;
end;
type
PUNICODE_STRING = ^UNICODE_STRING;
UNICODE_STRING = packed record
Len: Word;
MaxLen: Word;
Buf: PWChar;
end;
type IPC_DATA =
packed record
PPid: DWORD;
Pid: DWORD;
Parent: Array [0..MAX_PATH] of Char;
Process: Array [0..MAX_PATH] of Char;
end;
PIPC_DATA = ^IPC_DATA;
type
PPROCESS_BASIC_INFORMATION = ^PROCESS_BASIC_INFORMATION;
PROCESS_BASIC_INFORMATION = packed record
ExitStatus: ULONG;
PebBaseAddress: Pointer;
AffinityMask: ULONG;
BasePriority: ULONG;
UniqueProcessId: ULONG;
InheritedFromUniqueProcessId: ULONG;
end;
NTSTATUS = Integer;
const
STATUS_SUCCESS: NTSTATUS = 0;
var
CsrCreateProcessNext: function(hProcess: THandle;
hThread: THandle;
ClientId: PCLIENT_ID;
NtSession: Pointer;
Flags: DWORD;
DebugCid: PCLIENT_ID): NTSTATUS; stdcall = nil;
function GetPebBase(const hProcess: ULONG): ULONG;
var
pbi: PROCESS_BASIC_INFORMATION;
dwRet: ULONG;
const
ProcessBasicInformation = 0;
begin
result := 0;
if NtQueryInformationProcess(hProcess, ProcessBasicInformation, @pbi,
sizeof(pbi), @dwRet) = STATUS_SUCCESS then
result := ULONG(pbi.PebBaseAddress);
end;
function GetParentProcessId(const hProcess: ULONG): ULONG;
var
pbi: PROCESS_BASIC_INFORMATION;
ret: ULONG;
begin
result := 0;
if NtQueryInformationProcess(hProcess,
0,
@pbi,
sizeof(pbi),
@ret) = STATUS_SUCCESS then
result := pbi.InheritedFromUniqueProcessId;
end;
function AllowProcess(Data: PIPC_DATA): BOOL;
begin
if not SendIpcMessage(IPC, Data, sizeof(Data^), @result, sizeof(result)) then
result := True;
end;
function GetImageBase(const hProcess: ULONG; dwBase: PULONG): BOOL;
var dwRead: DWORD;
const
Offs = 8;
begin
dwBase^ := 0;
result := ReadProcessMemory(hProcess, Ptr(GetPebBase(hProcess) + Offs),
dwBase, sizeof(dwBase^), dwRead) and (dwRead = sizeof(dwBase^));
end;
function GetProcessImageName(const hProcess: ULONG; lpFileName: PChar; nSize: ULONG): BOOL;
var
Ret: ULONG;
us: PUNICODE_STRING;
lpv: DWORD;
const
MEM_SECTION_NAME = 2;
AllocSz = (MAX_PATH * sizeof(WCHAR)) + (sizeof(WORD) * 2) + sizeof(DWORD);
begin
result := False;
if (hProcess > 0) and (nSize > 0) and (lpFileName <> nil) then
begin
if GetImageBase(hProcess, @lpv) then
begin
us := VirtualAlloc(nil, AllocSz, MEM_COMMIT, PAGE_READWRITE);
if us <> nil then
begin
if (NtQueryVirtualMemory(hProcess, Ptr(lpv), MEM_SECTION_NAME, us, AllocSz, @Ret) = STATUS_SUCCESS) and
(Ret <> DWORD(-1)) then
begin
ZeroMemory(lpFileName, nSize);
if (nSize >= (us^.Len div sizeof(WCHAR))) then
result := WideCharToMultiByte(CP_ACP, 0, us^.Buf, -1, lpFileName, us^.Len, nil, nil) <> 0
else
SetLastError(ERROR_INSUFFICIENT_BUFFER);
end;
VirtualFree(us, 0, MEM_RELEASE);
end;
end;
end;
end;
function _TerminateProcess(const hThread: DWORD): BOOL;
begin
result := NtQueueApcThread(hThread, NtTerminateProcess, Ptr(DWORD(-1)), nil, nil) = STATUS_SUCCESS;
end;
function CsrCreateProcessCB(hProcess: THandle;
hThread: THandle;
ClientId: PCLIENT_ID;
NtSession: Pointer;
Flags: DWORD;
DebugCid: PCLIENT_ID): NTSTATUS; stdcall;
var
hParentProcess: THandle;
Data: IPC_DATA;
const
dwAccess = (PROCESS_QUERY_INFORMATION or PROCESS_VM_READ);
begin
result := CsrCreateProcessNext(hProcess, hThread, ClientId, NtSession, Flags, DebugCid);
if (result = STATUS_SUCCESS) then
begin
ZeroMemory(@Data, sizeof(Data));
GetProcessImageName(hProcess, @Data.Process, MAX_PATH);
Data.Pid := ClientID^.ProcessId;
Data.PPid := GetParentProcessId(hProcess);
hParentProcess := OpenProcess(dwAccess, False, Data.PPid);
GetProcessImageName(hParentProcess, @Data.Parent, MAX_PATH);
if hParentProcess > 0 then
CloseHandle(hParentProcess);
if not AllowProcess(@Data) then
_TerminateProcess(hThread);
end;
end;
procedure DLLMain(dwReason: DWORD);
begin
case dwReason of
DLL_PROCESS_ATTACH:
begin
DisableThreadLibraryCalls(hInstance);
NtTerminateProcess := GetProcAddress(GetModuleHandle('ntdll.dll'), 'NtTerminateProcess');
HookAPI('csrsrv.dll', 'CsrCreateProcess', @CsrCreateProcessCB, @CsrCreateProcessNext);
end;
DLL_PROCESS_DETACH:
UnhookApi(@CsrCreateProcessNext);
DLL_THREAD_ATTACH: {};
DLL_THREAD_DETACH: {};
end;
end;
begin
DLLProc := @DLLMain;
DLLMain(DLL_PROCESS_ATTACH);
end.
Code: Select all
unit uCP_Control;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, madCodeHook, StdCtrls;
type
TForm1 = class(TForm)
lb: TListBox;
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
var
CritSec: _RTL_CRITICAL_SECTION;
const
uFlags = (MB_TOPMOST or MB_ICONQUESTION or MB_SETFOREGROUND or MB_YESNO);
ipc_name = 'csrcp';
type
IPC_DATA = packed record
ParentPid: DWORD;
ProcessId: DWORD;
Parent: Array [0..MAX_PATH] of Char;
Process: Array [0..MAX_PATH] of Char;
end;
PIPC_DATA = ^IPC_DATA;
procedure MakeWin32Path(var FileName: string);
var
ch1: Char;
path: Array [0..MAX_PATH] of Char;
i1, i2: DWORD;
s1: string;
begin
if FileName <> '' then
begin
FileName := Trim(FileName);
s1 := FileName;
i2 := 0;
if s1[1] <> '\' then
begin
s1 := '\' + FileName;
inc(i2);
end;
for ch1 := 'A' to 'Z' do
begin
ZeroMemory(@path, sizeof(path));
if QueryDosDevice(PChar(string(ch1) + ':'), @path, sizeof(path)) <> 0 then
begin
i1 := lstrlenA(path);
if lstrcmpiA(@Path, PChar(Copy(s1, 1, i1))) = 0 then
begin
Delete(FileName, 1, i1 - i2);
FileName := ch1 + ':' + FileName;
end;
end;
end;
end;
end;
procedure AddProcess(const Process: string);
begin
Form1.LB.Items.Add(Process);
end;
function isProcessUnknown(const Process: string): BOOL;
begin
result := Form1.LB.Items.IndexOf(Process) = -1;
end;
procedure IPC_CB(name: PChar; messageBuf: Pointer;
messageLen: DWORD; answerBuf: Pointer;
answerLen: DWORD); stdcall;
var s1, s2: string;
begin
EnterCriticalSection(CritSec);
try
with PIPC_DATA(messageBuf)^ do
begin
SetLength(s1, lstrlenA(Process));
CopyMemory(@s1[1], @Process, Length(s1));
MakeWin32Path(s1);
BOOL(AnswerBuf^) := not isProcessUnknown(s1);
if not BOOL(AnswerBuf^) then
begin
SetLength(s2, lstrlenA(Parent));
CopyMemory(@s2[1], @Parent, Length(s2));
MakeWin32Path(s2);
BOOL(AnswerBuf^) := MessageBox(0,
PChar(Format('[%u]Parent: %s'#$0D#$0A'[%u]Process: %s', [ParentPid, s2,
ProcessId, s1])), 'Allow Process to Spawn?', uFlags) = IDYES;
if BOOL(AnswerBuf^) then
AddProcess(s1);
SetLength(s2, 0);
end;
SetLength(s1, 0);
end;
finally
LeaveCriticalSection(CritSec);
end;
end;
function GetCsrssPid: ULONG;
begin
// ntdll.dll!CsrGetProcessId() would work too for >= XP
GetWindowThreadProcessId(GetDesktopWindow(), @result);
end;
function InjectCsrss(bInject: BOOL): BOOL;
var hProcess: DWORD;
begin
result := False;
hProcess := OpenProcess(PROCESS_ALL_ACCESS, False, GetCsrssPid());
if hProcess > 0 then
begin
if bInject then
result := InjectLibrary(hProcess, 'csrcp_hook.dll')
else
result := UnInjectLIbrary(hProcess, 'csrcp_hook.dll');
CloseHandle(hProcess);
end;
end;
initialization
isMultiThread := True;
InitializeCriticalSection(CritSec);
if not CreateIPCQueue(ipc_name, ipc_cb) or
not InjectCsrss(True) then
Halt;
finalization
DeleteCriticalSection(CritSec);
DestroyIPCQueue(ipc_name);
InjectCsrss(False);
end.
Csrss in the current session is injected with the hook dll, the dll hooks CsrCreateProcess in order to catch newly created processes. Inside the hook callback we gather the process id and filenames for both the parent process and the to-be-spawned process. We ask the executable whether to allow or deny the process creation through madCodeHook's IPC functions. If allowed, the filename is stored in a listbox so the user isn't prompted again for that respective process. If denied, we queue an asynchronous procedure call (APC) to the target process' main thread which in turn executes NtTerminateProcess on itself which terminates the target process before its entrypoint is ever called. I chose to do this for a few reasons, returning anything other than STATUS_SUCCESS / NTSTATUS(0) will cause the OS to display error dialogs when denying a process. By allowing the process to be created we aren't breaking anything and we still can reliably and cleanly destroy the process with an APC before it ever gets a chance to execute its entrypoint.
Also, since newly created processes are caught at such an early stage standard Win32 APIs for obtaining filenames will fail i.e> psapi.dll!GetModuleFileNameExA/W(). This is because the process address space isn't completely initialized yet along with other internal structures such as the process environment block (PEB) so I use the section-backed memory to determine the filename. In order to query the correct module filename we need to first determine the imagebase / load address of the main executable. Luckily, the PEB field for the process ImageBase is filled in correctly so this is all we need.
If anybody has any questions I can always address them, I think that I have explained the project's inner-workings as best as possible. Keeping it in layman terms isn't always an easy thing to do when you're dealing with native APIs and undocumented structures.
--Iconic