ZwCreateSection hook code

c++ / delphi package - dll injection and api hooking
iconic
Site Admin
Posts: 1065
Joined: Wed Jun 08, 2005 5:08 am

ZwCreateSection hook code

Post by iconic »

I decided to hook Nt/ZwCreateSection and deny a process creation which in this example I made calc.exe. Runner and others on this forum may benefit from this example and I believe that you don't need to hook NtCreateProcess/NtCreateProcessEx at all, this should be sufficient. Rather than determine the image name from a section handle I am using the FileHandle (if non-NULL) to query the name + path of the image and it seems to work fine, it only took me 20 mins. to use this approach. I don't intend to provide code to utilize the section handle and as a matter of fact, it's not initialized until the function returns anyhow. The benefit of hooking Nt/ZwCreateSection is it fires before a process is created so it's very early detection.

Code: Select all

library ntcp;

{$IMAGEBASE $5FFFFFFF}

uses 
  Windows,
  madCodeHook,
  JwaWinType in 'JwaWinType.pas',
  madStrings,
  messages;

  var
     ZwCreateSectionNext: function(var SectionHandle: Cardinal;
                                       DesiredAccess: ACCESS_MASK;
                                    ObjectAttributes: POBJECT_ATTRIBUTES;
                                         SectionSize: PLARGE_INTEGER;
                                             Protect: DWORD;
                                          Attributes: DWORD;
                                          FileHandle: DWORD): NTSTATUS; stdcall;
 type
    TZwQueryObject = function(ObjectHandle:           Integer;
                              ObjectInformationClass: Integer;
                              ObjectInformation:      Pointer;
                              Length:                 Integer;
                              var ResultLength:       Integer): NTSTATUS; stdcall;

 const
   FILE_EXECUTE = $00000020;
          NTDLL = 'ntdll.dll';


function IsNT: Boolean; stdcall;
begin
 result := ((GetVersion() and $80000000)= 0);
end;

function NT_SUCCESS(const Status: Integer): WordBool;
begin
 result := status >= 0;
end;


function ExtractFileName(FileName: string): string;
begin
 result := FileName;
  while Pos('\', result) <> 0 do
  Delete(result, 1, Pos('\', result));
  while Pos('/', result) <> 0 do
  Delete(result, 1, Pos('/', result));
end;


function FileNameFromFileHandle(const hF: HFILE): string; stdcall;
 type
  UNICODE_STRING = packed record
           len: Word;
        maxlen: Word;
           buf: PWideChar;
 end;
  OBJECT_NAME_INFORMATION = record
   name: UNICODE_STRING;
 end;
 const
  ONI = 1;
  UNICODE_MAX_PATH = MAX_PATH *sizeof(WCHAR);
 var
                ret: Integer;
             status: Integer;
               pONI: ^OBJECT_NAME_INFORMATION;
      ZwQueryObject: TZwQueryObject;
begin
  result := '';
  @ZwQueryObject := GetProcAddress(GetModuleHandleW('ntdll.dll'), 'ZwQueryObject');
  if @ZwQueryObject = nil then
  Exit;
  GetMem(pONI, UNICODE_MAX_PATH);
  ZeroMemory(pOni, sizeof(OBJECT_NAME_INFORMATION));
  status := ZwQueryObject(hF, ONI, @pONI^, UNICODE_MAX_PATH, ret);  
if NT_SUCCESS(status) then
  result := WideToAnsiEx(@pONI^.name.buf^);
  FreeMem(pONI);
end;


function ZwCreateSectionCb(var SectionHandle: Cardinal;
                                     DesiredAccess: ACCESS_MASK;
                                  ObjectAttributes: POBJECT_ATTRIBUTES;
                                       SectionSize: PLARGE_INTEGER;
                                           Protect: DWORD;
                                        Attributes: DWORD;
                                        FileHandle: DWORD): NTSTATUS; stdcall;
begin                                           //* Todo:+ check protection against FILE_EXECUTE ?
if not (AmSystemProcess)                   and  //..
       (FileHandle <> INVALID_HANDLE_VALUE)and  // validate FileHandle
       (Attributes = SEC_IMAGE)            and  // executable image file
       (FileHandle > 0)                    then // == 0 backed up by paging file ?
begin
   if lstrcmpi(PChar(ExtractFileName(FileNameFromFileHandle(FileHandle))), 'calc.exe') <> 0 then
   result := ZwCreateSectionNext(
                                 SectionHandle,
                                 DesiredAccess,
                                 ObjectAttributes,
                                 SectionSize,
                                 Protect,
                                 Attributes,
                                 FileHandle
                                 )
                                 else
                                  result := NTSTATUS($C0000022);  //* Access Denied
                              end
           else
   result := ZwCreateSectionNext(
                                 SectionHandle,
                                 DesiredAccess,
                                 ObjectAttributes,
                                 SectionSize,
                                 Protect,
                                 Attributes,
                                 FileHandle
                                 );
end;

Procedure DLLMain(Code: Integer);
   begin 
    case Code of 
     DLL_PROCESS_DETACH: {};
     DLL_PROCESS_ATTACH:
     begin 
     if IsNT() then
     HookAPI(NTDLL, 'ZwCreateSection', @ZwCreateSectionCb, @ZwCreateSectionNext, SAFE_HOOKING);
      end; 
    end;
 end;

begin
 DLLProc := @DLLMain;
 DLLMain(DLL_PROCESS_ATTACH);
end.
--Iconic
Last edited by iconic on Thu Jul 20, 2006 3:20 am, edited 5 times in total.
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

Nice!

One little complaint: I'm not sure whether ZwCreateSection counts as being a "wide API". If it does, calling GetModuleHandleA and GetProcAddressA violates hooking rule 7.
iconic
Site Admin
Posts: 1065
Joined: Wed Jun 08, 2005 5:08 am

Post by iconic »

good question, I'm not 100% positive either. NT family tends to favor unicode while 9x favored ANSI so I would want to agree with you but both share same export memory address ( *Zw / *Nt ) in ring3 (you mentioned and I confirmed awhile back) and only differ in system level (ring0) where Zw should really be used, and Nt prefix is mainly (intended) to be used at application level (ring3). Both do the exact same thing and I noticed no problems at all when running the example I wrote. Also, I forget what the Zw prefix actually stands for, JEDI had a footnote on it once, but I never remember that "w" in "Zw" meaning unicode. To be on the safe side though, users who decide to mess with this should probably swap out the prefixes (Zw -> Nt) in the code since kernel mode uses unicode religiously and Zw prefixed APIs in NTDLL are technically a direct kernel service that extends into usermode.

--Iconic
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

That was not what I meant. NtCreateSection and ZwCreateSection are identical (in application land). But all native APIs are unicode by nature. So I'm wondering whether it's a good idea to use GetProcAddressA and GetModuleHandleA. I'd suggest to change that to GetProcAddressW and GetModuleHandleW.
iconic
Site Admin
Posts: 1065
Joined: Wed Jun 08, 2005 5:08 am

Post by iconic »

NtCreateSection and ZwCreateSection are identical (in application land).
yes.

So I'm wondering whether it's a good idea to use GetProcAddressA and GetModuleHandleA.

I see your point and I wonder too, I don't know. I would think one needs to use unicode APIs to deal with these native functions everywhere but I've yet to see it anywhere online. I rarely see win32 unicode APIs such as GetProcAddressW or GetModuleHandleW used on Native APIs? Are all these other code authors wrong, is it laziness, forgetfulness, or the fact that Windows can sometimes be forgiving and makes a harmless translation from ansi <-> unicode behind the scenes?

Since Native APIs operate with structures of counted unicode strings one would think that it's for sure a Unicode API so perhaps it is best to make the switch on those 2 APIs. I was browsing the forum and not to nit-pick or anything but I see rules getting more than bent nearly everytime I see a code example. If you look at these Native API callbacks most people use ANSI functions in the callback of an alleged "unicode API", I thought that was also a violation of your hooking rules?
I'd suggest to change that to GetProcAddressW and GetModuleHandleW.
Agreed. Although it most likely won't make too much of a difference as far as stability :D it most likely is correct.

EDIT: changes have been made. :wink:

--Iconic
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

iconic wrote:I would think one needs to use unicode APIs to deal with these native functions everywhere but I've yet to see it anywhere online. I rarely see win32 unicode APIs such as GetProcAddressW or GetModuleHandleW used on Native APIs?
When you only *use* the native APIs you're free to do whatever you like. The problem is only there if you *hook* a wide API and call ansi APIs inside of the callback function.
iconic wrote:Since Native APIs operate with structures of counted unicode strings one would think that it's for sure a Unicode API so perhaps it is best to make the switch on those 2 APIs. I was browsing the forum and not to nit-pick or anything but I see rules getting more than bent nearly everytime I see a code example. If you look at these Native API callbacks most people use ANSI functions in the callback of an alleged "unicode API", I thought that was also a violation of your hooking rules?
Yes, using ansi APIs inside of wide API callbacks is violating hooking rule 7. However, using ansi *functions* can be ok. Basically it depends on what these functions do inside. What we need to avoid is that any of the OS ansi -> wide conversion routines are called inside of a wide API callback.
iconic wrote:Agreed. Although it most likely won't make too much of a difference as far as stability :D it most likely is correct.
The thing with stability is that things can be stable in 99% of all cases, but suddenly in only one OS with only one application, you're ending up in stability problems. There was a time, when I didn't know hooking rule 7 myself. Somebody reported to me that in w2k (and only there) he had problems with one specific notepad replacement application. I analyzed that and found out about hooking rule 7. Afterwards I repeatedly stumbled over the very same thing. Sometimes it's unstable everywhere, sometimes only on some OSs in specific situations.

To be honest, until today I haven't even found out *why* exactly hooking rule 7 must be followed. I do not know. But the OS ansi -> wide conversion routines can make problems when being called in a hook callback function of a wide API.
jjlucsy
Posts: 76
Joined: Tue Sep 27, 2005 1:34 am

GetProcAddressW

Post by jjlucsy »

I was under the impression there is only GetProcAddress, no A or W version as PE headers store only ANSI. Am I missing something? My kernel32.dll only exports GetProcAddress.
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

You're quite right, my mistake - thanks for the correction!
jjlucsy
Posts: 76
Joined: Tue Sep 27, 2005 1:34 am

Post by jjlucsy »

I figured as much. :wink: I've done that myself a number of times. Just wanted to clarify for those less informed than we. :D
iconic
Site Admin
Posts: 1065
Joined: Wed Jun 08, 2005 5:08 am

Post by iconic »

Good deal.
I figured that much but didn't bother to check. Nonetheless there is a GetModuleHandleA/W and the only thing that the "W" version should do is take in unicode and then convert it to ansi.

--Iconic
jjlucsy
Posts: 76
Joined: Tue Sep 27, 2005 1:34 am

Post by jjlucsy »

iconic wrote:Nonetheless there is a GetModuleHandleA/W and the only thing that the "W" version should do is take in unicode and then convert it to ansi.
Actually, I believe under NT and above its the other way around, the GetModuleHandleA converts to Unicode and calls the GetModuleHandleW. I'm pretty sure that the kernel deals with Unicode exclusively. I know for a fact LoadLibraryA calls LoadLibraryW.
iconic
Site Admin
Posts: 1065
Joined: Wed Jun 08, 2005 5:08 am

Post by iconic »

kernel deals with Unicode exclusively
Yes.

From what I've read, haven't bothered to check, it's the way I put it. I think the module names are stored in ANSI but I'm not sure, you be may correct. *Edit* come to think of it GetModuleHandleA should pass it to GetModuleHandleW and make the unicode conversion from ANSI. Not all ansi APIs call the unicode version of the same API though (if one exists). It is common in some calls but it's not a rule.

--Iconic
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

In winNt most (not all, but most) ansi APIs end up in wide APIs.
iconic
Site Admin
Posts: 1065
Joined: Wed Jun 08, 2005 5:08 am

updated code example

Post by iconic »

I ran a few more tests and think that the end result is pretty stable altogether. I got rid of the SysUtils unit, the SAFE_HOOKING flag in HookAPI(), added a PAGE_EXECUTE check and now it asks the application whether to allow/deny the creation of the image.

I found that injecting/uninjecting repeatedly with the safe_hooking flag definitely caused problems and it was usually when I was uninjecting.
You guys are free to modify it as you like, and there still is a possibility of missing a process due to several reasons. The check for PAGE_EXECUTE should probably be extended to handle the possibility of other ORed flags in the Protect parameter (obviously) and of course if the FileHandle = 0 (since it's optional) the section is backed in memory and should be handled specially. I've grown bored with the test and just wanted to see if Nt/ZwCreateSection could be hooked with a stable outcome.


//DLL code

Code: Select all

library ntcp; 

{$IMAGEBASE $5FFFFFFF}

uses 
  Windows, 
  madCodeHook, 
  JwaWinType in 'JwaWinType.pas', 
  madStrings, 
  messages; 

  var 
     ZwCreateSectionNext: function(var SectionHandle: Cardinal; 
                                       DesiredAccess: ACCESS_MASK; 
                                    ObjectAttributes: POBJECT_ATTRIBUTES; 
                                         SectionSize: PLARGE_INTEGER; 
                                             Protect: DWORD; 
                                          Attributes: DWORD; 
                                          FileHandle: DWORD): NTSTATUS; stdcall; 
 type 
    TZwQueryObject = function(ObjectHandle:           Integer; 
                              ObjectInformationClass: Integer; 
                              ObjectInformation:      Pointer; 
                              Length:                 Integer; 
                              var ResultLength:       Integer): NTSTATUS; stdcall; 

 const 
   PAGE_EXECUTE = $00000010;
          NTDLL = 'ntdll.dll';


 function AllowProcess(ProcessName: string): BOOL;
begin 
  result := False; 
  SendIpcMessage('App', PChar(ProcessName), Length(ProcessName)+1, @result, sizeof(result)); 
end; 



function IsNT: Boolean; stdcall;
begin 
 result := ((GetVersion() and $80000000)= 0); 
end; 

function NT_SUCCESS(const Status: Integer): WordBool; 
begin 
 result := status >= 0; 
end; 


function ExtractFileName(FileName: string): string;
begin 
 result := FileName; 
  while Pos('\', result) <> 0 do 
  Delete(result, 1, Pos('\', result)); 
  while Pos('/', result) <> 0 do 
  Delete(result, 1, Pos('/', result));
end;

function isExe(const FileInfo: string): BOOL; stdcall;
begin
 result := lstrcmpi(PChar(Copy(FileInfo, Length(FileInfo)-3, 4)), '.exe') = 0;
end;


function FileNameFromFileHandle(const hF: HFILE): string; stdcall; 
 type 
  UNICODE_STRING = packed record 
           len: Word;
        maxlen: Word;
           buf: PWideChar;
 end; 
  OBJECT_NAME_INFORMATION = record 
   name: UNICODE_STRING; 
 end; 
 const 
  ONI = 1; 
  UNICODE_MAX_PATH = MAX_PATH *sizeof(WCHAR); 
 var 
                ret: Integer; 
             status: Integer; 
               pONI: ^OBJECT_NAME_INFORMATION; 
      ZwQueryObject: TZwQueryObject;
begin 
  result := ''; 
  @ZwQueryObject := GetProcAddress(GetModuleHandleW('ntdll.dll'), 'ZwQueryObject'); 
  if @ZwQueryObject = nil then 
  Exit; 
  GetMem(pONI, UNICODE_MAX_PATH); 
  ZeroMemory(pOni, sizeof(OBJECT_NAME_INFORMATION)); 
  status := ZwQueryObject(hF, ONI, @pONI^, UNICODE_MAX_PATH, ret);  
  if NT_SUCCESS(status) then
  result := ExtractFileName(WideToAnsiEx(@pONI^.name.buf^));
  FreeMem(pONI);
end;


function ZwCreateSectionCb(var SectionHandle: Cardinal; 
                                     DesiredAccess: ACCESS_MASK;
                                  ObjectAttributes: POBJECT_ATTRIBUTES;
                                       SectionSize: PLARGE_INTEGER;
                                           Protect: DWORD;
                                        Attributes: DWORD;
                                        FileHandle: DWORD): NTSTATUS; stdcall;
var strFile: string;
begin
if not (AmSystemProcess)                   and  //.. 
       (FileHandle <> INVALID_HANDLE_VALUE)and  // validate FileHandle 
       (Attributes = SEC_IMAGE)            and  // executable image file
       (FileHandle > 0)                    then // == 0 backed up by paging file ?
begin 
   strFile := FileNameFromFileHandle(FileHandle);
   if not isExe(strFile) then
    result := ZwCreateSectionNext(
                                 SectionHandle,
                                 DesiredAccess,
                                 ObjectAttributes,
                                 SectionSize,
                                 Protect,
                                 Attributes,
                                 FileHandle
                                 )
                                 else
   if AllowProcess(strFile) then
   result := ZwCreateSectionNext( 
                                 SectionHandle, 
                                 DesiredAccess, 
                                 ObjectAttributes,
                                 SectionSize,
                                 Protect, 
                                 Attributes, 
                                 FileHandle 
                                 ) 
                                 else 
                                  result := NTSTATUS($C0000022);  //* Access Denied 
                              end 
           else 
   result := ZwCreateSectionNext(
                                 SectionHandle,
                                 DesiredAccess,
                                 ObjectAttributes,
                                 SectionSize,
                                 Protect,
                                 Attributes,
                                 FileHandle
                                 );
   RenewHook(@ZwCreateSectionNext);
end; 

Procedure DLLMain(Code: Integer); 
   begin 
    case Code of 
     DLL_PROCESS_DETACH: {}; 
     DLL_PROCESS_ATTACH: 
     begin 
     if IsNT() then 
     HookAPI(NTDLL, 'ZwCreateSection', @ZwCreateSectionCb, @ZwCreateSectionNext);
      end; 
    end; 
 end; 

begin 
 DLLProc := @DLLMain; 
 DLLMain(DLL_PROCESS_ATTACH); 
end. 

The form code test just has a listbox on the form and that's it.
// Exe code

Code: Select all

procedure AddAllowedProcess(sname: string);
begin
 SendMessage(FindWindowEx(FindWindow('TForm1', nil), 0, 'TListBox', nil), LB_INSERTSTRING, 0, Integer(PChar(sName)));
end;

function isProcessUnknown(sname: string): Boolean;
begin
 result := SendMessage(FindWindowEx(FindWindow('TForm1', nil), 0, 'TListBox', nil), LB_FINDSTRINGEXACT, -1, Integer(PChar(sName))) = LB_ERR;
end;

procedure IPC_CB(name      : PChar; 
                 messageBuf: Pointer; 
                 messageLen: DWORD; 
                 answerBuf : Pointer; 
                 answerLen : DWORD); stdcall; 
begin
if not isProcessUnknown(PChar(MessageBuf)) then
begin
 BOOL(AnswerBuf^):= True;
 Exit;
end;
if MessageBox(0, PChar(MessageBuf), 'Allow this Process to Spawn?', MB_YESNO or MB_TASKMODAL or MB_TOPMOST) = ID_YES
 then
 begin
 BOOL(AnswerBuf^):= True;
 AddAllowedProcess(PChar(MessageBuf));
 end
 else
  BOOL(AnswerBuf^):= False;
end;

initialization
 begin
  CreateIPCQueue('App', ipc_cb);
  InjectLibrary(ALL_SESSIONS or SYSTEM_PROCESSES, 'ntcp.dll');
 end;

finalization
 UnInjectLibrary(ALL_SESSIONS or SYSTEM_PROCESSES, 'ntcp.dll');
--Iconic
Runner
Posts: 90
Joined: Tue Dec 14, 2004 1:04 pm

Post by Runner »

Sorry for being late. A was quite bussy lately.

That is great iconic. I was not aware that this is possible. My worries were that if denying the CreateSection to succed I would cause stabilty problems. So that is why i just traced NtCreateSection and the used the info in NtCreateProcess/Ex. I will try this aproach and see how it works. Thanks again for the info :wink:

Edit: But you only catch exe files. I was catching also .bat .com and other wanabe executables.
Post Reply