BSOne.SFC/Tocsg.Module/Bs1Flt/MTPMon/dist/_madCodeHook/madCodeHook.pas

12546 lines
547 KiB
Plaintext

// ***************************************************************
// madCodeHook.pas version: 4.2.2 · date: 2023-03-08
// -------------------------------------------------------------
// API/code hooking, DLL injection
// -------------------------------------------------------------
// Copyright (C) 1999 - 2023 www.madshi.net, All Rights Reserved
// ***************************************************************
// 2023-03-08 4.2.2 updated user mode DLL injection to match latest driver
// 2021-12-27 4.2.1 (1) fixed a CET bug
// (2) fixed rare crash when installing API hook
// (3) fixed rare issue with user mode injection into DotNet
// (4) fixed rare crash with weird docker configurations
// (5) added INJECT_ALLOW_THREAD flag
// 2021-05-12 4.2.0 (1) rewrite of many assembler stubs to make Intel CET happy
// (2) fixed: GetStoredThreadState() sometimes failed
// (3) fixed an IPC vulnerability
// (4) improved IPC reliability under heavy stress
// 2020-07-16 4.1.2 (1) re-added VMWARE_INJECTION_MODE option
// (2) added SET_SAFE_HOOKING_TIMEOUT option
// (3) added ALWAYS_CHECK_HOOKS option
// (4) SendIpcMessage now defaults to not handle messages
// (5) fixed potential thread timing bug in DestroyIpcQueue
// (6) return to trusted "old" user mode injection method
// 2018-11-17 4.1.1 (1) added ex/including Metro app injection functionality
// (2) added support for selectively activating IAT injection
// 2018-07-28 4.1.0 (1) added USE_IAT_DLL_INJECTION option
// (2) DLLs with no export are rejected for USE_IAT_DLL_INJECTION
// (3) little change to make latest Application Verifier happy
// (4) added "USE_ABSOLUTE_JMP" HookAPI/Code() flag
// 2018-05-17 4.0.5 (1) added support for driver DLL inject approval callback
// (2) added "callback" parameters to InjectLibraryA/W
// (3) avoid crash when uninstalling API hooks in Edge
// (4) improved LoadLibrary hook thread safety
// (5) avoid deadlock while checking for new/removed DLLs
// (6) improved ProcessIdToFileName for wow64 processes
// (7) added DISABLE_LDR_LOAD_DLL_SPECIAL_HOOK option
// (8) added DISABLE_PARALLEL_DLL_LOADING option
// 2017-12-22 4.0.4 (1) fixed: sending 32bit IPC from system to user failed
// (2) fixed: sending IPC from RuntimeBroker.exe could fail
// (3) fixed: memory leak in ProcessIdToFileName
// 2017-07-12 4.0.3 (1) fixed: initialization could eventually (rarely) crash
// (2) improved DestroyIpcQueue to avoid leaks and freezes
// (3) improved Chrome sandbox uninjection
// (4) improved "FOLLOW_JMP" to work with Bitdefender x64
// (5) CreateIpcQueue supports a custom security descriptor
// 2017-03-29 4.0.2 added "HOOK_LOAD_LIBRARY" option
// 2017-03-21 4.0.1 (1) fixed: uninject callback failed if no API was hooked
// (2) fixed: injecting dlls from within rundll failed
// (3) fixed: IPC answer didn't always arrive
// (4) fixed: dll injection handle leak
// (5) improved chrome sandbox uninjection
// (6) improved GetCallingModule reliability
// (7) faster performance when checking newly loaded dlls
// (8) added new "LIMITED_IPC_PORT" option
// 2016-08-24 4.0.0 (1) added option to do "permanent" DLL injection
// (2) added "STORE_THREAD_STATE" HookAPI flag
// (3) added "GetStoredThreadState()" API
// (4) added "RegisterUninjectCallback()" API
// (5) added extended support for hooking "ordinal" APIs
// (6) added "VERIFY_HOOK_ADDRESS" performance option
// (7) added "DISASM_HOOK_TARGET" performance options
// (8) hooks use 5-byte JMP now to improve compatability
// (9) improved injection method for newly created processes
// (a) improved safe unhooking counter logic
// (b) "CreateIpcQueueEx" renamed to "CreateIpcQueue"
// (c) added IPC "context" parameter
// (d) former "SECURE_MEMORY_MAPS" option is always on now
// (e) former "USE_NEW_IPC_LOGIC" option is always on now
// (f) former "VMWARE_INJECTION_MODE" option no longer needed
// (g) some small performance improvements and bugfixes
// (h) dropped win9x and NT4 support
// 2016-05-19 3.1.13 (1) fixed: regression could cause finalization crashes
// (2) fixed: SendIpcMessage blocked if queue didn't exist
// 2016-05-17 3.1.12 (1) fixed: some chrome shutdown crashes (when debugging)
// (2) fixed: hook uninstall could crash (when debugging)
// (3) fixed: SAFE_HOOKING could crash after uninjection
// (4) fixed: IPC reply sometimes didn't arrive (missing PID)
// (5) fixed: hook stub was allocated at wrong address (x64)
// 2016-03-16 3.1.11 (1) fixed: x64 jmp/call relocation miscalculation
// (2) added hook to detect delay loaded dlls
// (3) new process dll inject now always done in main thread
// (4) small performance improvements
// (5) fixed some PAGE_EXECUTE_READWRITE security issues
// (6) fixed rare crash when calling HookAPI
// (7) dll injection loader lock improvement
// 2015-09-10 3.1.10 (1) fixed: threading issue when to-be-hooked dll is loaded
// (2) fixed: some conflicts with other hook libraries (x64)
// (3) improved thread protection for multiple injections
// 2015-04-20 3.1.9 (1) fixed: rare injection/hook instability bug
// (2) fixed: rare IPC stability bug
// 2014-10-26 3.1.8 (1) fixed: RestoreCode sometimes produced incorrect code
// (2) fixed: hooking ntdll in non-large-address-aware x64
// processes crashed
// (3) FOLLOW_JMP now follows up to 10 JMPs in a row
// 2013-12-03 3.1.6 (1) fixed: CreateProcessEx failed for .Net processes
// (2) fixed: two rare crashes
// 2013-10-01 3.1.5 (1) added XE5 support
// (2) added support for Windows 8.1
// (3) improved FOLLOW_JMP implementation
// (4) revert aligned UNICODE_STRING (compatability problems)
// 2013-05-13 3.1.4 (1) fixed: FOLLOW_JMP eventually modified export tables
// (2) fixed: win9x hooking eventually crashed
// (3) aligned UNICODE_STRING in internal structure
// (4) "driver only" injection now works without admin rights
// 2013-03-13 3.1.3 (1) fixed: IPC messages sometimes contained wrong session id
// (2) fixed: uninjecting DLL twice at the same time crashed
// (3) fixed: injecting multiple 32bit dlls in x64 OS crashed
// (4) added new FOLLOW_JMP flag for HookAPI/Code
// 2012-09-05 3.1.2 (1) added support for Metro (AppContainer integrity) apps
// (2) fixed: uninjection crash in w2k3 error reporting service
// 2012-08-20 3.1.1 fixed: crash in CreateProcessEx (32bit)
// 2012-08-02 3.1.0 (1) added support for XE2 x64
// (2) improved internal LoadLibrary hook reliability
// 2012-06-12 3.0.4 fixed: CreateProcessEx didn't always work in XP64
// 2012-05-21 3.0.3 (1) fixed uninjection memory leak
// (2) improved 64bit injection into already running processes
// (3) RENEW_OVERWRITTEN_HOOKS can now also affect new hooks
// (4) fixed chrome sandbox uninjection problem
// 2011-09-08 3.0.2 make HookLoadLibrary work even if LoadLibraryExW is hooked
// 2011-03-27 3.0.1 (1) CreateGlobalFileMapping: LastError value is now restored
// (2) (Un)InjectLibraryA: improved ansi string conversion
// (3) added IsInjectionDriverInstalled/Running
// (4) added UninjectAllLibraries
// (5) added SetInjectionMethod
// (6) improved injection driver APIs' GetLastError handling
// (7) small change to make (un)injection slightly more stable
// (8) fixed win9x injection crash
// (9) sped up first HookAPI call
// (a) modified internal LoadLibrary hook (win7) once again
// (b) added SetInjectionMethod API
// (c) added UninjectAllLibrariesA/W API
// (d) added special handling for "wine"
// (e) increased default internal IPC timeout from 2s to 7s
// 2010-01-10 3.0.0 (1) added full support for 64bit OSs
// (2) DLL injection APIs changed
// (3) injection driver control APIs added
// (4) process black & white lists for DLL injection added
// (5) (Un)InstallMadCHook replaced by driver injection APIs
// (6) Is64bitOS added + Is64bitProcess exported
// (7) CreateProcessEx now works for 64bit processes
// (8) ProcessIdToFileName modified and unicode version added
// (9) added "INJECT_INTO/UNINJECT_FROM_RUNNING_PROCESSES"
// 2010-01-07 2.2l (1) privileges stay untouched in hook dlls (win 7 stability)
// (2) fixed: some allocation was still done at 0x5f040000
// (3) fixed: internal bug in CollectCache
// (4) modified LoadLibrary hook for Windows 7
// 2009-09-15 2.2k (1) fixed: DLL inj failed when some native APIs were hooked
// (2) fixed: SendIpcMessage sometimes failed in Windows 7
// (3) moved allocation from $71700000 to $71b00000 (msys bug)
// (4) fixed: LoadLibrary hook eventually crashed in x64 OSs
// (5) DONT_TOUCH_RUNNING_PROCESSES now only affects injection
// 2009-07-22 2.2j (1) other libs' hooks are not overwritten by default, anymore
// (2) DONT_RENEW_OVERWRITTEN_HOOKS -> RENEW_OVERWRITTEN_HOOKS
// (3) RestoreCode added
// (4) DLL injection thread now always uses 1MB stack size
// 2009-02-09 2.2i (1) Delphi 2009 support
// (2) added new option "DONT_TOUCH_RUNNING_PROCESSES"
// (3) added new option "DONT_RENEW_OVERWRITTEN_HOOKS"
// (4) fixed "USE_EXTERNAL_DRIVER_FILE": driver got deleted
// 2008-02-18 2.2h fixed DEP issue introduced with version 2.2f <sigh>
// 2008-01-19 2.2g fixed exception introduced with version 2.2f
// 2008-01-14 2.2f (1) new function "SetMadCHookOption" added
// (2) stability check was too careful with UPXed module
// (3) SeTakeOwnershipPrivilege is not enabled, anymore
// (4) improved cooperation with other hooking libraries
// - internal LoadLibrary hook modified to avoid conflicts
// - when unhooking fails, stability is still maintained
// (5) hooking unprotected (writable) code sometimes failed
// 2007-05-28 2.2e IPC works in Vista64 now, too
// 2007-03-28 2.2d (1) incompatability with ZoneAlarm AV / Kaspersky fixed
// (2) ALLOW_WINSOCK2_MIXTURE_MODE flag added
// (3) Vista ASLR incorrectly triggered mixture mode
// 2006-11-28 2.2c (1) endless recursion protection for tool functions
// (2) some first changes for 64bit OS support
// (3) Get/SetLastError value for LoadLibrary was destroyed
// (4) "MixtureCache" renamed to "CollectCache"
// (5) CollectHooks improves performance even more now
// (6) improved performance in internal LoadLibrary hook
// (7) little bug in dll injection fixed
// (8) official Vista (32bit) support added
// 2006-01-30 2.2b (1) Windows Vista (32bit) support added
// (2) Injection driver allocates at $71700000 now (winNT)
// 2005-11-28 2.2a (1) fixed deletion of invalid crit. section in finalization
// (2) auto unhooking bug fixed
// (3) GetCallingModule crash in non-com. Delphi edition fixed
// 2005-07-23 2.2 (1) fixed a little resource leak (1 critical section per dll)
// (2) NextHook variable stays usable after unhooking now
// (3) flag "DONT_COUNT" renamed to "NO_SAFE_UNHOOKING"
// (4) safe unhooking slightly improved (winNT)
// (5) "SAFE_HOOKING" -> thread safe hook installation (winNT)
// (6) turning "compiler -> optimization off" made problems
// (7) reduced double hooking problems with other hook libaries
// (8) mchInjDrv doesn't get listed in device manager, anymore
// (9) you can now permanently install the injection driver
// 2005-01-02 2.1e patch 6 bytes before page boundary: hook installation failed
// 2004-09-26 2.1d (1) memory leak in CreateIpcQueue fixed
// (2) win9x: injection skips newly created 16bit processes now
// 2004-08-05 2.1c (1) CURRENT_PROCESS is not ignored by UninjectLibrary anymore
// (2) enabling Backup/Restore privileges broke Samba support
// (3) some tweaks for win98, when IPC functions are stressed
// (4) xp sp2: IPC sending from under privileged account failed
// (5) IsHookInUse added
// 2004-04-25 2.1b (1) VirtualAlloc now allocates >= $5f040000 (winNT)
// (2) new injection driver version embedded
// 2004-04-12 2.1a (1) hook finalization: "nextHook" may be zeroed too early
// (2) safe unhooking improved (16 -> 280 thread capacity)
// (3) AMD64 NX: LocalAlloc -> VirtualAlloc (TCodeHook.Create)
// (4) GetTickCount doesn't overrun now, anymore
// 2004-03-07 2.1 (1) dll inject into new processes done by kernel driver (nt)
// (2) w2k3 support improved
// (3) AutoCAD debugger detection doesn't trigger anymore
// (4) ProtectMemory added where UnprotectMemory was used
// (5) PatchExportTable sometimes froze IE in win2k
// (6) special fix for a "Process Explorer" bug (see CheckMap)
// (7) InjectLibrary wide string handling bug fixed
// (8) bug in library path checking functions fixed
// (9) NO_MIXTURE_MODE flag added
// (a) the mixture mode can't be used for ws_32.dll
// (b) workaround for 2k/XP DuplicateHandle bug in AcLayers.dll
// (c) initialization ends with SetLastError(0), just in case
// (d) HookCode/API calls SetLastError(0) when it succeeds
// (e) CheckHooks doesn't modify the LastError value anymore
// (f) dummy win9x API detection improved
// (g) virtual memory allocation now >= $5f000000 (winNT)
// (h) CreateIpcQueueEx added with additional parameters
// 2003-11-10 2.0a (1) memory leak in API hooking code fixed
// (2) some bugs in Ipc functionality fixed
// (3) memory leak in madCHook.dll/lib fixed
// (4) call CloseHandle/ReleaseMutex/etc only for valid handles
// (5) error handling improved a bit
// 2003-08-10 2.0 (1) most code is totally rewritten/changed
// (2) HookCode parameters changed -> only one flags parameter
// (3) automatic mixture mode detection mightily improved
// (4) process wide hooking of system APIs in win9x more stable
// (5) shared PE sections are fully supported now (win9x)
// (6) dummy 9x wide APIs (e.g. CreateFileW) are not hooked
// (7) (Un)InjectLibrary: user/session/system wide injection!
// (8) InjectLibrary2 replaced by InjectLibrary (auto detect)
// (9) hook dlls protected from unauthorized FreeLibrary calls
// (a) fully automatic unhooking before dll unloading
// (b) safe unhooking implemented
// (c) static lib for Microsoft C++ added
// (d) CreateIpcQueue + SendIpcMessage + DestroyIpcQueue added
// (e) AmSystemProcess + AmUsingInputDesktop added
// (f) GetCurrentSessionId + GetInputSessionId added
// (g) GetCallingModule function added
// (h) ProcessIdToFileName added
// (i) Create/OpenGlobalMutex + Event + FileMapping added
// (j) WideToAnsi + AnsiToWide functions added
// (k) IAT patching doesn't stumble about compressed modules
// (l) try..except/try..finally works without SysUtils now
// (m) RenewHook function added
// (n) madCodeHook.dll -> madCHook.dll (8.3 dos name logic)
// (o) madIsBadModule tool added
// (p) several nice new demos added
// (q) UnhookAPI added (= UnhookCode, added just for the look)
// (r) AddAccessForEveryone added
// 2002-11-13 1.3k (1) using GetMem was a bad idea for dlls, now LocalAlloc
// (2) mixture mode hooks get uninstalled now, too
// (3) InjectLibraryW didn't allocate enough memory for dll path
// (4) InjectLibrary2(W) doesn't ask GetProcessVersion, anymore
// 2002-10-17 1.3j (1) InjectLibrary2(W) was not stdcall (ouch)
// (2) AtomicMove improves stability when (un)installing Hook
// 2002-10-06 1.3i improved reliability of CreateProcessEx in 9x (hopefully)
// 2002-10-03 1.3h (1) 1.3g introduced a bug in CreateProcessEx
// (2) InjectLibraryW added
// (3) InjectLibrary2(W) added for use in CreateProcess(W) hooks
// 2002-09-22 1.3g (1) added a FreeMem to TCodeHook.Destroy to fix a memory leak
// (2) CreateProcessExW added
// (3) GetProcAddressEx avoids IAT patching problems
// 2002-04-06 1.3f InstallMixturePatcher only did a part of what it should
// 2002-03-24 1.3e (1) mixture mode initialization changed again
// (2) CollectHooks/FlushHooks speed up mixture initialization
// (3) bug in PatchMyImportTables made HookCode crash sometimes
// 2002-03-15 1.3d CreateProcessEx now works with .net programs in NT family
// 2002-03-10 1.3c when using system wide hooking with mixture mode in win9x
// each process now patches its own import tables
// this gets rid of any (noticable) initialization delays
// 2002-02-23 1.3b mutexes were not entered, if they already existed
// 2001-07-28 1.3a serious bug (-> crash!) in CreateProcessEx fixed
// 2001-07-08 1.3 InjectLibrary added
// 2001-05-25 1.2b mixture: automatic IAT patching of new processes/modules (9x)
// normally not needed, because we also patch the export table
// but in rare cases the import table is hard coded
// 2001-04-20 1.2a you can now force HookCode to use the mixture mode
// 2000-12-23 1.2 new function CreateProcessEx -> dll injecting
// 2000-11-28 1.1c bug in PatchImportTable fixed (2nd missing VirtualProtectEx)
// 2000-11-26 1.1b (1) bug in PatchImportTable fixed (missing VirtualProtectEx)
// (2) support for w9x debug mode
// 2000-11-25 1.1a two bugs in the new hooking mode fixed
// 2000-11-23 1.1 (1) integrated enhanced import/export table patching added
// so now we should be able to hook really *every* API
// (2) no interfaces anymore to get rid of madBasic
// 2000-07-25 1.0d minor changes in order to get rid of SysUtils
unit madCodeHook;
{$I mad.inc} {$W-}
{$ifdef win64}{$define unlocked}{$endif}
// use ntdll entry point patching in XP and higher?
// advantage: dll gets initialized even before the statically linked dlls are
// disadvantage: dll can't be uninjected anymore
{ $define InjectLibraryPatchXp}
// enable logging?
{ $define log}
// enable automatic infinite recursion protection?
{$define CheckRecursion}
interface
uses Windows, madTypes, madDisAsm;
// ***************************************************************
const
// before installing an API hook madCodeHook does some security checks
// one check is verifying whether the to be hooked code was already modified
// in this case madCodeHook does not tempt to modify the code another time
// otherwise there would be a danger to run into stability issues
// with protected/compressed modules there may be false alarms, though
// so you can turn this check off
// param: unsused
DISABLE_CHANGED_CODE_CHECK = $00000003;
// when calling SendIpcMessage you can specify a timeout value
// this value only applies to how long madCodeHook waits for the reply
// there's an additional internal timeout value which specifies how long
// madCodeHook waits for the IPC message to be accepted by the queue owner
// the default value is 7000ms
// param: internal timeout value in ms
// example: SetMadCHookOption(SET_INTERNAL_IPC_TIMEOUT, PWideChar(5000));
SET_INTERNAL_IPC_TIMEOUT = $00000005;
// VMware: when disabling acceleration dll injection sometimes is delayed
// to work around this issue you can activate this special option
// it will result in a slightly modified dll injection logic
// as a side effect injection into DotNet applications may not work properly
// param: unused
VMWARE_INJECTION_MODE = $00000006;
// system wide dll injection normally injects the hook dll into both:
// (1) currently running processes
// (2) newly created processes
// this flag disables injection into already running processes
// param: unused
DONT_TOUCH_RUNNING_PROCESSES = $00000007;
// normally madCodeHook renews hooks only when they were removed
// hooks that were overwritten with some other code aren't renewed by default
// this behaviour allows other hooking libraries to co-exist with madCodeHook
// use this flag to force madCodeHook to always renew hooks
// this may result in other hooking libraries stopping to work correctly
// param: unused
RENEW_OVERWRITTEN_HOOKS = $00000009;
// system wide dll injection normally injects the hook dll into both:
// (1) currently running processes
// (2) newly created processes
// this option enabled/disables injection into already running processes
// param: bool
// default: true
INJECT_INTO_RUNNING_PROCESSES = $0000000a;
// system wide dll uninjection normally does two things:
// (1) uninjecting from all running processes
// (2) stop automatic injection into newly created processes
// this option controls if uninjection from running processes is performed
// param: bool
// default: true
UNINJECT_FROM_RUNNING_PROCESSES = $0000000b;
// by default madCodeHook allocates at <= $71af0000
// you can tell madCodeHook to allocate at the address you prefer
// param: LPVOID (must be < $80000000)
// default: $71af0000
X86_ALLOCATION_ADDRESS = $0000000c;
// By default madCodeHook performs several hook address verifications.
// You can disable that to save performance, but that means it's your
// responsibility then to provide proper addresses.
// Hook address verification includes things such as bypassing existing
// import/export table manipulations etc.
// param: bool
// default: true
VERIFY_HOOK_ADDRESS = $0000000d;
// By default madCodeHook disassembles the whole to-be-hooked API/code,
// to make sure it can be hooked without stability issues. You can disable
// this check, but if that produces stability issues, that's on your head.
// param: bool
// default: true
DISASM_HOOK_TARGET = $0000000e;
// madCodeHook has two different IPC solutions built in
// in Vista+ and in all 64 bit OSs the "old" IPC solution doesn't work
// so in these OSs the new IPC solution is always used
// in older OSs the old solution works, but the new one is used by default
// the new solution is based on undocumented internal Windows LPC APIs
// the old solution is based on pipes and memory mapped files
// you can optionally force the old IPC solution for the older OSs
// param: unused
USE_OLD_IPC_LOGIC = $0000000f;
// the IPC port is usually accessible by Everyone
// if you prefer, you can assign limited access rights to the port
// only SYSTEM and the creator then have access rights
// this is not recommended because it may block communication
// so don't use it, unless you know exactly what you're doing
// param: bool
// default: false
LIMITED_IPC_PORT = $00000010;
// By default, madCodeHook internally hooks LoadLibrary, so that it can
// automatically install your API hooks when the target DLL is loaded.
// This allows you to call HookAPI() in DllMain even for DLLs that are not
// loaded yet. You can disable this internal LoadLibrary hook, but it's not
// recommended. Set this option before you do the first HookAPI() call.
// param: bool
// default: true
HOOK_LOAD_LIBRARY = $00000011;
// By default, madCodeHook tries to hook LoadLibrary by modifying the code
// in LoadLibraryExW which internally calls LdrLoadDll. The purpose of this
// approach is that many many people like to hook LoadLibraryExW, so by
// hooking it in a different way there's a chance we can minimize conflicts
// with other hook libraries.
// However, you can disable this solution, for whatever reason. If you do,
// madCodeHook will use "conventional" LoadLibraryExW hooking instead.
// param: bool
// default: false
DISABLE_LDR_LOAD_DLL_SPECIAL_HOOK = $00000012;
// Starting with Windows 10, when a process is initialized, the OS likes to
// load DLLs in multiple threads. This might give a small speed improvement,
// but it can cause problems with our DLL injection technique. So madCodeHook
// by default disables "parallel loading" for processes your hook DLL gets
// injected into.
// param: bool
// default: true
DISABLE_PARALLEL_DLL_LOADING = $00000013;
// By default, the madCodeHook injection driver injects your DLL into newly
// created processes by hooking "NtTestAlert()". Which the OS loader usually
// calls the first time right before executing the EXE's "main()" entry
// point.
// Optionally, you can activate the new IAT patching logic, which will
// modify the EXE's import table in such a way that your hook DLL appears to
// be statically linked to by the EXE. This way the OS loader will actually
// do the dirty work for us and load your hook DLL together with all the
// other statically linked DLLs. Unfortunately this also means that the OS
// considers your hook DLL essential to the new process, so we can't ever
// uninject it again.
// param: bool
// default: false
USE_IAT_DLL_INJECTION = $00000014;
// When calling HookAPI/Code with the SAFE_HOOKING flag, madCodeHook
// by default waits forever (in a blocking way), until it's sure that
// installing the API hook can be done without stability issues.
// Optionally, you can tell madCodeHook to abort waiting after a specific
// timeout. This of course defeats the purpose of the SAFE_HOOKING flag
// to some extent. But it's your decision, of course.
// param: internal timeout value in ms (0 = no timeout)
// example: SetMadCHookOption(SET_SAFE_HOOKING_TIMEOUT, (LPCWSTR) 5000);
SET_SAFE_HOOKING_TIMEOUT = $00000015;
// Normally, madCodeHook only checks if API hooks need to be installed, if
// a *new* DLL is loaded into the current process. However, the check if a
// loaded DLL is new or not consumes time on its own. So this option allows
// you to skip the check if a loaded DLL is new or not, which may save some
// time. But then that also results in madCodeHook always checking if API
// hooks need to be installed, which also consumes time. Not really sure
// which way is faster overall. <sigh>
// param: bool
// default: false
ALWAYS_CHECK_HOOKS = $00000016;
// temp: Disable IPC fix 1.
// param: bool
// default: false
DISABLE_IPC_FIX_1 = $10000001;
// temp: Disable IPC fix 2.
// param: bool
// default: false
DISABLE_IPC_FIX_2 = $10000002;
// with this API you can configure some aspects of madCodeHook
// available options see constants above
function SetMadCHookOption (option: dword; param: PWideChar) : bool; stdcall;
// ***************************************************************
// When your DLL gets uninjected, your "uninject callback" will be called
// right before FreeLibrary(yourHook.dll) is executed, so your callback runs
// outside of DllMain.
// If your DLL gets unloaded by other means than uninjection, your callback
// will *not* be called at all.
type TUninjectCallback = procedure (context: pointer); stdcall;
procedure RegisterUninjectCallback (callback: TUninjectCallback; context: pointer); stdcall;
// ***************************************************************
const
// by default madCodeHook counts how many times any thread is currently
// running inside of your callback function
// this way unhooking can be safely synchronized to that counter
// sometimes you don't need/want this counting to happen, e.g.
// (1) if you don't plan to ever unhook, anyway
// (2) if the counting performance drop is too high for your taste
// (3) if you want to unhook from inside the hook callback function
// in those cases you can set the flag "NO_SAFE_UNHOOKING"
DONT_COUNT = $00000001; // old name - kept for compatability
NO_SAFE_UNHOOKING = $00000001; // new name
// optionally madCodeHook can use a special technique to make sure that
// hooking in multi threaded situations won't result in crashing threads
// this technique is not tested too well right now, so it's optional for now
// you can turn this feature on by setting the flag "SAFE_HOOKING"
// without this technique crashes can happen, if a thread is calling the API
// which we want to hook in exactly the moment when the hook is installed
SAFE_HOOKING = $00000020;
// by default madCodeHook will auto unhook everything in finalization
// you can disable that - do it on your own risk!
NO_AUTO_UNHOOKING = $00000100;
// madCodeHook implements two different API hooking methods
// the mixture mode is the second best method, it's only used if the main
// hooking method doesn't work for whatever reason (e.g. API code structure)
// normally madCodeHook chooses automatically which mode to use
// you can force madCodeHook to use the mixture mode by specifying the flag:
MIXTURE_MODE = $00000002;
// if you don't want madCodeHook to use the mixture mode, you can say so
// however, if the main hooking mode can't be used, hooking then simply fails
NO_MIXTURE_MODE = $00000010;
// winsock2 normally doesn't like the mixture mode
// however, I've found a way to convince winsock2 to accept mixture hooks
// this is a somewhat experimental feature, though
// so it must be turned on explicitly
ALLOW_WINSOCK2_MIXTURE_MODE = $00000080;
// By default, if the target API was already hooked by other hook library,
// madCodeHook switches to mixture mode (if possible).
// If the other hook library used code overwriting with a simple JMP,
// using the flag FOLLOW_JMP will instead make madCodeHook hook the callback
// function of the other hook library. This should work just fine. However,
// your hook will stop working in this case in the moment when the other
// hook library uninstalls its hook.
FOLLOW_JMP = $00000200;
// Use this flag to have madCodeHook store the thread state, every time the
// hooked function/API is called. The thread state is stored on the stack.
// You can retrieve it in your hook callback function by using the API
// "GetStoredThreadState".
STORE_THREAD_STATE = $00000400;
// By default, madCodeHook v4 uses a 5-byte relative JMP to hook an API.
// You can force madCodeHook to use the old v3 6-byte absolute JMP instead.
USE_ABSOLUTE_JMP = $00000800;
// Hook any code or a specific API.
// api:
// Name of the to-be-hooked API. Or a typecasted ordinal number.
// flags:
// See flags defined above.
// numStackParams:
// The parameter "numStackParams" is ignored, unless you specify the flag
// STORE_THREAD_STATE at the same time. In that case "numStackParams" should
// be set to the number of parameters that are stored on the stack. If you
// set this number too low, your callback function will receive random param
// values. Setting this number too high is not a problem. So if you're lazy,
// you can just stick with the default of 32, which should be high enough
// for all but the rarest situations. However, such a high value consumes
// additional stack space and CPU performance. So in order to optimize RAM
// and CPU consumption, it'd still be useful to set this parameter to the
// correct value, or at least near to the correct value. You can estimate
// high, just to be safe. A good estimate would be the number of parameters
// plus one, that should work for all calling conventions, and also for
// class methods.
function HookCode (code : pointer;
callbackFunc : pointer;
out nextHook : pointer;
flags : dword = 0;
numStackParams : dword = 32) : bool; stdcall;
function HookAPI (module, api : PAnsiChar;
callbackFunc : pointer;
out nextHook : pointer;
flags : dword = 0;
numStackParams : dword = 32) : bool; stdcall;
// some firewall/antivirus programs kill our hooks, so we need to renew them
function RenewHook (var nextHook: pointer) : bool; stdcall;
// is the hook callback function of the specified hook currently in use?
// 0: the hook callback function is not in use
// x: the hook callback function is in use x times
function IsHookInUse (var nextHook: pointer) : integer; stdcall;
// unhook again
function UnhookCode (var nextHook: pointer) : bool; stdcall;
function UnhookAPI (var nextHook: pointer) : bool; stdcall;
// putting all your "HookCode" calls into a "CollectHooks".."FlushHooks" frame
// can eventually speed up the installation of the hooks
procedure CollectHooks;
procedure FlushHooks;
// restores the original code of the API/function (only first 6 bytes)
// the original code is read from the dll file on harddisk
// you can use this function e.g. to remove the hook of another hook library
// don't use this to uninstall your own hooks, use UnhookCode for that purpose
function RestoreCode (code: pointer) : bool; stdcall;
// ***************************************************************
// same as CreateProcess
// additionally the dll "loadLibrary" is injected into the newly created process
// the dll gets loaded before the entry point of the exe module is called
function CreateProcessExA (applicationName, commandLine : PAnsiChar;
processAttr, threadAttr : PSecurityAttributes;
inheritHandles : bool;
creationFlags : dword;
environment : pointer;
currentDirectory : PAnsiChar;
const startupInfo : {$ifdef UNICODE} TStartupInfoA; {$else} TStartupInfo; {$endif}
var processInfo : TProcessInformation;
loadLibrary : PAnsiChar ) : bool; stdcall;
function CreateProcessExW (applicationName, commandLine : PWideChar;
processAttr, threadAttr : PSecurityAttributes;
inheritHandles : bool;
creationFlags : dword;
environment : pointer;
currentDirectory : PWideChar;
const startupInfo : {$ifdef UNICODE} TStartupInfoW; {$else} TStartupInfo; {$endif}
var processInfo : TProcessInformation;
loadLibrary : PWideChar ) : bool; stdcall;
function CreateProcessEx (applicationName, commandLine : PAnsiChar;
processAttr, threadAttr : PSecurityAttributes;
inheritHandles : bool;
creationFlags : dword;
environment : pointer;
currentDirectory : PAnsiChar;
const startupInfo : {$ifdef UNICODE} TStartupInfoA; {$else} TStartupInfo; {$endif}
var processInfo : TProcessInformation;
loadLibrary : AnsiString ) : boolean;
// ***************************************************************
const
// flags for injection/uninjection "session" parameter
ALL_SESSIONS : dword = dword(-1);
CURRENT_SESSION : dword = dword(-2);
// flags for inject approval callbacks (see below)
TARGET_PROCESS_IS_64BIT : dword = $00000001;
TARGET_PROCESS_IS_SYSTEM : dword = $00000002;
TARGET_PROCESS_IS_ELEVATED : dword = $00000004;
TARGET_PROCESS_IS_PROTECTED : dword = $00000008;
TARGET_PROCESS_IS_NEWLY_CREATED : dword = $00000010;
type
// callback approval definition for system/user wide dll injection
TInjectApprovalCallbackRoutine = function (
context : pointer; // you can use this for whatever purpose you like
processId : dword; // injection target process ID
parentId : dword; // which process started the target process?
sessionId : dword; // to which session does the process belong?
flags : dword; // see TARGET_PROCESS_IS_XXX flags (defined above)
imagePath : PWideChar; // exe file name/path
commandLine : PWideChar // full command line
) : bool; stdcall;
// same as Load/FreeLibrary, but is able to load/free the library in any process
function InjectLibraryA (libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall; overload;
function InjectLibraryW (libFileName: PWideChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall; overload;
function InjectLibrary (libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall; overload;
function UninjectLibraryA (libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall; overload;
function UninjectLibraryW (libFileName: PWideChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall; overload;
function UninjectLibrary (libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall; overload;
const
// options for Inject/UninjectLibrary
INJECT_SYSTEM_PROCESSES = $0001;
INJECT_PERMANENTLY = $0002;
INJECT_METRO_APPS = $0004;
INJECT_VIA_IAT_PATCHING = $0008;
INJECT_ALLOW_THREAD = $0010;
INJECT_GLOBAL_ENABLE_PARALLEL_DLL_LOADING = $10000000;
INJECT_GLOBAL_USE_IAT_DLL_INJECTION = $20000000;
// (un)injects a library to/from all processes of the specified session(s)
// driverName: name of the driver to use
// libFileName: full file path/name of the hook dll
// session: session id into which you want the dll to be injected
// "-1" or "ALL_SESSIONS" means all sessions
// "-2" or "CURRENT_SESSION" means the current session
// options: various options, see INJECT_xxx flags
// includeMask: list of exe file name/path masks into which the hook dll shall be injected
// you can use multiple masks separated by a "|" char
// you can either use a full path, or a file name, only
// leaving this parameter empty means that all processes are "included"
// Example: "c:\program files\*.exe|calc.exe|*.scr"
// excludeMask: list of exe file name/path masks which shall be excluded from dll injection
// the excludeMask has priority over the includeMask
// leaving this parameter empty means that no processes are "excluded"
// excludePIDs: list of process IDs which shall not be touched
// when used, the list must be terminated with a "0" PID item
// callback: will be called for each potential to-be-injected-into process
// callback decides whether to perform injection or not
// if callback doesn't reply within 5 sec timeout, a "true" reply is inferred
// caution: slow callback results in new processes starting delayed
// callbackContext: this pointer is forwarded to your callback function
// use for any purpose you like
function InjectLibraryA (driverName : PAnsiChar;
libFileName : PAnsiChar;
session : dword;
options : dword;
includeMask : PAnsiChar = nil;
excludeMask : PAnsiChar = nil;
excludePIDs : TPCardinal = nil;
callback : TInjectApprovalCallbackRoutine = nil;
callbackContext : pointer = nil;
timeOut : dword = 7000) : bool; stdcall; overload;
function InjectLibraryW (driverName : PWideChar;
libFileName : PWideChar;
session : dword;
options : dword;
includeMask : PWideChar = nil;
excludeMask : PWideChar = nil;
excludePIDs : TPCardinal = nil;
callback : TInjectApprovalCallbackRoutine = nil;
callbackContext : pointer = nil;
timeOut : dword = 7000) : bool; stdcall; overload;
function InjectLibrary (driverName : PAnsiChar;
libFileName : PAnsiChar;
session : dword;
options : dword;
includeMask : PAnsiChar = nil;
excludeMask : PAnsiChar = nil;
excludePIDs : TPCardinal = nil;
callback : TInjectApprovalCallbackRoutine = nil;
callbackContext : pointer = nil;
timeOut : dword = 7000) : bool; stdcall; overload;
function UninjectLibraryA (driverName : PAnsiChar;
libFileName : PAnsiChar;
session : dword;
options : dword;
includeMask : PAnsiChar = nil;
excludeMask : PAnsiChar = nil;
excludePIDs : TPCardinal = nil;
timeOut : dword = 7000) : bool; stdcall; overload;
function UninjectLibraryW (driverName : PWideChar;
libFileName : PWideChar;
session : dword;
options : dword;
includeMask : PWideChar = nil;
excludeMask : PWideChar = nil;
excludePIDs : TPCardinal = nil;
timeOut : dword = 7000) : bool; stdcall; overload;
function UninjectLibrary (driverName : PAnsiChar;
libFileName : PAnsiChar;
session : dword;
options : dword;
includeMask : PAnsiChar = nil;
excludeMask : PAnsiChar = nil;
excludePIDs : TPCardinal = nil;
timeOut : dword = 7000) : bool; stdcall; overload;
// uninjects every currently active system/session wide injection
function UninjectAllLibrariesW (driverName: PWideChar; excludePIDs: TPCardinal = nil; timeOutPerUninject: dword = 7000) : bool; stdcall;
function UninjectAllLibrariesA (driverName: PAnsiChar; excludePIDs: TPCardinal = nil; timeOutPerUninject: dword = 7000) : bool; stdcall;
function UninjectAllLibraries (driverName: PAnsiChar; excludePIDs: TPCardinal = nil; timeOutPerUninject: dword = 7000) : bool; stdcall;
// ***************************************************************
// you can use these APIs to permanently (un)install your injection driver
// it will survive reboots, it will stay installed until you uninstall it
// installing the same driver twice fails succeeds and updates all parameters
function InstallInjectionDriver (driverName, fileName32bit, fileName64bit, description: PWideChar) : bool; stdcall;
function UninstallInjectionDriver (driverName: PWideChar) : bool; stdcall;
// this will load and activate your injection driver
// it will stay active only until the next reboot
// you may prefer this technique if you want your product to not require any
// installation and uninstallation procedures
// loading the same driver twice fails with ERROR_SERVICE_ALREADY_RUNNING
function LoadInjectionDriver (driverName, fileName32bit, fileName64bit: PWideChar) : bool; stdcall;
// stopping the injection driver will only work:
// (1) if the driver was configured at build time to be stoppable
// (2) if you call StopInjectionDriver
// (3) if there are no dll injections currently running
// stopping the injection driver with the device manager or "sc" is blocked
// call Stop + Start to update an installed driver to a newer version
// call Stop + Load to update a loaded driver to a newer version
function StopInjectionDriver (driverName: PWideChar) : bool; stdcall;
function StartInjectionDriver (driverName: PWideChar) : bool; stdcall;
// checks whether the specific injection driver is installed
// returns true if you used InstallInjectionDriver
// returns false if you used LoadInjectionDriver
function IsInjectionDriverInstalled (driverName: PWideChar) : bool; stdcall;
// checks whether the specific injection driver is running
// returns true if you used InstallInjectionDriver and
// if the driver is running (not paused / stopped)
// returns true if you used LoadInjectionDriver
function IsInjectionDriverRunning (driverName: PWideChar) : bool; stdcall;
// ***************************************************************
// is the current process a service/system process?
function AmSystemProcess : bool; stdcall;
// is the current thread's desktop the input desktop?
// only in that case you should show messages boxes or other GUI stuff
// but please note that in XP fast user switching AmUsingInputDesktop may
// return true, although the current session is currently not visible
// XP fast user switching is implemented by using terminal server logic
// so each fast user session has its own window station and input desktop
function AmUsingInputDesktop : bool; stdcall;
// the following two functions can be used to get the session id of the
// current session and of the input session
// each terminal server (or XP fast user switching) session has its own id
// the "input session" is the one currently shown on the physical screen
function GetCurrentSessionId : dword; stdcall;
function GetInputSessionId : dword; stdcall;
// ***************************************************************
// which module called me? works only if your function has a stack frame
function GetCallingModule (returnAddress: pointer = nil) : HMODULE; stdcall;
type
TPThreadState = ^TThreadState;
TThreadState = record
{$ifdef win64}
rflags : NativeUInt;
r15 : NativeUInt;
r14 : NativeUInt;
r13 : NativeUInt;
r12 : NativeUInt;
r11 : NativeUInt;
r10 : NativeUInt;
r9 : NativeUInt;
r8 : NativeUInt;
rdi : NativeUInt;
rsi : NativeUInt;
rbp : NativeUInt;
rbx : NativeUInt;
rdx : NativeUInt;
rcx : NativeUInt;
rax : NativeUInt;
rsp : NativeUInt;
{$else}
rflags : NativeUInt;
edi : NativeUInt;
esi : NativeUInt;
ebp : NativeUInt;
esp : NativeUInt;
ebx : NativeUInt;
edx : NativeUInt;
ecx : NativeUInt;
eax : NativeUInt;
{$endif}
end;
// which state was the thread in when the hooked API/function was called
function GetStoredThreadState (var threadState: TPThreadState) : bool; stdcall;
// ***************************************************************
// find out what file the specified process was executed from
function ProcessIdToFileNameA (processId: dword; fileName: PAnsiChar; bufLenInChars: word) : bool; stdcall;
function ProcessIdToFileNameW (processId: dword; fileName: PWideChar; bufLenInChars: word) : bool; stdcall;
function ProcessIdToFileName (processId: dword; var fileName: AnsiString) : boolean;
// ***************************************************************
// global = normal + "access for everyone" + "non session specific"
function CreateGlobalMutex (name: PAnsiChar) : THandle; stdcall;
function OpenGlobalMutex (name: PAnsiChar) : THandle; stdcall;
function CreateGlobalEvent (name: PAnsiChar; manual, initialState: bool) : THandle; stdcall;
function OpenGlobalEvent (name: PAnsiChar ) : THandle; stdcall;
function CreateGlobalFileMapping (name: PAnsiChar; size: dword) : THandle; stdcall;
function OpenGlobalFileMapping (name: PAnsiChar; write: bool) : THandle; stdcall;
// ***************************************************************
// convert strings ansi <-> wide
// the result buffer must have a size of MAX_PATH characters (or more)
// please use these functions in nt wide API hook callback functions
// because the OS' own functions seem to confuse nt in hook callback functions
procedure AnsiToWide (ansi: PAnsiChar; wide: PWideChar); stdcall;
procedure WideToAnsi (wide: PWideChar; ansi: PAnsiChar); stdcall;
// ***************************************************************
// ipc (inter process communication) message services
// in contrast to SendMessage the following functions don't crash NT services
type
// this is how you get notified about incoming ipc messages
// you have to write a function which fits to this type definition
// and then you give it into "CreateIpcQueue"
// your callback function will then be called for each incoming message
// CAUTION: each ipc message is handled by a seperate thread, as a result
// your callback will be called by a different thread each time
TIpcCallback = procedure (name : PAnsiChar;
messageBuf : pointer;
messageLen : dword;
answerBuf : pointer;
answerLen : dword;
context : pointer); stdcall;
// create an ipc queue
// please choose a unique ipc name to avoid conflicts with other programs
// only one ipc queue with the same name can be open at the same time
// so if 2 programs try to create the same ipc queue, the second call will fail
// you can specify how many threads may be created to handle incoming messages
// if the order of the messages is crucial for you, set "maxThreadCount" to "1"
// in its current implementation "maxThreadCount" only supports "1" or unlimited
// the parameter "maxQueueLen" is not yet implemented at all
// the "context" is a simple pointer value that is forwarded to your callback
function CreateIpcQueue (ipc : PAnsiChar;
callback : TIpcCallback;
context : pointer = nil;
maxThreadCount : dword = 16;
maxQueueLen : dword = $1000;
securityDesc : PSecurityDescriptor = nil) : bool; stdcall;
// send an ipc message to whomever has created the ipc queue (doesn't matter)
// if you only fill the first 3 parameters, SendIpcMessage returns at once
// if you fill the next two parameters, too, SendIpcMessage will
// wait for an answer of the ipc queue owner
// you can further specify how long you're willing to wait for the answer
// and whether you want SendIpcMessage to handle messages while waiting
function SendIpcMessage(ipc : PAnsiChar;
messageBuf : pointer;
messageLen : dword;
answerBuf : pointer = nil;
answerLen : dword = 0;
answerTimeOut : dword = INFINITE;
handleMessages : bool = false ) : bool; stdcall;
// destroy the ipc queue again
// when the queue owning process quits, the ipc queue is automatically deleted
// only the queue owning process can destroy the queue
function DestroyIpcQueue (ipc: PAnsiChar) : bool; stdcall;
// ***************************************************************
// this function adds some access rights to the specified target
// the target can either be a process handle or a service handle
function AddAccessForEveryone (processOrService: THandle; access: dword) : bool; stdcall;
// ***************************************************************
// internal stuff, please do not use
function FindRealCode (code: pointer) : pointer;
function CheckLibFilePath (var libA: PAnsiChar; var libW: PWideChar;
var strA: AnsiString; var strW: AnsiString) : boolean;
procedure InitializeMadCHook;
procedure FinalizeMadCHook;
procedure AutoUnhook (moduleHandle: HMODULE);
procedure EnableAllPrivileges;
var amMchDll : boolean = false;
{$ifdef log}
procedure log(str: AnsiString; paramA: PAnsiChar = nil; paramW: PWideChar = nil; ph: THandle = 0);
{$endif}
{$ifdef protect}
procedure InitSpecialProcs;
{$endif}
implementation
uses madStrings, madTools, madRemote, madRipeMD;
// ***************************************************************
{$ifdef log}
function ExtractFileNameA(path: AnsiString) : AnsiString;
var i1 : integer;
begin
result := path;
for i1 := Length(result) downto 1 do
if result[i1] = '\' then begin
Delete(result, 1, i1);
break;
end;
end;
var firstLog : boolean = true;
doLog : boolean = false;
procedure log(str: AnsiString; paramA: PAnsiChar = nil; paramW: PWideChar = nil; ph: THandle = 0);
var fh : THandle;
c1 : dword;
st : TSystemTime;
time : AnsiString;
mutex : THandle;
s1 : AnsiString;
ws1 : array [0..MAX_PATH] of WideChar;
begin
if firstLog then begin
doLog := GetFileAttributesW('c:\madCodeHook.txt') <> maxCard;
firstLog := false;
end;
if doLog then begin
GetLocalTime(st);
time := IntToStrExA(dword(st.wHour), 2) + ':' +
IntToStrExA(dword(st.wMinute), 2) + ':' +
IntToStrExA(dword(st.wSecond), 2) + '-' +
IntToStrExA(dword(st.wMilliseconds), 3) + ' ';
GetModuleFileNameW(0, ws1, MAX_PATH);
s1 := WideToAnsiEx(ws1);
s1 := ExtractFileNameA(s1);
s1 := time + IntToHexExA(GetCurrentProcessID, 8) + ' ' + s1 + ' ';
ReplaceStrA(str, #$D#$A, #$D#$A + s1);
str := s1 + str + #$D#$A;
if paramW <> nil then
s1 := WideToAnsiEx(paramW)
else s1 := paramA;
ReplaceStrA(str, '%1', s1);
if ph <> 0 then begin
SetLength(s1, MAX_PATH + 1);
if not ProcessIdToFileNameA(ProcessHandleToId(ph), PAnsiChar(s1)) then
s1 := '';
ReplaceStrA(str, '%p', 'ph:' + IntToHexExA(ph) + ';pid:' + PAnsiChar(s1));
end;
mutex := CreateMutexW(nil, false, 'mAHLogMutex');
if mutex <> 0 then begin
WaitForSingleObject(mutex, INFINITE);
try
fh := CreateFileW('c:\madCodeHook.txt', GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, 0, 0);
if fh <> INVALID_HANDLE_VALUE then begin
SetFilePointer(fh, 0, nil, FILE_END);
WriteFile(fh, pointer(str)^, length(str), c1, nil);
CloseHandle(fh);
end;
finally ReleaseMutex(mutex) end;
CloseHandle(mutex);
end;
end;
end;
{$endif}
// ***************************************************************
{$ifdef protect}
{$include protect.inc}
{$endif}
// ***************************************************************
// SetMadCHookOption options
var
DisableChangedCodeCheck : boolean = false;
UseNewIpcLogic : boolean = true;
InternalIpcTimeout : dword = 7000;
VmWareInjectionMode : boolean = false;
RenewOverwrittenHooks : boolean = false;
InjectIntoRunningProcesses : boolean = true;
UninjectFromRunningProcesses : boolean = true;
X86AllocationAddress : pointer = pointer($71af0000);
VerifyHookAddress : boolean = true;
DisAsmHookTarget : boolean = true;
LimitedIpcPort : boolean = false;
HookLoadLibraryOption : boolean = true;
DisableLdrLoadDllSpecialHook : boolean = false;
DisableParallelDllLoading : boolean = true;
UseIatDllInjection : boolean = false;
DisableIpcFix1 : boolean = false;
DisableIpcFix2 : boolean = false;
SafeHookingTimeout : dword = 0;
AlwaysCheckHooks : boolean = false;
// ***************************************************************
// encrypted strings
const
// ntdll.dll
CLdrLoadDll : AnsiString = (* LdrLoadDll *) #$19#$31#$27#$19#$3A#$34#$31#$11#$39#$39;
CLdrGetDllHandle : AnsiString = (* LdrGetDllHandle *) #$19#$31#$27#$12#$30#$21#$11#$39#$39#$1D#$34#$3B#$31#$39#$30;
CLdrRegisterDllNotification : AnsiString = (* LdrRegisterDllNotification *) #$19#$31#$27#$07#$30#$32#$3C#$26#$21#$30#$27#$11#$39#$39#$1B#$3A#$21#$3C#$33#$3C#$36#$34#$21#$3C#$3A#$3B;
CLdrUnregisterDllNotification : AnsiString = (* LdrUnregisterDllNotification *) #$19#$31#$27#$00#$3B#$27#$30#$32#$3C#$26#$21#$30#$27#$11#$39#$39#$1B#$3A#$21#$3C#$33#$3C#$36#$34#$21#$3C#$3A#$3B;
CLdrResolveDelayLoadedAPI : AnsiString = (* LdrResolveDelayLoadedAPI *) #$19#$31#$27#$07#$30#$26#$3A#$39#$23#$30#$11#$30#$39#$34#$2C#$19#$3A#$34#$31#$30#$31#$14#$05#$1C;
CNtLoadDriver : AnsiString = (* NtLoadDriver *) #$1B#$21#$19#$3A#$34#$31#$11#$27#$3C#$23#$30#$27;
CNtUnloadDriver : AnsiString = (* NtUnloadDriver *) #$1B#$21#$00#$3B#$39#$3A#$34#$31#$11#$27#$3C#$23#$30#$27;
CNtTestAlert : AnsiString = (* NtTestAlert *) #$1B#$21#$01#$30#$26#$21#$14#$39#$30#$27#$21;
CNtAllocateVirtualMemory : AnsiString = (* NtAllocateVirtualMemory *) #$1B#$21#$14#$39#$39#$3A#$36#$34#$21#$30#$03#$3C#$27#$21#$20#$34#$39#$18#$30#$38#$3A#$27#$2C;
CNtFreeVirtualMemory : AnsiString = (* NtFreeVirtualMemory *) #$1B#$21#$13#$27#$30#$30#$03#$3C#$27#$21#$20#$34#$39#$18#$30#$38#$3A#$27#$2C;
CNtProtectVirtualMemory : AnsiString = (* NtProtectVirtualMemory *) #$1B#$21#$05#$27#$3A#$21#$30#$36#$21#$03#$3C#$27#$21#$20#$34#$39#$18#$30#$38#$3A#$27#$2C;
CNtDelayExecution : AnsiString = (* NtDelayExecution *) #$1B#$21#$11#$30#$39#$34#$2C#$10#$2D#$30#$36#$20#$21#$3C#$3A#$3B;
CKiUserExceptionDispatcher : AnsiString = (* KiUserExceptionDispatcher *) #$1E#$3C#$00#$26#$30#$27#$10#$2D#$36#$30#$25#$21#$3C#$3A#$3B#$11#$3C#$26#$25#$34#$21#$36#$3D#$30#$27;
CNtCreatePort : AnsiString = (* NtCreatePort *) #$1B#$21#$16#$27#$30#$34#$21#$30#$05#$3A#$27#$21;
CNtConnectPort : AnsiString = (* NtConnectPort *) #$1B#$21#$16#$3A#$3B#$3B#$30#$36#$21#$05#$3A#$27#$21;
CNtReplyWaitReceivePort : AnsiString = (* NtReplyWaitReceivePort *) #$1B#$21#$07#$30#$25#$39#$2C#$02#$34#$3C#$21#$07#$30#$36#$30#$3C#$23#$30#$05#$3A#$27#$21;
CNtReplyWaitReceivePortEx : AnsiString = (* NtReplyWaitReceivePortEx *) #$1B#$21#$07#$30#$25#$39#$2C#$02#$34#$3C#$21#$07#$30#$36#$30#$3C#$23#$30#$05#$3A#$27#$21#$10#$2D;
CNtAcceptConnectPort : AnsiString = (* NtAcceptConnectPort *) #$1B#$21#$14#$36#$36#$30#$25#$21#$16#$3A#$3B#$3B#$30#$36#$21#$05#$3A#$27#$21;
CNtCompleteConnectPort : AnsiString = (* NtCompleteConnectPort *) #$1B#$21#$16#$3A#$38#$25#$39#$30#$21#$30#$16#$3A#$3B#$3B#$30#$36#$21#$05#$3A#$27#$21;
CWineGetVersion : AnsiString = (* wine_get_version *) #$22#$3C#$3B#$30#$0A#$32#$30#$21#$0A#$23#$30#$27#$26#$3C#$3A#$3B;
CNtQueryObject : AnsiString = (* NtQueryObject *) #$1B#$21#$04#$20#$30#$27#$2C#$1A#$37#$3F#$30#$36#$21;
CNtQueryInformationToken : AnsiString = (* NtQueryInformationToken *) #$1B#$21#$04#$20#$30#$27#$2C#$1C#$3B#$33#$3A#$27#$38#$34#$21#$3C#$3A#$3B#$01#$3A#$3E#$30#$3B;
CNtQueueApcThread : AnsiString = (* NtQueueApcThread *) #$1B#$21#$04#$20#$30#$20#$30#$14#$25#$36#$01#$3D#$27#$30#$34#$31;
// kernel32.dll
CGetModuleHandleA : AnsiString = (* GetModuleHandleA *) #$12#$30#$21#$18#$3A#$31#$20#$39#$30#$1D#$34#$3B#$31#$39#$30#$14;
CLoadLibraryA : AnsiString = (* LoadLibraryA *) #$19#$3A#$34#$31#$19#$3C#$37#$27#$34#$27#$2C#$14;
CLoadLibraryExW : AnsiString = (* LoadLibraryExW *) #$19#$3A#$34#$31#$19#$3C#$37#$27#$34#$27#$2C#$10#$2D#$02;
CVirtualFree : AnsiString = (* VirtualFree *) #$03#$3C#$27#$21#$20#$34#$39#$13#$27#$30#$30;
CCreateMutexA : AnsiString = (* CreateMutexA *) #$16#$27#$30#$34#$21#$30#$18#$20#$21#$30#$2D#$14;
CGetModuleFileNameA : AnsiString = (* GetModuleFileNameA *) #$12#$30#$21#$18#$3A#$31#$20#$39#$30#$13#$3C#$39#$30#$1B#$34#$38#$30#$14;
CReleaseMutex : AnsiString = (* ReleaseMutex *) #$07#$30#$39#$30#$34#$26#$30#$18#$20#$21#$30#$2D;
CSetLastError : AnsiString = (* SetLastError *) #$06#$30#$21#$19#$34#$26#$21#$10#$27#$27#$3A#$27;
CLocalAlloc : AnsiString = (* LocalAlloc *) #$19#$3A#$36#$34#$39#$14#$39#$39#$3A#$36;
CLocalFree : AnsiString = (* LocalFree *) #$19#$3A#$36#$34#$39#$13#$27#$30#$30;
CVirtualQuery : AnsiString = (* VirtualQuery *) #$03#$3C#$27#$21#$20#$34#$39#$04#$20#$30#$27#$2C;
CWaitForMultipleObjects : AnsiString = (* WaitForMultipleObjects *) #$02#$34#$3C#$21#$13#$3A#$27#$18#$20#$39#$21#$3C#$25#$39#$30#$1A#$37#$3F#$30#$36#$21#$26;
CGetInputSessionId : AnsiString = (* WTSGetActiveConsoleSessionId *) #$02#$01#$06#$12#$30#$21#$14#$36#$21#$3C#$23#$30#$16#$3A#$3B#$26#$3A#$39#$30#$06#$30#$26#$26#$3C#$3A#$3B#$1C#$31;
CFlushInstructionCache : AnsiString = (* FlushInstructionCache *) #$13#$39#$20#$26#$3D#$1C#$3B#$26#$21#$27#$20#$36#$21#$3C#$3A#$3B#$16#$34#$36#$3D#$30;
CAddVecExceptHandler : AnsiString = (* AddVectoredExceptionHandler *) #$14#$31#$31#$03#$30#$36#$21#$3A#$27#$30#$31#$10#$2D#$36#$30#$25#$21#$3C#$3A#$3B#$1D#$34#$3B#$31#$39#$30#$27;
CRemoveVecExceptHandler : AnsiString = (* RemoveVectoredExceptionHandler *) #$07#$30#$38#$3A#$23#$30#$03#$30#$36#$21#$3A#$27#$30#$31#$10#$2D#$36#$30#$25#$21#$3C#$3A#$3B#$1D#$34#$3B#$31#$39#$30#$27;
CQueryFullProcessImageName : AnsiString = (* QueryFullProcessImageNameW *) #$04#$20#$30#$27#$2C#$13#$20#$39#$39#$05#$27#$3A#$36#$30#$26#$26#$1C#$38#$34#$32#$30#$1B#$34#$38#$30#$02;
CNtWow64QueryInfoProcess64 : AnsiString = (* NtWow64QueryInformationProcess64 *) #$1B#$21#$02#$3A#$22#$63#$61#$04#$20#$30#$27#$2C#$1C#$3B#$33#$3A#$27#$38#$34#$21#$3C#$3A#$3B#$05#$27#$3A#$36#$30#$26#$26#$63#$61;
CNtWow64ReadVirtualMemory64 : AnsiString = (* NtWow64ReadVirtualMemory64 *) #$1B#$21#$02#$3A#$22#$63#$61#$07#$30#$34#$31#$03#$3C#$27#$21#$20#$34#$39#$18#$30#$38#$3A#$27#$2C#$63#$61;
CQueueUserAPC : AnsiString = (* QueueUserAPC *) #$04#$20#$30#$20#$30#$00#$26#$30#$27#$14#$05#$16;
// advapi32.dll
CAdvApi32 : AnsiString = (* advapi32.dll *) #$34#$31#$23#$34#$25#$3C#$66#$67#$7B#$31#$39#$39;
CGetSecurityInfo : AnsiString = (* GetSecurityInfo *) #$12#$30#$21#$06#$30#$36#$20#$27#$3C#$21#$2C#$1C#$3B#$33#$3A;
CSetSecurityInfo : AnsiString = (* SetSecurityInfo *) #$06#$30#$21#$06#$30#$36#$20#$27#$3C#$21#$2C#$1C#$3B#$33#$3A;
CSetEntriesInAclA : AnsiString = (* SetEntriesInAclA *) #$06#$30#$21#$10#$3B#$21#$27#$3C#$30#$26#$1C#$3B#$14#$36#$39#$14;
CCloseServiceHandle : AnsiString = (* CloseServiceHandle *) #$16#$39#$3A#$26#$30#$06#$30#$27#$23#$3C#$36#$30#$1D#$34#$3B#$31#$39#$30;
CControlService : AnsiString = (* ControlService *) #$16#$3A#$3B#$21#$27#$3A#$39#$06#$30#$27#$23#$3C#$36#$30;
CQueryServiceStatus : AnsiString = (* QueryServiceStatus *) #$04#$20#$30#$27#$2C#$06#$30#$27#$23#$3C#$36#$30#$06#$21#$34#$21#$20#$26;
CDeleteService : AnsiString = (* DeleteService *) #$11#$30#$39#$30#$21#$30#$06#$30#$27#$23#$3C#$36#$30;
COpenSCManagerW : AnsiString = (* OpenSCManagerW *) #$1A#$25#$30#$3B#$06#$16#$18#$34#$3B#$34#$32#$30#$27#$02;
COpenServiceW : AnsiString = (* OpenServiceW *) #$1A#$25#$30#$3B#$06#$30#$27#$23#$3C#$36#$30#$02;
CStartServiceW : AnsiString = (* StartServiceW *) #$06#$21#$34#$27#$21#$06#$30#$27#$23#$3C#$36#$30#$02;
CCreateServiceW : AnsiString = (* CreateServiceW *) #$16#$27#$30#$34#$21#$30#$06#$30#$27#$23#$3C#$36#$30#$02;
CChangeServiceConfigW : AnsiString = (* ChangeServiceConfigW *) #$16#$3D#$34#$3B#$32#$30#$06#$30#$27#$23#$3C#$36#$30#$16#$3A#$3B#$33#$3C#$32#$02;
CQueryServiceConfigW : AnsiString = (* QueryServiceConfigW *) #$04#$20#$30#$27#$2C#$06#$30#$27#$23#$3C#$36#$30#$16#$3A#$3B#$33#$3C#$32#$02;
// user32.dll
CCharLowerBuffW : AnsiString = (* CharLowerBuffW *) #$16#$3D#$34#$27#$19#$3A#$22#$30#$27#$17#$20#$33#$33#$02;
// kernelbase.dll
CGetAppCtnrNamedObjectPath : AnsiString = (* GetAppContainerNamedObjectPath *) #$12#$30#$21#$14#$25#$25#$16#$3A#$3B#$21#$34#$3C#$3B#$30#$27#$1B#$34#$38#$30#$31#$1A#$37#$3F#$30#$36#$21#$05#$34#$21#$3D;
// dll names
CWs2_32 : AnsiString = (* ws2_32.dll *) #$22#$26#$67#$0A#$66#$67#$7B#$31#$39#$39;
// other strings
CSLSvc : AnsiString = (* slsvc.exe *) #$26#$39#$26#$23#$36#$7B#$30#$2D#$30;
CSeBackupPrivilege : AnsiString = (* SeBackupPrivilege *) #$06#$30#$17#$34#$36#$3E#$20#$25#$05#$27#$3C#$23#$3C#$39#$30#$32#$30;
CSeRestorePrivilege : AnsiString = (* SeRestorePrivilege *) #$06#$30#$07#$30#$26#$21#$3A#$27#$30#$05#$27#$3C#$23#$3C#$39#$30#$32#$30;
CSeTakeOwnershipPrivilege : AnsiString = (* SeTakeOwnershipPrivilege *) #$06#$30#$01#$34#$3E#$30#$1A#$22#$3B#$30#$27#$26#$3D#$3C#$25#$05#$27#$3C#$23#$3C#$39#$30#$32#$30;
CGlobal : AnsiString = (* Global\ *) #$12#$39#$3A#$37#$34#$39#$09;
CSession : AnsiString = (* Session\ *) #$06#$30#$26#$26#$3C#$3A#$3B#$09;
CMutex : AnsiString = (* Mutex *) #$18#$20#$21#$30#$2D;
CEvent : AnsiString = (* Event *) #$10#$23#$30#$3B#$21;
CMap : AnsiString = (* Map *) #$18#$34#$25;
CProcess : AnsiString = (* Process *) #$05#$27#$3A#$36#$30#$26#$26;
CApi : AnsiString = (* API *) #$14#$05#$1C;
CNamedBuffer : AnsiString = (* NamedBuffer *) #$1B#$34#$38#$30#$31#$17#$20#$33#$33#$30#$27;
CMixPrefix : AnsiString = (* mix *) #$38#$3C#$2D;
CMAHPrefix : AnsiString = (* mAH *) #$38#$14#$1D;
CMchLLEW2 : AnsiString = (* mchLLEW2 *) #$38#$36#$3D#$19#$19#$10#$02#$67;
CMchIInjT : AnsiString = (* mc5IInjT *) #$38#$36#$60#$1C#$1C#$3B#$3F#$01;
CType : AnsiString = (* Type *) #$01#$2C#$25#$30;
CErrorControl : AnsiString = (* ErrorControl *) #$10#$27#$27#$3A#$27#$16#$3A#$3B#$21#$27#$3A#$39;
CStart : AnsiString = (* Start *) #$06#$21#$34#$27#$21;
CImagePath : AnsiString = (* ImagePath *) #$1C#$38#$34#$32#$30#$05#$34#$21#$3D;
CSystemCcsServices : AnsiString = (* SYSTEM\CurrentControlSet\Services *) #$06#$0C#$06#$01#$10#$18#$09#$16#$20#$27#$27#$30#$3B#$21#$16#$3A#$3B#$21#$27#$3A#$39#$06#$30#$21#$09#$06#$30#$27#$23#$3C#$36#$30#$26;
CRegistryMachine : AnsiString = (* registry\machine *) #$27#$30#$32#$3C#$26#$21#$27#$2C#$09#$38#$34#$36#$3D#$3C#$3B#$30;
CIpc : AnsiString = (* Ipc2 *) #$1C#$25#$36#$67;
CAnswerBuf : AnsiString = (* AnswerBuf2 *) #$14#$3B#$26#$22#$30#$27#$17#$20#$33#$67;
CCounter : AnsiString = (* Cnt *) #$16#$3B#$21;
CRpcControlIpc : AnsiString = (* \RPC Control\mchIpc *) #$09#$07#$05#$16#$75#$16#$3A#$3B#$21#$27#$3A#$39#$09#$38#$36#$3D#$1C#$25#$36;
CSysWOW64 : AnsiString = (* SysWOW64 *) #$06#$2C#$26#$02#$1A#$02#$63#$61;
{$ifndef unlocked}
CEnumServicesStatusA : AnsiString = (* EnumServicesStatusA *) #$10#$3B#$20#$38#$06#$30#$27#$23#$3C#$36#$30#$26#$06#$21#$34#$21#$20#$26#$14;
CEnumServicesStatusW : AnsiString = (* EnumServicesStatusW *) #$10#$3B#$20#$38#$06#$30#$27#$23#$3C#$36#$30#$26#$06#$21#$34#$21#$20#$26#$02;
CNtQuerySystemInfo2 : AnsiString = (* NtQuerySystemInformation *) #$1B#$21#$04#$20#$30#$27#$2C#$06#$2C#$26#$21#$30#$38#$1C#$3B#$33#$3A#$27#$38#$34#$21#$3C#$3A#$3B;
CProcess32Next2 : AnsiString = (* Process32Next *) #$05#$27#$3A#$36#$30#$26#$26#$66#$67#$1B#$30#$2D#$21;
CEnumServicesStatusA2 : AnsiString = (* EnumServicesStatusA *) #$10#$3B#$20#$38#$06#$30#$27#$23#$3C#$36#$30#$26#$06#$21#$34#$21#$20#$26#$14;
CEnumServicesStatusW2 : AnsiString = (* EnumServicesStatusW *) #$10#$3B#$20#$38#$06#$30#$27#$23#$3C#$36#$30#$26#$06#$21#$34#$21#$20#$26#$02;
{$endif}
// ***************************************************************
var
UninjectCallbacks : array [0..15] of TUninjectCallback = (nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil);
UninjectCallbackContexts : array [0..15] of pointer = (nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil);
UninjectCallbackCount : integer = 0;
procedure InitAutoUnhook; forward;
procedure RegisterUninjectCallback(callback: TUninjectCallback; context: pointer); stdcall;
var i1 : integer;
begin
i1 := InterlockedIncrement(UninjectCallbackCount) - 1;
if (i1 >= 0) and (i1 < 16) then begin
InitAutoUnhook;
UninjectCallbacks[i1] := callback;
UninjectCallbackContexts[i1] := context;
end;
end;
procedure FireUninjectCallbacks;
var i1 : integer;
begin
for i1 := 0 to 15 do
if @UninjectCallbacks[i1] <> nil then
UninjectCallbacks[i1](UninjectCallbackContexts[i1]);
end;
// ***************************************************************
{$ifdef CheckRecursion}
function NotRecursedYet(caller, buf: pointer) : boolean;
function GetStackAddr : NativeUInt;
// ask the current thread's current stack pointer
asm
{$ifdef win64}
mov rax, rbp
{$else}
mov eax, ebp
{$endif}
end;
function GetStackTop : NativeUInt;
// ask the current thread's stack starting address
asm
{$ifdef win64}
mov rax, gs:[abs 8]
{$else}
mov eax, fs:[4]
{$endif}
end;
var stackAddr : NativeUInt;
stackTop : NativeUInt;
count : integer;
begin
stackAddr := GetStackAddr;
stackTop := GetStackTop;
count := 0;
result := true;
if (stackAddr + sizeOf(pointer) * 4 < stackTop) and (not IsBadReadPtrEx(pointer(stackAddr), stackTop - stackAddr)) then
while stackAddr + sizeOf(pointer) * 4 < stackTop do begin
if (TPAPointer(stackAddr)[0] = pointer({$ifdef win64} $7777777777777777 {$else} $77777777 {$endif})) and
(TPAPointer(stackAddr)[1] = pointer(stackAddr)) and
(TPAPointer(stackAddr)[2] = caller) and
(TPAPointer(stackAddr)[3] = pointer(stackAddr xor NativeUInt(caller))) then begin
inc(count);
if count = 7 then begin
result := false;
break;
end;
end;
inc(stackAddr, sizeOf(pointer));
end;
if result then begin
TPAPointer(buf)[0] := pointer({$ifdef win64} $7777777777777777 {$else} $77777777 {$endif});
TPAPointer(buf)[1] := buf;
TPAPointer(buf)[2] := caller;
TPAPointer(buf)[3] := pointer(TPANativeUInt(buf)[1] xor TPANativeUInt(buf)[2]);
end;
end;
{$endif}
// ***************************************************************
type
TUnicodeStr = record
len, maxLen : word;
str : PWideChar;
end;
TUnicodeStr32 = record
len, maxLen : word;
str : dword;
end;
TUnicodeStr64 = packed record
len, maxLen : word;
dummy : dword;
str : int64;
end;
TObjectAttributes = record
len : dword;
rootDir : THandle;
objName : ^TUnicodeStr;
attr : dword;
sd : pointer;
qos : pointer;
end;
// ***************************************************************
procedure AnsiToWide(ansi: PAnsiChar; wide: PWideChar); stdcall;
var {$ifdef CheckRecursion}
recTestBuf : array [0..3] of pointer;
{$endif}
s1 : AnsiString;
error : dword;
begin
error := GetLastError;
{$ifdef CheckRecursion}
if NotRecursedYet(@AnsiToWide, @recTestBuf) then begin
{$endif}
s1 := AnsiToWideEx(ansi);
Move(PAnsiChar(s1)^, wide^, Length(s1));
{$ifdef CheckRecursion}
recTestBuf[3] := nil;
end;
{$endif}
SetLastError(error);
end;
procedure WideToAnsi(wide: PWideChar; ansi: PAnsiChar); stdcall;
var {$ifdef CheckRecursion}
recTestBuf : array [0..3] of pointer;
{$endif}
s1 : AnsiString;
error : dword;
begin
error := GetLastError;
{$ifdef CheckRecursion}
if NotRecursedYet(@WideToAnsi, @recTestBuf) then begin
{$endif}
s1 := WideToAnsiEx(wide);
Move(PAnsiChar(s1)^, ansi^, Length(s1) + 1);
{$ifdef CheckRecursion}
recTestBuf[3] := nil;
end;
{$endif}
SetLastError(error);
end;
function WideToWideEx(wide: PWideChar; addTerminatingNull: boolean = true) : AnsiString;
begin
SetLength(result, lstrlenW(wide) * 2);
if result <> '' then
Move(wide^, pointer(result)^, Length(result));
if addTerminatingNull then
result := result + #0#0;
end;
function AnsiToWideEx2(const ansi: AnsiString; addTerminatingZero: boolean = true) : AnsiString;
var ws : UnicodeString;
begin
ws := UnicodeString(ansi);
SetLength(result, Length(ws) * 2);
if ws <> '' then
Move(pointer(ws)^, pointer(result)^, Length(result));
if addTerminatingZero then
result := result + #0#0;
end;
procedure AnsiToWide2(ansi: PAnsiChar; wide: PWideChar);
var s1 : AnsiString;
begin
s1 := AnsiToWideEx2(ansi);
Move(PAnsiChar(s1)^, wide^, Length(s1));
end;
function WideToAnsiEx2(wide: PWideChar) : AnsiString;
begin
result := AnsiString(UnicodeString(wide));
end;
// ***************************************************************
var SetErrorModeFunc : function (mode: dword) : dword stdcall = nil;
LoadLibraryAFunc : function (lib: PAnsiChar) : HMODULE stdcall = nil;
LoadLibraryWFunc : function (lib: PWideChar) : HMODULE stdcall = nil;
FreeLibraryFunc : function (module: HMODULE) : bool stdcall = nil;
GetModuleHandleAFunc : function (lib: PAnsiChar) : HMODULE stdcall = nil;
GetModuleHandleWFunc : function (lib: PWideChar) : HMODULE stdcall = nil;
GetLastErrorFunc : function : integer stdcall = nil;
VirtualFreeFunc : function (address: pointer; size: NativeUInt; flags: dword) : bool stdcall = nil;
GetVersionFunc : function : dword stdcall = nil;
CreateMutexAFunc : function (attr: PSecurityAttributes; initialOwner: bool; name: PAnsiChar) : THandle stdcall = nil;
GetModuleFileNameAFunc : function (module: HMODULE; fileName: PAnsiChar; size: dword) : dword stdcall = nil;
WaitForSingleObjectFunc : function (handle: THandle; timeOut: dword) : dword stdcall = nil;
GetCurrentProcessIdFunc : function : dword stdcall = nil;
OpenFileMappingAFunc : function (access: dword; inheritHandle: bool; name: PAnsiChar) : THandle stdcall = nil;
MapViewOfFileFunc : function (map: THandle; access, ofsHi, ofsLo: dword; size: NativeUInt) : pointer stdcall = nil;
UnmapViewOfFileFunc : function (addr: pointer) : bool stdcall = nil;
CloseHandleFunc : function (obj: THandle) : bool stdcall = nil;
ReleaseMutexFunc : function (mutex: THandle) : bool stdcall = nil;
SetLastErrorProc : procedure (error: dword) stdcall = nil;
LocalAllocFunc : function (flags: dword; size: NativeUInt) : pointer stdcall = nil;
LocalFreeFunc : function (addr: pointer) : NativeUInt stdcall = nil;
VirtualQueryFunc : function (addr: pointer; var mbi: TMemoryBasicInformation; size: NativeUInt) : NativeUInt stdcall = nil;
SleepProc : procedure (time: dword) stdcall = nil;
WaitForMultipleObjectsFunc : function (count: dword; handles: PWOHandleArray; waitAll: bool; timeOut: dword) : dword stdcall = nil;
EnterCriticalSectionFunc : procedure (var criticalSection: TRTLCriticalSection) stdcall = nil;
LeaveCriticalSectionFunc : procedure (var criticalSection: TRTLCriticalSection) stdcall = nil;
{$ifdef win64}
GetVersion2GB : pointer = nil;
GetLastError2GB : pointer = nil;
GetCurrentProcessId2GB : pointer = nil;
VirtualFree2GB : pointer = nil;
SetErrorMode2GB : pointer = nil;
LoadLibraryW2GB : pointer = nil;
GetModuleHandleW2GB : pointer = nil;
OpenFileMappingA2GB : pointer = nil;
MapViewOfFile2GB : pointer = nil;
UnmapViewOfFile2GB : pointer = nil;
CloseHandle2GB : pointer = nil;
FreeLibrary2GB : pointer = nil;
EnterCriticalSection2GB : pointer = nil;
LeaveCriticalSection2GB : pointer = nil;
{$endif}
procedure InitProcs(hookOnly: boolean);
begin
// todo: add critical section to make this totally thread safe (C++, too)
if @GetModuleHandleAFunc = nil then
if hookOnly then begin
if @GetModuleFileNameAFunc = nil then
GetModuleFileNameAFunc := KernelProc(CGetModuleFileNameA, true);
if @VirtualQueryFunc = nil then
VirtualQueryFunc := KernelProc(CVirtualQuery, true);
end else begin
GetModuleHandleWFunc := KernelProc(CGetModuleHandleW, true);
SetErrorModeFunc := KernelProc(CSetErrorMode, true);
LoadLibraryAFunc := KernelProc(CLoadLibraryA, true);
LoadLibraryWFunc := KernelProc(CLoadLibraryW, true);
FreeLibraryFunc := KernelProc(CFreeLibrary, true);
GetLastErrorFunc := KernelProc(CGetLastError, true);
VirtualFreeFunc := KernelProc(CVirtualFree, true);
GetVersionFunc := KernelProc(CGetVersion, true);
CreateMutexAFunc := KernelProc(CCreateMutexA, true);
GetModuleFileNameAFunc := KernelProc(CGetModuleFileNameA, true);
WaitForSingleObjectFunc := KernelProc(CWaitForSingleObject, true);
GetCurrentProcessIdFunc := KernelProc(CGetCurrentProcessId, true);
OpenFileMappingAFunc := KernelProc(COpenFileMappingA, true);
MapViewOfFileFunc := KernelProc(CMapViewOfFile, true);
UnmapViewOfFileFunc := KernelProc(CUnmapViewOfFile, true);
CloseHandleFunc := KernelProc(CCloseHandle, true);
ReleaseMutexFunc := KernelProc(CReleaseMutex, true);
SetLastErrorProc := KernelProc(CSetLastError, true);
LocalAllocFunc := KernelProc(CLocalAlloc, true);
LocalFreeFunc := KernelProc(CLocalFree, true);
VirtualQueryFunc := KernelProc(CVirtualQuery, true);
SleepProc := KernelProc(CSleep, true);
WaitForMultipleObjectsFunc := KernelProc(CWaitForMultipleObjects, true);
EnterCriticalSectionFunc := NtProc(CRtlEnterCriticalSection, true);
LeaveCriticalSectionFunc := NtProc(CRtlLeaveCriticalSection, true);
GetModuleHandleAFunc := KernelProc(CGetModuleHandleA, true);
end;
end;
// ***************************************************************
function AtomicMove(src, dst: pointer; len: integer) : boolean; // len = 1..8
function IsCmpxchg8bSupported : boolean;
{$ifdef win64}
begin
result := true;
end;
{$else}
asm
pushfd
pop eax
mov ecx, eax
xor eax, 00200000h
push eax
popfd
pushfd
pop eax
cmp eax, ecx
mov eax, 0
jz @quit
mov eax, 1
push ebx
dw $A20F // D4/D5 doesn't know the assembler instruction "cpuid"
pop ebx
test edx, $100
mov eax, 0
jz @quit
mov eax, 1
@quit:
end;
{$endif}
procedure cmpxchg8b;//(src, dst: pointer; len: integer); stdcall;
asm
{$ifdef win64}
.noframe
push rsi // 18 24 rsi
push rdi // 10 16 rdi
push rbx // 08 8 rbx
push rbp // 00 0 rbp
mov [rsp+28h], rcx // fill parameters in
mov [rsp+30h], rdx
mov [rsp+38h], r8
mov rbp, [rsp+30h] // move address of destination to rbp
mov rax, rbp // rax gets address of destination
mov edx, [rax+04h] // edx gets value at destination+4, HIGH DWORD
mov eax, [rax] // eax gets value at destination, LOW DWORD
// EDX:EAX is 8 bytes that destination points to
@tryAgain:
mov [rsp+40h], eax // local2 gets EAX, LOW DWORD
mov [rsp+44h], edx // local1 gets EDX, HIGH DWORD
mov rsi, [rsp+28h] // rsi gets source address parameter
lea rdi, [rsp+40h] // rdi gets address of first byte of local 2
mov rcx, [rsp+38h] // rcx gets length
cld // clear the direction flag
// thus, string instructions increment esi/edi
rep movsb // repeat cx times a move of a byte from [esi] to [edi] (cx will be 1-8)
// thus, the locals are populated with SOURCE
mov ebx, [rsp+40h] // ebx = LOW DWORD of Source value
mov ecx, [rsp+44h] // ecx = HIGH DWORD of Source value
db $F0 // lock ...
dd $004DC70F // ... cmpxchg8b qword ptr [ebp+$00]
jnz @tryAgain
pop rbp
pop rbx
pop rdi
pop rsi
ret
{$else}
add esp, -8
push esi
push edi
push ebx
push ebp
mov ebp, [esp+$20]
mov eax, ebp
mov edx, [eax+4]
mov eax, [eax]
@tryAgain:
mov [esp+$10], eax
mov [esp+$14], edx
mov esi, [esp+$1c]
lea edi, [esp+$10]
mov ecx, [esp+$24]
cld
rep movsb
mov ebx, [esp+$10]
mov ecx, [esp+$14]
db $F0 // lock ...
dd $004DC70F // ... cmpxchg8b qword ptr [ebp+$00]
jnz @tryAgain
pop ebp
pop ebx
pop edi
pop esi
add esp, 8
ret $c
{$endif}
end;
type TCmpxchg8b = procedure (src, dst: pointer; len: integer); stdcall;
begin
result := (len > 0) and (len <= 8) and IsCmpxchg8bSupported;
if result then
TCmpxchg8b(@cmpxchg8b)(src, dst, len);
end;
var FVista : integer = -1;
function IsVista : boolean;
begin
if FVista = -1 then
FVista := ord(byte(GetVersion) >= 6);
result := FVista = 1;
end;
var FMetro : integer = -1;
function IsMetro : boolean;
begin
if FMetro = -1 then
FMetro := ord((byte(GetVersion) > 6) or ((byte(GetVersion) = 6) and (byte(GetVersion shr 8) > 1)));
result := FMetro = 1;
end;
function GetProcessSid(processHandle: THandle; var saa: PSidAndAttributes) : boolean;
var token : THandle;
size : dword;
begin
result := false;
if OpenProcessToken(processHandle, TOKEN_QUERY, token) then begin
size := 0;
GetTokenInformation(token, TokenUser, nil, 0, size);
saa := pointer(LocalAlloc(LPTR, size * 2));
if GetTokenInformation(token, TokenUser, saa, size * 2, size) then
result := true
else LocalFree(HLOCAL(saa));
CloseHandle(token);
end;
end;
function CreateMetroSd(var sd: TSecurityDescriptor) : boolean;
// create a security descriptor which includes metro "AppContainer" apps
const CEveryoneSia : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 1));
CSecurityAppPackageAuthority : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 15));
type
TTrustee = record
multiple : pointer;
multop : dword;
form : dword;
type_ : dword;
sid : PSid;
end;
TExplicitAccess = record
access : dword;
mode : dword;
inherit : dword;
trustee : TTrustee;
end;
var ea : array [0..1] of TExplicitAccess;
dacl : PAcl;
sid1, sid2 : PSid;
seia : function (count: dword; ea: pointer; oldAcl: PAcl; var newAcl: PAcl) : dword; stdcall;
begin
result := false;
seia := GetProcAddress(LoadLibraryA(PAnsiChar(DecryptStr(CAdvApi32))), PAnsiChar(DecryptStr(CSetEntriesInAclA)));
if @seia <> nil then begin
sid1 := nil;
sid2 := nil;
if AllocateAndInitializeSid(CEveryoneSia, 1, 0, 0, 0, 0, 0, 0, 0, 0, sid1) and
AllocateAndInitializeSid(CSecurityAppPackageAuthority, 2, 2, 1, 0, 0, 0, 0, 0, 0, sid2) then begin
ZeroMemory(@ea, sizeOf(ea));
ea[0].access := STANDARD_RIGHTS_ALL or SPECIFIC_RIGHTS_ALL;
ea[0].mode := 2; // SET_ACCESS
ea[0].trustee.form := 0; // TRUSTEE_IS_SID
ea[0].trustee.type_ := 1; // TRUSTEE_IS_USER
ea[0].trustee.sid := sid1;
ea[1].access := STANDARD_RIGHTS_ALL or SPECIFIC_RIGHTS_ALL;
ea[1].mode := 2; // SET_ACCESS
ea[1].trustee.form := 0; // TRUSTEE_IS_SID
ea[1].trustee.type_ := 2; // TRUSTEE_IS_GROUP
ea[1].trustee.sid := sid2;
if seia(2, @ea, nil, dacl) = 0 then begin
result := true;
InitializeSecurityDescriptor(@sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(@sd, true, dacl, false);
end;
end;
if sid1 <> nil then
FreeSid(sid1);
if sid2 <> nil then
FreeSid(sid2);
end;
end;
var LimitedSa : TSecurityAttributes;
LimitedSd : TSecurityDescriptor;
UnlimitedSa : TSecurityAttributes;
UnlimitedSd : TSecurityDescriptor;
procedure InitSecAttr(var sa: TSecurityAttributes; var sd: TSecurityDescriptor; limited, allowEveryone: boolean);
const
CEveryoneSia : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 1));
CSystemSia : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 5));
CSecurityAppPackageAuthority : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 15));
type
TTrustee = record
multiple : pointer;
multop : dword;
form : dword;
type_ : dword;
sid : PSid;
end;
TExplicitAccess = record
access : dword;
mode : dword;
inherit : dword;
trustee : TTrustee;
end;
var seia : function (count: dword; ea: pointer; oldAcl: PAcl; var newAcl: PAcl) : dword; stdcall;
sid1, sid2, sid3 : PSid;
dacl : PAcl;
saa : PSidAndAttributes;
ea : array [0..3] of TExplicitAccess;
count : integer;
begin
ZeroMemory(@sa, sizeOf(sa));
ZeroMemory(@sd, sizeOf(sd));
if limited then begin
sid1 := nil;
sid2 := nil;
sid3 := nil;
saa := nil;
dacl := nil;
seia := GetProcAddress(GetModuleHandleA(PAnsiChar(DecryptStr(CAdvApi32))), PAnsiChar(DecryptStr(CSetEntriesInAclA)));
if @seia <> nil then begin
if AllocateAndInitializeSid(CEveryoneSia, 1, 0, 0, 0, 0, 0, 0, 0, 0, sid1) and
AllocateAndInitializeSid(CSystemSia, 1, 18, 0, 0, 0, 0, 0, 0, 0, sid2) and
GetProcessSid(GetCurrentProcess, saa) and (saa <> nil) then begin
ZeroMemory(@ea, sizeOf(ea));
ea[0].mode := 1; // GRANT_ACCESS
ea[0].trustee.form := 0; // TRUSTEE_IS_SID
ea[0].trustee.type_ := 1; // TRUSTEE_IS_USER
ea[0].access := SECTION_MAP_READ;
ea[0].trustee.sid := sid1;
ea[1].mode := 1; // GRANT_ACCESS
ea[1].trustee.form := 0; // TRUSTEE_IS_SID
ea[1].trustee.type_ := 1; // TRUSTEE_IS_USER
ea[1].access := STANDARD_RIGHTS_ALL or SPECIFIC_RIGHTS_ALL;
ea[1].trustee.sid := sid2;
ea[2].mode := 1; // GRANT_ACCESS
ea[2].trustee.form := 0; // TRUSTEE_IS_SID
ea[2].trustee.type_ := 1; // TRUSTEE_IS_USER
ea[2].access := STANDARD_RIGHTS_ALL or SPECIFIC_RIGHTS_ALL;
ea[2].trustee.sid := saa.Sid;
count := 3;
if IsMetro and AllocateAndInitializeSid(CSecurityAppPackageAuthority, 2, 2, 1, 0, 0, 0, 0, 0, 0, sid3) then begin
// this adds access for metro "AppContainer" apps
ea[3].access := SECTION_MAP_READ;
ea[3].mode := 1; // GRANT_ACCESS
ea[3].trustee.form := 0; // TRUSTEE_IS_SID
ea[3].trustee.type_ := 2; // TRUSTEE_IS_GROUP
ea[3].trustee.sid := sid3;
inc(count);
end else
sid3 := nil;
if allowEveryone then begin
if seia(count, @ea, nil, dacl) <> 0 then
dacl := nil;
end else
if seia(count - 1, @(ea[1]), nil, dacl) <> 0 then
dacl := nil;
end;
end;
InitializeSecurityDescriptor(@sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(@sd, true, dacl, false);
if sid1 <> nil then
FreeSid(sid1);
if sid2 <> nil then
FreeSid(sid2);
if sid3 <> nil then
FreeSid(sid3);
if saa <> nil then
LocalFree(HLOCAL(saa));
end else
if IsMetro then
CreateMetroSd(sd)
else begin
InitializeSecurityDescriptor(@sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(@sd, true, nil, false);
end;
sa.nLength := sizeOf(sa);
sa.lpSecurityDescriptor := @sd;
sa.bInheritHandle := false;
end;
function InternalCreateMutex(name: PAnsiChar; global: boolean) : THandle;
begin
if global then
result := CreateMutexW(@UnlimitedSa, false, pointer(AnsiToWideEx(DecryptStr(CGlobal) + name)))
else
result := 0;
if result = 0 then
result := CreateMutexW(@UnlimitedSa, false, pointer(AnsiToWideEx(name)));
end;
function CreateGlobalMutex(name: PAnsiChar) : THandle; stdcall;
begin
result := InternalCreateMutex(name, true);
end;
function CreateLocalMutex(name: PAnsiChar) : THandle; stdcall;
begin
result := InternalCreateMutex(name, false);
end;
function OpenGlobalMutex(name: PAnsiChar) : THandle; stdcall;
begin
result := OpenMutexW(SYNCHRONIZE, false, pointer(AnsiToWideEx(DecryptStr(CGlobal) + name)));
if result = 0 then
result := OpenMutexW(SYNCHRONIZE, false, pointer(AnsiToWideEx(name)));
end;
var BaseGetNamedObjectDirectory : function (var handle: THandle) : dword; stdcall = nil;
function InitBaseGetNamedObjectDirectory : boolean;
begin
if @BaseGetNamedObjectDirectory = nil then begin
BaseGetNamedObjectDirectory := GetProcAddress(GetModuleHandle(kernel32), 'BaseGetNamedObjectDirectory');
if @BaseGetNamedObjectDirectory = nil then
BaseGetNamedObjectDirectory := pointer(1);
end;
result := NativeUInt(@BaseGetNamedObjectDirectory) > 1;
end;
function ZwCreateSection(var sectionHandle: THandle; desiredAccess: ACCESS_MASK; var objectAttr: TObjectAttributes; var maximumSize: int64; sectionPageProtection, allocationAttributes: dword; fileHandle: THandle) : dword; stdcall; external 'ntdll.dll';
function InternalCreateFileMapping(name: PAnsiChar; size: dword; global: boolean; limited: boolean = true) : THandle;
var sa : PSecurityAttributes;
objAttr : TObjectAttributes;
uniStr : TUnicodeStr;
s1 : AnsiString;
size64 : int64;
begin
result := 0;
if limited then
sa := @LimitedSa
else
sa := @UnlimitedSa;
if InitBaseGetNamedObjectDirectory then begin
objAttr.len := sizeOf(objAttr);
BaseGetNamedObjectDirectory(objAttr.rootDir);
objAttr.objName := @uniStr;
objAttr.attr := $80; // OBJ_OPENIF
objAttr.sd := sa.lpSecurityDescriptor;
objAttr.qos := nil;
size64 := size;
if global then begin
s1 := AnsiToWideEx(DecryptStr(CGlobal) + name);
uniStr.len := Length(s1) - 2;
uniStr.maxLen := Length(s1);
uniStr.str := pointer(s1);
ZwCreateSection(result, STANDARD_RIGHTS_REQUIRED or SECTION_QUERY or SECTION_MAP_WRITE or SECTION_MAP_READ, objAttr, size64, PAGE_READWRITE, SEC_COMMIT, 0);
end;
if result = 0 then begin
s1 := AnsiToWideEx(name);
uniStr.len := Length(s1) - 2;
uniStr.maxLen := Length(s1);
uniStr.str := pointer(s1);
ZwCreateSection(result, STANDARD_RIGHTS_REQUIRED or SECTION_QUERY or SECTION_MAP_WRITE or SECTION_MAP_READ, objAttr, size64, PAGE_READWRITE, SEC_COMMIT, 0);
end;
end;
if result = 0 then begin
if global then
result := CreateFileMappingW(INVALID_HANDLE_VALUE, sa, PAGE_READWRITE, 0, size, pointer(AnsiToWideEx(DecryptStr(CGlobal) + name)));
if result = 0 then
result := CreateFileMappingW(INVALID_HANDLE_VALUE, sa, PAGE_READWRITE, 0, size, pointer(AnsiToWideEx(name)));
end;
end;
function CreateGlobalFileMapping(name: PAnsiChar; size: dword) : THandle; stdcall;
begin
EnableAllPrivileges;
result := InternalCreateFileMapping(name, size, true);
end;
function CreateLocalFileMapping(name: PAnsiChar; size: dword) : THandle; stdcall;
begin
result := InternalCreateFileMapping(name, size, false);
end;
var NtQueryObject : function (handle: THandle; infoClass: NativeUInt; buf: pointer; bufSize: NativeUInt; returnSize: TPCardinal) : HRESULT; stdcall = nil;
function InternalOpenFileMapping(name: PAnsiChar; write: bool; global: boolean) : THandle;
function FindFileMappingHandle(const name: AnsiString) : THandle;
// browse through the process handles to find a specific memory mapped file
var buf : TPAPointer;
function GetKernelObjectTypeName(handle: THandle) : AnsiString;
begin
if SUCCEEDED(NtQueryObject(handle, 2, buf, 2048, nil)) and (buf[1] <> nil) then
result := WideToAnsiEx(PWideChar(buf[1]))
else
result := '';
end;
function GetKernelObjectName(handle: THandle) : AnsiString;
begin
if SUCCEEDED(NtQueryObject(handle, 1, buf, 2048, nil)) and (buf[1] <> nil) then begin
result := WideToAnsiEx(PWideChar(buf[1]))
end else
result := '';
end;
var i1 : integer;
s1 : AnsiString;
begin
result := 0;
if @NtQueryObject = nil then
NtQueryObject := NtProc(CNtQueryObject);
if @NtQueryObject <> nil then begin
buf := VirtualAlloc(nil, 2048, MEM_COMMIT, PAGE_READWRITE);
for i1 := 1 to $1000 do
try
if IsTextEqualA(GetKernelObjectTypeName(i1 * 4), 'Section') then begin
s1 := GetKernelObjectName(i1 * 4);
if Length(s1) > Length(name) then begin
Delete(s1, 1, Length(s1) - Length(name));
if IsTextEqualA(s1, name) and DuplicateHandle(GetCurrentProcess, i1 * 4, GetCurrentProcess, @result, 0, false, DUPLICATE_SAME_ACCESS) then
break;
end;
end;
except end;
end;
VirtualFree(buf, 0, MEM_RELEASE);
end;
var access : dword;
le : dword;
begin
if write then
access := FILE_MAP_ALL_ACCESS
else access := FILE_MAP_READ;
if global then
result := OpenFileMappingW(access, false, pointer(AnsiToWideEx(DecryptStr(CGlobal) + name)))
else
result := 0;
if result = 0 then begin
le := GetLastError;
result := OpenFileMappingW(access, false, pointer(AnsiToWideEx(name)));
if result = 0 then
SetLastError(le);
end;
if (result = 0) and (GetLastError in [5, 6]) then begin
// chrome sandbox processes don't seem to be allowed to open memory mapped files, ouch
// so we do some special processing to locate already open handles which fit our needs
le := GetLastError;
result := FindFileMappingHandle(name);
if result <> 0 then
SetLastError(0)
else
SetLastError(le);
end;
end;
function OpenGlobalFileMapping(name: PAnsiChar; write: bool) : THandle; stdcall;
begin
result := InternalOpenFileMapping(name, write, true);
end;
(*function OpenLocalFileMapping(name: PAnsiChar; write: bool) : THandle; stdcall;
begin
result := InternalOpenFileMapping(name, write, false);
end;*)
function ZwCreateEvent(var eventHandle: THandle; desiredAccess: ACCESS_MASK; var objAttr: TObjectAttributes; eventType: NativeUInt; initialState: BOOL) : dword; stdcall; external 'ntdll.dll';
function CreateGlobalEvent(name: PAnsiChar; manual, initialState: bool) : THandle; stdcall;
var objAttr : TObjectAttributes;
uniStr : TUnicodeStr;
s1 : AnsiString;
evType : NativeUInt;
begin
result := 0;
if InitBaseGetNamedObjectDirectory then begin
objAttr.len := sizeOf(objAttr);
BaseGetNamedObjectDirectory(objAttr.rootDir);
objAttr.objName := @uniStr;
objAttr.attr := $80; // OBJ_OPENIF
objAttr.sd := UnlimitedSa.lpSecurityDescriptor;
objAttr.qos := nil;
if manual then
evType := 0
else
evType := 1;
s1 := AnsiToWideEx(DecryptStr(CGlobal) + name);
uniStr.len := Length(s1) - 2;
uniStr.maxLen := Length(s1);
uniStr.str := pointer(s1);
ZwCreateEvent(result, EVENT_ALL_ACCESS, objAttr, evType, initialState);
if result = 0 then begin
s1 := AnsiToWideEx(name);
uniStr.len := Length(s1) - 2;
uniStr.maxLen := Length(s1);
uniStr.str := pointer(s1);
ZwCreateEvent(result, EVENT_ALL_ACCESS, objAttr, evType, initialState);
end;
end;
if result = 0 then begin
result := CreateEventW(@UnlimitedSa, manual, initialState, pointer(AnsiToWideEx(DecryptStr(CGlobal) + name)));
if result = 0 then
result := CreateEventW(@UnlimitedSa, manual, initialState, pointer(AnsiToWideEx(name)));
end;
end;
function OpenGlobalEvent(name: PAnsiChar) : THandle; stdcall;
begin
result := OpenEventW(EVENT_ALL_ACCESS, false, pointer(AnsiToWideEx(DecryptStr(CGlobal) + name)));
if result = 0 then
result := OpenEventW(EVENT_ALL_ACCESS, false, pointer(AnsiToWideEx(name)));
end;
function ApiSpecialName(prefix: AnsiString; api: pointer) : AnsiString;
begin
result := DecryptStr(CProcess) + ' ' + IntToHexExA(GetCurrentProcessID, 8);
result := DecryptStr(prefix) + ', ' + result + ', ' + DecryptStr(CApi) + ' ' + IntToHexExA(NativeUInt(api), {$ifdef win64} 16 {$else} 8 {$endif});
end;
function ResolveMixtureMode(var code: pointer) : boolean;
var map : THandle;
buf : pointer;
s1 : AnsiString;
begin
result := false;
s1 := DecryptStr(CNamedBuffer) + ', ' + ApiSpecialName(CMixPrefix, code);
map := OpenGlobalFileMapping(PAnsiChar(s1), true);
if map <> 0 then begin
buf := MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
if buf <> nil then begin
code := pointer(buf^);
UnmapViewOfFile(buf);
result := true;
end;
CloseHandle(map);
end;
end;
function FindRealCode(code: pointer) : pointer;
function IsAlreadyKnown : boolean;
var s1 : AnsiString;
c1 : THandle;
begin
s1 := DecryptStr(CNamedBuffer) + ', ' + ApiSpecialName(CMAHPrefix, code);
c1 := OpenGlobalFileMapping(PAnsiChar(s1), false);
result := c1 <> 0;
if result then
CloseHandle(c1);
end;
function CheckCode : boolean;
var module : HMODULE;
pexp : PImageExportDirectory;
i1 : integer;
c1 : dword;
begin
result := ResolveMixtureMode(code) or IsAlreadyKnown;
if (not result) and GetExportDirectory(code, module, pexp) then
with pexp^ do
for i1 := 0 to NumberOfFunctions - 1 do begin
c1 := TPACardinal(module + AddressOfFunctions)^[i1];
if pointer(module + c1) = code then begin
result := true;
break;
end;
end;
end;
begin
{$ifdef log}
log('FindRealCode (code: ' + IntToHexExA(NativeUInt(code)) + ')');
{$endif}
result := code;
if code <> nil then
if CheckCode then
result := code
else
if word(code^) = $25FF then begin
{$ifdef win64}
// RIP relative addressing
code := TPPointer(NativeUInt(code) + 6 + TPCardinal(NativeUInt(code) + 2)^)^;
{$else}
code := TPPointer(TPPointer(NativeUInt(code) + 2)^)^;
{$endif}
if CheckCode then
result := code
else
if byte(code^) = $68 then begin // w9x debug mode?
code := TPPointer(NativeUInt(code) + 1)^;
if CheckCode then
result := code;
end;
end;
{$ifdef log}
log('FindRealCode -> ' + IntToHexExA(NativeUInt(result)));
{$endif}
end;
function VirtualAlloc2(size: integer; preferredAddr: pointer) : pointer;
begin
{$ifdef win64}
if (NativeUInt(preferredAddr) >= $70000000) and (NativeUInt(preferredAddr) < $80000000) then
// let's skip the area which is usually used by windows system dlls
// in 64 bit we have enough memory address range available above of 0x80000000
// so there's no need to allocate where we could collide with the system dlls
preferredAddr := pointer($80000000);
result := AllocMemEx(size, 0, preferredAddr);
{$else}
result := AllocMemEx(size);
{$endif}
end;
{$ifndef win64}
function FindWs2InternalProcList(module: HMODULE) : pointer;
// Winsock2 internally has an additional list of some exported APIs
// we need to find this list and modify it, too
// or else Winsock2 will refuse to initialize, when we change the export table
var nh : PImageNtHeaders;
pexp : PImageExportDirectory;
ish : PImageSectionHeader;
i1, i2 : integer;
buf : array [0..9] of NativeUInt;
begin
result := nil;
if module <> 0 then begin
nh := pointer(GetImageNtHeaders(module));
pexp := GetImageExportDirectory(module);
if (nh <> nil) and (pexp <> nil) then begin
NativeUInt(ish) := NativeUInt(nh) + sizeOf(TImageNtHeaders);
for i1 := 0 to integer(nh^.FileHeader.NumberOfSections) - 1 do begin
if (ish.Name[0] = ord('.')) and
((ish.Name[1] = ord('d')) or (ish.Name[1] = ord('D'))) and
((ish.Name[2] = ord('a')) or (ish.Name[2] = ord('A'))) and
((ish.Name[3] = ord('t')) or (ish.Name[3] = ord('T'))) and
((ish.Name[4] = ord('a')) or (ish.Name[4] = ord('A'))) and
(ish.Name[5] = 0) then begin
for i2 := 0 to high(buf) do
buf[i2] := module + TPACardinal(module + pexp^.AddressOfFunctions)[i2];
i2 := PosPCharA(@buf, PAnsiChar(module + ish.VirtualAddress), sizeOf(buf), ish.SizeOfRawData);
if i2 >= 0 then
NativeUInt(result) := module + ish.VirtualAddress + dword(i2);
break;
end;
inc(ish);
end;
end;
end;
end;
{$endif}
function PatchExportTable(module: HMODULE; old, new: pointer; ws2procList: pointer) : boolean;
{$ifndef win64}
function HackWinsock2IfNecessary(ws2procList: TPAPointer; old, new: pointer) : boolean;
var i1 : integer;
op : dword;
begin
result := ws2procList = nil;
if not result then begin
i1 := 0;
while ws2procList[i1] <> nil do
if ws2procList[i1] = old then begin
if VirtualProtect(ws2procList[i1], 4, PAGE_EXECUTE_READWRITE, @op) then begin
ws2procList[i1] := new;
result := true;
VirtualProtect(ws2procList[i1], 4, op, @op);
end;
break;
end else
inc(i1);
end;
end;
{$endif}
var pexp : PImageExportDirectory;
i1 : integer;
c1 : dword;
p1 : TPCardinal;
op : dword;
nh : PImageNtHeaders32;
begin
{$ifdef log}
log('PatchExportTable (old: ' + IntToHexExA(NativeUInt(old)) +
'; new: ' + IntToHexExA(NativeUInt(new)) + ')');
{$endif}
result := false;
try
pexp := nil;
try
if (module <> 0) and (TPWord(module)^ = CEMAGIC) then begin
nh := pointer(module + dword(pointer(module + CENEWHDR)^));
if nh^.signature = CPEMAGIC then
if nh^.OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR64_MAGIC then
pexp := pointer(module + PImageOptionalHeader64(@nh^.OptionalHeader).DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
else pexp := pointer(module + nh^.OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
end;
except end;
if pexp <> nil then
with pexp^ do
for i1 := 0 to NumberOfFunctions - 1 do begin
c1 := TPACardinal(module + AddressOfFunctions)^[i1];
if pointer(module + c1) = old then begin
{$ifndef win64} if HackWinsock2IfNecessary(ws2procList, old, new) then {$endif} begin
p1 := @TPACardinal(module + AddressOfFunctions)^[i1];
if VirtualProtect(p1, 4, PAGE_EXECUTE_READWRITE, @op) then begin
p1^ := NativeUInt(new) - module;
result := true;
VirtualProtect(p1, 4, op, @op);
end {$ifndef win64} else
HackWinsock2IfNecessary(ws2procList, new, old) {$endif};
end;
break;
end;
end;
except end;
{$ifdef log}
log('PatchExportTable (old: ' + IntToHexExA(NativeUInt(old)) +
'; new: ' + IntToHexExA(NativeUInt(new)) + ') -> ' + booleanToCharA(result));
{$endif}
end;
procedure PatchImportTable(module: HMODULE; old, new: pointer);
var nh : {$ifdef win64} PImageNtHeaders64 {$else} PImageNtHeaders {$endif};
id : PImageImportDirectory;
thunk : ^pointer;
op : dword;
begin
nh := pointer(GetImageNtHeaders(module));
if (nh <> nil) and
(nh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress <> 0) and
(nh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress < nh.OptionalHeader.SizeOfImage) then begin
id := pointer(module + nh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
if id <> nil then
while (id.Name_ <> 0) and (id.ThunkArray <> 0) and (id.ThunkArray < nh.OptionalHeader.SizeOfImage) do begin
thunk := pointer(module + id.ThunkArray);
while thunk^ <> nil do begin
if (thunk^ = old) and VirtualProtect(thunk, sizeOf(pointer), PAGE_EXECUTE_READWRITE, @op) then begin
thunk^ := new;
VirtualProtect(thunk, sizeOf(pointer), op, @op);
end;
thunk := pointer(NativeUInt(thunk) + sizeOf(pointer));
end;
id := pointer(NativeUInt(id) + sizeOf(TImageImportDirectory));
end;
end;
end;
procedure PatchMyImportTables(old, new: pointer);
var p1 : pointer;
mbi : TMemoryBasicInformation;
module : HMODULE;
arrCh : array [0..7] of wideChar;
begin
p1 := nil;
module := 0;
while VirtualQuery(p1, mbi, sizeOf(mbi)) = sizeOf(mbi) do begin
if (mbi.State = MEM_COMMIT) and (mbi.AllocationBase <> nil) and (HMODULE(mbi.AllocationBase) <> module) then begin
module := HMODULE(mbi.AllocationBase);
if GetModuleFileNameW(module, arrCh, 2) <> 0 then
PatchImportTable(module, old, new);
end;
p1 := pointer(NativeUInt(p1) + mbi.RegionSize);
end;
end;
(* Caution: currently only 32bit support
function HookReloc(module: HMODULE; old, new: pointer; var next: pointer) : boolean;
type TImageBaseRelocation = record
VirtualAddress : dword;
SizeOfBlock : dword;
end;
var
nh : PImageNtHeaders;
rel1 : ^TImageBaseRelocation;
rel2 : NativeUInt;
i1 : integer;
item : TPWord;
data : ^TPPointer;
opcode : TPWord;
cb, ce : NativeUInt;
p1 : pointer;
c1 : dword;
begin
result := false;
nh := GetImageNtHeaders(module);
if nh <> nil then
with nh^.OptionalHeader do begin
cb := module + BaseOfCode + 2;
ce := module + BaseOfCode + SizeOfCode;
with DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC] do begin
NativeUInt(rel1) := module + VirtualAddress;
rel2 := NativeUInt(rel1) + Size;
end;
while (NativeUInt(rel1) < rel2) and (rel1^.VirtualAddress <> 0) do begin
NativeUInt(item) := NativeUInt(rel1) + sizeOf(rel1^);
for i1 := 1 to (rel1.SizeOfBlock - sizeOf(rel1^)) div 2 do begin
if item^ and $f000 = $3000 then begin
NativeUInt(data) := module + rel1.VirtualAddress + item^ and $0fff;
NativeUInt(opcode) := NativeUInt(data) - 2;
if (NativeUInt(data) >= cb) and (NativeUInt(data) < ce) and
(data^ <> @next) and
( (opcode^ = $25ff) or (opcode^ = $15ff) ) and
TryRead(data^, @p1, 4) and
(p1 = old) and
VirtualProtect(data^, 4, PAGE_EXECUTE_READWRITE, c1) then begin
result := true;
data^^ := new;
VirtualProtect(data^, 4, c1, c1);
end;
end;
inc(item);
end;
NativeUInt(rel1) := NativeUInt(rel1) + rel1^.SizeOfBlock;
end;
end;
end; *)
function HookCodeX(owner : HMODULE;
moduleH : HMODULE;
module : AnsiString;
api : AnsiString;
ordinal : dword;
code : pointer;
callbackFunc : pointer;
out nextHook : pointer;
flags : dword;
numStackParams : dword) : boolean; forward;
// ***************************************************************
var IsWine : boolean = false;
var
CollectSection : TRTLCriticalSection;
CollectCache : array of packed record
thread : dword;
refCount : integer;
// in RAM a lot of patching can happen
// sometimes madCodeHook has to make sure that it can get original unpatched information
// for such purposes it opens the original exe/dll files on harddisk
// in order to speed things up, we're caching the last opened file here
moduleOrg : HMODULE; // which loaded exe/dll module is currently open?
moduleFile : pointer; // where is the file mapped into memory?
end;
function CollectCache_Open(force: boolean) : integer;
var i1 : integer;
begin
EnterCriticalSection(CollectSection);
result := -1;
for i1 := 0 to high(CollectCache) do
if CollectCache[i1].thread = GetCurrentThreadID then begin
result := i1;
break;
end;
if (result = -1) and force then begin
result := Length(CollectCache);
SetLength(CollectCache, result + 1);
CollectCache[result].thread := GetCurrentThreadID;
end;
end;
procedure CollectCache_Close;
begin
LeaveCriticalSection(CollectSection);
end;
procedure CollectCache_AddRef;
var i1 : integer;
begin
i1 := CollectCache_Open(true);
try
inc(CollectCache[i1].refCount);
finally CollectCache_Close end;
end;
function CollectCache_NeedModuleFileMap(module: HMODULE) : pointer;
var i1 : integer;
buf : pointer;
begin
result := nil;
if (not IsWine) and (CollectCache <> nil) then begin
buf := nil;
i1 := CollectCache_Open(false);
try
if i1 <> -1 then begin
if CollectCache[i1].moduleOrg <> module then begin
if CollectCache[i1].moduleOrg <> 0 then begin
buf := CollectCache[i1].moduleFile;
CollectCache[i1].moduleOrg := 0;
CollectCache[i1].moduleFile := nil;
end;
end else
result := CollectCache[i1].moduleFile;
end;
finally CollectCache_Close end;
if i1 <> -1 then begin
if buf <> nil then
UnmapViewOfFile(buf);
if result = nil then begin
buf := NeedModuleFileMap(module);
if buf <> nil then begin
i1 := CollectCache_Open(false);
try
if i1 <> -1 then begin
CollectCache[i1].moduleOrg := module;
CollectCache[i1].moduleFile := buf;
result := buf;
end else
UnmapViewOfFile(buf);
finally CollectCache_Close end;
end;
end;
end;
end;
end;
procedure CollectCache_Release;
var buf : pointer;
i1 : integer;
begin
buf := nil;
i1 := CollectCache_Open(false);
try
if i1 <> -1 then begin
dec(CollectCache[i1].refCount);
if CollectCache[i1].refCount = 0 then begin
buf := CollectCache[i1].moduleFile;
CollectCache[i1] := CollectCache[high(CollectCache)];
SetLength(CollectCache, high(CollectCache));
end;
end;
finally CollectCache_Close end;
if buf <> nil then
UnmapViewOfFile(buf);
end;
// ***************************************************************
type
// type for absolute jmp calls
TAbsoluteJmp = packed record // jmp [target]
opcode : byte; // $ff
modRm : byte; // $25, 00101001, Mod = 00, Reg = 100, R/M = 101 : 32 bit displacement follows
target : dword; // @(absolute target) which is pointer to an address (32 bits)
end;
// type for absolute jumps
THookStub = packed record
jmpNextHook : TAbsoluteJmp; // $FF / $25 / @absTarget
absTarget : pointer; // absolute target
end;
THookEntry = record
HookProc : pointer;
PNextHook : TPPointer;
end;
THookQueueItems = array [0..maxInt shr 5 - 1] of THookEntry;
THookQueue = record
ItemCount : integer;
Capacity : integer;
MapHandle : THandle;
PatchAddr : ^TAbsoluteJmp;
OldCode : TAbsoluteJmp;
NewCode : TAbsoluteJmp;
Items : THookQueueItems;
end;
const
CHookEntrySize = sizeOf(THookEntry);
CHookQueueSize = sizeOf(THookQueue) - sizeOf(THookQueueItems);
// ***************************************************************
const
PAGE_SIZE = 4096;
// CInUseCount = min((PAGE_SIZE * 2) / sizeOf(TInUseItem), (PAGE_SIZE * 2 - sizeOf(pointer) * 2) / (sizeOf(dword) + sizeOf(pointer)));
CInUseCount = 546;
type
{$ifdef win64}
TInUseItem = packed record
popReturn : dword; // $08c48348
call : word; // $15ff
callOffset : integer; // rip relative displacement
jmp : byte; // $e9
jmpOffset : integer; // relative target
end;
{$else}
TInUseItem = packed record
popReturn : array [0..2] of byte; // $04c483
call : word; // $15ff
callTarget : pointer; // address of call target
jmp : byte; // $e9
jmpOffset : integer; // relative target
end;
{$endif}
TInUseCodeArr = packed array [0..CInUseCount - 1] of TInUseItem;
TInUseThreadArr = packed array [0..CInUseCount - 1] of dword;
TInUseTargetArr = packed array [0..CInUseCount - 1] of pointer;
TCodeHook = class
public
FValid : boolean;
FNewHook : boolean;
FHookedFunc : pointer;
FMapHandle : THandle;
FCallbackFunc : pointer;
FPNextHook : TPPointer;
FPHookStub : ^THookStub;
FPHookStubTarget : ^pointer;
FInUseCodeArr : ^TInUseCodeArr;
FInUseThreadArr : ^TInUseThreadArr;
FInUseTargetArr : ^TInUseTargetArr;
FDestroying : boolean;
FLeakUnhook : boolean;
FDoubleHook : boolean;
FModuleH : HMODULE;
FStoreThreadState : boolean;
FSafeHooking : boolean;
FNoAutoUnhook : boolean;
FIsWinsock2 : boolean;
FPatchExport : boolean;
FTramp : pointer;
FPatchAddr : ^TAbsoluteJmp;
FNewCode : TAbsoluteJmp;
FOldCode : TAbsoluteJmp;
constructor Create (moduleH: HMODULE; hookThisFunc, callbackFunc: pointer; out nextHook: pointer; flags, numParams: dword);
destructor Destroy; override;
procedure WritePatch (mutex: THandle; buf: pointer);
function IsHookInUse : integer;
end;
(*
procedure InUseEnter;
asm
{$ifdef win64}
.noframe
push rdi
push rsi
push rdx
push rcx
push rax
push rbx
mov rbx, $1111111111111111
mov rdi, $2222222222222222
mov rsi, $3333333333333333
mov rdx, [rsp+30h]
mov rcx, 546 // CInUseCount
xor rax, rax
@loop:
lock cmpxchg [rdi], rdx
jz @success
add rbx, 15
add rdi, 8
add rsi, 4
xor rax, rax
loop @loop
jmp @fail
@success:
mov [rsp+30h], rbx
mov rax, gs:[$30]
mov rax, [rax+$48]
mov dword ptr [rsi], eax
pop rbx
pop rax
pop rcx
pop rdx
pop rsi
pop rdi
jmp [rsp]
@fail:
pop rbx
pop rax
pop rcx
pop rdx
pop rsi
pop rdi
db 0ffh
db 025h
dd 0
dq 0
{$else}
push edi
push esi
push edx
push ecx
push eax
push ebx
mov ebx, $11111111
mov edi, $22222222
mov esi, $33333333
mov edx, [esp+$18]
mov ecx, 546 // CInUseCount
xor eax, eax
@loop:
lock cmpxchg [edi], edx
jz @success
add ebx, 14
add edi, 4
add esi, 4
xor eax, eax
loop @loop
jmp @fail
@success:
mov [esp+$18], ebx
mov eax, fs:[$18];
mov eax, [eax+$24]
mov [esi], eax
pop ebx
pop eax
pop ecx
pop edx
pop esi
pop edi
jmp [esp]
@fail:
pop ebx
pop eax
pop ecx
pop edx
pop esi
pop edi
db $e9
dd $44444444
{$endif}
end;
*)
const
// We use fixed compiled code for x86 and x64 targets to make sure the code
// is the same for all compilers. This helps with dynamic code replacements.
{$ifdef win64}
CInUseEnter : array[0..128] of Byte = (
$57, $56, $52, $51, $50, $53, $48, $BB, $11, $11, $11, $11, $11, $11, $11, $11,
$48, $BF, $22, $22, $22, $22, $22, $22, $22, $22, $48, $BE, $33, $33, $33, $33,
$33, $33, $33, $33, $48, $8B, $54, $24, $30, $48, $B9, $22, $02, $00, $00, $00,
$00, $00, $00, $48, $31, $C0, $F0, $48, $0F, $B1, $17, $74, $13, $48, $83, $C3,
$0F, $48, $83, $C7, $08, $48, $83, $C6, $04, $48, $31, $C0, $E2, $E8, $EB, $1D,
$48, $89, $5C, $24, $30, $65, $48, $8B, $04, $25, $30, $00, $00, $00, $48, $8B,
$40, $48, $89, $06, $5B, $58, $59, $5A, $5E, $5F, $FF, $24, $24, $5B, $58, $59,
$5A, $5E, $5F, $FF, $25, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
$00
);
{$else}
CInUseEnter : array [0..88] of byte = (
$57, $56, $52, $51, $50, $53, $BB, $11, $11, $11, $11, $BF, $22, $22, $22, $22,
$BE, $33, $33, $33, $33, $8B, $54, $24, $18, $B9, $22, $02, $00, $00, $31, $C0,
$F0, $0F, $B1, $17, $74, $0F, $83, $C3, $0E, $83, $C7, $04, $83, $C6, $04, $31,
$C0, $E2, $ED, $EB, $19, $89, $5C, $24, $18, $64, $8B, $05, $18, $00, $00, $00,
$8B, $40, $24, $89, $06, $5B, $58, $59, $5A, $5E, $5F, $FF, $24, $24, $5B, $58,
$59, $5A, $5E, $5F, $E9, $44, $44, $44, $44
);
{$endif}
(*
procedure InUseLeave;
asm
{$ifdef win64}
.noframe
push rax
push rcx
push rdx
// eax := index
mov rax, [rsp+$18]
mov rdx, $1111111111111111
sub rax, rdx
xor rdx, rdx
mov rcx, 15
div rcx
dec rax
// edx := @InUseTargetArr[index]
mov rcx, $2222222222222222
lea rdx, [rcx + rax * 8]
// retAddr := InUseTargetArr[index];
mov rax, [rdx]
mov [rsp+$18], rax
// InUseTargetArr[index] := 0;
lock and qword ptr [rdx], 0
pop rdx
pop rcx
pop rax
ret
{$else}
push eax
push ecx
push edx
// eax := index
mov eax, [esp+$c]
mov edx, $11111111
sub eax, edx
xor edx, edx
mov ecx, 14
div ecx
dec eax
// edx := @InUseTargetArr[index]
lea edx, [eax * 4 + $22222222]
// retAddr := InUseTargetArr[index];
mov eax, [edx]
mov [esp+$c], eax
// InUseTargetArr[index] := 0;
lock and [edx], 0
pop edx
pop ecx
pop eax
ret
{$endif}
end;
*)
const
{$ifdef win64}
CInUseLeave : array [0..73] of byte = (
$50, $51, $52, $48, $8B, $44, $24, $18, $48, $BA, $11, $11, $11, $11, $11, $11,
$11, $11, $48, $29, $D0, $48, $31, $D2, $48, $B9, $0F, $00, $00, $00, $00, $00,
$00, $00, $48, $F7, $F1, $48, $FF, $C8, $48, $B9, $22, $22, $22, $22, $22, $22,
$22, $22, $48, $8D, $14, $C1, $48, $8B, $02, $48, $89, $44, $24, $18, $F0, $48,
$81, $22, $00, $00, $00, $00, $5A, $59, $58, $C3
);
{$else}
CInUseLeave : array [0..44] of byte = (
$50, $51, $52, $8B, $44, $24, $0C, $BA, $11, $11, $11, $11, $29, $D0, $31, $D2,
$B9, $0E, $00, $00, $00, $F7, $F1, $48, $8D, $14, $85, $22, $22, $22, $22, $8B,
$02, $89, $44, $24, $0C, $F0, $83, $22, $00, $5A, $59, $58, $C2
);
{$endif}
(*
procedure InUse_ThreadState;
asm
{$ifdef win64}
.noframe
// keep 4 stack items untouched
sub rsp, 4*8
// store current thread state to stack
push rsp
push rax
push rcx
push rdx
push rbx
push rbp
push rsi
push rdi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
pushfq
// add a signature and checksum
mov rax, 6D63683474687374h // 'mch4thst'
push rax
xor rax, rsp
push rax
// let's reserve some space here for private use
sub rsp, 20h
// copy the parameters and return address
lea rsi, [rsp+472] // (numParams + 23) * 8 + 4*8
mov rcx, 32 // (numParams + 0)
@copyLoop:
mov rax, [rsi]
sub rsi, 8
push rax
loop @copyLoop
// here comes (mostly) the usual InUseEnter code
mov rdi, $2222222222222222 // InUseTargetArr
mov rsi, $3333333333333333 // InUseThreadArr
mov rcx, 546 // CInUseCount
xor rax, rax
@inUseLoop:
// search for an empty (zero) InUseTargetArr array index
lock cmpxchg [rdi], rdi
jz @success
add rdi, 8
add rsi, 4
xor rax, rax
loop @inUseLoop
xor rdi, rdi
jmp @finish
@success:
// store GetCurrentThreadId in InUseThreadArr
mov rax, gs:[$30]
mov rax, [rax+$48]
mov dword ptr [rsi], eax
@finish:
// store the InUseTargetArr pointer in the private storage area
mov [rsp+280], rdi // (numParams + 3) * 8
// restore the registers by using the stored thread state
mov rax, [rsp+424] // (numParams + 21) * 8
mov rcx, [rsp+416] // (numParams + 20) * 8
mov rdx, [rsp+408] // (numParams + 19) * 8
mov rbx, [rsp+400] // (numParams + 18) * 8
mov rsi, [rsp+384] // (numParams + 16) * 8
mov rdi, [rsp+376] // (numParams + 15) * 8
// correct rsp to account for the 4 untouched stack items
add qword ptr [rsp+432], 4*8 // (numParams + 22) * 8
// here we call the callback function
db 0ffh
db 015h
dd 002h
jmp @skipTarget
dq 0
@skipTarget:
// the callback function returned
// we don't know how many stack items the callback function removed from the stack
// we "measure" that here by locating our magic stack signature
push rax
push rcx
push rdx
mov rdx, 6D63683474687374h // 'mch4thst'
lea rax, [rsp+320] // (numParams + 8) * 8
@searchLoop:
mov rcx, [rax]
cmp rcx, rdx
jz @searchMaybeComplete
sub rax, 8
jmp @searchLoop
@searchMaybeComplete:
// we found the stack signature, now let's verify the checksum
mov rcx, [rax-8]
xor rcx, rax
cmp rcx, rdx
jz @searchDefinitelyComplete
sub rax, 8
jmp @searchLoop
@searchDefinitelyComplete:
// ok, we found it, clear the marker
xor qword ptr [rax], rdx
// now let's do some math
lea rcx, [rsp+320] // (numParams + 8) * 8
sub rax, rcx
// eax now contains the negative number of stack items which the callback function removed from the stack
// for example, if the callback function has a "ret 16", then eax is now -16
// now we fetch InUseTargetArr pointer from the private storage area and clear it
mov rcx, [rsp+rax+304] // (numParams + 6) * 8
cmp rcx, 0
jz @skipClear
lock and qword ptr [rcx], 0
@skipClear:
// finally let's clean up
// unfortunately thanks to stupid CET this is rather complicated
// basically we have to perform a "ret x" jump which matches the one of the callback function
neg rax
mov rcx, rax
shr rax, 2
add rcx, rax
shr rax, 1
add rcx, rax
add rcx, 9
call @myself
@myself:
pop rax
add rax, rcx
pop rdx
pop rcx
jmp rax
// return table, max supported number of parameters is 64
pop rax; add rsp, 472; ret $000 // (numParams + 23) * 8 + 4*8
pop rax; add rsp, 464; ret $008 // (numParams + 22) * 8 + 4*8
pop rax; add rsp, 456; ret $010 // (numParams + 21) * 8 + 4*8
pop rax; add rsp, 448; ret $018 // (numParams + 20) * 8 + 4*8
pop rax; add rsp, 440; ret $020 // (numParams + 19) * 8 + 4*8
pop rax; add rsp, 432; ret $028 // (numParams + 18) * 8 + 4*8
pop rax; add rsp, 424; ret $030 // (numParams + 17) * 8 + 4*8
pop rax; add rsp, 416; ret $038 // (numParams + 16) * 8 + 4*8
pop rax; add rsp, 408; ret $040 // (numParams + 15) * 8 + 4*8
pop rax; add rsp, 400; ret $048 // (numParams + 14) * 8 + 4*8
pop rax; add rsp, 392; ret $050 // (numParams + 13) * 8 + 4*8
pop rax; add rsp, 384; ret $058 // (numParams + 12) * 8 + 4*8
pop rax; add rsp, 376; ret $060 // (numParams + 11) * 8 + 4*8
pop rax; add rsp, 368; ret $068 // (numParams + 10) * 8 + 4*8
pop rax; add rsp, 360; ret $070 // (numParams + 9) * 8 + 4*8
pop rax; add rsp, 352; ret $078 // (numParams + 8) * 8 + 4*8
pop rax; add rsp, 344; ret $080 // (numParams + 7) * 8 + 4*8
pop rax; add rsp, 336; ret $088 // (numParams + 6) * 8 + 4*8
pop rax; add rsp, 328; ret $090 // (numParams + 5) * 8 + 4*8
pop rax; add rsp, 320; ret $098 // (numParams + 4) * 8 + 4*8
pop rax; add rsp, 312; ret $0a0 // (numParams + 3) * 8 + 4*8
pop rax; add rsp, 304; ret $0a8 // (numParams + 2) * 8 + 4*8
pop rax; add rsp, 296; ret $0b0 // (numParams + 1) * 8 + 4*8
pop rax; add rsp, 288; ret $0b8 // (numParams + 0) * 8 + 4*8
pop rax; add rsp, 280; ret $0c0 // (numParams - 1) * 8 + 4*8
pop rax; add rsp, 272; ret $0c8 // (numParams - 2) * 8 + 4*8
pop rax; add rsp, 264; ret $0d0 // (numParams - 3) * 8 + 4*8
pop rax; add rsp, 256; ret $0d8 // (numParams - 4) * 8 + 4*8
pop rax; add rsp, 248; ret $0e0 // (numParams - 5) * 8 + 4*8
pop rax; add rsp, 240; ret $0e8 // (numParams - 6) * 8 + 4*8
pop rax; add rsp, 232; ret $0f0 // (numParams - 7) * 8 + 4*8
pop rax; add rsp, 224; ret $0f8 // (numParams - 8) * 8 + 4*8
pop rax; add rsp, 216; ret $100 // (numParams - 9) * 8 + 4*8
pop rax; add rsp, 208; ret $108 // (numParams - 10) * 8 + 4*8
pop rax; add rsp, 200; ret $110 // (numParams - 11) * 8 + 4*8
pop rax; add rsp, 192; ret $118 // (numParams - 12) * 8 + 4*8
pop rax; add rsp, 184; ret $120 // (numParams - 13) * 8 + 4*8
pop rax; add rsp, 176; ret $128 // (numParams - 14) * 8 + 4*8
pop rax; add rsp, 168; ret $130 // (numParams - 15) * 8 + 4*8
pop rax; add rsp, 160; ret $138 // (numParams - 16) * 8 + 4*8
pop rax; add rsp, 152; ret $140 // (numParams - 17) * 8 + 4*8
pop rax; add rsp, 144; ret $148 // (numParams - 18) * 8 + 4*8
pop rax; add rsp, 136; ret $150 // (numParams - 19) * 8 + 4*8
pop rax; add rsp, 136; ret $158 // (numParams - 20) * 8 + 4*8
pop rax; add rsp, 136; ret $160 // (numParams - 21) * 8 + 4*8
pop rax; add rsp, 136; ret $168 // (numParams - 22) * 8 + 4*8
pop rax; add rsp, 136; ret $170 // (numParams - 23) * 8 + 4*8
pop rax; add rsp, 136; ret $178 // (numParams - 24) * 8 + 4*8
pop rax; add rsp, 136; ret $180 // (numParams - 25) * 8 + 4*8
pop rax; add rsp, 136; ret $188 // (numParams - 26) * 8 + 4*8
pop rax; add rsp, 136; ret $190 // (numParams - 27) * 8 + 4*8
pop rax; add rsp, 136; ret $198 // (numParams - 28) * 8 + 4*8
pop rax; add rsp, 136; ret $1a0 // (numParams - 29) * 8 + 4*8
pop rax; add rsp, 136; ret $1a8 // (numParams - 30) * 8 + 4*8
pop rax; add rsp, 136; ret $1b0 // (numParams - 31) * 8 + 4*8
pop rax; add rsp, 136; ret $1b8 // (numParams - 32) * 8 + 4*8
pop rax; add rsp, 136; ret $1c0 // (numParams - 33) * 8 + 4*8
pop rax; add rsp, 136; ret $1c8 // (numParams - 34) * 8 + 4*8
pop rax; add rsp, 136; ret $1d0 // (numParams - 35) * 8 + 4*8
pop rax; add rsp, 136; ret $1d8 // (numParams - 36) * 8 + 4*8
pop rax; add rsp, 136; ret $1e0 // (numParams - 37) * 8 + 4*8
pop rax; add rsp, 136; ret $1e8 // (numParams - 38) * 8 + 4*8
pop rax; add rsp, 136; ret $1f0 // (numParams - 39) * 8 + 4*8
pop rax; add rsp, 136; ret $1f8 // (numParams - 40) * 8 + 4*8
pop rax; add rsp, 136; ret $200 // (numParams - 41) * 8 + 4*8
{$else}
// keep 4 stack items untouched
sub esp, 4*4
// store current thread state to stack
pushad
pushfd
// add a signature and checksum
mov eax, 'mch4'
push eax
xor eax, esp
push eax
// let's reserve some space here for private use
sub esp, 10h
// copy the parameters and return address
lea esi, [esp+204] // (numParams + 15) * 4 + 4*4
mov ecx, 32 // (numParams + 0)
@copyLoop:
mov eax, [esi]
sub esi, 4
push eax
loop @copyLoop
// here comes (mostly) the usual InUseEnter code
mov edi, $22222222 // InUseTargetArr
mov esi, $33333333 // InUseThreadArr
mov ecx, 546 // CInUseCount
xor eax, eax
@inUseLoop:
// search for an empty (zero) InUseTargetArr array index
lock cmpxchg [edi], edi
jz @success
add edi, 4
add esi, 4
xor eax, eax
loop @inUseLoop
xor edi, edi
jmp @finish
@success:
// store GetCurrentThreadId in InUseThreadArr
mov eax, fs:[$18];
mov eax, [eax+$24]
mov [esi], eax
@finish:
// store the InUseTargetArr pointer in the private storage area
mov [esp+140], edi // (numParams + 3) * 4
// restore the registers by using the stored thread state
mov eax, [esp+184] // (numParams + 14) * 4
mov ecx, [esp+180] // (numParams + 13) * 4
mov edx, [esp+176] // (numParams + 12) * 4
mov ebx, [esp+172] // (numParams + 11) * 4
mov esi, [esp+160] // (numParams + 8) * 4
mov edi, [esp+156] // (numParams + 7) * 4
// correct rsp to account for the 4 untouched stack items
add [esp+168], 4*4 // (numParams + 10) * 4
// here we call the callback function
db $e8
dd $44444444
// the callback function returned
// we don't know how many stack items the callback function removed from the stack
// we "measure" that here by locating our magic stack signature
push eax
push ecx
lea eax, [esp+156] // (numParams + 7) * 4
@searchLoop:
mov ecx, [eax]
cmp ecx, 'mch4'
jz @searchMaybeComplete
sub eax, 4
jmp @searchLoop
@searchMaybeComplete:
// we found the stack signature, now let's verify the checksum
mov ecx, [eax-4]
xor ecx, eax
cmp ecx, 'mch4'
jz @searchDefinitelyComplete
sub eax, 4
jmp @searchLoop
@searchDefinitelyComplete:
// ok, we found it, clear the marker
and dword ptr [eax], 0
// now let's do some math
lea ecx, [esp+156] // (numParams + 7) * 4
sub eax, ecx
// eax now contains the negative number of stack items which the callback function removed from the stack
// for example, if the callback function has a "ret 16", then eax is now -16
// now we fetch InUseTargetArr pointer from the private storage area and clear it
mov ecx, [esp+eax+148] // (numParams + 5) * 4
cmp ecx, 0
jz @skipClear
lock and [ecx], 0
@skipClear:
// finally let's clean up
// unfortunately thanks to stupid CET this is rather complicated
// basically we have to perform a "ret x" jump which matches the one of the callback function
neg eax
mov ecx, eax
shl ecx, 1
shr eax, 1
add ecx, eax
add ecx, 6
call @myself
@myself:
pop eax
add eax, ecx
pop ecx
jmp eax
// return table, max supported number of parameters is 64
pop eax; add esp, 204; ret $00 // (numParams + 15) * 4 + 4*4
pop eax; add esp, 200; ret $04 // (numParams + 14) * 4 + 4*4
pop eax; add esp, 196; ret $08 // (numParams + 13) * 4 + 4*4
pop eax; add esp, 192; ret $0c // (numParams + 12) * 4 + 4*4
pop eax; add esp, 188; ret $10 // (numParams + 11) * 4 + 4*4
pop eax; add esp, 184; ret $14 // (numParams + 10) * 4 + 4*4
pop eax; add esp, 180; ret $18 // (numParams + 9) * 4 + 4*4
pop eax; add esp, 176; ret $1c // (numParams + 8) * 4 + 4*4
pop eax; add esp, 172; ret $20 // (numParams + 7) * 4 + 4*4
pop eax; add esp, 168; ret $24 // (numParams + 6) * 4 + 4*4
pop eax; add esp, 164; ret $28 // (numParams + 5) * 4 + 4*4
pop eax; add esp, 160; ret $2c // (numParams + 4) * 4 + 4*4
pop eax; add esp, 156; ret $30 // (numParams + 3) * 4 + 4*4
pop eax; add esp, 152; ret $34 // (numParams + 2) * 4 + 4*4
pop eax; add esp, 148; ret $38 // (numParams + 1) * 4 + 4*4
pop eax; add esp, 144; ret $3c // (numParams + 0) * 4 + 4*4
pop eax; add esp, 140; ret $40 // (numParams - 1) * 4 + 4*4
pop eax; add esp, 136; ret $44 // (numParams - 2) * 4 + 4*4
pop eax; add esp, 132; ret $48 // (numParams - 3) * 4 + 4*4
pop eax; add esp, 132; ret $4c // (numParams - 4) * 4 + 4*4
pop eax; add esp, 132; ret $50 // (numParams - 5) * 4 + 4*4
pop eax; add esp, 132; ret $54 // (numParams - 6) * 4 + 4*4
pop eax; add esp, 132; ret $58 // (numParams - 7) * 4 + 4*4
pop eax; add esp, 132; ret $5c // (numParams - 8) * 4 + 4*4
pop eax; add esp, 132; ret $60 // (numParams - 9) * 4 + 4*4
pop eax; add esp, 132; ret $64 // (numParams - 10) * 4 + 4*4
pop eax; add esp, 132; ret $68 // (numParams - 11) * 4 + 4*4
pop eax; add esp, 132; ret $6c // (numParams - 12) * 4 + 4*4
pop eax; add esp, 132; ret $70 // (numParams - 13) * 4 + 4*4
pop eax; add esp, 132; ret $74 // (numParams - 14) * 4 + 4*4
pop eax; add esp, 132; ret $78 // (numParams - 15) * 4 + 4*4
pop eax; add esp, 132; ret $7c // (numParams - 16) * 4 + 4*4
pop eax; add esp, 132; ret $80 // (numParams - 17) * 4 + 4*4
pop eax; add esp, 132; ret $84 // (numParams - 18) * 4 + 4*4
pop eax; add esp, 132; ret $88 // (numParams - 19) * 4 + 4*4
pop eax; add esp, 132; ret $8c // (numParams - 20) * 4 + 4*4
pop eax; add esp, 132; ret $90 // (numParams - 21) * 4 + 4*4
pop eax; add esp, 132; ret $94 // (numParams - 22) * 4 + 4*4
pop eax; add esp, 132; ret $98 // (numParams - 23) * 4 + 4*4
pop eax; add esp, 132; ret $9c // (numParams - 24) * 4 + 4*4
pop eax; add esp, 132; ret $a0 // (numParams - 25) * 4 + 4*4
pop eax; add esp, 132; ret $a4 // (numParams - 26) * 4 + 4*4
pop eax; add esp, 132; ret $a8 // (numParams - 27) * 4 + 4*4
pop eax; add esp, 132; ret $ac // (numParams - 28) * 4 + 4*4
pop eax; add esp, 132; ret $b0 // (numParams - 29) * 4 + 4*4
pop eax; add esp, 132; ret $b4 // (numParams - 30) * 4 + 4*4
pop eax; add esp, 132; ret $b8 // (numParams - 31) * 4 + 4*4
pop eax; add esp, 132; ret $bc // (numParams - 32) * 4 + 4*4
pop eax; add esp, 132; ret $c0 // (numParams - 33) * 4 + 4*4
pop eax; add esp, 132; ret $c4 // (numParams - 34) * 4 + 4*4
pop eax; add esp, 132; ret $c8 // (numParams - 35) * 4 + 4*4
pop eax; add esp, 132; ret $cc // (numParams - 36) * 4 + 4*4
pop eax; sub esp, 132; ret $d0 // (numParams - 37) * 4 + 4*4
pop eax; add esp, 132; ret $d4 // (numParams - 38) * 4 + 4*4
pop eax; add esp, 132; ret $d8 // (numParams - 39) * 4 + 4*4
pop eax; add esp, 132; ret $dc // (numParams - 40) * 4 + 4*4
pop eax; add esp, 132; ret $e0 // (numParams - 41) * 4 + 4*4
pop eax; add esp, 132; ret $e4 // (numParams - 42) * 4 + 4*4
pop eax; add esp, 132; ret $e8 // (numParams - 43) * 4 + 4*4
pop eax; add esp, 132; ret $ec // (numParams - 44) * 4 + 4*4
pop eax; add esp, 132; ret $f0 // (numParams - 45) * 4 + 4*4
pop eax; add esp, 132; ret $f4 // (numParams - 46) * 4 + 4*4
pop eax; add esp, 132; ret $f8 // (numParams - 47) * 4 + 4*4
pop eax; add esp, 132; ret $fc // (numParams - 48) * 4 + 4*4
pop eax; add esp, 132; ret $100 // (numParams - 49) * 4 + 4*4
{$endif}
end;
procedure InUse_ThreadStateEnd; begin end;
//*)
const
{$ifdef win64}
CInUse_ThreadState : array[0..1070] of Byte = (
$48, $83, $EC, $20, $54, $50, $51, $52, $53, $55, $56, $57, $41, $50, $41, $51,
$41, $52, $41, $53, $41, $54, $41, $55, $41, $56, $41, $57, $9C, $48, $B8, $74,
$73, $68, $74, $34, $68, $63, $6D, $50, $48, $31, $E0, $50, $48, $83, $EC, $20,
$48, $8D, $B4, $24, $D8, $01, $00, $00, $48, $B9, $20, $00, $00, $00, $00, $00,
$00, $00, $48, $8B, $06, $48, $83, $EE, $08, $50, $E2, $F6, $48, $BF, $22, $22,
$22, $22, $22, $22, $22, $22, $48, $BE, $33, $33, $33, $33, $33, $33, $33, $33,
$48, $B9, $22, $02, $00, $00, $00, $00, $00, $00, $48, $31, $C0, $F0, $48, $0F,
$B1, $3F, $74, $12, $48, $83, $C7, $08, $48, $83, $C6, $04, $48, $31, $C0, $E2,
$EC, $48, $31, $FF, $EB, $0F, $65, $48, $8B, $04, $25, $30, $00, $00, $00, $48,
$8B, $40, $48, $89, $06, $48, $89, $BC, $24, $18, $01, $00, $00, $48, $8B, $84,
$24, $A8, $01, $00, $00, $48, $8B, $8C, $24, $A0, $01, $00, $00, $48, $8B, $94,
$24, $98, $01, $00, $00, $48, $8B, $9C, $24, $90, $01, $00, $00, $48, $8B, $B4,
$24, $80, $01, $00, $00, $48, $8B, $BC, $24, $78, $01, $00, $00, $48, $83, $84,
$24, $B0, $01, $00, $00, $20, $FF, $15, $02, $00, $00, $00, $EB, $08, $00, $00,
$00, $00, $00, $00, $00, $00, $50, $51, $52, $48, $BA, $74, $73, $68, $74, $34,
$68, $63, $6D, $48, $8D, $84, $24, $40, $01, $00, $00, $48, $8B, $08, $48, $39,
$D1, $74, $06, $48, $83, $E8, $08, $EB, $F2, $48, $8B, $48, $F8, $48, $31, $C1,
$48, $39, $D1, $74, $06, $48, $83, $E8, $08, $EB, $E0, $48, $31, $10, $48, $8D,
$8C, $24, $40, $01, $00, $00, $48, $29, $C8, $48, $8B, $8C, $04, $30, $01, $00,
$00, $48, $83, $F9, $00, $74, $08, $F0, $48, $81, $21, $00, $00, $00, $00, $48,
$F7, $D8, $48, $89, $C1, $48, $C1, $E8, $02, $48, $01, $C1, $48, $D1, $E8, $48,
$01, $C1, $48, $83, $C1, $09, $E8, $00, $00, $00, $00, $58, $48, $01, $C8, $5A,
$59, $48, $FF, $E0, $58, $48, $81, $C4, $D8, $01, $00, $00, $C2, $00, $00, $58,
$48, $81, $C4, $D0, $01, $00, $00, $C2, $08, $00, $58, $48, $81, $C4, $C8, $01,
$00, $00, $C2, $10, $00, $58, $48, $81, $C4, $C0, $01, $00, $00, $C2, $18, $00,
$58, $48, $81, $C4, $B8, $01, $00, $00, $C2, $20, $00, $58, $48, $81, $C4, $B0,
$01, $00, $00, $C2, $28, $00, $58, $48, $81, $C4, $A8, $01, $00, $00, $C2, $30,
$00, $58, $48, $81, $C4, $A0, $01, $00, $00, $C2, $38, $00, $58, $48, $81, $C4,
$98, $01, $00, $00, $C2, $40, $00, $58, $48, $81, $C4, $90, $01, $00, $00, $C2,
$48, $00, $58, $48, $81, $C4, $88, $01, $00, $00, $C2, $50, $00, $58, $48, $81,
$C4, $80, $01, $00, $00, $C2, $58, $00, $58, $48, $81, $C4, $78, $01, $00, $00,
$C2, $60, $00, $58, $48, $81, $C4, $70, $01, $00, $00, $C2, $68, $00, $58, $48,
$81, $C4, $68, $01, $00, $00, $C2, $70, $00, $58, $48, $81, $C4, $60, $01, $00,
$00, $C2, $78, $00, $58, $48, $81, $C4, $58, $01, $00, $00, $C2, $80, $00, $58,
$48, $81, $C4, $50, $01, $00, $00, $C2, $88, $00, $58, $48, $81, $C4, $48, $01,
$00, $00, $C2, $90, $00, $58, $48, $81, $C4, $40, $01, $00, $00, $C2, $98, $00,
$58, $48, $81, $C4, $38, $01, $00, $00, $C2, $A0, $00, $58, $48, $81, $C4, $30,
$01, $00, $00, $C2, $A8, $00, $58, $48, $81, $C4, $28, $01, $00, $00, $C2, $B0,
$00, $58, $48, $81, $C4, $20, $01, $00, $00, $C2, $B8, $00, $58, $48, $81, $C4,
$18, $01, $00, $00, $C2, $C0, $00, $58, $48, $81, $C4, $10, $01, $00, $00, $C2,
$C8, $00, $58, $48, $81, $C4, $08, $01, $00, $00, $C2, $D0, $00, $58, $48, $81,
$C4, $00, $01, $00, $00, $C2, $D8, $00, $58, $48, $81, $C4, $F8, $00, $00, $00,
$C2, $E0, $00, $58, $48, $81, $C4, $F0, $00, $00, $00, $C2, $E8, $00, $58, $48,
$81, $C4, $E8, $00, $00, $00, $C2, $F0, $00, $58, $48, $81, $C4, $E0, $00, $00,
$00, $C2, $F8, $00, $58, $48, $81, $C4, $D8, $00, $00, $00, $C2, $00, $01, $58,
$48, $81, $C4, $D0, $00, $00, $00, $C2, $08, $01, $58, $48, $81, $C4, $C8, $00,
$00, $00, $C2, $10, $01, $58, $48, $81, $C4, $C0, $00, $00, $00, $C2, $18, $01,
$58, $48, $81, $C4, $B8, $00, $00, $00, $C2, $20, $01, $58, $48, $81, $C4, $B0,
$00, $00, $00, $C2, $28, $01, $58, $48, $81, $C4, $A8, $00, $00, $00, $C2, $30,
$01, $58, $48, $81, $C4, $A0, $00, $00, $00, $C2, $38, $01, $58, $48, $81, $C4,
$98, $00, $00, $00, $C2, $40, $01, $58, $48, $81, $C4, $90, $00, $00, $00, $C2,
$48, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $50, $01, $58, $48, $81,
$C4, $88, $00, $00, $00, $C2, $58, $01, $58, $48, $81, $C4, $88, $00, $00, $00,
$C2, $60, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $68, $01, $58, $48,
$81, $C4, $88, $00, $00, $00, $C2, $70, $01, $58, $48, $81, $C4, $88, $00, $00,
$00, $C2, $78, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $80, $01, $58,
$48, $81, $C4, $88, $00, $00, $00, $C2, $88, $01, $58, $48, $81, $C4, $88, $00,
$00, $00, $C2, $90, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $98, $01,
$58, $48, $81, $C4, $88, $00, $00, $00, $C2, $A0, $01, $58, $48, $81, $C4, $88,
$00, $00, $00, $C2, $A8, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $B0,
$01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $B8, $01, $58, $48, $81, $C4,
$88, $00, $00, $00, $C2, $C0, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2,
$C8, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $D0, $01, $58, $48, $81,
$C4, $88, $00, $00, $00, $C2, $D8, $01, $58, $48, $81, $C4, $88, $00, $00, $00,
$C2, $E0, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $E8, $01, $58, $48,
$81, $C4, $88, $00, $00, $00, $C2, $F0, $01, $58, $48, $81, $C4, $88, $00, $00,
$00, $C2, $F8, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $00, $02
);
{$else}
CInUse_ThreadState : array [0..891] of byte = (
$83, $EC, $10, $60, $9C, $B8, $34, $68, $63, $6D, $50, $31, $E0, $50, $83, $EC,
$10, $8D, $B4, $24, $CC, $00, $00, $00, $B9, $20, $00, $00, $00, $8B, $06, $83,
$EE, $04, $50, $E2, $F8, $BF, $22, $22, $22, $22, $BE, $33, $33, $33, $33, $B9,
$22, $02, $00, $00, $31, $C0, $F0, $0F, $B1, $3F, $74, $0E, $83, $C7, $04, $83,
$C6, $04, $31, $C0, $E2, $F0, $31, $FF, $EB, $0C, $64, $8B, $05, $18, $00, $00,
$00, $8B, $40, $24, $89, $06, $89, $BC, $24, $8C, $00, $00, $00, $8B, $84, $24,
$B8, $00, $00, $00, $8B, $8C, $24, $B4, $00, $00, $00, $8B, $94, $24, $B0, $00,
$00, $00, $8B, $9C, $24, $AC, $00, $00, $00, $8B, $B4, $24, $A0, $00, $00, $00,
$8B, $BC, $24, $9C, $00, $00, $00, $83, $84, $24, $A8, $00, $00, $00, $10, $E8,
$44, $44, $44, $44, $50, $51, $8D, $84, $24, $9C, $00, $00, $00, $8B, $08, $81,
$F9, $34, $68, $63, $6D, $74, $05, $83, $E8, $04, $EB, $F1, $8B, $48, $FC, $31,
$C1, $81, $F9, $34, $68, $63, $6D, $74, $05, $83, $E8, $04, $EB, $DF, $83, $20,
$00, $8D, $8C, $24, $9C, $00, $00, $00, $29, $C8, $8B, $8C, $04, $94, $00, $00,
$00, $83, $F9, $00, $74, $04, $F0, $83, $21, $00, $F7, $D8, $89, $C1, $D1, $E1,
$D1, $E8, $01, $C1, $83, $C1, $06, $E8, $00, $00, $00, $00, $58, $01, $C8, $59,
$FF, $E0, $58, $81, $C4, $CC, $00, $00, $00, $C2, $00, $00, $58, $81, $C4, $C8,
$00, $00, $00, $C2, $04, $00, $58, $81, $C4, $C4, $00, $00, $00, $C2, $08, $00,
$58, $81, $C4, $C0, $00, $00, $00, $C2, $0C, $00, $58, $81, $C4, $BC, $00, $00,
$00, $C2, $10, $00, $58, $81, $C4, $B8, $00, $00, $00, $C2, $14, $00, $58, $81,
$C4, $B4, $00, $00, $00, $C2, $18, $00, $58, $81, $C4, $B0, $00, $00, $00, $C2,
$1C, $00, $58, $81, $C4, $AC, $00, $00, $00, $C2, $20, $00, $58, $81, $C4, $A8,
$00, $00, $00, $C2, $24, $00, $58, $81, $C4, $A4, $00, $00, $00, $C2, $28, $00,
$58, $81, $C4, $A0, $00, $00, $00, $C2, $2C, $00, $58, $81, $C4, $9C, $00, $00,
$00, $C2, $30, $00, $58, $81, $C4, $98, $00, $00, $00, $C2, $34, $00, $58, $81,
$C4, $94, $00, $00, $00, $C2, $38, $00, $58, $81, $C4, $90, $00, $00, $00, $C2,
$3C, $00, $58, $81, $C4, $8C, $00, $00, $00, $C2, $40, $00, $58, $81, $C4, $88,
$00, $00, $00, $C2, $44, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $48, $00,
$58, $81, $C4, $84, $00, $00, $00, $C2, $4C, $00, $58, $81, $C4, $84, $00, $00,
$00, $C2, $50, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $54, $00, $58, $81,
$C4, $84, $00, $00, $00, $C2, $58, $00, $58, $81, $C4, $84, $00, $00, $00, $C2,
$5C, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $60, $00, $58, $81, $C4, $84,
$00, $00, $00, $C2, $64, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $68, $00,
$58, $81, $C4, $84, $00, $00, $00, $C2, $6C, $00, $58, $81, $C4, $84, $00, $00,
$00, $C2, $70, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $74, $00, $58, $81,
$C4, $84, $00, $00, $00, $C2, $78, $00, $58, $81, $C4, $84, $00, $00, $00, $C2,
$7C, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $80, $00, $58, $81, $C4, $84,
$00, $00, $00, $C2, $84, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $88, $00,
$58, $81, $C4, $84, $00, $00, $00, $C2, $8C, $00, $58, $81, $C4, $84, $00, $00,
$00, $C2, $90, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $94, $00, $58, $81,
$C4, $84, $00, $00, $00, $C2, $98, $00, $58, $81, $C4, $84, $00, $00, $00, $C2,
$9C, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $A0, $00, $58, $81, $C4, $84,
$00, $00, $00, $C2, $A4, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $A8, $00,
$58, $81, $C4, $84, $00, $00, $00, $C2, $AC, $00, $58, $81, $C4, $84, $00, $00,
$00, $C2, $B0, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $B4, $00, $58, $81,
$C4, $84, $00, $00, $00, $C2, $B8, $00, $58, $81, $C4, $84, $00, $00, $00, $C2,
$BC, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $C0, $00, $58, $81, $C4, $84,
$00, $00, $00, $C2, $C4, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $C8, $00,
$58, $81, $C4, $84, $00, $00, $00, $C2, $CC, $00, $58, $81, $EC, $84, $00, $00,
$00, $C2, $D0, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $D4, $00, $58, $81,
$C4, $84, $00, $00, $00, $C2, $D8, $00, $58, $81, $C4, $84, $00, $00, $00, $C2,
$DC, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $E0, $00, $58, $81, $C4, $84,
$00, $00, $00, $C2, $E4, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $E8, $00,
$58, $81, $C4, $84, $00, $00, $00, $C2, $EC, $00, $58, $81, $C4, $84, $00, $00,
$00, $C2, $F0, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $F4, $00, $58, $81,
$C4, $84, $00, $00, $00, $C2, $F8, $00, $58, $81, $C4, $84, $00, $00, $00, $C2,
$FC, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $00, $01
);
{$endif}
(*
procedure NoUse_ThreadState;
asm
{$ifdef win64}
.noframe
// keep 4 stack items untouched
sub rsp, 4*8
// store current thread state to stack
push rsp
push rax
push rcx
push rdx
push rbx
push rbp
push rsi
push rdi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
pushfq
// add a signature and checksum
mov rax, 6D63683474687374h // 'mch4thst'
push rax
xor rax, rsp
push rax
// let's reserve some space here for private use
sub rsp, 20h
// copy the parameters and return address
lea rsi, [rsp+472] // (numParams + 23) * 8 + 4*8
mov rcx, 32 // (numParams + 0)
@copyLoop:
mov rax, [rsi]
sub rsi, 8
push rax
loop @copyLoop
// restore the registers by using the stored thread state
mov rax, [rsp+424] // (numParams + 21) * 8
mov rcx, [rsp+416] // (numParams + 20) * 8
mov rsi, [rsp+384] // (numParams + 16) * 8
// correct rsp to account for the 4 untouched stack items
add qword ptr [rsp+432], 4*8 // (numParams + 22) * 8
// here we call the callback function
db 0ffh
db 015h
dd 002h
jmp @skipTarget
dq 0
@skipTarget:
// the callback function returned
// we don't know how many stack items the callback function removed from the stack
// we "measure" that here by locating our magic stack signature
push rax
push rcx
push rdx
mov rdx, 6D63683474687374h // 'mch4thst'
lea rax, [rsp+320] // (numParams + 8) * 8
@searchLoop:
mov rcx, [rax]
cmp rcx, rdx
jz @searchMaybeComplete
sub rax, 8
jmp @searchLoop
@searchMaybeComplete:
// we found the stack signature, now let's verify the checksum
mov rcx, [rax-8]
xor rcx, rax
cmp rcx, rdx
jz @searchDefinitelyComplete
sub rax, 8
jmp @searchLoop
@searchDefinitelyComplete:
// ok, we found it, clear the marker
xor qword ptr [rax], rdx
// now let's do some math
lea rcx, [rsp+320] // (numParams + 8) * 8
sub rax, rcx
// eax now contains the negative number of stack items which the callback function removed from the stack
// for example, if the callback function has a "ret 16", then eax is now -16
// now let's clean up
// unfortunately thanks to stupid CET this is rather complicated
// basically we have to perform a "ret x" jump which matches the one of the callback function
neg rax
mov rcx, rax
shr rax, 2
add rcx, rax
shr rax, 1
add rcx, rax
add rcx, 9
call @myself
@myself:
pop rax
add rax, rcx
pop rdx
pop rcx
jmp rax
// return table, max supported number of parameters is 64
pop rax; add rsp, 472; ret $000 // (numParams + 23) * 8 + 4*8
pop rax; add rsp, 464; ret $008 // (numParams + 22) * 8 + 4*8
pop rax; add rsp, 456; ret $010 // (numParams + 21) * 8 + 4*8
pop rax; add rsp, 448; ret $018 // (numParams + 20) * 8 + 4*8
pop rax; add rsp, 440; ret $020 // (numParams + 19) * 8 + 4*8
pop rax; add rsp, 432; ret $028 // (numParams + 18) * 8 + 4*8
pop rax; add rsp, 424; ret $030 // (numParams + 17) * 8 + 4*8
pop rax; add rsp, 416; ret $038 // (numParams + 16) * 8 + 4*8
pop rax; add rsp, 408; ret $040 // (numParams + 15) * 8 + 4*8
pop rax; add rsp, 400; ret $048 // (numParams + 14) * 8 + 4*8
pop rax; add rsp, 392; ret $050 // (numParams + 13) * 8 + 4*8
pop rax; add rsp, 384; ret $058 // (numParams + 12) * 8 + 4*8
pop rax; add rsp, 376; ret $060 // (numParams + 11) * 8 + 4*8
pop rax; add rsp, 368; ret $068 // (numParams + 10) * 8 + 4*8
pop rax; add rsp, 360; ret $070 // (numParams + 9) * 8 + 4*8
pop rax; add rsp, 352; ret $078 // (numParams + 8) * 8 + 4*8
pop rax; add rsp, 344; ret $080 // (numParams + 7) * 8 + 4*8
pop rax; add rsp, 336; ret $088 // (numParams + 6) * 8 + 4*8
pop rax; add rsp, 328; ret $090 // (numParams + 5) * 8 + 4*8
pop rax; add rsp, 320; ret $098 // (numParams + 4) * 8 + 4*8
pop rax; add rsp, 312; ret $0a0 // (numParams + 3) * 8 + 4*8
pop rax; add rsp, 304; ret $0a8 // (numParams + 2) * 8 + 4*8
pop rax; add rsp, 296; ret $0b0 // (numParams + 1) * 8 + 4*8
pop rax; add rsp, 288; ret $0b8 // (numParams + 0) * 8 + 4*8
pop rax; add rsp, 280; ret $0c0 // (numParams - 1) * 8 + 4*8
pop rax; add rsp, 272; ret $0c8 // (numParams - 2) * 8 + 4*8
pop rax; add rsp, 264; ret $0d0 // (numParams - 3) * 8 + 4*8
pop rax; add rsp, 256; ret $0d8 // (numParams - 4) * 8 + 4*8
pop rax; add rsp, 248; ret $0e0 // (numParams - 5) * 8 + 4*8
pop rax; add rsp, 240; ret $0e8 // (numParams - 6) * 8 + 4*8
pop rax; add rsp, 232; ret $0f0 // (numParams - 7) * 8 + 4*8
pop rax; add rsp, 224; ret $0f8 // (numParams - 8) * 8 + 4*8
pop rax; add rsp, 216; ret $100 // (numParams - 9) * 8 + 4*8
pop rax; add rsp, 208; ret $108 // (numParams - 10) * 8 + 4*8
pop rax; add rsp, 200; ret $110 // (numParams - 11) * 8 + 4*8
pop rax; add rsp, 192; ret $118 // (numParams - 12) * 8 + 4*8
pop rax; add rsp, 184; ret $120 // (numParams - 13) * 8 + 4*8
pop rax; add rsp, 176; ret $128 // (numParams - 14) * 8 + 4*8
pop rax; add rsp, 168; ret $130 // (numParams - 15) * 8 + 4*8
pop rax; add rsp, 160; ret $138 // (numParams - 16) * 8 + 4*8
pop rax; add rsp, 152; ret $140 // (numParams - 17) * 8 + 4*8
pop rax; add rsp, 144; ret $148 // (numParams - 18) * 8 + 4*8
pop rax; add rsp, 136; ret $150 // (numParams - 19) * 8 + 4*8
pop rax; add rsp, 136; ret $158 // (numParams - 20) * 8 + 4*8
pop rax; add rsp, 136; ret $160 // (numParams - 21) * 8 + 4*8
pop rax; add rsp, 136; ret $168 // (numParams - 22) * 8 + 4*8
pop rax; add rsp, 136; ret $170 // (numParams - 23) * 8 + 4*8
pop rax; add rsp, 136; ret $178 // (numParams - 24) * 8 + 4*8
pop rax; add rsp, 136; ret $180 // (numParams - 25) * 8 + 4*8
pop rax; add rsp, 136; ret $188 // (numParams - 26) * 8 + 4*8
pop rax; add rsp, 136; ret $190 // (numParams - 27) * 8 + 4*8
pop rax; add rsp, 136; ret $198 // (numParams - 28) * 8 + 4*8
pop rax; add rsp, 136; ret $1a0 // (numParams - 29) * 8 + 4*8
pop rax; add rsp, 136; ret $1a8 // (numParams - 30) * 8 + 4*8
pop rax; add rsp, 136; ret $1b0 // (numParams - 31) * 8 + 4*8
pop rax; add rsp, 136; ret $1b8 // (numParams - 32) * 8 + 4*8
pop rax; add rsp, 136; ret $1c0 // (numParams - 33) * 8 + 4*8
pop rax; add rsp, 136; ret $1c8 // (numParams - 34) * 8 + 4*8
pop rax; add rsp, 136; ret $1d0 // (numParams - 35) * 8 + 4*8
pop rax; add rsp, 136; ret $1d8 // (numParams - 36) * 8 + 4*8
pop rax; add rsp, 136; ret $1e0 // (numParams - 37) * 8 + 4*8
pop rax; add rsp, 136; ret $1e8 // (numParams - 38) * 8 + 4*8
pop rax; add rsp, 136; ret $1f0 // (numParams - 39) * 8 + 4*8
pop rax; add rsp, 136; ret $1f8 // (numParams - 40) * 8 + 4*8
pop rax; add rsp, 136; ret $200 // (numParams - 41) * 8 + 4*8
{$else}
// keep 4 stack items untouched
sub esp, 4*4
// store current thread state to stack
pushad
pushfd
// add a signature and checksum
mov eax, 'mch4'
push eax
xor eax, esp
push eax
// let's reserve some space here for private use
sub esp, 10h
// copy the parameters and return address
lea esi, [esp+204] // (numParams + 15) * 4 + 4*4
mov ecx, 32 // (numParams + 0)
@copyLoop:
mov eax, [esi]
sub esi, 4
push eax
loop @copyLoop
// restore the registers by using the stored thread state
mov eax, [esp+188] // (numParams + 14) * 4
mov ecx, [esp+184] // (numParams + 13) * 4
mov esi, [esp+164] // (numParams + 8) * 4
// correct rsp to account for the 4 untouched stack items
add [esp+172], 4*4 // (numParams + 10) * 4
// here we call the callback function
db $e8
dd $44444444
// the callback function returned
// we don't know how many stack items the callback function removed from the stack
// we "measure" that here by locating our magic stack signature
push eax
push ecx
lea eax, [esp+156] // (numParams + 7) * 4
@searchLoop:
mov ecx, [eax]
cmp ecx, 'mch4'
jz @searchMaybeComplete
sub eax, 4
jmp @searchLoop
@searchMaybeComplete:
// we found the stack signature, now let's verify the checksum
mov ecx, [eax-4]
xor ecx, eax
cmp ecx, 'mch4'
jz @searchDefinitelyComplete
sub eax, 4
jmp @searchLoop
@searchDefinitelyComplete:
// ok, we found it, clear the marker
and dword ptr [eax], 0
// now let's do some math
lea ecx, [esp+156] // (numParams + 7) * 4
sub eax, ecx
// eax now contains the negative number of stack items which the callback function removed from the stack
// for example, if the callback function has a "ret 16", then eax is now -16
// now let's clean up
// unfortunately thanks to stupid CET this is rather complicated
// basically we have to perform a "ret x" jump which matches the one of the callback function
neg eax
mov ecx, eax
shl ecx, 1
shr eax, 1
add ecx, eax
add ecx, 6
call @myself
@myself:
pop eax
add eax, ecx
pop ecx
jmp eax
// return table, max supported number of parameters is 64
pop eax; add esp, 204; ret $00 // (numParams + 15) * 4 + 4*4
pop eax; add esp, 200; ret $04 // (numParams + 14) * 4 + 4*4
pop eax; add esp, 196; ret $08 // (numParams + 13) * 4 + 4*4
pop eax; add esp, 192; ret $0c // (numParams + 12) * 4 + 4*4
pop eax; add esp, 188; ret $10 // (numParams + 11) * 4 + 4*4
pop eax; add esp, 184; ret $14 // (numParams + 10) * 4 + 4*4
pop eax; add esp, 180; ret $18 // (numParams + 9) * 4 + 4*4
pop eax; add esp, 176; ret $1c // (numParams + 8) * 4 + 4*4
pop eax; add esp, 172; ret $20 // (numParams + 7) * 4 + 4*4
pop eax; add esp, 168; ret $24 // (numParams + 6) * 4 + 4*4
pop eax; add esp, 164; ret $28 // (numParams + 5) * 4 + 4*4
pop eax; add esp, 160; ret $2c // (numParams + 4) * 4 + 4*4
pop eax; add esp, 156; ret $30 // (numParams + 3) * 4 + 4*4
pop eax; add esp, 152; ret $34 // (numParams + 2) * 4 + 4*4
pop eax; add esp, 148; ret $38 // (numParams + 1) * 4 + 4*4
pop eax; add esp, 144; ret $3c // (numParams + 0) * 4 + 4*4
pop eax; add esp, 140; ret $40 // (numParams - 1) * 4 + 4*4
pop eax; add esp, 136; ret $44 // (numParams - 2) * 4 + 4*4
pop eax; add esp, 132; ret $48 // (numParams - 3) * 4 + 4*4
pop eax; add esp, 132; ret $4c // (numParams - 4) * 4 + 4*4
pop eax; add esp, 132; ret $50 // (numParams - 5) * 4 + 4*4
pop eax; add esp, 132; ret $54 // (numParams - 6) * 4 + 4*4
pop eax; add esp, 132; ret $58 // (numParams - 7) * 4 + 4*4
pop eax; add esp, 132; ret $5c // (numParams - 8) * 4 + 4*4
pop eax; add esp, 132; ret $60 // (numParams - 9) * 4 + 4*4
pop eax; add esp, 132; ret $64 // (numParams - 10) * 4 + 4*4
pop eax; add esp, 132; ret $68 // (numParams - 11) * 4 + 4*4
pop eax; add esp, 132; ret $6c // (numParams - 12) * 4 + 4*4
pop eax; add esp, 132; ret $70 // (numParams - 13) * 4 + 4*4
pop eax; add esp, 132; ret $74 // (numParams - 14) * 4 + 4*4
pop eax; add esp, 132; ret $78 // (numParams - 15) * 4 + 4*4
pop eax; add esp, 132; ret $7c // (numParams - 16) * 4 + 4*4
pop eax; add esp, 132; ret $80 // (numParams - 17) * 4 + 4*4
pop eax; add esp, 132; ret $84 // (numParams - 18) * 4 + 4*4
pop eax; add esp, 132; ret $88 // (numParams - 19) * 4 + 4*4
pop eax; add esp, 132; ret $8c // (numParams - 20) * 4 + 4*4
pop eax; add esp, 132; ret $90 // (numParams - 21) * 4 + 4*4
pop eax; add esp, 132; ret $94 // (numParams - 22) * 4 + 4*4
pop eax; add esp, 132; ret $98 // (numParams - 23) * 4 + 4*4
pop eax; add esp, 132; ret $9c // (numParams - 24) * 4 + 4*4
pop eax; add esp, 132; ret $a0 // (numParams - 25) * 4 + 4*4
pop eax; add esp, 132; ret $a4 // (numParams - 26) * 4 + 4*4
pop eax; add esp, 132; ret $a8 // (numParams - 27) * 4 + 4*4
pop eax; add esp, 132; ret $ac // (numParams - 28) * 4 + 4*4
pop eax; add esp, 132; ret $b0 // (numParams - 29) * 4 + 4*4
pop eax; add esp, 132; ret $b4 // (numParams - 30) * 4 + 4*4
pop eax; add esp, 132; ret $b8 // (numParams - 31) * 4 + 4*4
pop eax; add esp, 132; ret $bc // (numParams - 32) * 4 + 4*4
pop eax; add esp, 132; ret $c0 // (numParams - 33) * 4 + 4*4
pop eax; add esp, 132; ret $c4 // (numParams - 34) * 4 + 4*4
pop eax; add esp, 132; ret $c8 // (numParams - 35) * 4 + 4*4
pop eax; add esp, 132; ret $cc // (numParams - 36) * 4 + 4*4
pop eax; sub esp, 132; ret $d0 // (numParams - 37) * 4 + 4*4
pop eax; add esp, 132; ret $d4 // (numParams - 38) * 4 + 4*4
pop eax; add esp, 132; ret $d8 // (numParams - 39) * 4 + 4*4
pop eax; add esp, 132; ret $dc // (numParams - 40) * 4 + 4*4
pop eax; add esp, 132; ret $e0 // (numParams - 41) * 4 + 4*4
pop eax; add esp, 132; ret $e4 // (numParams - 42) * 4 + 4*4
pop eax; add esp, 132; ret $e8 // (numParams - 43) * 4 + 4*4
pop eax; add esp, 132; ret $ec // (numParams - 44) * 4 + 4*4
pop eax; add esp, 132; ret $f0 // (numParams - 45) * 4 + 4*4
pop eax; add esp, 132; ret $f4 // (numParams - 46) * 4 + 4*4
pop eax; add esp, 132; ret $f8 // (numParams - 47) * 4 + 4*4
pop eax; add esp, 132; ret $fc // (numParams - 48) * 4 + 4*4
pop eax; add esp, 132; ret $100 // (numParams - 49) * 4 + 4*4
{$endif}
end;
//*)
const
{$ifdef win64}
CNoUse_ThreadState : array [0..943] of byte = (
$48, $83, $EC, $20, $54, $50, $51, $52, $53, $55, $56, $57, $41, $50, $41, $51,
$41, $52, $41, $53, $41, $54, $41, $55, $41, $56, $41, $57, $9C, $48, $B8, $74,
$73, $68, $74, $34, $68, $63, $6D, $50, $48, $31, $E0, $50, $48, $83, $EC, $20,
$48, $8D, $B4, $24, $D8, $01, $00, $00, $48, $B9, $20, $00, $00, $00, $00, $00,
$00, $00, $48, $8B, $06, $48, $83, $EE, $08, $50, $E2, $F6, $48, $8B, $84, $24,
$A8, $01, $00, $00, $48, $8B, $8C, $24, $A0, $01, $00, $00, $48, $8B, $B4, $24,
$80, $01, $00, $00, $48, $83, $84, $24, $B0, $01, $00, $00, $20, $FF, $15, $02,
$00, $00, $00, $EB, $08, $00, $00, $00, $00, $00, $00, $00, $00, $50, $51, $52,
$48, $BA, $74, $73, $68, $74, $34, $68, $63, $6D, $48, $8D, $84, $24, $40, $01,
$00, $00, $48, $8B, $08, $48, $39, $D1, $74, $06, $48, $83, $E8, $08, $EB, $F2,
$48, $8B, $48, $F8, $48, $31, $C1, $48, $39, $D1, $74, $06, $48, $83, $E8, $08,
$EB, $E0, $48, $31, $10, $48, $8D, $8C, $24, $40, $01, $00, $00, $48, $29, $C8,
$48, $F7, $D8, $48, $89, $C1, $48, $C1, $E8, $02, $48, $01, $C1, $48, $D1, $E8,
$48, $01, $C1, $48, $83, $C1, $09, $E8, $00, $00, $00, $00, $58, $48, $01, $C8,
$5A, $59, $48, $FF, $E0, $58, $48, $81, $C4, $D8, $01, $00, $00, $C2, $00, $00,
$58, $48, $81, $C4, $D0, $01, $00, $00, $C2, $08, $00, $58, $48, $81, $C4, $C8,
$01, $00, $00, $C2, $10, $00, $58, $48, $81, $C4, $C0, $01, $00, $00, $C2, $18,
$00, $58, $48, $81, $C4, $B8, $01, $00, $00, $C2, $20, $00, $58, $48, $81, $C4,
$B0, $01, $00, $00, $C2, $28, $00, $58, $48, $81, $C4, $A8, $01, $00, $00, $C2,
$30, $00, $58, $48, $81, $C4, $A0, $01, $00, $00, $C2, $38, $00, $58, $48, $81,
$C4, $98, $01, $00, $00, $C2, $40, $00, $58, $48, $81, $C4, $90, $01, $00, $00,
$C2, $48, $00, $58, $48, $81, $C4, $88, $01, $00, $00, $C2, $50, $00, $58, $48,
$81, $C4, $80, $01, $00, $00, $C2, $58, $00, $58, $48, $81, $C4, $78, $01, $00,
$00, $C2, $60, $00, $58, $48, $81, $C4, $70, $01, $00, $00, $C2, $68, $00, $58,
$48, $81, $C4, $68, $01, $00, $00, $C2, $70, $00, $58, $48, $81, $C4, $60, $01,
$00, $00, $C2, $78, $00, $58, $48, $81, $C4, $58, $01, $00, $00, $C2, $80, $00,
$58, $48, $81, $C4, $50, $01, $00, $00, $C2, $88, $00, $58, $48, $81, $C4, $48,
$01, $00, $00, $C2, $90, $00, $58, $48, $81, $C4, $40, $01, $00, $00, $C2, $98,
$00, $58, $48, $81, $C4, $38, $01, $00, $00, $C2, $A0, $00, $58, $48, $81, $C4,
$30, $01, $00, $00, $C2, $A8, $00, $58, $48, $81, $C4, $28, $01, $00, $00, $C2,
$B0, $00, $58, $48, $81, $C4, $20, $01, $00, $00, $C2, $B8, $00, $58, $48, $81,
$C4, $18, $01, $00, $00, $C2, $C0, $00, $58, $48, $81, $C4, $10, $01, $00, $00,
$C2, $C8, $00, $58, $48, $81, $C4, $08, $01, $00, $00, $C2, $D0, $00, $58, $48,
$81, $C4, $00, $01, $00, $00, $C2, $D8, $00, $58, $48, $81, $C4, $F8, $00, $00,
$00, $C2, $E0, $00, $58, $48, $81, $C4, $F0, $00, $00, $00, $C2, $E8, $00, $58,
$48, $81, $C4, $E8, $00, $00, $00, $C2, $F0, $00, $58, $48, $81, $C4, $E0, $00,
$00, $00, $C2, $F8, $00, $58, $48, $81, $C4, $D8, $00, $00, $00, $C2, $00, $01,
$58, $48, $81, $C4, $D0, $00, $00, $00, $C2, $08, $01, $58, $48, $81, $C4, $C8,
$00, $00, $00, $C2, $10, $01, $58, $48, $81, $C4, $C0, $00, $00, $00, $C2, $18,
$01, $58, $48, $81, $C4, $B8, $00, $00, $00, $C2, $20, $01, $58, $48, $81, $C4,
$B0, $00, $00, $00, $C2, $28, $01, $58, $48, $81, $C4, $A8, $00, $00, $00, $C2,
$30, $01, $58, $48, $81, $C4, $A0, $00, $00, $00, $C2, $38, $01, $58, $48, $81,
$C4, $98, $00, $00, $00, $C2, $40, $01, $58, $48, $81, $C4, $90, $00, $00, $00,
$C2, $48, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $50, $01, $58, $48,
$81, $C4, $88, $00, $00, $00, $C2, $58, $01, $58, $48, $81, $C4, $88, $00, $00,
$00, $C2, $60, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $68, $01, $58,
$48, $81, $C4, $88, $00, $00, $00, $C2, $70, $01, $58, $48, $81, $C4, $88, $00,
$00, $00, $C2, $78, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $80, $01,
$58, $48, $81, $C4, $88, $00, $00, $00, $C2, $88, $01, $58, $48, $81, $C4, $88,
$00, $00, $00, $C2, $90, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $98,
$01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $A0, $01, $58, $48, $81, $C4,
$88, $00, $00, $00, $C2, $A8, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2,
$B0, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $B8, $01, $58, $48, $81,
$C4, $88, $00, $00, $00, $C2, $C0, $01, $58, $48, $81, $C4, $88, $00, $00, $00,
$C2, $C8, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $D0, $01, $58, $48,
$81, $C4, $88, $00, $00, $00, $C2, $D8, $01, $58, $48, $81, $C4, $88, $00, $00,
$00, $C2, $E0, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $E8, $01, $58,
$48, $81, $C4, $88, $00, $00, $00, $C2, $F0, $01, $58, $48, $81, $C4, $88, $00,
$00, $00, $C2, $F8, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $00, $02
);
{$else}
CNoUse_ThreadState : array [0..798] of byte = (
$83, $EC, $10, $60, $9C, $B8, $34, $68, $63, $6D, $50, $31, $E0, $50, $83, $EC,
$10, $8D, $B4, $24, $CC, $00, $00, $00, $B9, $20, $00, $00, $00, $8B, $06, $83,
$EE, $04, $50, $E2, $F8, $8B, $84, $24, $BC, $00, $00, $00, $8B, $8C, $24, $B8,
$00, $00, $00, $8B, $B4, $24, $A4, $00, $00, $00, $83, $84, $24, $AC, $00, $00,
$00, $10, $E8, $44, $44, $44, $44, $50, $51, $8D, $84, $24, $9C, $00, $00, $00,
$8B, $08, $81, $F9, $34, $68, $63, $6D, $74, $05, $83, $E8, $04, $EB, $F1, $8B,
$48, $FC, $31, $C1, $81, $F9, $34, $68, $63, $6D, $74, $05, $83, $E8, $04, $EB,
$DF, $83, $20, $00, $8D, $8C, $24, $9C, $00, $00, $00, $29, $C8, $F7, $D8, $89,
$C1, $D1, $E1, $D1, $E8, $01, $C1, $83, $C1, $06, $E8, $00, $00, $00, $00, $58,
$01, $C8, $59, $FF, $E0, $58, $81, $C4, $CC, $00, $00, $00, $C2, $00, $00, $58,
$81, $C4, $C8, $00, $00, $00, $C2, $04, $00, $58, $81, $C4, $C4, $00, $00, $00,
$C2, $08, $00, $58, $81, $C4, $C0, $00, $00, $00, $C2, $0C, $00, $58, $81, $C4,
$BC, $00, $00, $00, $C2, $10, $00, $58, $81, $C4, $B8, $00, $00, $00, $C2, $14,
$00, $58, $81, $C4, $B4, $00, $00, $00, $C2, $18, $00, $58, $81, $C4, $B0, $00,
$00, $00, $C2, $1C, $00, $58, $81, $C4, $AC, $00, $00, $00, $C2, $20, $00, $58,
$81, $C4, $A8, $00, $00, $00, $C2, $24, $00, $58, $81, $C4, $A4, $00, $00, $00,
$C2, $28, $00, $58, $81, $C4, $A0, $00, $00, $00, $C2, $2C, $00, $58, $81, $C4,
$9C, $00, $00, $00, $C2, $30, $00, $58, $81, $C4, $98, $00, $00, $00, $C2, $34,
$00, $58, $81, $C4, $94, $00, $00, $00, $C2, $38, $00, $58, $81, $C4, $90, $00,
$00, $00, $C2, $3C, $00, $58, $81, $C4, $8C, $00, $00, $00, $C2, $40, $00, $58,
$81, $C4, $88, $00, $00, $00, $C2, $44, $00, $58, $81, $C4, $84, $00, $00, $00,
$C2, $48, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $4C, $00, $58, $81, $C4,
$84, $00, $00, $00, $C2, $50, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $54,
$00, $58, $81, $C4, $84, $00, $00, $00, $C2, $58, $00, $58, $81, $C4, $84, $00,
$00, $00, $C2, $5C, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $60, $00, $58,
$81, $C4, $84, $00, $00, $00, $C2, $64, $00, $58, $81, $C4, $84, $00, $00, $00,
$C2, $68, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $6C, $00, $58, $81, $C4,
$84, $00, $00, $00, $C2, $70, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $74,
$00, $58, $81, $C4, $84, $00, $00, $00, $C2, $78, $00, $58, $81, $C4, $84, $00,
$00, $00, $C2, $7C, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $80, $00, $58,
$81, $C4, $84, $00, $00, $00, $C2, $84, $00, $58, $81, $C4, $84, $00, $00, $00,
$C2, $88, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $8C, $00, $58, $81, $C4,
$84, $00, $00, $00, $C2, $90, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $94,
$00, $58, $81, $C4, $84, $00, $00, $00, $C2, $98, $00, $58, $81, $C4, $84, $00,
$00, $00, $C2, $9C, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $A0, $00, $58,
$81, $C4, $84, $00, $00, $00, $C2, $A4, $00, $58, $81, $C4, $84, $00, $00, $00,
$C2, $A8, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $AC, $00, $58, $81, $C4,
$84, $00, $00, $00, $C2, $B0, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $B4,
$00, $58, $81, $C4, $84, $00, $00, $00, $C2, $B8, $00, $58, $81, $C4, $84, $00,
$00, $00, $C2, $BC, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $C0, $00, $58,
$81, $C4, $84, $00, $00, $00, $C2, $C4, $00, $58, $81, $C4, $84, $00, $00, $00,
$C2, $C8, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $CC, $00, $58, $81, $EC,
$84, $00, $00, $00, $C2, $D0, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $D4,
$00, $58, $81, $C4, $84, $00, $00, $00, $C2, $D8, $00, $58, $81, $C4, $84, $00,
$00, $00, $C2, $DC, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $E0, $00, $58,
$81, $C4, $84, $00, $00, $00, $C2, $E4, $00, $58, $81, $C4, $84, $00, $00, $00,
$C2, $E8, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $EC, $00, $58, $81, $C4,
$84, $00, $00, $00, $C2, $F0, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $F4,
$00, $58, $81, $C4, $84, $00, $00, $00, $C2, $F8, $00, $58, $81, $C4, $84, $00,
$00, $00, $C2, $FC, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $00, $01
);
{$endif}
function WasCodeChanged(module: HMODULE; code: pointer; out orgCode: int64) : boolean;
function ApplyRelocation(nh: PImageNtHeaders; module: HMODULE; code, arr: pointer; size: dword) : boolean;
type TImageBaseRelocation = record
VirtualAddress : dword;
SizeOfBlock : dword;
end;
var
rel1 : ^TImageBaseRelocation;
rel2 : NativeUInt;
i1 : integer;
item : TPWord;
data : pointer;
begin
result := false;
with nh^.OptionalHeader do begin
rel1 := pointer(module + DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
rel2 := NativeUInt(rel1) + DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
while (NativeUInt(rel1) < rel2) and (rel1^.VirtualAddress <> 0) do begin
item := pointer(NativeUInt(rel1) + sizeOf(rel1^));
for i1 := 1 to (rel1.SizeOfBlock - sizeOf(rel1^)) div 2 do begin
if item^ and $f000 = $3000 then begin // IMAGE_REL_BASED_HIGHLOW - x86
data := pointer(module + rel1.VirtualAddress + item^ and $0fff);
if (NativeUInt(data) >= NativeUInt(code)) and (NativeUInt(data) < NativeUInt(code) + size) then begin
data := pointer(NativeUInt(arr) + (NativeUInt(data) - NativeUInt(code)));
dword(data^) := module + dword(data^) - ImageBase;
end;
end else
if item^ and $f000 = $a000 then begin // IMAGE_REL_BASED_DIR64 - x64
data := pointer(module + rel1.VirtualAddress + item^ and $0fff);
if (NativeUInt(data) >= NativeUInt(code)) and (NativeUInt(data) < NativeUInt(code) + size) then begin
data := pointer(NativeUInt(arr) + (NativeUInt(data) - NativeUInt(code)));
int64(data^) := int64(module) + int64(data^) - int64(ImageBase);
end;
end;
inc(item);
end;
NativeUInt(rel1) := NativeUInt(rel1) + rel1^.SizeOfBlock;
end;
end;
end;
function IsCompressedModule(nh1, nh2: PImageNtHeaders) : boolean;
var sh1 : PImageSectionHeader;
begin
sh1 := pointer(NativeUInt(@nh1^.OptionalHeader) + sizeOf(TImageOptionalHeader));
result := (nh1^.OptionalHeader.BaseOfCode <> nh2^.OptionalHeader.BaseOfCode ) or
{$ifndef win64}
(nh1^.OptionalHeader.BaseOfData <> nh2^.OptionalHeader.BaseOfData ) or
{$endif}
(nh1^.FileHeader.NumberOfSections <> nh2^.FileHeader.NumberOfSections) or
((sh1^.Name[0] = ord('U')) and (sh1^.Name[1] = ord('P')) and (sh1^.Name[2] = ord('X')));
end;
var buf : pointer;
nh1, nh2 : PImageNtHeaders;
c1 : dword;
arr : array [0..19] of byte;
begin
result := false;
orgCode := 0;
if module <> 0 then begin
nh1 := pointer(GetImageNtHeaders(module));
if (nh1 <> nil) and (NativeUInt(code) > module) and (NativeUInt(code) < module + GetSizeOfImage(pointer(nh1))) then begin
buf := CollectCache_NeedModuleFileMap(module);
if buf <> nil then begin
c1 := VirtualToRaw(pointer(nh1), NativeUInt(code) - module);
nh2 := pointer(GetImageNtHeaders(NativeUInt(buf)));
if (nh2 <> nil) and (c1 < GetSizeOfImage(pointer(nh2))) then
try
result := (not IsCompressedModule(nh1, nh2)) and
( (dword(pointer(NativeUInt(code) + 0)^) <> dword(pointer(NativeUInt(buf) + c1 + 0)^)) or
( word(pointer(NativeUInt(code) + 4)^) <> word(pointer(NativeUInt(buf) + c1 + 4)^)) );
if result then begin
Move(pointer(NativeUInt(buf) + c1 - 4)^, arr[0], sizeOf(arr));
ApplyRelocation(nh2, module, pointer(NativeUInt(code) - 4), @arr, sizeOf(arr) - 4);
result := (dword(pointer(NativeUInt(@arr[4]) + 0)^) <> dword(pointer(NativeUInt(code) + 0)^)) or
( word(pointer(NativeUInt(@arr[4]) + 4)^) <> word(pointer(NativeUInt(code) + 4)^));
if result and (dword(pointer(NativeUInt(@arr[4]) + 8)^) = dword(pointer(NativeUInt(code) + 8)^)) then
Move(arr[4], orgCode, sizeOf(orgCode));
end;
except end;
end;
end;
end;
end;
constructor TCodeHook.Create(moduleH: HMODULE; hookThisFunc, callbackFunc: pointer; out nextHook: pointer; flags, numParams: dword);
function CheckMap(var map: THandle) : boolean;
var buf : ^pointer;
begin
if map <> 0 then begin
// there is a map, but is it filled?
// in win2k the Process Explorer duplicates one of our map handles :-(
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if buf <> nil then begin
if buf^ = nil then begin
CloseHandle(map);
map := 0;
end;
UnmapViewOfFile(buf);
end else begin
CloseHandle(map);
map := 0;
end;
end;
result := map <> 0;
end;
var cc1, cc2 : pointer;
c1 : dword;
ci : TCodeInfo;
mutex : THandle;
map : THandle;
buf : pointer;
s1, s3 : AnsiString;
p1, p2 : pointer;
fi : TFunctionInfo;
i1 : integer;
tableStub : ^THookStub;
pa : pointer;
error : dword;
keepMapFile : boolean;
orgCode : int64;
op : dword;
ab : TPAByte;
{$ifdef win64}
sparePage : pointer;
{$else}
pp : TPPointer;
{$endif}
jmpSize : dword;
displace : NativeInt;
begin
error := 0;
FPHookStub := nil;
FPHookStubTarget := nil;
{$ifdef win64}
if numParams < 4 then
numParams := 4
else
if odd(numParams) then
inc(numParams);
{$else}
if numParams < 2 then
numParams := 2;
{$endif}
CollectCache_AddRef;
try
nextHook := hookThisFunc;
FModuleH := moduleH;
FStoreThreadState := flags and STORE_THREAD_STATE <> 0;
FSafeHooking := flags and SAFE_HOOKING <> 0;
FNoAutoUnhook := flags and NO_AUTO_UNHOOKING <> 0;
FInUseCodeArr := nil;
FInUseThreadArr := nil;
FInUseTargetArr := nil;
{$ifdef win64}
// (in case we're later using our MIXTURE_MODE)
// we need a page which is located *before* mpHookStub
// this is due to RIP relative addressing
sparePage := VirtualAlloc2(PAGE_SIZE, hookThisFunc);
if ((NativeUInt(sparePage) > NativeUInt(hookThisFunc)) and (NativeUInt(sparePage) - NativeUInt(hookThisFunc) >= $7fff0000)) or
((NativeUInt(sparePage) < NativeUInt(hookThisFunc)) and (NativeUInt(hookThisFunc) - NativeUInt(sparePage) >= $7fff0000)) then begin
// This is probably a 64bit process which is not large address aware.
// Furthermore the to-be-hooked API is probably an ntdll.dll API.
// This situation is currently not supported by madCodeHook because the
// OS doesn't allow madCodeHook to allocate memory near ntdll.dll in this situation.
VirtualFree(sparePage, 0, MEM_RELEASE);
error := CErrorNo_CodeNotInterceptable;
exit;
end;
try
{$endif}
// Allocate contiguous buffer for hookStub and inUse stuff
// page 1: PAGE_EXECUTE_READ, ["NextHook" calls this, it's a 6-byte JMP, JMP address stored in page 4][InUseEnter][InUseLeave]
// page 2+3: PAGE_EXECUTE_READ, [InUseCodeArr[CInUseCount]]
// pages 4+5: PAGE_READWRITE, [JMP address for JMP from page 1][HookCallback address][InUseThreadArr[CInUseCount]][InUseTargetArr[CInUseCount]]
FPHookStub := VirtualAlloc2(PAGE_SIZE * 5, {$ifdef win64} sparePage {$else} nil {$endif});
FPHookStubTarget := pointer(NativeUInt(FPHookStub) + PAGE_SIZE * 3);
FPHookStub^.jmpNextHook.opcode := $ff;
FPHookStub^.jmpNextHook.modRm := $25;
{$ifdef win64}
// caution: we need to use RIP relative addressing here!
FPHookStub^.jmpNextHook.target := PAGE_SIZE * 3 - 6;
{$else}
FPHookStub^.jmpNextHook.target := dword(FPHookStubTarget);
{$endif}
FHookedFunc := hookThisFunc;
pa := FHookedFunc;
FCallbackFunc := callbackFunc;
FPNextHook := @nextHook;
keepMapFile := false;
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)));
{$endif}
s1 := ApiSpecialName(CMAHPrefix, FHookedFunc);
mutex := CreateLocalMutex(PAnsiChar(DecryptStr(CMutex) + ', ' + s1));
try
if mutex <> 0 then
WaitForSingleObject(mutex, INFINITE);
try
map := OpenGlobalFileMapping(PAnsiChar(DecryptStr(CNamedBuffer) + ', ' + s1), true);
FNewHook := not CheckMap(map);
try
if flags and USE_ABSOLUTE_JMP <> 0 then
jmpSize := 6
else
jmpSize := 5;
if FNewHook then begin
if flags and MIXTURE_MODE = 0 then
if flags and $80000000 = 0 then begin
if DisasmHookTarget then
fi := ParseFunction(FHookedFunc)
else begin
cc1 := FHookedFunc;
repeat
ci := ParseCode(cc1);
cc1 := ci.Next;
until (not ci.IsValid) or (NativeUInt(cc1) - NativeUInt(pa) >= jmpSize) or (ci.Opcode in [$c2..$c3, $ca..$cb, $cf]);
if NativeUInt(cc1) - NativeUInt(pa) >= jmpSize then begin
fi.IsValid := true;
fi.Interceptable := true;
end;
end;
if (not fi.IsValid) or (not fi.Interceptable) or ((not DisableChangedCodeCheck) and WasCodeChanged(moduleH, FHookedFunc, orgCode)) then begin
flags := flags or MIXTURE_MODE;
if (not fi.IsValid) or (not fi.Interceptable) then begin
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
' -> not interceptable -> MIXTURE');
ParseFunction(FHookedFunc, s3);
log(s3);
{$endif}
end else begin
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
' -> already hooked by another hooking library? -> MIXTURE');
ParseFunction(FHookedFunc, s3);
log(s3);
{$endif}
error := CErrorNo_DoubleHook;
end;
if flags and FOLLOW_JMP <> 0 then begin
{$ifdef win64}
// check for weird hooking methods
ab := FHookedFunc;
if (ab[0] = $48) and (ab[1] = $c7) and (ab[2] = $c0) and (((ab[7] = $ff) and (ab[8] = $e0)) or ((ab[7] = $50) and (ab[8] = $c3))) then begin
// FRAPS
// 48c7c0???????? mov rax, $00000000????????
// ffe0 jmp rax
ci.Jmp := true;
ci.Target := pointer(dword(pointer(NativeUInt(FHookedFunc) + 3)^));
end else
if (ab[0] = $48) and (ab[1] = $b8) and (((ab[10] = $ff) and (ab[11] = $e0)) or ((ab[10] = $50) and (ab[11] = $c3))) then begin
// Bitdefender
// 48b8???????????????? mov rax, $????????????????
// 50 push rax
// c3 ret
ci.Jmp := true;
ci.Target := pointer(pointer(NativeUInt(FHookedFunc) + 2)^);
end else
{$endif}
ci := ParseCode(FHookedFunc);
if ci.Jmp and (ci.Target <> nil) then begin
fi := ParseFunction(ci.Target);
for i1 := 1 to 10 do
if fi.IsValid and (not fi.Interceptable) then begin
ci := ParseCode(ci.Target);
if ci.Jmp and (ci.Target <> nil) then
fi := ParseFunction(ci.Target)
else
break;
end else
break;
if fi.IsValid and fi.Interceptable then begin
pa := ci.Target;
flags := flags and (not MIXTURE_MODE);
{$ifdef win64}
VirtualFree(sparePage, 0, MEM_RELEASE);
VirtualFree(FPHookStub, 0, MEM_RELEASE);
sparePage := VirtualAlloc2(PAGE_SIZE, pa);
FPHookStub := VirtualAlloc2(PAGE_SIZE * 5, sparePage);
FPHookStubTarget := pointer(NativeUInt(FPHookStub) + PAGE_SIZE * 3);
FPHookStub^.jmpNextHook.opcode := $ff;
FPHookStub^.jmpNextHook.modRm := $25;
FPHookStub^.jmpNextHook.target := PAGE_SIZE * 3 - 6;
{$endif}
end;
end;
end;
end;
end;
if flags and MIXTURE_MODE <> 0 then
if (flags and NO_MIXTURE_MODE = 0) and (moduleH <> 0) then begin
{$ifndef win64}
FIsWinsock2 := GetModuleHandleW(pointer(AnsiToWideEx(DecryptStr(CWs2_32)))) = moduleH;
if FIsWinsock2 and (flags and ALLOW_WINSOCK2_MIXTURE_MODE <> 0) then
p1 := FindWs2InternalProcList(moduleH)
else p1 := nil;
if (not FIsWinsock2) or (p1 <> nil) then begin
{$endif}
{$ifdef win64}
p1 := nil;
tableStub := sparePage;
sparePage := nil;
{$else}
tableStub := VirtualAlloc2(PAGE_SIZE, nil);
{$endif}
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
' -> MIXTURE -> ' + IntToHexExA(NativeUInt(tableStub)));
{$endif}
tableStub^.jmpNextHook.opcode := $FF;
tableStub^.jmpNextHook.modRm := $25;
{$ifdef win64}
tableStub^.jmpNextHook.target := 0;
{$else}
tableStub^.jmpNextHook.target := dword(@tableStub^.absTarget);
{$endif}
tableStub^.absTarget := FHookedFunc;
VirtualProtect(tableStub, PAGE_SIZE, PAGE_EXECUTE_READ, @op);
CheckProcAddress := ResolveMixtureMode;
s3 := ApiSpecialName(CMixPrefix, tableStub);
map := CreateLocalFileMapping(PAnsiChar(DecryptStr(CNamedBuffer) + ', ' + s3), sizeOf(pointer));
if map <> 0 then begin
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if buf <> nil then begin
pointer(buf^) := FHookedFunc;
UnmapViewOfFile(buf);
end else begin
CloseHandle(map);
map := 0;
end;
end;
if (map <> 0) and PatchExportTable(moduleH, FHookedFunc, tableStub, p1) then begin
FPatchExport := true;
if FIsWinsock2 then
// we've hooked Winsock2 by using the mixture mode
// in order to improve the efficieny of our manipulations
// we're increasing the load count of the Winsock2 dll
// this way we won't have to redo the hook all the time
// if the application unloads and reloads the dll
LoadLibraryW(pointer(AnsiToWideEx(DecryptStr(CWs2_32))));
try
PatchMyImportTables(FHookedFunc, tableStub);
except end;
pa := tableStub;
keepMapFile := true;
error := 0;
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
' -> MIXTURE -> ' + IntToHexExA(NativeUInt(tableStub)) + ' -> done');
{$endif}
end else begin
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
' -> MIXTURE -> ' + IntToHexExA(NativeUInt(tableStub)) + ' -> error!');
{$endif}
if map <> 0 then
CloseHandle(map);
VirtualFree(tableStub, 0, MEM_RELEASE);
if error = 0 then
error := CErrorNo_CodeNotInterceptable;
exit;
end;
{$ifndef win64}
end else begin
(*ci := ParseCode(FHookedFunc);
if ci.Jmp and
( (ci.Opcode in [$e9..$eb]) or
((ci.Opcode = $ff) and (ci.ModRm and $30 = $20)) ) and
(ci.Target <> nil) then begin
fi := ParseFunction(ci.Target);
if fi.IsValid and fi.Interceptable then begin
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
' -> ws2_32.dll was already hooked -> we hooked the other hook''s callback function!');
{$endif}
pa := ci.Target;
end else begin
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
' -> MIXTURE -> ws2_32.dll can''t be hooked in mixture mode!');
{$endif}
error := CErrorNo_CodeNotInterceptable;
exit;
end;
end else begin *)
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
' -> MIXTURE -> ws2_32.dll can''t be hooked in mixture mode!');
{$endif}
error := CErrorNo_CodeNotInterceptable;
exit;
// end;
end;
{$endif}
end else begin
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
' -> MIXTURE -> NO_MIXTURE_MODE flag specified!');
{$endif}
error := CErrorNo_CodeNotInterceptable;
exit;
end;
end;
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)));
{$endif}
buf := nil;
try
if FNewHook then begin
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) + ' -> new hook');
{$endif}
// Allocate contiguous buffer for trampoline code and target address
// page 1: PAGE_EXECUTE_READ, [trampoline][unsed space][absolute JMP]
// page 2: PAGE_READWRITE, [JMP address for JMP from page 1]
FTramp := VirtualAlloc2(PAGE_SIZE * 2, pa);
cc1 := pa;
cc2 := FTramp;
repeat
ci := ParseCode(cc1);
if ci.RelTarget then begin
{$ifdef win64}
if (ci.Target <> nil) and (ci.TargetSize = 4) and ((ci.Opcode = $E8) or (ci.Opcode = $E9)) then begin
// Relative jumps can only jump 32bit far. When relocating code, the final
// address could be further away. So we replace relative jmp/call calls with
// a small assembler stub which supports any 64bit target address without
// changing any registers.
word(cc2^) := $ff48; // jmp/call [rip + 2]
inc(NativeUInt(cc2), 2);
if ci.Call then
byte(cc2^) := $15
else
byte(cc2^) := $25;
inc(NativeUInt(cc2));
dword(cc2^) := 2;
inc(NativeUInt(cc2), 4);
word(cc2^) := $08eb; // jmp +8 (skip "dq target")
inc(NativeUInt(cc2), 2);
pointer(cc2^) := ci.Target; // dq target
inc(NativeUInt(cc2), 8);
end else begin
{$endif}
if ci.TargetSize = 1 then begin
if ci.Opcode <> $EB then begin
// ($70 - $7F) 1 byte Jcc calls -> 4 byte Jcc calls
TPAByte(cc2)^[0] := $0F;
TPAByte(cc2)^[1] := $80 + ci.Opcode and $F;
NativeUInt(cc2) := NativeUInt(cc2) + 6;
end else begin
TPByte(cc2)^ := $E9;
NativeUInt(cc2) := NativeUInt(cc2) + 5;
end;
end else if ci.TargetSize = 2 then begin
// kill 16 bit prefix
c1 := NativeUInt(ci.Next) - NativeUInt(cc1);
Move(pointer(NativeUInt(cc1) + 1)^, cc2^, c1 - 3);
NativeUInt(cc2) := NativeUInt(cc2) + c1 + 1;
end else begin
c1 := NativeUInt(ci.Next) - NativeUInt(cc1);
Move(cc1^, cc2^, c1);
NativeUInt(cc2) := NativeUInt(cc2) + c1;
end;
TPInteger(NativeUInt(cc2) - 4)^ := int64(NativeUInt(ci.Target)) - int64(NativeUInt(cc2));
{$ifdef win64}
end;
{$endif}
end else begin
{$ifdef win64}
if (ci.Call or ci.Jmp) and ci.RipRelative then begin
// "jmp/call [RIP + someConstant]"
displace := NativeInt(ci.DisplDword) - NativeInt((NativeUInt(cc2) + (NativeUInt(ci.Next) - NativeUInt(cc1))));
if (displace >= low(integer)) and (displace <= high(integer)) then begin
// We can just modify this instruction, it's still within 2GB distance.
c1 := NativeUInt(ci.Next) - NativeUInt(cc1);
Move(cc1^, cc2^, c1);
integer(pointer(NativeUInt(cc2) + (NativeUInt(ci.PDispl) - NativeUInt(cc1)))^) := displace;
NativeUInt(cc2) := NativeUInt(cc2) + c1;
end else begin
// Our new code is more than 2GB away from the original displacement data.
// Ideally we should read out the displacement in real time and jmp/call it.
// Unfortunately we can't, because due to CET there's no way (I know of) to
// jmp/call an address without using registers, and sadly, we can't modify any registers.
// So instead we hard code the jmp/call address to the target it had when installing the hook.
// Hopefully in 99.99% of all situations this will be fine.
word(cc2^) := $ff48; // jmp/call [rip + 2]
inc(NativeUInt(cc2), 2);
if ci.Call then
byte(cc2^) := $15
else
byte(cc2^) := $25;
inc(NativeUInt(cc2));
dword(cc2^) := 2;
inc(NativeUInt(cc2), 4);
word(cc2^) := $08eb; // jmp +8 (skip "dq target")
inc(NativeUInt(cc2), 2);
pointer(cc2^) := ci.Target; // dq target
inc(NativeUInt(cc2), 8);
end;
end else begin
{$endif}
c1 := NativeUInt(ci.Next) - NativeUInt(cc1);
Move(cc1^, cc2^, c1);
{$ifdef win64}
if ci.RipRelative then
integer(pointer(NativeUInt(cc2) + (NativeUInt(ci.PDispl) - NativeUInt(cc1)))^) := NativeInt(ci.DisplDword) - NativeInt((NativeUInt(cc2) + (NativeUInt(ci.Next) - NativeUInt(cc1))));
{$endif}
NativeUInt(cc2) := NativeUInt(cc2) + c1;
{$ifdef win64}
end;
{$endif}
end;
cc1 := ci.Next;
until NativeUInt(cc1) - NativeUInt(pa) >= jmpSize;
TPByte(cc2)^ := $e9;
NativeUInt(cc2) := NativeUInt(cc2) + 5;
TPInteger(NativeUInt(cc2) - 4)^ := int64(NativeUInt(cc1)) - int64(NativeUInt(cc2));
cc2 := pointer(NativeUInt(FTramp) + PAGE_SIZE - 6);
TPByte(cc2)^ := $ff;
cc2 := pointer(NativeUInt(cc2) + 1);
TPByte(cc2)^ := $25;
cc2 := pointer(NativeUInt(cc2) + 1);
{$ifdef win64}
// caution: we need to use RIP relative addressing!
TPCardinal(cc2)^ := 0;
{$else}
TPCardinal(cc2)^ := NativeUInt(FTramp) + PAGE_SIZE;
{$endif}
VirtualProtect(FTramp, PAGE_SIZE, PAGE_EXECUTE_READ, @op);
VirtualProtect(pointer(NativeUInt(FTramp) + PAGE_SIZE), PAGE_SIZE, PAGE_READWRITE, @op);
map := CreateLocalFileMapping(PAnsiChar(DecryptStr(CNamedBuffer) + ', ' + s1), sizeOf(pointer));
if map <> 0 then begin
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if buf <> nil then begin
TPPointer(buf)^ := VirtualAlloc2(CHookQueueSize + 8 * CHookEntrySize, nil);
VirtualProtect(TPPointer(buf)^, CHookQueueSize + 8 * CHookEntrySize, PAGE_READWRITE, @c1);
with THookQueue(TPPointer(buf)^^) do begin
ItemCount := 2;
Capacity := 8;
PatchAddr := pa;
OldCode := PatchAddr^;
if flags and USE_ABSOLUTE_JMP <> 0 then begin
NewCode.opcode := $ff;
NewCode.modRm := $25;
{$ifdef win64}
// caution: we need to use RIP relative addressing!
NewCode.target := NativeUInt(FTramp) + PAGE_SIZE - (NativeUInt(PatchAddr) + 6);
{$else}
NewCode.target := NativeUInt(FTramp) + PAGE_SIZE;
{$endif}
end else begin
NewCode := OldCode;
// For compatability with older madCodeHook builds, we keep Old/NewCode at 6 bytes.
// Doing so makes sure that we can still share the hook chain queue with older builds.
// But our new patch really uses a 5 byte JMP now.
TPByte(@NewCode)^ := $e9;
TPCardinal(NativeUInt(@NewCode) + 1)^ := NativeUInt(FTramp) + PAGE_SIZE - 6 - (NativeUInt(PatchAddr) + 5);
end;
pointer(pointer(NativeUInt(FTramp) + PAGE_SIZE)^) := FTramp;
if not keepMapFile then
MapHandle := map
else MapHandle := 0;
FMapHandle := map;
Items[0].HookProc := nil;
Items[0].PNextHook := pointer(NativeUInt(FTramp) + PAGE_SIZE);
Items[1].HookProc := FTramp;
Items[1].PNextHook := nil;
end;
end;
end;
end else begin
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
with THookQueue(TPPointer(buf)^^) do begin
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) + ' -> hook no ' + IntToStrExA(ItemCount - 1));
{$endif}
FTramp := Items[ItemCount - 1].HookProc;
FMapHandle := MapHandle;
if ItemCount = Capacity then begin
p1 := VirtualAlloc2(CHookQueueSize + Capacity * 2 * CHookEntrySize, nil);
VirtualProtect(p1, CHookQueueSize + Capacity * 2 * CHookEntrySize, PAGE_READWRITE, @op);
Move(TPPointer(buf)^^, p1^, CHookQueueSize + Capacity * CHookEntrySize);
THookQueue(p1^).Capacity := Capacity * 2;
p2 := TPPointer(buf)^;
TPPointer(buf)^ := p1;
VirtualFree(p2, 0, MEM_RELEASE);
end;
end;
end;
TPPointer(NativeUInt(FPHookStub) + PAGE_SIZE * 3 + sizeOf(pointer))^ := callbackFunc;
if flags and NO_SAFE_UNHOOKING = 0 then begin
NativeUInt(FInUseCodeArr) := NativeUInt(FPHookStub) + PAGE_SIZE;
NativeUInt(FInUseThreadArr) := NativeUInt(FPHookStub) + PAGE_SIZE * 3 + sizeOf(pointer) * 2;
NativeUInt(FInUseTargetArr) := NativeUInt(FInUseThreadArr) + CInUseCount * sizeOf(dword);
p1 := pointer(NativeUInt(FPHookStub) + sizeOf(FPHookStub^));
if flags and STORE_THREAD_STATE = 0 then begin
// initialize InUseEnter
Move(CInUseEnter, p1^, sizeOf(CInUseEnter));
{$ifdef win64}
TPPointer (NativeUInt(p1) + 8)^ := FInUseCodeArr;
TPPointer (NativeUInt(p1) + 18)^ := FInUseTargetArr;
TPPointer (NativeUInt(p1) + 28)^ := FInUseThreadArr;
TPPointer (NativeUInt(p1) + 121)^ := FCallbackFunc;
{$else}
TPPointer (NativeUInt(p1) + 7)^ := FInUseCodeArr;
TPPointer (NativeUInt(p1) + 12)^ := FInUseTargetArr;
TPPointer (NativeUInt(p1) + 17)^ := FInUseThreadArr;
TPCardinal(NativeUInt(p1) + 85)^ := NativeUInt(FCallbackFunc) - NativeUInt(p1) - sizeOf(CInUseEnter);
{$endif}
FCallbackFunc := p1;
// initialize InUseLeave
NativeUInt(p1) := NativeUInt(p1) + sizeOf(CInUseEnter);
Move(CInUseLeave, p1^, sizeOf(CInUseLeave));
{$ifdef win64}
TPPointer (NativeUInt(p1) + 10)^ := FInUseCodeArr;
TPPointer (NativeUInt(p1) + 42)^ := FInUseTargetArr;
{$else}
TPPointer (NativeUInt(p1) + 8)^ := FInUseCodeArr;
TPPointer (NativeUInt(p1) + 27)^ := FInUseTargetArr;
{$endif}
c1 := sizeOf(FPHookStub^) + sizeOf(CInUseEnter);
// initialize FInUseCodeArr
for i1 := 0 to CInUseCount - 1 do
with FInUseCodeArr[i1] do begin
call := $15ff;
jmp := $e8;
jmpOffset := NativeUInt(FPHookStub) + c1 - NativeUInt(@FInUseCodeArr[i1].jmp) - 5;
{$ifdef win64}
popReturn := $08c48348;
callOffset := NativeUInt(FPHookStub) + PAGE_SIZE * 3 + sizeOf(pointer) - NativeUInt(@FInUseCodeArr[i1].call) - 6;
{$else}
popReturn[0] := $83;
popReturn[1] := $c4;
popReturn[2] := $04;
callTarget := pointer(NativeUInt(FPHookStub) + PAGE_SIZE * 3 + sizeOf(pointer));
{$endif}
end;
end else begin
// initialize InUse_ThreadState
Move(CInUse_ThreadState, p1^, sizeOf(CInUse_ThreadState));
{$ifdef win64}
TPCardinal(NativeUInt(p1) + 52)^ := (numParams + 23) * 8 + 4*8;
TPCardinal(NativeUInt(p1) + 58)^ := (numParams + 0);
TPPointer (NativeUInt(p1) + 78)^ := FInUseTargetArr;
TPPointer (NativeUInt(p1) + 88)^ := FInUseThreadArr;
TPCardinal(NativeUInt(p1) + 153)^ := (numParams + 3) * 8;
TPCardinal(NativeUInt(p1) + 161)^ := (numParams + 21) * 8;
TPCardinal(NativeUInt(p1) + 169)^ := (numParams + 20) * 8;
TPCardinal(NativeUInt(p1) + 177)^ := (numParams + 19) * 8;
TPCardinal(NativeUInt(p1) + 185)^ := (numParams + 18) * 8;
TPCardinal(NativeUInt(p1) + 193)^ := (numParams + 16) * 8;
TPCardinal(NativeUInt(p1) + 201)^ := (numParams + 15) * 8;
TPCardinal(NativeUInt(p1) + 209)^ := (numParams + 22) * 8;
TPPointer (NativeUInt(p1) + 222)^ := FCallbackFunc;
TPCardinal(NativeUInt(p1) + 247)^ := (numParams + 8) * 8;
TPCardinal(NativeUInt(p1) + 290)^ := (numParams + 8) * 8;
TPCardinal(NativeUInt(p1) + 301)^ := (numParams + 6) * 8;
for i1 := 0 to numParams do begin
TPCardinal(NativeUInt(p1) + 360 + dword(i1) * 11)^ := (numParams + 23 - dword(i1)) * 8 + 4*8;
TPWord (NativeUInt(p1) + 365 + dword(i1) * 11)^ := dword(i1) * 8;
end;
{$else}
TPCardinal(NativeUInt(p1) + 20)^ := (numParams + 15) * 4 + 4*4;
TPCardinal(NativeUInt(p1) + 25)^ := (numParams + 0);
TPPointer (NativeUInt(p1) + 38)^ := FInUseTargetArr;
TPPointer (NativeUInt(p1) + 43)^ := FInUseThreadArr;
TPCardinal(NativeUInt(p1) + 89)^ := (numParams + 3) * 4;
TPCardinal(NativeUInt(p1) + 96)^ := (numParams + 14) * 4;
TPCardinal(NativeUInt(p1) + 103)^ := (numParams + 13) * 4;
TPCardinal(NativeUInt(p1) + 110)^ := (numParams + 12) * 4;
TPCardinal(NativeUInt(p1) + 117)^ := (numParams + 11) * 4;
TPCardinal(NativeUInt(p1) + 124)^ := (numParams + 8) * 4;
TPCardinal(NativeUInt(p1) + 131)^ := (numParams + 7) * 4;
TPCardinal(NativeUInt(p1) + 138)^ := (numParams + 10) * 4;
TPCardinal(NativeUInt(p1) + 144)^ := NativeUInt(FCallbackFunc) - NativeUInt(p1) - 148;
TPCardinal(NativeUInt(p1) + 153)^ := (numParams + 7) * 4;
TPCardinal(NativeUInt(p1) + 196)^ := (numParams + 7) * 4;
TPCardinal(NativeUInt(p1) + 205)^ := (numParams + 5) * 4;
for i1 := 0 to numParams do begin
TPCardinal(NativeUInt(p1) + 245 + dword(i1) * 10)^ := (numParams + 15 - dword(i1)) * 4 + 4*4;
TPWord (NativeUInt(p1) + 250 + dword(i1) * 10)^ := dword(i1) * 4;
end;
{$endif}
FCallbackFunc := p1;
end;
// initialize FInUseThreadArr and FInUseTargetArr
ZeroMemory(FInUseThreadArr, CInUseCount * sizeOf(dword ));
ZeroMemory(FInUseTargetArr, CInUseCount * sizeOf(pointer));
end else
if flags and STORE_THREAD_STATE <> 0 then begin
// initialize NoUse_ThreadState
NativeUInt(p1) := NativeUInt(FPHookStub) + sizeOf(FPHookStub^);
Move(CNoUse_ThreadState, p1^, sizeOf(CNoUse_ThreadState));
{$ifdef win64}
TPCardinal(NativeUInt(p1) + 52)^ := (numParams + 23) * 8 + 4*8;
TPCardinal(NativeUInt(p1) + 58)^ := (numParams + 0);
TPCardinal(NativeUInt(p1) + 80)^ := (numParams + 21) * 8;
TPCardinal(NativeUInt(p1) + 88)^ := (numParams + 20) * 8;
TPCardinal(NativeUInt(p1) + 96)^ := (numParams + 16) * 8;
TPCardinal(NativeUInt(p1) + 104)^ := (numParams + 22) * 8;
TPPointer (NativeUInt(p1) + 117)^ := FCallbackFunc;
TPCardinal(NativeUInt(p1) + 142)^ := (numParams + 8) * 8;
TPCardinal(NativeUInt(p1) + 185)^ := (numParams + 8) * 8;
for i1 := 0 to numParams do begin
TPCardinal(NativeUInt(p1) + 233 + dword(i1) * 11)^ := (numParams + 23 - dword(i1)) * 8 + 4*8;
TPWord (NativeUInt(p1) + 238 + dword(i1) * 11)^ := dword(i1) * 8;
end;
{$else}
TPCardinal(NativeUInt(p1) + 20)^ := (numParams + 15) * 4 + 4*4;
TPCardinal(NativeUInt(p1) + 25)^ := (numParams + 0);
TPCardinal(NativeUInt(p1) + 40)^ := (numParams + 14) * 4;
TPCardinal(NativeUInt(p1) + 47)^ := (numParams + 13) * 4;
TPCardinal(NativeUInt(p1) + 54)^ := (numParams + 8) * 4;
TPCardinal(NativeUInt(p1) + 61)^ := (numParams + 10) * 4;
TPCardinal(NativeUInt(p1) + 67)^ := NativeUInt(FCallbackFunc) - NativeUInt(p1) - 71;
TPCardinal(NativeUInt(p1) + 76)^ := (numParams + 7) * 4;
TPCardinal(NativeUInt(p1) + 119)^ := (numParams + 7) * 4;
for i1 := 0 to numParams do begin
TPCardinal(NativeUInt(p1) + 152 + dword(i1) * 10)^ := (numParams + 15 - dword(i1)) * 4 + 4*4;
TPWord (NativeUInt(p1) + 157 + dword(i1) * 10)^ := dword(i1) * 4;
end;
{$endif}
FCallbackFunc := p1;
end;
FPNextHook^ := FPHookStub;
with THookQueue(TPPointer(buf)^^) do begin
FPatchAddr := pointer(PatchAddr);
FNewCode := NewCode;
FOldCode := OldCode;
Items[ItemCount] := Items[ItemCount - 1];
Items[ItemCount - 1].HookProc := FCallbackFunc;
Items[ItemCount - 1].PNextHook := pointer(FPHookStubTarget);
Items[ItemCount - 1].PNextHook^ := Items[ItemCount ].HookProc;
Items[ItemCount - 2].PNextHook^ := Items[ItemCount - 1].HookProc;
inc(ItemCount);
end;
FValid := true;
if TPByte(@FNewCode)^ = $e9 then
jmpSize := 5
else
jmpSize := 6;
if FNewHook or RenewOverwrittenHooks or
( (not IsBadReadPtrEx(FPatchAddr, jmpSize)) and
(TPCardinal(NativeUInt(FPatchAddr))^ = TPCardinal(NativeUInt(@FOldCode))^) and
( ((jmpSize = 5) and (TPByte(NativeUInt(FPatchAddr) + 4)^ = TPByte(NativeUInt(@FOldCode) + 4)^)) or
((jmpSize = 6) and (TPWord(NativeUInt(FPatchAddr) + 4)^ = TPWord(NativeUInt(@FOldCode) + 4)^)) ) ) then
WritePatch(mutex, buf);
{$ifdef log}
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) + ' -> done');
{$endif}
finally
if buf <> nil then
UnmapViewOfFile(buf);
end;
finally
if (map <> 0) and (not FNewHook) then
CloseHandle(map);
end;
finally
if mutex <> 0 then
ReleaseMutex(mutex);
end;
finally
if mutex <> 0 then
CloseHandle(mutex );
if FValid then begin
VirtualProtect(pointer(NativeUInt(FPHookStub) + PAGE_SIZE * 0), PAGE_SIZE * 3, PAGE_EXECUTE_READ, @op);
VirtualProtect(pointer(NativeUInt(FPHookStub) + PAGE_SIZE * 3), PAGE_SIZE * 2, PAGE_READWRITE, @op);
end else begin
VirtualFree(FPHookStub, 0, MEM_RELEASE);
FPHookStub := nil;
FPHookStubTarget := nil;
end;
end;
{$ifdef win64}
finally
if sparePage <> nil then
VirtualFree(sparePage, 0, MEM_RELEASE);
end;
{$endif}
finally
CollectCache_Release;
SetLastError(error);
end;
end;
function GetStoredThreadState(var threadState: TPThreadState) : bool; stdcall;
function GetEsp : NativeUInt;
asm
{$ifdef win64}
mov rax, rsp
{$else}
mov eax, esp
{$endif}
end;
var esp_ : NativeUInt;
mi : TMemoryBasicInformation;
begin
esp_ := GetEsp;
result := false;
threadState := nil;
if VirtualQuery(pointer(esp_), mi, sizeOf(mi)) = sizeOf(mi) then
repeat
{$ifdef win64}
if (TPNativeUInt(esp_)^ = $6D63683474687374) and (TPNativeUInt(esp_ - 8)^ = $6D63683474687374 xor esp_) then begin
threadState := pointer(esp_ + 8);
result := true;
break;
end;
{$else}
if (TPCardinal(esp_)^ = $6D636834) and (TPCardinal(esp_ - 4)^ = $6D636834 xor esp_) then begin
threadState := pointer(esp_ + 4);
result := true;
break;
end;
{$endif}
inc(esp_, sizeOf(NativeUInt));
until esp_ >= NativeUInt(mi.BaseAddress) + mi.RegionSize;
end;
var
NtQuerySystemInformation : function (infoClass: NativeUInt; buffer: pointer; bufSize: dword; returnSize: TPCardinal) : dword; stdcall = nil;
FlushInstructionCacheProc : function (process: THandle; baseAddress: pointer; size: NativeUInt) : bool; stdcall = nil;
NtOpenThreadProc : function (var hThread: THandle; access: dword; objAttr, clientId: pointer) : dword; stdcall = nil;
QueryFullProcessImageName : function (process: THandle; flags: dword; exeName: PWideChar; var size: dword) : bool; stdcall = nil;
function GetOtherThreads(needHandles: boolean; out ids: TDACardinal; out handles: TDANativeUInt) : boolean;
// get a list of thread ids and optionally also thread handles
type
TNtProcessInfo = packed record
offset : dword;
numThreads : integer;
d1 : array [0..11] of dword;
d2 : pointer;
name : PWideChar;
d3 : pointer;
pid : NativeUInt;
parentPid : NativeUInt;
handleCount : dword;
sessionId : dword;
d4 : dword;
d5 : array [0..11] of pointer;
d6 : array [0..5] of dword;
d7 : NativeUInt;
threads : array [0..maxInt shr 7 - 1] of packed record
startAddr_nt4 : pointer;
pid_nt4 : dword;
tid_nt4 : dword;
d8 : array [44..52] of dword;
startAddr_nt5 : pointer;
pid_nt5 : NativeUInt;
tid_nt5 : NativeUInt;
d9 : dword;
end;
end;
const
THREAD_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED or SYNCHRONIZE or $3FF;
CNtObjAttr : TObjectAttributes = (len: sizeOf(TObjectAttributes); rootDir: 0; objName: nil; attr: 0; sd: nil; qos: nil);
var
c1, c2 : dword;
th : THandle;
p1 : pointer;
npi : ^TNtProcessInfo;
i1 : integer;
cid : array [0..1] of NativeUInt;
begin
result := false;
ids := nil;
handles := nil;
if @NtQuerySystemInformation = nil then
NtQuerySystemInformation := NtProc(CNtQuerySystemInformation);
if @NtOpenThreadProc = nil then
NtOpenThreadProc := NtProc(CNtOpenThread);
if (@NtQuerySystemInformation <> nil) and (@NtOpenThreadProc <> nil) then begin
c1 := 0;
NtQuerySystemInformation(5, nil, 0, @c1);
p1 := nil;
try
if c1 = 0 then begin
c1 := $10000;
repeat
c1 := c1 * 2;
LocalFree(HLOCAL(p1));
p1 := pointer(LocalAlloc(LPTR, c1));
c2 := NtQuerySystemInformation(5, p1, c1, nil);
until (c2 = 0) or (c1 = $400000);
end else begin
c1 := c1 * 2;
p1 := pointer(LocalAlloc(LPTR, c1));
c2 := NtQuerySystemInformation(5, p1, c1, nil);
end;
if c2 = 0 then begin
npi := p1;
while true do begin
if npi^.pid = GetCurrentProcessId then
for i1 := 0 to npi^.numThreads - 1 do begin
if byte(GetVersion) > 4 then
c1 := npi^.threads[i1].tid_nt5
else
c1 := npi^.threads[i1].tid_nt4;
if c1 <> GetCurrentThreadId then begin
cid[0] := 0;
cid[1] := c1;
if (not needHandles) or (NtOpenThreadProc(th, THREAD_ALL_ACCESS, @CNtObjAttr, @cid) = 0) then begin
SetLength(ids, Length(ids) + 1);
ids[high(ids)] := c1;
if needHandles then begin
SetLength(handles, Length(handles) + 1);
handles[high(handles)] := th;
end;
result := true;
end;
end;
end;
if npi^.offset = 0 then
break;
npi := pointer(NativeUInt(npi) + npi^.offset);
end;
end;
finally LocalFree(HLOCAL(p1)) end;
end;
end;
function OpenFirstThread(ph: THandle; pid: dword) : THandle;
// get a handle to the first thread of the specified process
type
TNtProcessInfo = packed record
offset : dword;
numThreads : integer;
d1 : array [0..11] of dword;
d2 : pointer;
name : PWideChar;
d3 : pointer;
pid : NativeUInt;
parentPid : NativeUInt;
handleCount : dword;
sessionId : dword;
d4 : dword;
d5 : array [0..11] of pointer;
d6 : array [0..5] of dword;
d7 : NativeUInt;
threads : array [0..maxInt shr 7 - 1] of packed record
startAddr_nt4 : pointer;
pid_nt4 : dword;
tid_nt4 : dword;
d8 : array [44..52] of dword;
startAddr_nt5 : pointer;
pid_nt5 : NativeUInt;
tid_nt5 : NativeUInt;
d9 : dword;
end;
end;
const
THREAD_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED or SYNCHRONIZE or $3FF;
CNtObjAttr : TObjectAttributes = (len: sizeOf(TObjectAttributes); rootDir: 0; objName: nil; attr: 0; sd: nil; qos: nil);
var
c1, c2 : dword;
th : THandle;
p1 : pointer;
npi : ^TNtProcessInfo;
cid : array [0..1] of NativeUInt;
begin
result := 0;
if @NtQuerySystemInformation = nil then
NtQuerySystemInformation := NtProc(CNtQuerySystemInformation);
if @NtOpenThreadProc = nil then
NtOpenThreadProc := NtProc(CNtOpenThread);
if (@NtQuerySystemInformation <> nil) and (@NtOpenThreadProc <> nil) then begin
c1 := 0;
NtQuerySystemInformation(5, nil, 0, @c1);
p1 := nil;
try
if c1 = 0 then begin
c1 := $10000;
repeat
c1 := c1 * 2;
LocalFree(HLOCAL(p1));
p1 := pointer(LocalAlloc(LPTR, c1));
c2 := NtQuerySystemInformation(5, p1, c1, nil);
until (c2 = 0) or (c1 = $400000);
end else begin
c1 := c1 * 2;
p1 := pointer(LocalAlloc(LPTR, c1));
c2 := NtQuerySystemInformation(5, p1, c1, nil);
end;
if c2 = 0 then begin
npi := p1;
while true do begin
if npi^.pid = pid then begin
if npi^.numThreads > 0 then begin
if byte(GetVersion) > 4 then
c1 := npi^.threads[0].tid_nt5
else c1 := npi^.threads[0].tid_nt4;
cid[0] := 0;
cid[1] := c1;
if NtOpenThreadProc(th, THREAD_ALL_ACCESS, @CNtObjAttr, @cid) = 0 then
result := th;
end;
break;
end;
if npi^.offset = 0 then
break;
npi := pointer(NativeUInt(npi) + npi^.offset);
end;
end;
finally LocalFree(HLOCAL(p1)) end;
end;
end;
procedure FlushInstructionCache(code: pointer; size: dword);
begin
if @FlushInstructionCacheProc = nil then
FlushInstructionCacheProc := KernelProc(CFlushInstructionCache);
if @FlushInstructionCacheProc <> nil then
FlushInstructionCacheProc(GetCurrentProcess, code, size);
end;
var OldEip, NewEip : NativeUInt;
RtlDispatchExceptionNext : function (excRec: PExceptionRecord; excCtxt: PContext) : integer; stdcall;
AddVectoredExceptionHandler : function (firstHandler: bool; handler: pointer) : THandle; stdcall;
RemoveVectoredExceptionHandler : function (handle: THandle) : dword; stdcall;
procedure TCodeHook.WritePatch(mutex: THandle; buf: pointer);
var rdecMutex : THandle;
function VectoredExceptionHandler(var exceptInfo: TExceptionPointers) : integer; stdcall;
begin
if (exceptInfo.ContextRecord.{$ifdef win64}rip{$else}eip{$endif} = OldEip) and
( (exceptInfo.ExceptionRecord.ExceptionCode = STATUS_PRIVILEGED_INSTRUCTION ) or
( (exceptInfo.ExceptionRecord.ExceptionCode = STATUS_ACCESS_VIOLATION) and
(exceptInfo.ExceptionRecord.ExceptionInformation[0] = 0 ) and
(exceptInfo.ExceptionRecord.ExceptionInformation[1] = $ffffffff ) ) ) then begin
exceptInfo.ContextRecord.{$ifdef win64}rip{$else}eip{$endif} := NewEip;
result := -1;
end else
result := 0;
end;
function InitRtlDispatchException(oeip, neip: pointer; var handle: THandle) : boolean;
function RtlDispatchExceptionCallback(var excRec: TExceptionRecord; var excCtxt: TContext) : integer; stdcall;
begin
if (excCtxt.{$ifdef win64}rip{$else}eip{$endif} = OldEip) and
( (excRec.ExceptionCode = STATUS_PRIVILEGED_INSTRUCTION ) or
( (excRec.ExceptionCode = STATUS_ACCESS_VIOLATION) and
(excRec.ExceptionInformation[0] = 0 ) and
(excRec.ExceptionInformation[1] = $ffffffff ) ) ) then begin
excCtxt.{$ifdef win64}rip{$else}eip{$endif} := NewEip;
result := -1;
end else
result := RtlDispatchExceptionNext(@excRec, @excCtxt);
end;
var s1 : AnsiString;
{$ifndef win64}
c1 : NativeUInt;
c2 : dword;
{$endif}
begin
result := false;
handle := 0;
AddVectoredExceptionHandler := KernelProc(CAddVecExceptHandler);
RemoveVectoredExceptionHandler := KernelProc(CRemoveVecExceptHandler);
SetLength(s1, 5);
s1[1] := 'r'; // Rtl
s1[2] := 'd'; // Dispatch
s1[3] := 'e'; // Exception
s1[4] := 'c'; // Callback
s1[5] := ' ';
s1 := DecryptStr(CMutex) + ', ' + s1 + IntToHexExA(GetCurrentProcessId);
rdecMutex := CreateMutexA(nil, false, PAnsiChar(s1));
if rdecMutex <> 0 then begin
WaitForSingleObject(rdecMutex, INFINITE);
OldEip := NativeUInt(oeip);
NewEip := NativeUInt(neip);
if (@AddVectoredExceptionHandler = nil) or (@RemoveVectoredExceptionHandler = nil) then begin
{$ifndef win64}
c1 := NativeUInt(NtProc(CKiUserExceptionDispatcher, true));
if (dword(pointer(c1 )^) = $04244c8b) and // mov ecx, [esp+4] ; PContext
(dword(pointer(c1 + 4)^) and $00ffffff = $241c8b) and // mov ebx, [esp+0] ; PExceptionRecord
(byte (pointer(c1 + 7)^) = $51) and // push ecx
(byte (pointer(c1 + 8)^) = $53) and // push ebx
(byte (pointer(c1 + 9)^) = $e8) and // call RtlDispatchException
VirtualProtect(pointer(c1 + 10), 4, PAGE_EXECUTE_READWRITE, @c2) then begin
RtlDispatchExceptionNext := pointer(c1 + 14 + dword(pointer(c1 + 10)^));
dword(pointer(c1 + 10)^) := NativeUInt(@RtlDispatchExceptionCallback) - c1 - 14;
VirtualProtect(pointer(c1 + 10), 4, c2, @c2);
result := true;
end;
{$endif}
end else begin
handle := AddVectoredExceptionHandler(bool(1), @VectoredExceptionHandler);
result := handle <> 0;
end;
if not result then begin
ReleaseMutex(rdecMutex);
CloseHandle(rdecMutex);
end;
end;
end;
procedure DoneRtlDispatchException(handle: THandle);
{$ifndef win64}
var c1 : NativeUInt;
c2 : dword;
{$endif}
begin
if (@AddVectoredExceptionHandler = nil) or (@RemoveVectoredExceptionHandler = nil) then begin
{$ifndef win64}
c1 := NativeUInt(NtProc(CKiUserExceptionDispatcher, true));
if VirtualProtect(pointer(c1 + 10), 4, PAGE_EXECUTE_READWRITE, @c2) then begin
dword(pointer(c1 + 10)^) := NativeUInt(@RtlDispatchExceptionNext) - c1 - 14;
VirtualProtect(pointer(c1 + 10), 4, c2, @c2);
end;
{$endif}
end else
RemoveVectoredExceptionHandler(handle);
ReleaseMutex(rdecMutex);
CloseHandle(rdecMutex);
end;
var wait : boolean;
s1 : AnsiString;
map : THandle;
pa : pointer;
op : dword;
b2 : boolean;
ok : boolean;
hlt : byte;
tids : TDACardinal;
ths : TDANativeUInt;
i1 : integer;
ctxt : TContext;
c1, c2 : NativeUInt;
ci : TCodeInfo;
p1 : pointer;
vectoredHandle : THandle;
tick : dword;
jmpSize : dword;
begin
tids := nil;
ths := nil;
if FValid and (not FDestroying) then begin
pa := nil;
wait := mutex = 0;
s1 := ApiSpecialName(CMAHPrefix, FHookedFunc);
if wait then
mutex := CreateLocalMutex(PAnsiChar(DecryptStr(CMutex) + ', ' + s1));
if mutex <> 0 then
try
if wait then
WaitForSingleObject(mutex, INFINITE);
try
if buf = nil then
map := OpenGlobalFileMapping(PAnsiChar(DecryptStr(CNamedBuffer) + ', ' + s1), true)
else
map := 0;
if (buf <> nil) or (map <> 0) then
try
if buf = nil then
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if buf <> nil then
try
with THookQueue(TPPointer(buf)^^) do begin
if TPByte(@NewCode)^ = $e9 then
jmpSize := 5
else
jmpSize := 6;
if (TPCardinal(NativeUInt(PatchAddr))^ <> TPCardinal(NativeUInt(@NewCode))^) or
((jmpSize = 5) and (TPByte(NativeUInt(PatchAddr) + 4)^ <> TPByte(NativeUInt(@NewCode) + 4)^)) or
((jmpSize = 6) and (TPWord(NativeUInt(PatchAddr) + 4)^ <> TPWord(NativeUInt(@NewCode) + 4)^)) then begin
pa := PatchAddr;
ok := VirtualProtect(PatchAddr, 8, PAGE_EXECUTE_READWRITE, @op);
if ok then begin
ci := ParseCode(PatchAddr);
b2 := FSafeHooking and
(NativeUInt(ci.Next) - NativeUInt(ci.This) < jmpSize) and
InitRtlDispatchException(PatchAddr, FTramp, vectoredHandle);
if b2 then begin
hlt := $f4; // asm HLT instruction
if not AtomicMove(@hlt, PatchAddr, 1) then
byte(pointer(PatchAddr)^) := hlt;
FlushInstructionCache(PatchAddr, 1);
if GetOtherThreads(true, tids, ths) then
for i1 := 0 to high(ths) do begin
tick := GetTickCount;
while (SafeHookingTimeout = 0) or (GetTickCount - tick < SafeHookingTimeout) do begin
ctxt.ContextFlags := CONTEXT_CONTROL;
if (not GetThreadContext(ths[i1], ctxt)) or
(ctxt.{$ifdef win64}rip{$else}eip{$endif} <= NativeUInt(PatchAddr)) or
(ctxt.{$ifdef win64}rip{$else}eip{$endif} >= NativeUInt(PatchAddr) + 5) then
break;
Sleep(10);
end;
CloseHandle(ths[i1]);
end;
end;
if not AtomicMove(@NewCode, PatchAddr, jmpSize) then
Move(NewCode, PatchAddr^, jmpSize);
FlushInstructionCache(PatchAddr, jmpSize);
if b2 then begin
c1 := NativeUInt(NtProc(CKiUserExceptionDispatcher, true));
c2 := c1 + 14 + dword(pointer(c1 + 10)^);
if GetOtherThreads(true, tids, ths) then
for i1 := 0 to high(ths) do begin
tick := GetTickCount;
while (SafeHookingTimeout = 0) or (GetTickCount - tick < SafeHookingTimeout) do begin
ctxt.ContextFlags := CONTEXT_CONTROL;
if (not GetThreadContext(ths[i1], ctxt)) or
( (ctxt.{$ifdef win64}rip{$else}eip{$endif} <> NativeUInt(PatchAddr)) and
( (ctxt.{$ifdef win64}rip{$else}eip{$endif} < c1) or (ctxt.{$ifdef win64}rip{$else}eip{$endif} > c1 + 10) ) and
( {$ifdef win64} ctxt.rip and $ffffffffffff0000 <> c2 and $ffffffffffff0000 {$else} ctxt.eip and $ffff0000 <> c2 and $ffff0000 {$endif}) ) then
break;
Sleep(10);
end;
CloseHandle(ths[i1]);
end;
end;
VirtualProtect(PatchAddr, 8, op, @op);
if b2 then
DoneRtlDispatchException(vectoredHandle);
end;
end;
end;
finally
if map <> 0 then
UnmapViewOfFile(buf);
end;
finally
if map <> 0 then
CloseHandle(map);
end;
if (pa <> nil) and FPatchExport then begin
{$ifndef win64}
if FIsWinsock2 then
p1 := FindWs2InternalProcList(FModuleH)
else
{$endif}
p1 := nil;
if (not FIsWinsock2) or (p1 <> nil) then
PatchExportTable(FModuleH, FHookedFunc, pa, p1);
end;
finally
if wait then
ReleaseMutex(mutex);
end;
finally
if wait then
CloseHandle(mutex);
end;
end;
end;
function TCodeHook.IsHookInUse : integer;
var i1, i2 : integer;
tids : TDACardinal;
ths : TDANativeUInt;
begin
result := 0;
tids := nil;
ths := nil;
if FValid and (FInUseTargetArr <> nil) then begin
GetOtherThreads(false, tids, ths);
for i1 := 0 to CInUseCount - 1 do
if FInUseTargetArr[i1] <> nil then
if FInUseThreadArr[i1] = GetCurrentThreadId then
inc(result)
else
for i2 := 0 to high(tids) do
if FInUseThreadArr[i1] = tids[i2] then begin
inc(result);
break;
end;
end;
end;
destructor TCodeHook.Destroy;
var mutex : THandle;
map : THandle;
buf : pointer;
s1 : AnsiString;
i1, i2 : integer;
b1, b2 : boolean;
tramp : pointer;
queue : pointer;
tids : TDACardinal;
ths : TDANativeUInt;
ctxt : TContext;
op : dword;
mbi : TMemoryBasicInformation;
jmpSize : dword;
begin
FDestroying := true;
tids := nil;
ths := nil;
if FValid then begin
tramp := nil;
queue := nil;
s1 := ApiSpecialName(CMAHPrefix, FHookedFunc);
mutex := CreateLocalMutex(PAnsiChar(DecryptStr(CMutex) + ', ' + s1));
try
if mutex <> 0 then
WaitForSingleObject(mutex, INFINITE);
try
map := FMapHandle;
if FMapHandle = 0 then
map := OpenGlobalFileMapping(PAnsiChar(DecryptStr(CNamedBuffer) + ', ' + s1), true);
if map <> 0 then
try
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if buf <> nil then
try
with THookQueue(TPPointer(buf)^^) do begin
if TPByte(@NewCode)^ = $e9 then
jmpSize := 5
else
jmpSize := 6;
for i1 := 1 to ItemCount - 2 do
if (Items[i1].HookProc = FCallbackFunc) and
(Items[i1].PNextHook = {$ifdef win64} pointer(FPHookStubTarget) {$else} pointer(FPHookStub^.jmpNextHook.target) {$endif}) then begin
for i2 := i1 to ItemCount - 2 do
Items[i2] := Items[i2 + 1];
Items[i1 - 1].PNextHook^ := Items[i1].HookProc;
dec(ItemCount);
if (ItemCount = 2) and (MapHandle <> 0) then begin
if (not IsBadReadPtrEx(PatchAddr, jmpSize)) and
(TPCardinal(NativeUInt(PatchAddr))^ = TPCardinal(NativeUInt(@NewCode))^) and
( ((jmpSize = 5) and (TPByte(NativeUInt(PatchAddr) + 4)^ = TPByte(NativeUInt(@NewCode) + 4)^)) or
((jmpSize = 6) and (TPWord(NativeUInt(PatchAddr) + 4)^ = TPWord(NativeUInt(@NewCode) + 4)^)) ) then begin
try
b1 := VirtualProtect(PatchAddr, 8, PAGE_EXECUTE_READWRITE, @op);
except
// Edge might raise "The operation was blocked as the process prohibits dynamic code generation."
b1 := false;
end;
if b1 then begin
if not AtomicMove(@OldCode, PatchAddr, jmpSize) then
Move(OldCode, PatchAddr^, jmpSize);
FlushInstructionCache(PatchAddr, jmpSize);
VirtualProtect(PatchAddr, 8, op, @op);
tramp := Items[1].HookProc;
end;
end;
queue := TPPointer(buf)^;
TPPointer(buf)^ := nil;
CloseHandle(MapHandle);
end;
break;
end;
end;
finally UnmapViewOfFile(buf) end;
finally
if FMapHandle = 0 then
CloseHandle(map);
end;
finally if mutex <> 0 then ReleaseMutex(mutex) end;
finally if mutex <> 0 then CloseHandle(mutex) end;
if (not FDoubleHook) and (FPNextHook <> nil) then
FPNextHook^ := FHookedFunc;
if FInUseCodeArr <> nil then begin
GetOtherThreads(true, tids, ths);
try
b1 := false;
while true do begin
b2 := true;
for i2 := 0 to high(ths) do begin
ctxt.ContextFlags := CONTEXT_CONTROL;
if GetThreadContext(ths[i2], ctxt) and
(ctxt.{$ifdef win64}rip{$else}eip{$endif} >= NativeUInt(FPHookStub)) and
(ctxt.{$ifdef win64}rip{$else}eip{$endif} <= NativeUInt(FPHookStub) + PAGE_SIZE * 3) then begin
b2 := false;
break;
end;
end;
if FInUseTargetArr <> nil then
for i1 := 0 to CInUseCount - 1 do
if FInUseTargetArr[i1] <> nil then
if FInUseThreadArr[i1] = GetCurrentThreadId then begin
b2 := false;
break;
end else begin
for i2 := 0 to high(tids) do
if FInUseThreadArr[i1] = tids[i2] then begin
b2 := false;
break;
end;
if not b2 then
break;
end;
if b2 then
break;
b1 := true;
if FLeakUnhook then
exit;
Sleep(500);
end;
finally
for i2 := 0 to high(ths) do
CloseHandle(ths[i2]);
end;
if b1 then
Sleep(500);
end;
if tramp <> nil then
if (VirtualQuery(tramp, mbi, sizeOf(mbi)) <> sizeOf(mbi)) or
((mbi.Protect <> PAGE_EXECUTE_READWRITE) and (mbi.Protect <> PAGE_EXECUTE_READ)) then
LocalFree(HLOCAL(tramp))
else
VirtualFree(tramp, 0, MEM_RELEASE);
if queue <> nil then
VirtualFree(queue, 0, MEM_RELEASE);
end;
VirtualFree(FPHookStub, 0, MEM_RELEASE);
FInUseCodeArr := nil;
FInUseTargetArr := nil;
FPHookStub := nil;
FPHookStubTarget := nil;
end;
type
THookItem = record
owner : HMODULE;
moduleH : HMODULE;
module : AnsiString;
moduleW : AnsiString;
api : AnsiString;
ordinal : dword;
code : pointer;
ch : TCodeHook;
nextHook : TPPointer;
callback : pointer;
flags : dword;
numParams : integer;
end;
var
HookSection : TRTLCriticalSection;
HookReady : boolean = false;
HookList : array of THookItem;
type
IMAGE_DELAYLOAD_DESCRIPTOR = record
AllAttributes : dword;
DllNameRVA : dword;
ModuleHandleRVA : dword;
ImportAddressTableRVA : dword;
ImportNameTableRVA : dword;
BoundImportAddressTableRVA : dword;
UnloadInformationTableRVA : dword;
TimeDateStamp : dword;
end;
var LoadLibraryExWNextHook : function (lib: PWideChar; file_: THandle; flags: dword) : HMODULE stdcall = nil;
LdrLoadDllNextHook : function (path: pointer; flags: pointer; var name: TUnicodeStr; var handle: HMODULE) : dword stdcall = nil;
LdrResolveDelayLoadedAPINextHook : function (base: pointer; var desc: IMAGE_DELAYLOAD_DESCRIPTOR; dllhook, syshook, addr: pointer; flags: ULONG) : pointer stdcall = nil;
LoadLibraryHooked : boolean = false;
LoadLibraryExWDone : boolean = false;
LoadLibraryCallsNtDll : pointer = nil;
LdrGetDllHandle : function (dllPath: PWideChar; dllChars: pointer; var dllName: TUnicodeStr; out handle: HMODULE) : dword stdcall = nil;
LdrRegisterDllNotification : function (flags: ULONG; callback, context: pointer; out cookie: pointer) : dword; stdcall = nil;
LdrUnregisterDllNotification : function (cookie: pointer) : dword; stdcall = nil;
LdrDllNotificationCookie : pointer = nil;
procedure CheckHooks(dll: HMODULE); forward;
type
TLdrDllNotificationData = record
Flags : ULONG; // Reserved.
FullDllName : pointer; // The full path name of the DLL module.
BaseDllName : pointer; // The base file name of the DLL module.
DllBase : pointer; // A pointer to the base address for the DLL in memory.
SizeOfImage : ULONG; // The size of the DLL image, in bytes.
end;
procedure LdrDllNotificationFunction(reason: ULONG; var data: TLdrDllNotificationData; context: pointer); stdcall;
begin
CheckHooks(HMODULE(data.DllBase));
end;
function GetModuleHandleEx(var us: TUnicodeStr) : HMODULE;
begin
if @LdrGetDllHandle <> nil then begin
// Calling GetModuleHandleW in a LoadLibrary hook callback can sometimes
// screw up strings (don't ask me why). We work around this by calling
// LdrGetDllHandle instead, which doesn't have this problem.
if LdrGetDllHandle(nil, nil, us, result) <> 0 then
result := 0;
end else
result := GetModuleHandleW(us.str);
end;
function LoadLibraryExWCallbackProc(lib: PWideChar; file_: THandle; flags: dword) : HMODULE; stdcall;
var module : HMODULE;
le : dword;
us : TUnicodeStr;
begin
module := 0;
if not AlwaysCheckHooks then begin
le := GetLastError;
try
us.str := lib;
us.len := lstrlenW(lib) * 2;
us.maxLen := us.len + 2;
module := GetModuleHandleEx(us);
except module := 0 end;
SetLastError(le);
end;
result := LoadLibraryExWNextHook(lib, file_, flags);
if (result <> 0) and (result <> module) and (flags and LOAD_LIBRARY_AS_DATAFILE = 0) then
CheckHooks(result);
end;
function LdrLoadDllCallbackProc(path, flags: pointer; var name: TUnicodeStr; var handle: HMODULE) : dword stdcall;
var module : HMODULE;
le : dword;
begin
module := 0;
if not AlwaysCheckHooks then begin
le := GetLastError;
try
module := GetModuleHandleEx(name);
except module := 0 end;
SetLastError(le);
end;
result := LdrLoadDllNextHook(path, flags, name, handle);
if (handle <> 0) and (handle <> module) then
CheckHooks(handle);
end;
function LdrResolveDelayLoadedAPICallbackProc(base: pointer; var desc: IMAGE_DELAYLOAD_DESCRIPTOR; dllhook, syshook, addr: pointer; flags: ULONG) : pointer stdcall;
var module : HMODULE;
le : dword;
name : AnsiString;
us : TUnicodeStr;
mbi : TMemoryBasicInformation;
begin
module := 0;
if not AlwaysCheckHooks then begin
le := GetLastError;
try
if (desc.DllNameRVA <> 0) then begin
name := AnsiToWideEx(PAnsiChar(base) + desc.DllNameRVA, true);
us.str := pointer(name);
us.len := lstrlenW(us.str) * 2;
us.maxLen := us.len + 2;
module := GetModuleHandleEx(us);
end;
except module := 0 end;
SetLastError(le);
end;
result := LdrResolveDelayLoadedAPINextHook(base, desc, dllhook, syshook, addr, flags);
if (result <> nil) and (module = 0) then begin
le := GetLastError;
if (VirtualQuery(result, mbi, sizeOf(MEMORY_BASIC_INFORMATION)) = sizeof(MEMORY_BASIC_INFORMATION)) and (mbi.State = MEM_COMMIT) and (mbi.AllocationBase <> nil) then
CheckHooks(HMODULE(mbi.AllocationBase));
SetLastError(le);
end;
end;
procedure HookLoadLibrary;
type TMchLLEW = packed record
name : array [0..7] of AnsiChar;
jmp : ^THookStub;
end;
type THookStubEx = packed record
jmpNextHook : TAbsoluteJmp; // $FF / $25 / @absTarget
absTarget : pointer; // absolute target
self : pointer;
end;
var map : THandle;
newMap : boolean;
buf1 : ^TMchLLEW;
buf2 : ^THookStubEx;
s1 : AnsiString;
i1 : integer;
c1 : dword;
fi : TFunctionInfo;
p1 : pointer;
api : pointer;
orgCode : int64;
op : dword;
begin
if not LoadLibraryHooked then begin
// I had high hopes for the "LdrRegisterDllNotification" API to replace my old self-made LoadLibrary API hook.
// However, plays out "LdrRegisterDllNotification" is rather sensitive/unstable. Several things don't work,
// inside of the callback function. Furthermore, in Windows 7 x64 (and only there) CorelDraw X6 crashes inside
// of the callback, when hooking a WinSock API. The crash seems to be caused by trying to disassemble the
// WinSock API code. Even a "__try __except(1)" block isn't able to solve the crash. So right now I see no
// other option than to continue using my self-mode hook instead of the Ldr* API.
LdrRegisterDllNotification := nil; // NtProc(CLdrRegisterDllNotification);
LdrUnregisterDllNotification := nil; // NtProc(CLdrUnregisterDllNotification);
if (@LdrRegisterDllNotification = nil) or (@LdrUnregisterDllNotification = nil) or (LdrRegisterDllNotification(0, @LdrDllNotificationFunction, nil, LdrDllNotificationCookie) <> 0) then begin
LdrDllNotificationCookie := nil;
if @LdrGetDllHandle = nil then
LdrGetDllHandle := NtProc(CLdrGetDllHandle);
if not LoadLibraryExWDone then begin
LoadLibraryExWDone := true;
if not DisableLdrLoadDllSpecialHook then begin
s1 := DecryptStr(CMchLLEW2);
map := CreateFileMappingA(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, sizeOf(TMchLLEW), PAnsiChar(s1 + IntToHexExA(GetCurrentProcessId)));
if map <> 0 then begin
newMap := GetLastError <> ERROR_ALREADY_EXISTS;
buf1 := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if buf1 <> nil then begin
for i1 := 1 to 50 do
if newMap or (TPInt64(@buf1^.name)^ = TPInt64(s1)^) then
break
else Sleep(50);
if newMap or (TPInt64(@buf1^.name)^ <> TPInt64(s1)^) then begin
p1 := NtProc(CLdrLoadDll, true);
api := KernelProc(CLoadLibraryExW, true);
orgCode := int64(api^);
if not RestoreCode(api) then
orgCode := 0;
fi := ParseFunction(api);
buf2 := VirtualAlloc2(sizeOf(THookStubEx), fi.EntryPoint);
for i1 := 0 to high(fi.FarCalls) do
if fi.FarCalls[i1].Target = p1 then begin
if (fi.FarCalls[i1].PTarget <> nil) or (fi.FarCalls[i1].PPTarget <> nil) then begin
buf2^.jmpNextHook.opcode := $ff;
buf2^.jmpNextHook.modRm := $25;
{$ifdef win64}
buf2^.jmpNextHook.target := 0;
{$else}
buf2^.jmpNextHook.target := dword(@buf2^.absTarget);
{$endif}
buf2^.absTarget := p1;
buf2^.self := buf2;
VirtualProtect(buf2, sizeOf(THookStubEx), PAGE_EXECUTE_READ, @op);
buf1^.jmp := pointer(buf2);
if fi.FarCalls[i1].RelTarget then begin
if VirtualProtect(fi.FarCalls[i1].PTarget, 8, PAGE_EXECUTE_READWRITE, @op) then begin
c1 := NativeUInt(buf2) - NativeUInt(fi.FarCalls[i1].CodeAddr1) - 5;
if not AtomicMove(@c1, fi.FarCalls[i1].PTarget, 4) then
dword(fi.FarCalls[i1].PTarget^) := c1;
VirtualProtect(fi.FarCalls[i1].PTarget, 8, op, @op);
LoadLibraryCallsNtDll := buf2;
map := 0;
end;
end else
if fi.FarCalls[i1].PTarget <> nil then begin
if VirtualProtect(fi.FarCalls[i1].PTarget, 8, PAGE_EXECUTE_READWRITE, @op) then begin
if not AtomicMove(@buf2, fi.FarCalls[i1].PTarget, sizeOf(pointer)) then
pointer(fi.FarCalls[i1].PTarget^) := buf2;
VirtualProtect(fi.FarCalls[i1].PTarget, 8, op, @op);
LoadLibraryCallsNtDll := buf2;
map := 0;
end;
end else begin
if VirtualProtect(fi.FarCalls[i1].PPTarget, 8, PAGE_EXECUTE_READWRITE, @op) then begin
{$ifdef win64}
// RIP-relative addressing
c1 := NativeUInt(@buf2.Self) - NativeUInt(fi.FarCalls[i1].CodeAddr2);
{$else}
c1 := NativeUInt(@buf2.Self);
{$endif}
if not AtomicMove(@c1, fi.FarCalls[i1].PPTarget, 4) then
TPCardinal(fi.FarCalls[i1].PPTarget)^ := c1;
VirtualProtect(fi.FarCalls[i1].PPTarget, 8, op, @op);
LoadLibraryCallsNtDll := buf2;
map := 0;
end;
end;
end;
break;
end;
if orgCode <> 0 then begin
if VirtualProtect(api, 8, PAGE_EXECUTE_READWRITE, @op) then begin
if not AtomicMove(@orgCode, api, 8) then
int64(api^) := orgCode;
FlushInstructionCache(api, 8);
VirtualProtect(api, 8, op, @op);
end;
end;
if LoadLibraryCallsNtDll = nil then begin
UnmapViewOfFile(buf1);
VirtualFree(buf2, 0, MEM_RELEASE);
end else
TPInt64(@buf1^.name)^ := TPInt64(s1)^;
end else begin
LoadLibraryCallsNtDll := buf1^.jmp;
UnmapViewOfFile(buf1);
end;
end;
if map <> 0 then
CloseHandle(map);
end;
end;
end;
if LoadLibraryCallsNtDll <> nil then
HookCodeX(0, 0, '', '', 0, LoadLibraryCallsNtDll, @LdrLoadDllCallbackProc, @LdrLoadDllNextHook, 0, 0)
else
HookCodeX(0, 0, '', '', 0, FindRealCode(KernelProc(CLoadLibraryExW, true)), @LoadLibraryExWCallbackProc, @LoadLibraryExWNextHook, 0, 0);
HookCodeX(0, 0, '', '', 0, FindRealCode(NtProc(CLdrResolveDelayLoadedAPI, true)), @LdrResolveDelayLoadedAPICallbackProc, @LdrResolveDelayLoadedAPINextHook, 0, 0);
end;
LoadLibraryHooked := true;
end;
end;
function UnhookCodeEx (var nextHook: pointer; dontUnhookHelperHooks, wait: boolean) : boolean; forward;
procedure UnhookLoadLibrary(wait: boolean);
begin
if LoadLibraryHooked then begin
if LdrDllNotificationCookie <> nil then begin
LdrUnregisterDllNotification(LdrDllNotificationCookie);
LdrDllNotificationCookie := nil;
end;
if @LoadLibraryExWNextHook <> nil then UnhookCodeEx(@LoadLibraryExWNextHook, true, wait);
if @LdrLoadDllNextHook <> nil then UnhookCodeEx(@LdrLoadDllNextHook, true, wait);
if @LdrResolveDelayLoadedAPINextHook <> nil then UnhookCodeEx(@LdrResolveDelayLoadedAPINextHook, true, wait);
LoadLibraryHooked := false;
end;
end;
var
AutoUnhookCounter : integer = -1;
AutoUnhookMarker : array [0..31] of byte = ($00, $00, $00, $00, $00, $00, $00, $00, $38, $34, $31, $16, $1D, $3A, $3A, $3E, $14, $20, $21, $3A, $00, $3B, $3D, $3A, $3A, $3E, $18, $34, $27, $3E, $30, $27);
procedure AutoUnhookUninject(moduleHandle: HMODULE);
// this gets called from the DLL uninjection thread
begin
if InterlockedIncrement(AutoUnhookCounter) <> 0 then begin
// only the first uninjection thread gets to actually perform unhooking
// the 2nd (and 3rd etc) thread is stopped right here to avoid conflicts
ExitThread(0);
end;
AutoUnhook(moduleHandle);
FireUninjectCallbacks;
end;
procedure InitAutoUnhook;
begin
TPPointer(@AutoUnhookMarker)^ := @AutoUnhookUninject;
end;
{$ifdef unlocked}
function HookCodeX(owner : HMODULE;
moduleH : HMODULE;
module : AnsiString;
api : AnsiString;
ordinal : dword;
code : pointer;
callbackFunc : pointer;
out nextHook : pointer;
flags : dword;
numStackParams : dword) : boolean;
{$else}
var forbiddenMutex : THandle = 0;
function HookCodeX_(owner : HMODULE;
moduleH : HMODULE;
module : AnsiString;
api : AnsiString;
ordinal : dword;
code : pointer;
callbackFunc : pointer;
out nextHook : pointer;
flags : dword;
numStackParams : dword) : boolean;
{$endif}
var ch1 : TCodeHook;
c1 : dword;
b1 : boolean;
s1 : AnsiString;
i1 : integer;
doubleInstalled : boolean;
begin
result := false;
{$ifdef log}
SetLength(s1, MAX_PATH + 1);
GetModuleFileNameA(owner, PAnsiChar(s1), MAX_PATH);
s1 := PAnsiChar(s1);
log('HookCodeX (owner: ' + IntToHexExA(owner) + ' (' + s1 + ')' +
'; moduleH: ' + IntToHexExA(moduleH) +
'; module: ' + module +
'; api: ' + api +
'; ordinal: ' + IntToStrExA(ordinal) +
'; code: ' + IntToHexExA(NativeUInt(code)) +
'; callback: ' + IntToHexExA(NativeUInt(callbackFunc)) +
'; nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
'; flags: ' + IntToHexExA(flags) +
'; numStackParams: ' + IntToStrExA(numStackParams) +
')');
{$endif}
InitProcs(true);
SetLastError(0);
{$ifndef win64}
InitUnprotectMemory;
{$endif}
if code <> nil then begin
try
if moduleH = 0 then begin
FindModuleA(code, moduleH, s1);
ch1 := TCodeHook.Create(moduleH, code, callbackFunc, nextHook, flags, numStackParams);
end else
ch1 := TCodeHook.Create(moduleH, code, callbackFunc, nextHook, flags, numStackParams);
if not ch1.FValid then begin
c1 := GetLastError;
ch1.Free;
SetLastError(c1);
ch1 := nil;
moduleH := 0;
end;
except ch1 := nil end;
end else
ch1 := nil;
if (ch1 <> nil) or ((code = nil) and (module <> '')) then begin
result := true;
if not HookReady then begin
InitializeCriticalSection(HookSection);
HookReady := true;
end;
doubleInstalled := false;
EnterCriticalSection(HookSection);
try
for i1 := 0 to high(HookList) do
if HookList[i1].nextHook = @nextHook then begin
// There's already a hook for this "nextHook" variable installed.
// This can happen if multiple threads are loading dlls at the same
// time, and if madCodeHook detects a new DLL it's supposed to hook.
// In that situation madCodeHook might try to install an API hook for
// the new DLL from multiple threads simultaneously.
// Our new hook is superfluous, so we will delete it right away.
// But it has already modified the "nextHook" shared variable of the
// already existing hook. So we need to restore the original value.
if HookList[i1].ch <> nil then
HookList[i1].ch.FPNextHook^ := HookList[i1].ch.FPHookStub;
doubleInstalled := true;
break;
end;
if not doubleInstalled then begin
c1 := Length(HookList);
SetLength(HookList, c1 + 1);
HookList[c1].owner := owner;
HookList[c1].moduleH := moduleH;
HookList[c1].module := module;
HookList[c1].moduleW := AnsiToWideEx(module);
HookList[c1].api := api;
HookList[c1].ordinal := ordinal;
HookList[c1].code := code;
HookList[c1].ch := ch1;
HookList[c1].nextHook := @nextHook;
HookList[c1].callback := callbackFunc;
HookList[c1].flags := flags;
HookList[c1].numParams := numStackParams;
end;
b1 := (owner <> 0) and (not LoadLibraryHooked);
finally LeaveCriticalSection(HookSection) end;
if doubleInstalled and (ch1 <> nil) then begin
ch1.FDoubleHook := true;
ch1.Free;
end;
if b1 then begin
if HookLoadLibraryOption then
HookLoadLibrary;
InitAutoUnhook;
end;
end;
if result then
SetLastError(0);
{$ifdef log}
SetLength(s1, MAX_PATH + 1);
GetModuleFileNameA(owner, PAnsiChar(s1), MAX_PATH);
s1 := PAnsiChar(s1);
log('HookCodeX (owner: ' + IntToHexExA(owner) + ' (' + s1 + ')' +
'; moduleH: ' + IntToHexExA(moduleH) +
'; module: ' + module +
'; api: ' + api +
'; ordinal: ' + ordinal +
'; code: ' + IntToHexExA(NativeUInt(code)) +
'; callback: ' + IntToHexExA(NativeUInt(callbackFunc)) +
'; nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
'; flags: ' + IntToHexExA(flags) +
'; numStackParams: ' + IntToStrExA(numStackParams) +
') -> ' + booleanToCharA(result));
{$endif}
end;
{$ifndef unlocked}
function HookCodeX(owner : HMODULE;
moduleH : HMODULE;
module : AnsiString;
api : AnsiString;
ordinal : dword;
code : pointer;
callbackFunc : pointer;
out nextHook : pointer;
flags : dword;
numStackParams : dword) : boolean;
var FindHookCodeX3Addr : NativeUInt;
procedure JmpRel;
asm
push dword ptr [esp]
add [esp+4], eax
end;
function HookCodeX3(owner : HMODULE;
moduleH : HMODULE;
module : AnsiString;
api : AnsiString;
ordinal : dword;
code : pointer;
callbackFunc : pointer;
out nextHook : pointer;
flags : dword;
numStackParams : dword) : boolean;
begin
result := false;
end;
procedure HookCodeX2;
asm
db $82
db $0d
jmp HookCodeX_
end;
function FindHookCodeX5(api: AnsiString; ofs: integer) : pointer;
begin
result := pointer(NativeUInt(@HookCodeX3) - 2);
end;
function FindHookCodeX4(api: AnsiString; ofs: integer) : pointer;
function CmpHookThis(api: AnsiString) : bool;
begin
result := (api <> DecryptStr(CProcess32Next )) and
(api <> DecryptStr(CEnumServicesStatusA )) and
(api <> DecryptStr(CEnumServicesStatusW )) and
(api <> DecryptStr(CNtQuerySystemInformation));
end;
asm
cmp ofs, 0
jz @Data
push ofs
call CmpHookThis
cmp eax, 0
jnz @Ok
pop eax
mov eax, offset HookCodeX3
sub eax, 2
ret
@Ok:
pop ecx
mov eax, offset @Data
add eax, ecx
mov eax, [eax]
ret
@Data:
db $82
db $1d
db $00
dd offset HookCodeX2
end;
procedure FindHookCodeX3;
asm
cmp eax, 0
jnz @zeroParam
mov eax, 8
call JmpRel
@zeroParam:
mov eax, offset FindHookCodeX5
ret
db $82
db $05
mov eax, offset FindHookCodeX4
end;
procedure FindHookCodeX2;
asm
mov [esp], eax
mov eax, [eax]
xor [esp], eax
xor eax, eax
end;
function FindHookCodeX1(param: pointer) : NativeUInt;
asm
call FindHookCodeX2
end;
var FindHookCode1 : function (api: AnsiString; offset: integer) : NativeUInt;
FindHookCode2 : function (owner : HMODULE;
moduleH : HMODULE;
module : AnsiString;
api : AnsiString;
ordinal : dword;
code : pointer;
callbackFunc : pointer;
out nextHook : pointer;
flags : dword;
numStackParams : dword) : boolean;
begin
FindHookCodeX3Addr := NativeUInt(@FindHookCodeX3Addr) xor NativeUInt(@FindHookCodeX3);
FindHookCode1 := pointer(FindHookCodeX1(@FindHookCodeX3Addr));
FindHookCode2 := pointer(FindHookCode1(api, 3) + 2);
result := FindHookCode2(owner, moduleH, module, api, ordinal, code, callbackFunc, nextHook, flags, numStackParams);
end;
procedure CheckForbiddenAPIs(api: AnsiString);
begin
if (api = DecryptStr(CProcess32Next2 )) or
(api = DecryptStr(CEnumServicesStatusA2)) or
(api = DecryptStr(CEnumServicesStatusW2)) or
(api = DecryptStr(CNtQuerySystemInfo2 )) then
if (forbiddenMutex = 0) and (not AmSystemProcess) and AmUsingInputDesktop then begin
forbiddenMutex := CreateMutex(nil, false, 'forbiddenAPIsMutex');
if GetLastError <> ERROR_ALREADY_EXISTS then
MessageBoxA(0, PAnsiChar('You''ve tried to hook one of the following APIs: ' + #$D#$A + #$D#$A +
'- ' + DecryptStr(CProcess32Next2) + #$D#$A +
'- ' + DecryptStr(CEnumServicesStatusA2) + #$D#$A +
'- ' + DecryptStr(CEnumServicesStatusW2) + #$D#$A +
'- ' + DecryptStr(CNtQuerySystemInfo2) + #$D#$A + #$D#$A +
'These APIs are usually hooked in order to hide a ' + #$D#$A +
'process. Of course madCodeHook can do that just ' + #$D#$A +
'fine. But I don''t want virus/trojan writers to misuse ' + #$D#$A +
'madCodeHook for illegal purposes. So I''ve decided ' + #$D#$A +
'to not allow these APIs to be hooked.' + #$D#$A + #$D#$A +
'If you absolutely have to hook these APIs, and if ' + #$D#$A +
'you have a commercial madCodeHook license, you ' + #$D#$A +
'may contact me.'),
'madCodeHook warning...', 0);
end;
end;
{$endif}
function HookCode(code : pointer;
callbackFunc : pointer;
out nextHook : pointer;
flags : dword = 0;
numStackParams : dword = 32) : bool; stdcall;
var s1, s2 : AnsiString;
module : HMODULE;
begin
result := false;
{$ifdef log}
log('HookCode (code: ' + IntToHexExA(NativeUInt(code)) +
'; callback: ' + IntToHexExA(NativeUInt(callbackFunc)) +
'; nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
'; flags: ' + IntToHexExA(flags) +
'; numStackParams: ' + IntToStrExA(numStackParams) +
')');
{$endif}
if code <> nil then begin
s1 := '';
s2 := '';
module := 0;
if VerifyHookAddress then begin
code := FindRealCode(code);
if FindModule(code, module, s1) then begin
s2 := GetImageProcName(module, code, false);
if s2 = '' then begin
module := 0;
s1 := '';
end;
end;
end;
result := HookCodeX(GetCallingModule, module, s1, s2, 0, code, callbackFunc, nextHook, flags, numStackParams);
{$ifndef unlocked}
CheckForbiddenAPIs(s2);
{$endif}
end else
SetLastError(ERROR_INVALID_PARAMETER);
{$ifdef log}
log('HookCode (code: ' + IntToHexExA(NativeUInt(code)) +
'; callback: ' + IntToHexExA(NativeUInt(callbackFunc)) +
'; nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
'; flags: ' + IntToHexExA(flags) +
'; numStackParams: ' + IntToStrExA(numStackParams) +
') -> ' + booleanToCharA(result));
{$endif}
end;
function HookAPI(module, api : PAnsiChar;
callbackFunc : pointer;
out nextHook : pointer;
flags : dword = 0;
numStackParams : dword = 32) : bool; stdcall;
var dll : HMODULE;
code : pointer;
ordinal : dword;
s2 : AnsiString;
begin
result := false;
if NativeUInt(api) < $10000 then begin
ordinal := dword(api);
api := nil;
end else
ordinal := 0;
{$ifdef log}
log('HookAPI (module: ' + module +
'; api: ' + AnsiString(api) +
'; ordinal: ' + IntToStrExA(ordinal) +
'; callback: ' + IntToHexExA(NativeUInt(callbackFunc)) +
'; nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
'; flags: ' + IntToHexExA(flags) +
'; numStackParams: ' + IntToStrExA(numStackParams) +
')');
{$endif}
if (module <> '') and ((ordinal > 0) or (api <> '')) then begin
dll := GetModuleHandleW(pointer(AnsiToWideEx(module)));
if dll <> 0 then begin
if VerifyHookAddress then begin
if ordinal = 0 then begin
code := GetImageProcAddress(dll, api, true);
if code <> nil then begin
code := FindRealCode(code);
s2 := GetImageProcName(dll, code, false);
if s2 <> '' then
api := PAnsiChar(s2);
end;
end else
code := GetImageProcAddress(dll, ordinal);
end else begin
if ordinal > 0 then
code := GetProcAddress(dll, PAnsiChar(ordinal))
else
code := GetProcAddress(dll, api);
ResolveMixtureMode(code);
end;
end else
code := nil;
result := HookCodeX(GetCallingModule, dll, module, api, ordinal, code, callbackFunc, nextHook, flags, numStackParams);
{$ifndef unlocked}
CheckForbiddenAPIs(s2);
{$endif}
end else
SetLastError(ERROR_INVALID_PARAMETER);
{$ifdef log}
log('HookAPI (module: ' + module +
'; api: ' + AnsiString(api) +
'; ordinal: ' + IntToStrExA(ordinal) +
'; callback: ' + IntToHexExA(NativeUInt(callbackFunc)) +
'; nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
'; flags: ' + IntToHexExA(flags) +
'; numStackParams: ' + IntToStrExA(numStackParams) +
') -> ' + booleanToCharA(result));
{$endif}
end;
function RenewHook(var nextHook: pointer) : bool; stdcall;
var {$ifdef CheckRecursion}
recTestBuf : array [0..3] of pointer;
{$endif}
ch : TCodeHook;
i1 : integer;
le : dword;
begin
result := false;
le := GetLastError;
{$ifdef CheckRecursion}
if NotRecursedYet(@RenewHook, @recTestBuf) then begin
{$endif}
{$ifdef log}
log('RenewHook (nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) + ')');
{$endif}
if HookReady then begin
ch := nil;
EnterCriticalSection(HookSection);
try
for i1 := high(HookList) downto 0 do
if HookList[i1].nextHook = @nextHook then begin
result := true;
ch := HookList[i1].ch;
break;
end;
finally LeaveCriticalSection(HookSection) end;
if ch <> nil then
ch.WritePatch(0, nil);
end;
{$ifdef log}
log('RenewHook (nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) + ') -> ' + booleanToCharA(result));
{$endif}
{$ifdef CheckRecursion}
recTestBuf[3] := nil;
end;
{$endif}
SetLastError(le);
end;
function IsHookInUse(var nextHook: pointer) : integer; stdcall;
var {$ifdef CheckRecursion}
recTestBuf : array [0..3] of pointer;
{$endif}
ch : TCodeHook;
i1 : integer;
le : dword;
begin
result := 0;
le := GetLastError;
{$ifdef CheckRecursion}
if NotRecursedYet(@IsHookInUse, @recTestBuf) then begin
{$endif}
{$ifdef log}
log('IsHookInUse (nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) + ')');
{$endif}
if HookReady then begin
ch := nil;
EnterCriticalSection(HookSection);
try
for i1 := high(HookList) downto 0 do
if HookList[i1].nextHook = @nextHook then begin
ch := HookList[i1].ch;
break;
end;
finally LeaveCriticalSection(HookSection) end;
if ch <> nil then
result := ch.IsHookInUse;
end;
{$ifdef log}
log('IsHookInUse (nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) + ') -> ' + IntToStrExA(result));
{$endif}
{$ifdef CheckRecursion}
recTestBuf[3] := nil;
end;
{$endif}
SetLastError(le);
end;
function UnhookCodeEx(var nextHook: pointer; dontUnhookHelperHooks, wait: boolean) : boolean;
var ch : TCodeHook;
i1, i2 : integer;
b1 : boolean;
begin
result := false;
{$ifdef log}
log('UnhookCode (nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
', dontUnhookHelperHooks: ' + booleanToCharA(dontUnhookHelperHooks) +
', wait: ' + booleanToCharA(wait) + ')');
{$endif}
if HookReady then begin
EnterCriticalSection(HookSection);
try
ch := nil;
for i1 := high(HookList) downto 0 do
if HookList[i1].nextHook = @nextHook then begin
result := true;
ch := HookList[i1].ch;
i2 := high(HookList);
HookList[i1] := HookList[i2];
SetLength(HookList, i2);
break;
end;
b1 := result and (not dontUnhookHelperHooks);
if b1 then
for i1 := 0 to high(HookList) do
if HookList[i1].owner <> 0 then begin
b1 := false;
break;
end;
finally LeaveCriticalSection(HookSection) end;
if result then begin
if ch <> nil then begin
ch.FLeakUnhook := not wait;
ch.Free;
end;
if b1 then
UnhookLoadLibrary(wait);
end;
end;
{$ifdef log}
log('UnhookCode (nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
', dontUnhookHelperHooks: ' + booleanToCharA(dontUnhookHelperHooks) +
', wait: ' + booleanToCharA(wait) + ') -> ' + booleanToCharA(result));
{$endif}
end;
function UnhookCode(var nextHook: pointer) : bool; stdcall;
begin
result := UnhookCodeEx(nextHook, false, true);
end;
function UnhookAPI(var nextHook: pointer) : bool; stdcall;
begin
result := UnhookCodeEx(nextHook, false, true);
end;
function IsProcessBlocked : boolean; forward;
var LastFullCheckHooksTime : dword = 0;
procedure CheckHooks(dll: HMODULE);
var {$ifdef CheckRecursion}
recTestBuf : array [0..3] of pointer;
{$endif}
i1, i2 : integer;
ach : array of THookItem;
m1 : HMODULE;
p1 : pointer;
error : dword;
b1 : boolean;
fullCheck : boolean;
dllName : PWideChar;
jmpSize : dword;
begin
error := GetLastError;
{$ifdef CheckRecursion}
if NotRecursedYet(@CheckHooks, @recTestBuf) then begin
{$endif}
{$ifdef log}
log('CheckHooks (dll: ' + IntToHexExA(dll) + ')');
{$endif}
if not IsProcessBlocked then begin
try
if (dll <> 0) and HookReady then begin
// we don't check *all* API hooks more often than once every 50ms
fullCheck := (LastFullCheckHooksTime = 0) or (TickDif(LastFullCheckHooksTime) > 50);
dllName := nil;
if not fullCheck then begin
dllName := pointer(LocalAlloc(LPTR, MAX_PATH * 2 + 2));
if GetModuleFileNameW(dll, dllName, MAX_PATH) = 0 then
fullCheck := true;
end;
if fullCheck then
LastFullCheckHooksTime := GetTickCount;
i2 := 0;
EnterCriticalSection(HookSection);
try
SetLength(ach, Length(HookList));
for i1 := 0 to high(HookList) do
if (HookList[i1].module <> '') and (fullCheck or (PosPCharW(PWideChar(pointer(HookList[i1].moduleW)), dllName) > 0)) then begin
ach[i2] := HookList[i1];
inc(i2);
end;
finally LeaveCriticalSection(HookSection) end;
if dllName <> nil then
LocalFree(HLOCAL(dllName));
b1 := false;
try
for i1 := 0 to i2 - 1 do
with ach[i1] do begin
if (ch = nil) or (TPByte(@ch.FNewCode)^ = $e9) then
jmpSize := 5
else
jmpSize := 6;
if (module <> '') and
( (ch = nil) or
IsBadReadPtrEx(ch.FPatchAddr, sizeOf(ch.FNewCode)) or
(TPCardinal(NativeUInt(ch.FPatchAddr))^ <> TPCardinal(NativeUInt(@ch.FNewCode))^) or
((jmpSize = 5) and (TPByte(NativeUInt(ch.FPatchAddr) + 4)^ <> TPByte(NativeUInt(@ch.FNewCode) + 4)^)) or
((jmpSize = 6) and (TPWord(NativeUInt(ch.FPatchAddr) + 4)^ <> TPWord(NativeUInt(@ch.FNewCode) + 4)^)) ) then begin
if not b1 then begin
b1 := true;
CollectCache_AddRef;
end;
m1 := GetModuleHandleW(pointer(moduleW));
if ordinal > 0 then
p1 := FindRealCode(GetImageProcAddress(m1, ordinal))
else
p1 := FindRealCode(GetImageProcAddress(m1, PAnsiChar(api), true));
if ((ch = nil) and (m1 <> 0)) or ((ch <> nil) and (code <> p1)) then begin
UnhookCodeEx(nextHook^, true, true);
HookCodeX(owner, m1, module, api, ordinal, p1, callback, nextHook^, flags, numParams);
end else
if ch <> nil then begin
if TPByte(@ch.FNewCode)^ = $e9 then
jmpSize := 5
else
jmpSize := 6;
if RenewOverwrittenHooks or
( (not IsBadReadPtrEx(ch.FPatchAddr, jmpSize)) and
(TPCardinal(NativeUInt(ch.FPatchAddr))^ = TPCardinal(NativeUInt(@ch.FOldCode))^) and
( ((jmpSize = 5) and (TPByte(NativeUInt(ch.FPatchAddr) + 4)^ = TPByte(NativeUInt(@ch.FOldCode) + 4)^)) or
((jmpSize = 6) and (TPWord(NativeUInt(ch.FPatchAddr) + 4)^ = TPWord(NativeUInt(@ch.FOldCode) + 4)^)) ) ) then
RenewHook(nextHook^);
end;
end;
end;
finally
if b1 then
CollectCache_Release;
end;
end;
except end;
end;
{$ifdef log}
log('CheckHooks (dll: ' + IntToHexExA(dll) + ') -> +');
{$endif}
{$ifdef CheckRecursion}
recTestBuf[3] := nil;
end;
{$endif}
SetLastError(error);
end;
procedure CollectHooks;
begin
{$ifdef log}
log('CollectHooks');
{$endif}
CollectCache_AddRef;
end;
procedure FlushHooks;
begin
{$ifdef log}
log('FlushHooks');
{$endif}
CollectCache_Release;
end;
function RestoreCode(code: pointer) : bool; stdcall;
var module : HMODULE;
orgCode : int64;
s1 : AnsiString;
op : dword;
begin
{$ifdef log}
log('RestoreCode (code: ' + IntToHexExA(NativeUInt(code)) + ')');
{$endif}
result := false;
CollectCache_AddRef;
try
if FindModule(code, module, s1) and WasCodeChanged(module, code, orgCode) and (orgCode <> 0) then begin
{$ifndef win64}
InitUnprotectMemory;
{$endif}
if VirtualProtect(code, 8, PAGE_EXECUTE_READWRITE, @op) then begin
result := true;
if not AtomicMove(@orgCode, code, sizeOf(orgCode)) then
int64(code^) := orgCode;
FlushInstructionCache(code, sizeOf(orgCode));
VirtualProtect(code, 8, op, @op);
end;
end;
finally CollectCache_Release end;
{$ifdef log}
log('RestoreCode (code: ' + IntToHexExA(NativeUInt(code)) + ') -> ' + booleanToCharA(result));
{$endif}
end;
// ***************************************************************
function AmUsingInputDesktop : bool; stdcall;
var {$ifdef CheckRecursion}
recTestBuf : array [0..3] of pointer;
{$endif}
s1, s2 : AnsiString;
h1 : THandle;
c1 : dword;
error : dword;
begin
error := GetLastError;
{$ifdef CheckRecursion}
result := false;
if NotRecursedYet(@AmUsingInputDesktop, @recTestBuf) then begin
{$endif}
{$ifdef log}
log('AmUsingInputDesktop');
{$endif}
SetLength(s1, MAX_PATH + 1);
s1[1] := #0;
h1 := OpenInputDesktop(0, false, READ_CONTROL or GENERIC_READ);
GetUserObjectInformationA(h1, UOI_NAME, PAnsiChar(s1), MAX_PATH, c1);
CloseDesktop(h1);
s1 := PAnsiChar(s1);
SetLength(s2, MAX_PATH + 1);
s2[1] := #0;
h1 := GetThreadDesktop(GetCurrentThreadId);
GetUserObjectInformationA(h1, UOI_NAME, PAnsiChar(s2), MAX_PATH, c1);
CloseDesktop(h1);
s2 := PAnsiChar(s2);
result := (s1 <> '') and (s1 = s2);
{$ifdef log}
log('AmUsingInputDesktop -> ' + booleanToCharA(result));
{$endif}
{$ifdef CheckRecursion}
recTestBuf[3] := nil;
end;
{$endif}
SetLastError(error);
end;
function InternalGetCallingModule(returnAddress: pointer) : HMODULE;
var {$ifdef CheckRecursion}
recTestBuf : array [0..3] of pointer;
{$endif}
mbi : TMemoryBasicInformation;
w1 : word;
i1 : integer;
index : integer;
error : dword;
begin
result := 0;
error := GetLastError;
{$ifdef CheckRecursion}
if NotRecursedYet(@InternalGetCallingModule, @recTestBuf) then begin
{$endif}
{$ifdef log}
log('GetCallingModule');
{$endif}
if (VirtualQuery(returnAddress, mbi, sizeOf(mbi)) <> sizeOf(mbi)) or (not TryRead(mbi.AllocationBase, @w1, 2)) or (w1 <> CEMAGIC) then begin
if HookReady then begin
EnterCriticalSection(HookSection);
try
for i1 := high(HookList) downto 0 do begin
if (HookList[i1].ch <> nil) and
(HookList[i1].ch.FInUseCodeArr <> nil) and
(NativeUInt(returnAddress) >= NativeUInt(HookList[i1].ch.FInUseCodeArr)) and
(NativeUInt(returnAddress) < NativeUInt(HookList[i1].ch.FInUseCodeArr) + sizeOf(TInUseCodeArr)) then begin
index := (NativeUInt(returnAddress) - NativeUInt(HookList[i1].ch.FInUseCodeArr)) div sizeOf(TInUseItem);
returnAddress := HookList[i1].ch.FInUseTargetArr[index];
if HookList[i1].ch.FStoreThreadState then
{$ifdef win64}
returnAddress := pointer(pointer(NativeUInt(returnAddress) + $20)^);
{$else}
returnAddress := pointer(pointer(NativeUInt(returnAddress) + $10)^);
{$endif}
if (VirtualQuery(returnAddress, mbi, sizeOf(mbi)) = sizeOf(mbi)) and
TryRead(mbi.AllocationBase, @w1, 2) and (w1 = CEMAGIC) then
result := HMODULE(mbi.AllocationBase);
break;
end;
end;
finally LeaveCriticalSection(HookSection) end;
end;
end else
result := HMODULE(mbi.AllocationBase);
{$ifdef log}
log('GetCallingModule -> ' + IntToHexExA(result));
{$endif}
{$ifdef CheckRecursion}
recTestBuf[3] := nil;
end;
{$endif}
SetLastError(error);
end;
{$ifdef win64}
function RtlGetCallersAddress(var caller, callersCaller: pointer) : HRESULT; stdcall; external 'ntdll.dll';
function GetCallingModule(returnAddress: pointer = nil) : HMODULE; stdcall;
var dummy : pointer;
error : dword;
begin
if returnAddress = nil then begin
error := GetLastError;
RtlGetCallersAddress(dummy, returnAddress);
SetLastError(error);
end;
result := InternalGetCallingModule(returnAddress);
end;
{$else}
{$W+}
function GetCallingModule(returnAddress: pointer = nil) : HMODULE; stdcall;
var ebp_ : NativeUInt;
error : dword;
begin
asm
mov ebp_, ebp
end;
if returnAddress = nil then begin
error := GetLastError;
if TryRead(pointer(ebp_), @ebp_, sizeOf(pointer)) then
TryRead(pointer(ebp_ + sizeOf(pointer)), @returnAddress, sizeOf(pointer));
SetLastError(error);
end;
result := InternalGetCallingModule(returnAddress);
end;
{$W-}
{$endif}
var NtQueryInformationProcess : function (ph: THandle; level: dword; buf: pointer; len: dword; retLen: pointer) : dword; stdcall = nil;
function GetProcessSessionId(pid: dword) : dword;
var {$ifdef CheckRecursion}
recTestBuf : array [0..3] of pointer;
{$endif}
pid2sid : function (processId: dword; var sessionId: dword) : bool; stdcall;
error : dword;
begin
error := GetLastError;
{$ifdef CheckRecursion}
result := 0;
if NotRecursedYet(@GetProcessSessionId, @recTestBuf) then begin
{$endif}
{$ifdef log}
log('GetProcessSessionId (pid: ' + IntToHexExA(pid) + ')');
{$endif}
pid2sid := KernelProc(CProcessIdToSessionId);
if ((@pid2sid = nil) or (not pid2sid(pid, result))) and (pid = GetCurrentProcessId) then begin
if @NtQueryInformationProcess = nil then
NtQueryInformationProcess := NtProc(CNtQueryInformationProcess);
if (@NtQueryInformationProcess = nil) or (NtQueryInformationProcess(GetCurrentProcess, 24 (*ProcessSessionInformation*), @result, sizeOf(result), nil) <> 0) then
result := 0;
end;
{$ifdef log}
log('GetProcessSessionId (pid: ' + IntToHexExA(pid) + ') -> ' + IntToStrExA(result));
{$endif}
{$ifdef CheckRecursion}
recTestBuf[3] := nil;
end;
{$endif}
SetLastError(error);
end;
function IsSystemProcess(processHandle: THandle) : boolean;
var {$ifdef CheckRecursion}
recTestBuf : array [0..3] of pointer;
{$endif}
saa : PSidAndAttributes;
error : dword;
session : dword;
begin
error := GetLastError;
{$ifdef CheckRecursion}
result := false;
if NotRecursedYet(@IsSystemProcess, @recTestBuf) then begin
{$endif}
result := true;
saa := nil;
if GetProcessSid(processHandle, saa) and (saa <> nil) then begin
result := (saa^.Sid = nil) or (TPAByte(saa^.Sid)^[1] <= 1);
LocalFree(HLOCAL(saa));
end else begin
// asking the Sid of a process in another session sometimes doesn't work
// processes in other sessions are usually not system processes
session := GetProcessSessionId(ProcessHandleToId(processHandle));
if (session <> 0) and (session <> GetProcessSessionId(GetCurrentProcessId)) then
result := false;
end;
{$ifdef CheckRecursion}
recTestBuf[3] := nil;
end;
{$endif}
SetLastError(error);
end;
type
PROCESS_BASIC_INFORMATION = record
exitStatus : NativeUInt;
pebBaseAddress : NativeUInt;
affinityMask : NativeUInt;
basePriority : NativeUInt;
pid : NativeUInt;
parentPid : NativeUInt;
end;
PROCESS_EXTENDED_BASIC_INFORMATION = record
Size : NativeUInt;
BasicInfo : PROCESS_BASIC_INFORMATION;
Flags : dword;
end;
function GetPeb(process: THandle) : NativeUInt;
// get the process environment block ("peb")
var pbi : PROCESS_BASIC_INFORMATION;
begin
if @NtQueryInformationProcess = nil then
NtQueryInformationProcess := NtProc(CNtQueryInformationProcess);
if (@NtQueryInformationProcess <> nil) and (NtQueryInformationProcess(process, 0, @pbi, sizeOf(pbi), nil) = 0) then
result := pbi.pebBaseAddress
else result := 0;
end;
{$ifndef win64}
var NtWow64QueryInformationProcess64 : function (ph: THandle; level: dword; buf: pointer; len, retLen: dword) : dword; stdcall = nil;
function GetPeb64(process: THandle) : int64;
// get the 64bit process environment block ("peb")
var pbi : record
ExitStatus : dword;
PebBaseAddress : int64;
AffinityMask : int64;
BasePriority : dword;
ProcessId : int64;
ParentProcessId : int64;
end;
begin
if @NtWow64QueryInformationProcess64 = nil then
NtWow64QueryInformationProcess64 := NtProc(CNtWow64QueryInfoProcess64);
if (@NtWow64QueryInformationProcess64 <> nil) and (NtWow64QueryInformationProcess64(process, 0, @pbi, sizeOf(pbi), 0) = 0) then
result := pbi.PebBaseAddress
else result := 0;
end;
var NtWow64ReadVirtualMemory64 : function (ph: THandle; baseAddress: int64; buf: pointer; bufSize: int64; var read: int64) : dword; stdcall = nil;
function ReadProcessMemory64(process: THandle; baseAddress: int64; buf: pointer; bufSize: dword) : boolean;
var read64 : int64;
begin
if @NtWow64ReadVirtualMemory64 = nil then
NtWow64ReadVirtualMemory64 := NtProc(CNtWow64ReadVirtualMemory64);
result := (@NtWow64ReadVirtualMemory64 <> nil) and
(NtWow64ReadVirtualMemory64(process, baseAddress, buf, bufSize, read64) = 0) and
(read64 = bufSize);
end;
{$endif}
var CsVersion : integer = -1;
function IsProcessBlocked : boolean;
// beginning with 2k3sp1 TRtlCriticalVersion.LockCount behaves differently
function IsNewCs : boolean;
var cs : TRtlCriticalSection;
begin
if CsVersion = -1 then begin
InitializeCriticalSection(cs);
EnterCriticalSection(cs);
if cs.LockCount <> 0 then
CsVersion := 1
else
CsVersion := 0;
LeaveCriticalSection(cs);
DeleteCriticalSection(cs);
end;
result := CsVersion = 1;
end;
function GetTebIndex(index: NativeUInt) : pointer;
asm
{$ifdef win64}
.noframe
mov rax, gs:[abs $30]
mov rax, [rax + $60]
add rax, index
mov rax, [rax]
{$else}
mov ecx, fs:[$30]
add ecx, index
mov eax, [ecx]
{$endif}
end;
function IsSectionLocked(section: pointer) : boolean;
begin
result := section <> nil;
if result then
if IsNewCS then
result := PRtlCriticalSection(section).LockCount and 1 = 0
else
result := PRtlCriticalSection(section).LockCount > -1;
end;
begin
result := IsSectionLocked(GetTebIndex({$ifdef win64}$110{$else}$a0{$endif})); // loader lock -> initialization
end;
(*function IsNativeProcess(processHandle: THandle) : boolean;
var peb, subSystem, c1 : NativeUInt;
begin
peb := GetPeb(processHandle);
result := (peb <> 0) and
ReadProcessMemory(processHandle, pointer(peb + $b4), @subSystem, 4, c1) and
(subSystem = IMAGE_SUBSYSTEM_NATIVE);
end;*)
function IsProtectedProcess(process: THandle) : boolean;
var pbi : PROCESS_EXTENDED_BASIC_INFORMATION;
begin
if @NtQueryInformationProcess = nil then
NtQueryInformationProcess := NtProc(CNtQueryInformationProcess);
ZeroMemory(@pbi, sizeOf(pbi));
pbi.Size := sizeof(pbi);
if (@NtQueryInformationProcess <> nil) and (NtQueryInformationProcess(process, 0, @pbi, sizeOf(pbi), nil) = 0) then
result := pbi.Flags and $1 <> 0
else result := false;
end;
function IsMetroApp(process: THandle) : boolean;
var pbi : PROCESS_EXTENDED_BASIC_INFORMATION;
begin
if @NtQueryInformationProcess = nil then
NtQueryInformationProcess := NtProc(CNtQueryInformationProcess);
ZeroMemory(@pbi, sizeOf(pbi));
pbi.Size := sizeof(pbi);
if (@NtQueryInformationProcess <> nil) and (NtQueryInformationProcess(process, 0, @pbi, sizeOf(pbi), nil) = 0) then
result := pbi.Flags and $f0 <> 0
else result := false;
end;
function AmSystemProcess : bool; stdcall;
begin
{$ifdef log}
log('AmSystemProcess');
{$endif}
result := IsSystemProcess(GetCurrentProcess);
{$ifdef log}
log('AmSystemProcess -> ' + booleanToCharA(result));
{$endif}
end;
function GetCurrentSessionId : dword; stdcall;
begin
{$ifdef log}
log('GetCurrentSessionId');
{$endif}
result := GetProcessSessionId(GetCurrentProcessId);
{$ifdef log}
log('GetCurrentSessionId -> ' + IntToHexExA(result));
{$endif}
end;
function GetInputSessionId : dword; stdcall;
var {$ifdef CheckRecursion}
recTestBuf : array [0..3] of pointer;
{$endif}
gacsid : function : dword; stdcall;
error : dword;
begin
error := GetLastError;
{$ifdef CheckRecursion}
result := 0;
if NotRecursedYet(@GetInputSessionId, @recTestBuf) then begin
{$endif}
{$ifdef log}
log('GetInputSessionId');
{$endif}
gacsid := KernelProc(CGetInputSessionId);
if @gacsid <> nil then
result := gacsid
else result := 0;
{$ifdef log}
log('GetInputSessionId -> ' + IntToHexExA(result));
{$endif}
{$ifdef CheckRecursion}
recTestBuf[3] := nil;
end;
{$endif}
SetLastError(error);
end;
function ProcessIdToFileNameEx(processId: dword; fileNameA: PAnsiChar; fileNameW: PWideChar; bufLenInChars: dword; enumIfNecessary: boolean) : boolean;
// find out what file the specified process was executed from
const PROCESS_QUERY_LIMITED_INFORMATION = $1000;
var {$ifdef CheckRecursion}
recTestBuf : array [0..3] of pointer;
{$endif}
prcs : TDAProcess;
i1 : integer;
error : dword;
ph : THandle;
len1 : NativeUInt;
len2 : dword;
peb : NativeUInt;
peb64 : int64;
pp : NativeUInt;
ifn : TUnicodeStr;
{$ifndef win64}
pp64 : int64;
ifn64 : TUnicodeStr64;
{$endif}
ifnLen : dword;
flags : dword;
readOk : boolean;
arrChW : array [0..MAX_PATH - 1] of wideChar;
begin
result := false;
error := GetLastError;
prcs := nil;
{$ifdef CheckRecursion}
if NotRecursedYet(@ProcessIdToFileNameEx, @recTestBuf) then begin
{$endif}
if fileNameA <> nil then
fileNameW := pointer(LocalAlloc(LPTR, bufLenInChars * 2));
fileNameW[0] := '?';
fileNameW[1] := #0;
if processId <> 0 then begin
if @QueryFullProcessImageName = nil then begin
QueryFullProcessImageName := KernelProc(CQueryFullProcessImageName);
if @QueryFullProcessImageName = nil then
QueryFullProcessImageName := pointer(1);
end;
if NativeUInt(@QueryFullProcessImageName) > 1 then begin
ph := OpenProcess(PROCESS_QUERY_INFORMATION, false, processId);
if ph = 0 then
ph := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, processId);
if ph <> 0 then begin
len2 := bufLenInChars;
result := QueryFullProcessImageName(ph, 0, fileNameW, len2);
CloseHandle(ph);
end;
end else begin
ph := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, processId);
if ph <> 0 then begin
peb := GetPeb(ph);
{$ifndef win64}
if Is64bitOS then
peb64 := GetPeb64(ph)
else
{$endif}
peb64 := 0;
if (peb <> 0) or (peb64 <> 0) then begin
readOk := false;
ifnLen := 0;
if peb64 <> 0 then begin
{$ifndef win64}
if ReadProcessMemory64(ph, peb64 + $20, @ pp64, sizeOf( pp64)) and (pp64 <> 0) and
ReadProcessMemory64(ph, pp64 + $08, @flags, sizeOf(flags)) and
ReadProcessMemory64(ph, pp64 + $60, @ifn64, sizeOf(ifn64)) and (ifn64.len > 0) and (ifn64.str <> 0) then begin
if ifn64.len >= bufLenInChars * 2 then begin
ifn64.str := ifn64.str + ifn64.len - (bufLenInChars - 1) * 2;
ifn64.len := (bufLenInChars - 1) * 2;
end;
if flags and $1 = 0 then // PROCESS_PARAMETERS_NORMALIZED
ifn64.str := ifn64.str + pp64;
readOk := ReadProcessMemory64(ph, ifn64.str, fileNameW, ifn64.len);
ifnLen := ifn64.len;
end;
{$endif}
end else
{$ifdef win64}
if ReadProcessMemory(ph, pointer(peb + $20), @ pp, sizeOf( pp), len1) and (len1 = sizeOf( pp)) and (pp <> 0) and
ReadProcessMemory(ph, pointer( pp + $08), @flags, sizeOf(flags), len1) and (len1 = sizeOf(flags)) and
ReadProcessMemory(ph, pointer( pp + $60), @ ifn, sizeOf( ifn), len1) and (len1 = sizeOf( ifn)) and (ifn.len > 0) and (ifn.str <> nil) then begin
{$else}
if ReadProcessMemory(ph, pointer(peb + $10), @ pp, sizeOf( pp), len1) and (len1 = sizeOf( pp)) and (pp <> 0) and
ReadProcessMemory(ph, pointer( pp + $08), @flags, sizeOf(flags), len1) and (len1 = sizeOf(flags)) and
ReadProcessMemory(ph, pointer( pp + $38), @ ifn, sizeOf( ifn), len1) and (len1 = sizeOf( ifn)) and (ifn.len > 0) and (ifn.str <> nil) then begin
{$endif}
if ifn.len >= bufLenInChars * 2 then begin
ifn.str := pointer(NativeUInt(ifn.str) + dword(ifn.len) - (bufLenInChars - 1) * 2);
ifn.len := (bufLenInChars - 1) * 2;
end;
if flags and $1 = 0 then // PROCESS_PARAMETERS_NORMALIZED
NativeUInt(ifn.str) := NativeUInt(ifn.str) + pp;
readOk := ReadProcessMemory(ph, ifn.str, fileNameW, ifn.len, len1) and (len1 = ifn.len);
ifnLen := ifn.len;
end;
if readOk then begin
fileNameW[ifnLen div 2] := #0;
if (ifnLen > 8) and (fileNameW[0] = '\') and (fileNameW[1] = '?') and (fileNameW[2] = '?') and (fileNameW[3] = '\') then
Move(fileNameW[4], fileNameW[0], ifnLen + 2 - 4 * 2)
else
if (ifnLen > 24) and (fileNameW[0] = '\') and (fileNameW[1] = 'S') and (fileNameW[ 2] = 'y') and (fileNameW[ 3] = 's') and
(fileNameW[4] = 't') and (fileNameW[5] = 'e') and (fileNameW[ 6] = 'm') and (fileNameW[ 7] = 'R') and
(fileNameW[8] = 'o') and (fileNameW[9] = 'o') and (fileNameW[10] = 't') and (fileNameW[11] = '\') then begin
len2 := GetWindowsDirectoryW(arrChW, MAX_PATH);
if dword(ifnLen) div 2 + 1 - 11 + len2 >= bufLenInChars then begin
ifnLen := (bufLenInChars - 1 + 11 - len2) * 2;
fileNameW[ifnLen div 2] := #0;
end;
Move(fileNameW[11], fileNameW[len2], ifnLen + 2 - 11 * 2);
Move(arrChW[0], fileNameW[0], len2 * 2);
end;
result := true;
end;
end;
CloseHandle(ph);
end;
end;
end;
if (not result) and enumIfNecessary then begin
prcs := EnumProcesses;
for i1 := 0 to high(prcs) do
if prcs[i1].id = processId then begin
result := true;
if dword(Length(prcs[i1].exeFile)) >= bufLenInChars then
SetLength(prcs[i1].exeFile, bufLenInChars - 1);
lstrcpyW(fileNameW, PWideChar(prcs[i1].exeFile));
break;
end;
end;
if fileNameA <> nil then begin
if result then
WideToAnsi(fileNameW, fileNameA);
LocalFree(HLOCAL(fileNameW));
end;
{$ifdef CheckRecursion}
recTestBuf[3] := nil;
end;
{$endif}
SetLastError(error);
end;
function ProcessIdToFileNameA(processId: dword; fileName: PAnsiChar; bufLenInChars: word) : bool; stdcall;
begin
result := ProcessIdToFileNameEx(processId, fileName, nil, bufLenInChars, true);
end;
function ProcessIdToFileNameW(processId: dword; fileName: PWideChar; bufLenInChars: word) : bool; stdcall;
begin
result := ProcessIdToFileNameEx(processId, nil, fileName, bufLenInChars, true);
end;
function ProcessIdToFileName(processId: dword; var fileName: AnsiString) : boolean;
var buf : PWideChar;
begin
buf := pointer(LocalAlloc(LPTR, 64 * 1024));
result := ProcessIdToFileNameEx(processId, nil, buf, 32 * 1024, true);
if result and (buf[0] <> #0) then begin
SetLength(fileName, lstrlenW(buf));
WideToAnsi(buf, PAnsiChar(fileName));
end else
fileName := '';
LocalFree(HLOCAL(buf));
end;
function GetProcessCommandLine(processHandle: THandle; is64bitProcess: boolean) : UnicodeString;
// retrieve the command line of the specified process
var {$ifdef CheckRecursion}
recTestBuf : array [0..3] of pointer;
{$endif}
error : dword;
len1 : NativeUInt;
len2 : dword;
peb : NativeUInt;
peb64 : int64;
pp : NativeUInt;
ifn : TUnicodeStr;
{$ifndef win64}
pp64 : int64;
ifn64 : TUnicodeStr64;
{$endif}
flags : dword;
arrChW : array [0..MAX_PATH - 1] of wideChar;
begin
result := '';
error := GetLastError;
{$ifdef CheckRecursion}
if NotRecursedYet(@GetProcessCommandLine, @recTestBuf) then begin
{$endif}
if processHandle <> 0 then begin
peb := GetPeb(processHandle);
{$ifndef win64}
if Is64bitOS then
peb64 := GetPeb64(processHandle)
else
{$endif}
peb64 := 0;
if (peb <> 0) or (peb64 <> 0) then begin
if peb64 <> 0 then begin
{$ifndef win64}
if ReadProcessMemory64(processHandle, peb64 + $20, @ pp64, sizeOf( pp64)) and (pp64 <> 0) and
ReadProcessMemory64(processHandle, pp64 + $08, @flags, sizeOf(flags)) and
ReadProcessMemory64(processHandle, pp64 + $70, @ifn64, sizeOf(ifn64)) and (ifn64.len > 0) and (ifn64.str <> 0) then begin
if flags and $1 = 0 then // PROCESS_PARAMETERS_NORMALIZED
ifn64.str := ifn64.str + pp64;
SetLength(result, ifn64.len div 2);
if not ReadProcessMemory64(processHandle, ifn64.str, PWideChar(result), ifn64.len) then
result := '';
end;
{$endif}
end else
{$ifdef win64}
if ReadProcessMemory(processHandle, pointer(peb + $20), @ pp, sizeOf( pp), len1) and (len1 = sizeOf( pp)) and (pp <> 0) and
ReadProcessMemory(processHandle, pointer( pp + $08), @flags, sizeOf(flags), len1) and (len1 = sizeOf(flags)) and
ReadProcessMemory(processHandle, pointer( pp + $70), @ ifn, sizeOf( ifn), len1) and (len1 = sizeOf( ifn)) and (ifn.len > 0) and (ifn.str <> nil) then begin
{$else}
if ReadProcessMemory(processHandle, pointer(peb + $10), @ pp, sizeOf( pp), len1) and (len1 = sizeOf( pp)) and (pp <> 0) and
ReadProcessMemory(processHandle, pointer( pp + $08), @flags, sizeOf(flags), len1) and (len1 = sizeOf(flags)) and
ReadProcessMemory(processHandle, pointer( pp + $40), @ ifn, sizeOf( ifn), len1) and (len1 = sizeOf( ifn)) and (ifn.len > 0) and (ifn.str <> nil) then begin
{$endif}
if flags and $1 = 0 then // PROCESS_PARAMETERS_NORMALIZED
NativeUInt(ifn.str) := NativeUInt(ifn.str) + pp;
SetLength(result, ifn.len div 2);
if not ReadProcessMemory(processHandle, ifn.str, PWideChar(result), ifn.len, len1) or (len1 <> ifn.len) then
result := '';
end;
result := PWideChar(result);
if (Length(result) > 4) and (result[1] = '\') and (result[2] = '?') and (result[3] = '?') and (result[4] = '\') then
Delete(result, 1, 4)
else
if (Length(result) > 12) and (result[1] = '\') and (result[ 2] = 'S') and (result[ 3] = 'y') and (result[ 4] = 's') and
(result[5] = 't') and (result[ 6] = 'e') and (result[ 7] = 'm') and (result[ 8] = 'R') and
(result[9] = 'o') and (result[10] = 'o') and (result[11] = 't') and (result[12] = '\') then begin
len2 := GetWindowsDirectoryW(arrChW, MAX_PATH);
arrChW[len2] := #0;
result := arrChW + Copy(result, 12, maxInt);
end;
end;
end;
{$ifdef CheckRecursion}
recTestBuf[3] := nil;
end;
{$endif}
SetLastError(error);
end;
// ***************************************************************
const
SERVICE_KERNEL_DRIVER = $00000001;
SERVICE_SYSTEM_START = $00000001;
SERVICE_ERROR_NORMAL = $00000001;
SERVICE_CONTROL_STOP = 1;
SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED or $3f;
SERVICE_ALL_ACCESS = $01ff;
SERVICE_QUERY_STATUS = $0004;
SERVICE_STOPPED = 1;
SERVICE_RUNNING = 4;
type
TServiceStatus = record
dwServiceType : dword;
dwCurrentState : dword;
dwControlsAccepted : dword;
dwWin32ExitCode : dword;
dwServiceSpecificExitCode : dword;
dwCheckpoint : dword;
dwWaitHint : dword;
end;
TQueryServiceConfig = record
serviceType : dword;
startType : dword;
errorControl : dword;
pathName : PWideChar;
loadOrderGroup : PWideChar;
tagId : dword;
dependencies : PWideChar;
startName : PWideChar;
displayName : PWideChar;
end;
var
CloseServiceHandle : function (handle: THandle) : bool; stdcall;
ControlService : function (service: THandle; control: dword; var ss: TServiceStatus) : bool; stdcall;
QueryServiceStatus : function (service: THandle; var ss: TServiceStatus) : bool; stdcall;
DeleteService : function (service: THandle) : bool; stdcall;
OpenSCManagerW : function (machine, database: PWideChar; access: dword) : THandle; stdcall;
OpenServiceW : function (scMan: THandle; service: PWideChar; access: dword) : THandle; stdcall;
StartServiceW : function (service: THandle; argCnt: dword; args: pointer) : bool; stdcall;
CreateServiceW : function (scMan: THandle; service, display: PWideChar;
access, serviceType, startType, errorControl: dword;
pathName, loadOrderGroup: PWideChar; tagId: pointer;
dependencies, startName, password: PWideChar) : THandle; stdcall;
ChangeServiceConfigW : function (service: THandle; serviceType, startType, errorControl: dword;
pathName, loadOrderGroup: PWideChar; tagId: pointer;
dependencies, startName, password, displayName: PWideChar) : bool; stdcall;
QueryServiceConfigW : function (service: THandle; var buf: TQueryServiceConfig;
size: dword; var needed: dword) : bool; stdcall;
function InitServiceProcs : boolean;
var dll : HMODULE;
begin
result := @OpenSCManagerW <> nil;
if not result then begin
dll := LoadLibraryA(PAnsiChar(DecryptStr(CAdvApi32)));
CloseServiceHandle := GetProcAddress(dll, PAnsiChar(DecryptStr(CCloseServiceHandle)));
ControlService := GetProcAddress(dll, PAnsiChar(DecryptStr(CControlService)));
QueryServiceStatus := GetProcAddress(dll, PAnsiChar(DecryptStr(CQueryServiceStatus)));
DeleteService := GetProcAddress(dll, PAnsiChar(DecryptStr(CDeleteService)));
OpenSCManagerW := GetProcAddress(dll, PAnsiChar(DecryptStr(COpenSCManagerW)));
OpenServiceW := GetProcAddress(dll, PAnsiChar(DecryptStr(COpenServiceW)));
StartServiceW := GetProcAddress(dll, PAnsiChar(DecryptStr(CStartServiceW)));
CreateServiceW := GetProcAddress(dll, PAnsiChar(DecryptStr(CCreateServiceW)));
ChangeServiceConfigW := GetProcAddress(dll, PAnsiChar(DecryptStr(CChangeServiceConfigW)));
QueryServiceConfigW := GetProcAddress(dll, PAnsiChar(DecryptStr(CQueryServiceConfigW)));
result := @OpenSCManagerW <> nil;
end;
end;
function CheckService(driverName, fileName, description: PWideChar; start: boolean) : boolean;
var h1, h2 : THandle;
c1 : dword;
qsc : ^TQueryServiceConfig;
ss : TServiceStatus;
le : dword;
begin
result := false;
if InitServiceProcs then begin
// first we contact the service control manager
h1 := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
if h1 <> 0 then begin
// okay, that worked, now we try to open our service
h2 := OpenServiceW(h1, driverName, SERVICE_ALL_ACCESS);
if h2 <> 0 then begin
// our service is installed, let's check the parameters
c1 := 0;
QueryServiceConfigW(h2, TQueryServiceConfig(nil^), 0, c1);
if c1 <> 0 then begin
qsc := pointer(LocalAlloc(LPTR, c1 * 2));
if QueryServiceConfigW(h2, qsc^, c1 * 2, c1) then begin
if not QueryServiceStatus(h2, ss) then
ss.dwCurrentState := SERVICE_STOPPED;
// all is fine if either the parameters are already correct or:
// (1) if the service isn't running yet and
// (2) we're able to successfully reset the parameters
result := ( (qsc^.serviceType = SERVICE_KERNEL_DRIVER) and
(qsc^.startType = SERVICE_SYSTEM_START ) and
(qsc^.errorControl = SERVICE_ERROR_NORMAL ) and
(PosTextW(fileName, qsc^.pathName) > 0) ) or
( ChangeServiceConfigW(h2, SERVICE_KERNEL_DRIVER, SERVICE_SYSTEM_START, SERVICE_ERROR_NORMAL,
fileName, nil, nil, nil, nil, nil, description) and
(ss.dwCurrentState = SERVICE_STOPPED) );
if start then
result := result and ((ss.dwCurrentState = SERVICE_RUNNING) or StartServiceW(h2, 0, nil));
end;
le := GetLastError;
LocalFree(HLOCAL(qsc));
SetLastError(le);
end;
le := GetLastError;
CloseServiceHandle(h2);
SetLastError(le);
end;
le := GetLastError;
CloseServiceHandle(h1);
SetLastError(le);
end;
end;
end;
function InstallInjectionDriver(driverName, fileName32bit, fileName64bit, description: PWideChar) : bool; stdcall;
var fileName : PWideChar;
c1, c2 : THandle;
sla : AnsiString;
slw : AnsiString;
libA : PAnsiChar;
le : dword;
begin
result := false;
libA := nil;
EnableAllPrivileges;
if Is64bitOS then
fileName := fileName64bit
else
fileName := fileName32bit;
if InitServiceProcs and CheckLibFilePath(libA, fileName, sla, slw) then begin
// first we contact the service control manager
c1 := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
if c1 <> 0 then begin
// okay, that worked, now we try to open our service
c2 := OpenServiceW(c1, driverName, SERVICE_ALL_ACCESS);
if c2 <> 0 then begin
// our service is already installed, so let's check the parameters
result := CheckService(driverName, fileName, description, false);
le := GetLastError;
CloseServiceHandle(c2);
SetLastError(le);
end else begin
// probably our service is not installed yet, so we do that now
c2 := CreateServiceW(c1, driverName, description,
SERVICE_ALL_ACCESS or STANDARD_RIGHTS_ALL,
SERVICE_KERNEL_DRIVER, SERVICE_SYSTEM_START,
SERVICE_ERROR_NORMAL, fileName, nil, nil, nil, nil, nil);
if c2 <> 0 then begin
// installation went smooth
result := true;
CloseServiceHandle(c2);
end;
end;
le := GetLastError;
CloseServiceHandle(c1);
SetLastError(le);
if result then begin
result := StartInjectionDriver(driverName);
if not result then begin
le := GetLastError;
UninstallInjectionDriver(driverName);
SetLastError(le);
end;
end;
end;
end;
end;
function UninstallInjectionDriver(driverName: PWideChar) : bool; stdcall;
var c1, c2 : THandle;
le : dword;
begin
result := false;
if (driverName = nil) or (driverName[0] = #0) then
exit;
EnableAllPrivileges;
if InitServiceProcs then begin
// first we contact the service control manager
c1 := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
if c1 <> 0 then begin
// okay, that worked, now we try to open our service
c2 := OpenServiceW(c1, driverName, SERVICE_ALL_ACCESS or _DELETE);
if c2 <> 0 then begin
// our service is installed, let's try to remove it
result := DeleteService(c2);
le := GetLastError;
CloseServiceHandle(c2);
SetLastError(le);
end;
le := GetLastError;
CloseServiceHandle(c1);
SetLastError(le);
end;
end;
end;
// ***************************************************************
const
FILE_DEVICE_UNKNOWN = $22;
METHOD_BUFFERED = 0;
FILE_READ_ACCESS = 1;
FILE_WRITE_ACCESS = 2;
function CTL_CODE(devtype, func, method, acc: dword) : dword;
begin
result := devtype shl 16 + acc shl 14 + func shl 2 + method;
end;
function Ioctl_InjectDll : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $800, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
function Ioctl_UninjectDll : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $801, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
function Ioctl_AllowUnload : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $802, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
function Ioctl_InjectMethod : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $803, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
function Ioctl_EnumInject : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $804, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
function Ioctl_InjectApproval : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $805, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
function Ioctl_AddPids : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $806, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
function Ioctl_Deactivate : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $807, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
type
TDrvCmdHeader = packed record
Size : dword;
Hash : TDigest;
end;
TDrvCmdAllowUnload = packed record
Header : TDrvCmdHeader;
Allow : boolean;
end;
TDrvCmdInjectMethod = packed record
Header : TDrvCmdHeader;
Method : boolean;
end;
TDrvCmdEnumInject = packed record
Header : TDrvCmdHeader;
Index : integer;
end;
TDrvCmdDll = packed record
Header : TDrvCmdHeader;
OwnerHash : TDigest;
Session : dword;
Flags : dword;
X86AllocAddr : dword;
Dummy : array [0..9] of int64; // used by driver for temporary storage
Name : array [0..259] of WideChar;
IncludeLen : dword;
ExcludeLen : dword;
Data : array [0..0] of WideChar;
end;
TDrvCmdInjectApproval = packed record
Header : TDrvCmdHeader;
ApprovalId : dword;
Approved : boolean;
Dll : TDrvCmdDll;
end;
TDrvCmdAddPids = packed record
Header : TDrvCmdHeader;
PidCount : dword;
Pids : array [0..0] of dword;
end;
function SendDriverCommand(const driverName: UnicodeString; command: dword; var inbuf: TDrvCmdHeader; outbuf: pointer; outbufSize: dword) : boolean;
var fh : THandle;
c1 : dword;
pb1 : ^byte;
i1 : integer;
byte1 : byte;
le : dword;
buf : ^TDrvCmdHeader;
begin
fh := CreateFileW(PWideChar('\\.\' + driverName), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
if fh <> INVALID_HANDLE_VALUE then begin
buf := pointer(LocalAlloc(LPTR, inbuf.Size));
Move(inbuf, buf^, inbuf.Size);
c1 := GetCurrentThreadId;
pb1 := pointer(NativeUInt(buf) + sizeOf(TDrvCmdHeader));
for i1 := 0 to integer(buf.Size) - sizeOf(TDrvCmdHeader) - 1 do begin
pb1^ := pb1^ xor byte(c1) xor (i1 and $ff);
inc(pb1);
end;
buf.Hash := Hash(pointer(NativeUInt(buf) + sizeOf(TDrvCmdHeader)), buf.Size - sizeOf(TDrvCmdHeader));
pb1 := pointer(NativeUInt(buf) + sizeOf(TDrvCmdHeader));
for i1 := 0 to integer(buf.Size) - sizeOf(TDrvCmdHeader) - 1 do begin
byte1 := buf.Hash[i1 mod sizeOf(buf.Hash)];
pb1^ := pb1^ xor (byte1 shr 4) xor ((byte1 and $f) shl 4);
inc(pb1);
end;
result := DeviceIoControl(fh, command, buf, buf.Size, outbuf, outbufSize, c1, nil);
le := GetLastError;
CloseHandle(fh);
LocalFree(HLOCAL(buf));
SetLastError(le);
end else
result := false;
end;
procedure RegDelTree(key: HKEY; name: PWideChar);
var arrChW : PWideChar;
hk : HKEY;
c1 : dword;
begin
if RegOpenKeyExW(key, name, 0, KEY_ALL_ACCESS, hk) = 0 then begin
c1 := 0;
arrChW := pointer(LocalAlloc(LPTR, MAX_PATH * 2));
while RegEnumKeyW(hk, c1, arrChW, MAX_PATH) = 0 do begin
RegDelTree(hk, arrChW);
inc(c1);
end;
LocalFree(HLOCAL(arrChW));
RegCloseKey(hk);
end;
RegDeleteKeyW(key, name);
end;
function ConvertNtErrorCode(error: dword) : dword;
const CRtlNtStatusToDosError : AnsiString = (* RtlNtStatusToDosError *) #$07#$21#$39#$1B#$21#$06#$21#$34#$21#$20#$26#$01#$3A#$11#$3A#$26#$10#$27#$27#$3A#$27;
var ns2de : function (ntstatus: dword) : dword; stdcall;
begin
result := error;
if result and $c0000000 <> 0 then begin
ns2de := NtProc(CRtlNtStatusToDosError);
if @ns2de <> nil then
result := ns2de(result);
end;
end;
function StopInjectionDriver(driverName: PWideChar) : bool; stdcall;
var allow_ : TDrvCmdAllowUnload;
c1, c2 : THandle;
ss : TServiceStatus;
stopAllowed : boolean;
hk1, hk2 : HKEY;
arrChW2 : array [0..MAX_PATH] of WideChar;
nud : function (name: pointer) : dword; stdcall;
name : TUnicodeStr;
le : dword;
begin
result := false;
if (driverName = nil) or (driverName[0] = #0) then
exit;
EnableAllPrivileges;
allow_.Header.Size := sizeOf(allow_);
allow_.Allow := true;
stopAllowed := SendDriverCommand(driverName, Ioctl_AllowUnload, allow_.Header, nil, 0);
if InitServiceProcs then begin
c1 := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
if c1 = 0 then
c1 := OpenSCManagerW(nil, nil, 0);
if c1 <> 0 then begin
c2 := OpenServiceW(c1, PWideChar(driverName), SERVICE_ALL_ACCESS);
if c2 <> 0 then begin
ControlService(c2, SERVICE_CONTROL_STOP, ss);
result := QueryServiceStatus(c2, ss) and (ss.dwCurrentState = SERVICE_STOPPED);
CloseServiceHandle(c2);
end;
CloseServiceHandle(c1);
if (not result) and
(RegOpenKeyExA(HKEY_LOCAL_MACHINE, PAnsiChar(DecryptStr(CSystemCcsServices)), 0, KEY_ALL_ACCESS, hk1) = 0) then begin
if RegOpenKeyExW(HKEY_LOCAL_MACHINE, driverName, 0, KEY_READ, hk2) <> 0 then begin
// the service registry key for the driver is not there
// but we can still communicate with the driver
// that means that "LoadInjectionDriver" was used
// in order to unload that driver, we need to restore the registry
if RegCreateKeyExW(hk1, driverName, 0, nil, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nil, hk2, nil) = 0 then begin
c1 := 1;
if RegSetValueExA(hk2, PAnsiChar(DecryptStr(CType)), 0, REG_DWORD, @c1, 4) = 0 then begin
AnsiToWide(PAnsiChar('\' + DecryptStr(CRegistryMachine) + '\' + DecryptStr(CSystemCcsServices) + '\'), arrChW2);
lstrcatW(arrChW2, driverName);
name.str := arrChW2;
name.len := lstrlenW(name.str) * 2;
name.maxlen := name.len + 2;
nud := NtProc(CNtUnloadDriver);
if @nud <> nil then begin
SetLastError(ConvertNtErrorCode(nud(@name)));
result := GetLastError = 0;
end else
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
end;
le := GetLastError;
RegCloseKey(hk2);
RegDelTree(hk1, driverName);
SetLastError(le);
end;
end else begin
le := GetLastError;
RegCloseKey(hk2);
SetLastError(le);
end;
le := GetLastError;
RegCloseKey(hk1);
SetLastError(le);
end;
end;
end;
if (not result) and stopAllowed then begin
// stopping the driver failed, so let's secure stopping again
allow_.Allow := false;
le := GetLastError;
SendDriverCommand(driverName, Ioctl_AllowUnload, allow_.Header, nil, 0);
SetLastError(le);
end;
end;
function StartInjectionDriver(driverName: PWideChar) : bool; stdcall;
var c1, c2 : THandle;
ss : TServiceStatus;
le : dword;
begin
result := false;
if (driverName = nil) or (driverName[0] = #0) then
exit;
EnableAllPrivileges;
if InitServiceProcs then begin
c1 := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
if c1 = 0 then
c1 := OpenSCManagerW(nil, nil, 0);
if c1 <> 0 then begin
c2 := OpenServiceW(c1, PWideChar(driverName), SERVICE_ALL_ACCESS);
if c2 <> 0 then begin
result := (QueryServiceStatus(c2, ss) and (ss.dwCurrentState = SERVICE_RUNNING)) or
StartServiceW(c2, 0, nil);
le := GetLastError;
CloseServiceHandle(c2);
SetLastError(le);
end;
le := GetLastError;
CloseServiceHandle(c1);
SetLastError(le);
end;
end;
end;
function IsInjectionDriverInstalled(driverName: PWideChar) : bool; stdcall;
var c1, c2 : THandle;
le : dword;
begin
result := false;
if (driverName = nil) or (driverName[0] = #0) then
exit;
EnableAllPrivileges;
if InitServiceProcs then begin
c1 := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
if c1 = 0 then
c1 := OpenSCManagerW(nil, nil, 0);
if c1 <> 0 then begin
c2 := OpenServiceW(c1, PWideChar(driverName), SERVICE_QUERY_STATUS);
if c2 <> 0 then begin
result := true;
CloseServiceHandle(c2);
end;
le := GetLastError;
CloseServiceHandle(c1);
SetLastError(le);
end;
end;
end;
function IsInjectionDriverRunning(driverName: PWideChar) : bool; stdcall;
var fh : THandle;
begin
if (driverName = nil) or (driverName[0] = #0) then begin
result := false;
exit;
end;
EnableAllPrivileges;
fh := CreateFileW(PWideChar('\\.\' + UnicodeString(driverName)), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
result := fh <> INVALID_HANDLE_VALUE;
if result then
CloseHandle(fh);
end;
type
// types for the inject approval callback logic
TInjectApprovalCallbackItem = record
DriverName : UnicodeString;
Callback : TInjectApprovalCallbackRoutine;
CallbackContext : pointer;
Overl : OVERLAPPED;
AbortEvent : THandle;
Dll : TDrvCmdDll; // this must be last in the structure, because the exact size varies
end;
PInjectApprovalCallbackItem = ^TInjectApprovalCallbackItem;
TInjectApprovalCallbackList = array of PInjectApprovalCallbackItem;
var
// global vars for the inject approval callback logic
InjectApprovalCallbackReady : boolean = false;
InjectApprovalCallbackSection : TRTLCriticalSection;
InjectApprovalCallbackEmptyEvent : THandle = 0;
InjectApprovalCallbackList : TInjectApprovalCallbackList = nil;
InjectApprovalCallbackCount : integer = 0;
function InjectApprovalCallbackThread(info: PInjectApprovalCallbackItem) : dword; stdcall;
// every injection approval callback runs in its own thread
type
TInjectApprovalDriverMsg = packed record
TargetProcessId : dword;
ParentProcessId : dword;
SessionId : dword;
Flags : dword;
ApprovalId : dword;
end;
var injAppr : ^TDrvCmdInjectApproval;
fh : THandle;
c1 : dword;
pb1 : ^byte;
i1 : integer;
byte1 : byte;
outBuf : TInjectApprovalDriverMsg;
appr : boolean;
ph : THandle;
ws1, ws2 : UnicodeString;
wait : array [0..1] of THandle;
emptyEvent : THandle;
begin
injAppr := pointer(LocalAlloc(LPTR, info.Dll.Header.Size - sizeOf(TDrvCmdDll) + sizeOf(TDrvCmdInjectApproval)));
if injAppr <> nil then begin
// We send a DeviceIoControl message down to the driver.
// The driver will keep it "pending" until it wants to notify us about something.
// In order to allow us to cancel the whole thing, we use asynchronous communication.
fh := CreateFileW(PWideChar('\\.\' + info.DriverName), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
if fh <> INVALID_HANDLE_VALUE then begin
ZeroMemory(@outBuf, sizeOf(outBuf));
ZeroMemory(@info.Overl, sizeOf(info.Overl));
info.Overl.hEvent := CreateEvent(nil, true, false, nil);
// we will wait until either the driver replies, or until we want to cancel
wait[0] := info.Overl.hEvent;
wait[1] := info.AbortEvent;
appr := true;
// We do the whole thing in a loop, until we cancel, or until the driver reports an error.
// The driver will report an error if it's unloaded, or if the injection request was stopped.
// So we don't need to manually cleanup when unloading the driver or stopping an injection request!
while true do begin
injAppr.Header.Size := info.Dll.Header.Size - sizeOf(TDrvCmdDll) + sizeOf(TDrvCmdInjectApproval);
injAppr.ApprovalId := outBuf.ApprovalId;
injAppr.Approved := appr;
Move(info.Dll, injAppr.Dll, info.Dll.Header.Size);
c1 := GetCurrentThreadId;
pb1 := pointer(NativeUInt(injAppr) + sizeOf(TDrvCmdHeader));
for i1 := 0 to integer(injAppr.Header.Size) - sizeOf(TDrvCmdHeader) - 1 do begin
pb1^ := pb1^ xor byte(c1) xor (i1 and $ff);
inc(pb1);
end;
injAppr.Header.Hash := Hash(pointer(NativeUInt(injAppr) + sizeOf(TDrvCmdHeader)), injAppr.Header.Size - sizeOf(TDrvCmdHeader));
pb1 := pointer(NativeUInt(injAppr) + sizeOf(TDrvCmdHeader));
for i1 := 0 to integer(injAppr.Header.Size) - sizeOf(TDrvCmdHeader) - 1 do begin
byte1 := injAppr.Header.Hash[i1 mod sizeOf(injAppr.Header.Hash)];
pb1^ := pb1^ xor (byte1 shr 4) xor ((byte1 and $f) shl 4);
inc(pb1);
end;
// DeviceIoControl is supposed to fail with ERROR_IO_PENDING, because we're using asynchronous communication mode
if (not DeviceIoControl(fh, Ioctl_InjectApproval, injAppr, injAppr.Header.Size, @outBuf, sizeof(outBuf), c1, @info.Overl)) and (GetLastError <> ERROR_IO_PENDING) then
break;
// WAIT_OBJECT_0 means the driver sent us a message, everything else means we cancel
if WaitForMultipleObjects(2, @wait, false, INFINITE) <> WAIT_OBJECT_0 then
break;
// Now we fetch the driver information and call the registered callback.
// The next DeviceIoControl call in the loop will inform the driver about the callback's result value.
if GetOverlappedResult(fh, info.Overl, c1, false) then begin
SetLength(ws1, 32 * 1024);
ProcessIdToFileNameW(outBuf.TargetProcessId, PWideChar(ws1), 32 * 1024);
ws1 := PWideChar(ws1);
ph := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, outBuf.TargetProcessId);
if ph <> 0 then begin
if IsElevatedProcess(ph) then
outBuf.Flags := outBuf.Flags or TARGET_PROCESS_IS_ELEVATED;
ws2 := GetProcessCommandLine(ph, outBuf.Flags and TARGET_PROCESS_IS_64BIT <> 0);
CloseHandle(ph);
end;
appr := info.Callback(info.CallbackContext, outBuf.TargetProcessId, outBuf.ParentProcessId, outBuf.SessionId, outBuf.Flags, PWideChar(ws1), PWideChar(ws2));
end;
ResetEvent(info.Overl.hEvent);
end;
// we want to stop, for one reason or another, so we cancel all open async communication and cleanup
CancelIo(fh);
CloseHandle(info.Overl.hEvent);
CloseHandle(fh);
end;
LocalFree(HLOCAL(injAppr));
end;
CloseHandle(info.AbortEvent);
emptyEvent := 0;
// now let's remove ourselves from the global list
EnterCriticalSection(InjectApprovalCallbackSection);
try
for i1 := 0 to InjectApprovalCallbackCount - 1 do
if InjectApprovalCallbackList[i1] = info then begin
InjectApprovalCallbackList[i1] := InjectApprovalCallbackList[InjectApprovalCallbackCount - 1];
InjectApprovalCallbackList[InjectApprovalCallbackCount - 1] := nil;
dec(InjectApprovalCallbackCount);
end;
if InjectApprovalCallbackCount = 0 then
// we were the last active callback, so let's set the "empty" event
emptyEvent := InjectApprovalCallbackEmptyEvent;
finally
LeaveCriticalSection(InjectApprovalCallbackSection);
end;
// finally cleanup the rest and exit
info.DriverName := '';
LocalFree(HLOCAL(info));
if emptyEvent <> 0 then
SetEvent(emptyEvent);
result := 0;
end;
function CancelAllInjectApprovalCallbacks : boolean;
// This cleans up all inject approval callback.
// This gets called when madCodeHook is finalized.
var i1 : integer;
begin
result := false;
// First of all we tell all inject approval callback threads to shut down.
EnterCriticalSection(InjectApprovalCallbackSection);
try
for i1 := 0 to InjectApprovalCallbackCount - 1 do begin
result := true;
SetEvent(InjectApprovalCallbackList[i1].AbortEvent);
end;
finally
LeaveCriticalSection(InjectApprovalCallbackSection);
end;
// Then we wait until the list of active inject approval callbacks it empty.
WaitForSingleObject(InjectApprovalCallbackEmptyEvent, 5000);
end;
function ReportInjectedPidsToDriver(driverName: PWideChar; pids: array of dword; dllFileName: PWideChar; session: dword; options: dword; includeMask, excludeMask: PWideChar) : boolean;
var addPids : ^TDrvCmdAddPids;
dll : ^TDrvCmdDll;
size : dword;
le : dword;
begin
result := false;
size := sizeOf(addPids^) - sizeOf(dword) + Length(pids) * sizeOf(dword) + sizeof(dll^) + lstrlenW(includeMask) * 2 + 2 + lstrlenW(excludeMask) * 2 + 2;
addPids := pointer(LocalAlloc(LPTR, size));
if addPids <> nil then begin
addPids.Header.Size := size;
addPids.PidCount := Length(pids);
Move(pids[0], addPids.Pids[0], Length(pids) * sizeOf(dword));
dll := pointer(NativeUInt(addPids) + sizeOf(addPids^) - sizeOf(dword) + dword(Length(pids)) * sizeOf(dword));
dll.Header.Size := sizeOf(dll^) + lstrlenW(includeMask) * 2 + 2 + lstrlenW(excludeMask) * 2 + 2;
dll.Session := session;
dll.Flags := options;
if not DisableParallelDllLoading then
dll.Flags := dll.Flags or INJECT_GLOBAL_ENABLE_PARALLEL_DLL_LOADING;
if UseIatDllInjection then
dll.Flags := dll.Flags or INJECT_GLOBAL_USE_IAT_DLL_INJECTION;
dll.X86AllocAddr := dword(X86AllocationAddress) + $10000;
lstrcpyW(dll.Name, dllFileName);
dll.IncludeLen := lstrlenW(includeMask);
dll.ExcludeLen := lstrlenW(excludeMask);
if includeMask <> nil then
lstrcpyW(dll.Data, includeMask);
if excludeMask <> nil then
lstrcpyW(pointer(NativeUInt(@dll.Data) + dll.IncludeLen * 2 + 2), excludeMask);
result := SendDriverCommand(driverName, Ioctl_AddPids, addPids.Header, nil, 0);
le := GetLastError;
LocalFree(HLOCAL(addPids));
SetLastError(le);
end;
end;
function JectDll(driverName: PWideChar; inject: boolean; dllFileName: PWideChar; session: dword; options: dword; includeMask, excludeMask: PWideChar; callback: TInjectApprovalCallbackRoutine; callbackContext: pointer; pidsValid: TPBoolean; pids: TPDACardinal) : boolean;
function CompareMem(buf1, buf2: pointer; len: integer) : boolean;
begin
result := true;
while len >= 4 do begin
if dword(buf1^) <> dword(buf2^) then begin
result := false;
exit;
end;
inc(NativeUInt(buf1), 4);
inc(NativeUInt(buf2), 4);
dec(len, 4);
end;
while len > 0 do begin
if byte(buf1^) <> byte(buf2^) then begin
result := false;
exit;
end;
inc(NativeUInt(buf1), 1);
inc(NativeUInt(buf2), 1);
dec(len, 1);
end;
end;
function GetFileHash(fileName: PWideChar; var fileHash: TDigest; maxFileSize: dword) : boolean;
// open a file and calculate a hash of the file contents
// MaxFileSize: can be used to limit the hash to a part of the file, only
var fh, map : THandle;
buf : pointer;
size : dword;
begin
result := false;
fh := CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
if fh <> INVALID_HANDLE_VALUE then begin
map := CreateFileMappingA(fh, nil, PAGE_READONLY, 0, 0, nil);
if map <> 0 then begin
buf := MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
if buf <> nil then begin
size := GetFileSize(fh, nil);
if (maxFileSize <> 0) and (maxFileSize < size) then
size := maxFileSize;
fileHash := Hash(buf, size);
result := true;
UnmapViewOfFile(buf);
end;
CloseHandle(map);
end;
CloseHandle(fh);
end;
end;
var dll : ^TDrvCmdDll;
size : dword;
sla : AnsiString;
slw : AnsiString;
libA : PAnsiChar;
le : dword;
buf : ^TDrvCmdDll;
found : boolean;
hk1, hk2 : HKEY;
c1, c2 : dword;
ok : boolean;
name1, name2 : AnsiString;
arrChW : array [0..MAX_PATH] of WideChar;
info : PInjectApprovalCallbackItem;
th : THandle;
tid : dword;
pidCount : integer;
begin
result := false;
libA := nil;
if not CheckLibFilePath(libA, dllFileName, sla, slw) then
exit;
EnableAllPrivileges;
size := sizeOf(dll^) + lstrlenW(includeMask) * 2 + 2 + lstrlenW(excludeMask) * 2 + 2;
dll := pointer(LocalAlloc(LPTR, size));
if dll <> nil then begin
if session = CURRENT_SESSION then
session := GetCurrentSessionId;
dll.Header.Size := size;
dll.Session := session;
dll.Flags := options;
if not DisableParallelDllLoading then
dll.Flags := dll.Flags or INJECT_GLOBAL_ENABLE_PARALLEL_DLL_LOADING;
if UseIatDllInjection then
dll.Flags := dll.Flags or INJECT_GLOBAL_USE_IAT_DLL_INJECTION;
dll.X86AllocAddr := dword(X86AllocationAddress) + $10000;
lstrcpyW(dll.Name, dllFileName);
dll.IncludeLen := lstrlenW(includeMask);
dll.ExcludeLen := lstrlenW(excludeMask);
if includeMask <> nil then
lstrcpyW(dll.Data, includeMask);
if excludeMask <> nil then
lstrcpyW(pointer(NativeUInt(@dll.Data) + dll.IncludeLen * 2 + 2), excludeMask);
pidCount := 0;
if not inject then begin
pids^ := nil;
pidsValid^ := false;
if SendDriverCommand(driverName, Ioctl_Deactivate, dll.Header, @pidCount, sizeOf(pidCount)) then begin
pidsValid^ := true;
SetLength(pids^, pidCount);
end else
pidCount := 0;
end;
if options and INJECT_PERMANENTLY <> 0 then begin
// permanent injection requests are stored in the registry, so they require the driver to be installed (not loaded)
if RegOpenKeyExA(HKEY_LOCAL_MACHINE, PAnsiChar(DecryptStr(CSystemCcsServices)), 0, KEY_ALL_ACCESS, hk1) = 0 then begin
if RegOpenKeyExW(hk1, driverName, 0, KEY_ALL_ACCESS, hk2) = 0 then begin
// we were able to open the registry key, which means 1) we have enough privileges and 2) the driver is installed
if inject then
result := SendDriverCommand(driverName, Ioctl_InjectDll, dll.Header, nil, 0)
else
result := SendDriverCommand(driverName, Ioctl_UninjectDll, dll.Header, pointer(pids^), pidCount * sizeOf(dword));
if result or (not inject) then
for c1 := 1 to 999 do begin
// let's loop through all 999 possible permanent dll injection requests
name1 := 'DLL ' + IntToStrExA(c1, 3);
if RegQueryValueExA(hk2, PAnsiChar(name1), nil, nil, nil, @size) = 0 then begin
// a permanent injection request with the number "c1" exists
if size = dll.Header.Size then begin
// and the size matches, so we check if the content also matches
buf := pointer(LocalAlloc(LPTR, size));
found := RegQueryValueExA(hk2, PAnsiChar(name1), nil, nil, pointer(buf), @size) = 0;
if found then begin
buf.OwnerHash := dll.OwnerHash;
found := CompareMem(buf, dll, dll.Header.Size);
end;
LocalFree(HLOCAL(buf));
if found then begin
// The content also matches, so we found an exact match.
// uninjection: This is what we were looking for, let's delete it.
if (not inject) and (RegDeleteValueA(hk2, PAnsiChar(name1)) = 0) then
// now let's decrement the numbers of the following permanent injection requests
for c2 := c1 + 1 to 999 do begin
name2 := 'DLL ' + IntToStrExA(c2, 3);
if RegQueryValueExA(hk2, PAnsiChar(name2), nil, nil, nil, @size) <> 0 then
break;
buf := pointer(LocalAlloc(LPTR, size));
ok := (RegQueryValueExA(hk2, PAnsiChar(name2), nil, nil, pointer(buf), @size) = 0) and
(RegSetValueExA(hk2, PAnsiChar(name1), 0, REG_BINARY, buf, size) = 0) and
(RegDeleteValueA(hk2, PAnsiChar(name2)) = 0);
LocalFree(HLOCAL(buf));
if not ok then
break;
name1 := name2;
end;
break;
end;
end;
end else begin
// there's no injection request with the number "c1"
if inject then begin
GetModuleFileNameW(0, arrChW, MAX_PATH);
if GetFileHash(arrChW, dll.OwnerHash, 1 * 1024 * 1024) then
// injection: so let's store the new request under this number
RegSetValueExA(hk2, PAnsiChar(name1), 0, REG_BINARY, dll, dll.Header.Size);
end;
break;
end;
end;
RegCloseKey(hk2);
end;
RegCloseKey(hk1);
end;
end else
if inject then
result := SendDriverCommand(driverName, Ioctl_InjectDll, dll.Header, nil, 0)
else
result := SendDriverCommand(driverName, Ioctl_UninjectDll, dll.Header, pointer(pids^), pidCount * sizeOf(dword));
le := GetLastError;
if @callback <> nil then begin
// The caller wants to register a callback for injection approval.
// We create a private thread for that purpose.
info := pointer(LocalAlloc(LPTR, sizeof(TInjectApprovalCallbackItem) + dll.Header.Size - sizeof(TDrvCmdDll)));
if info <> nil then begin
Move(dll^, info.Dll, dll.Header.Size);
th := CreateThread(nil, 0, @InjectApprovalCallbackThread, info, CREATE_SUSPENDED, tid);
if th <> 0 then begin
// Creating the thread succeeded.
// Now we store information about this callback into the global list.
info.DriverName := driverName;
info.Callback := callback;
info.CallbackContext := callbackContext;
info.AbortEvent := CreateEvent(nil, true, false, nil);
if not InjectApprovalCallbackReady then begin
InjectApprovalCallbackReady := true;
InitializeCriticalSection(InjectApprovalCallbackSection);
InjectApprovalCallbackEmptyEvent := CreateEvent(nil, true, false, nil);
end;
EnterCriticalSection(InjectApprovalCallbackSection);
try
if InjectApprovalCallbackCount = Length(InjectApprovalCallbackList) then
SetLength(InjectApprovalCallbackList, InjectApprovalCallbackCount + 16);
InjectApprovalCallbackList[InjectApprovalCallbackCount] := info;
inc(InjectApprovalCallbackCount);
ResetEvent(InjectApprovalCallbackEmptyEvent);
finally
LeaveCriticalSection(InjectApprovalCallbackSection);
end;
// finally resume the injection approval callback thread
ResumeThread(th);
CloseHandle(th);
end else begin
// creating the thread failed, for unknown reasons
LocalFree(HLOCAL(info));
end;
end;
end;
LocalFree(HLOCAL(dll));
SetLastError(le);
end;
end;
function StartDllInjection(driverName, dllFileName: PWideChar; session: dword; options: dword; includeMask, excludeMask: PWideChar; callback: TInjectApprovalCallbackRoutine; callbackContext: pointer) : bool; stdcall;
begin
if (driverName = nil) or (driverName[0] = #0) then begin
result := false;
exit;
end;
result := JectDll(driverName, true, dllFileName, session, options, includeMask, excludeMask, callback, callbackContext, nil, nil);
end;
function StopDllInjection(driverName, dllFileName: PWideChar; session: dword; options: dword; includeMask, excludeMask: PWideChar; out pidsValid: boolean; out pids: TDACardinal) : bool; stdcall;
begin
if (driverName = nil) or (driverName[0] = #0) then begin
result := false;
exit;
end;
result := JectDll(driverName, false, dllFileName, session, options, includeMask, excludeMask, nil, nil, @pidsValid, @pids);
end;
function LoadInjectionDriver(driverName, fileName32bit, fileName64bit: PWideChar) : bool; stdcall;
var fileName : PWideChar;
c1, c2, c3 : dword;
arrChW2 : array [0..MAX_PATH] of WideChar;
hk1, hk2 : HKEY;
nld : function (name: pointer) : dword; stdcall;
wImagePath : array [0..9] of WideChar;
name : TUnicodeStr;
sla : AnsiString;
slw : AnsiString;
libA : PAnsiChar;
le : dword;
begin
result := false;
if (driverName = nil) or (driverName[0] = #0) then
exit;
libA := nil;
if Is64bitOS then
fileName := fileName64bit
else
fileName := fileName32bit;
if not CheckLibFilePath(libA, fileName, sla, slw) then
exit;
EnableAllPrivileges;
arrChW2[0] := '\';
arrChW2[1] := '?';
arrChW2[2] := '?';
arrChW2[3] := '\';
arrChW2[4] := #0;
lstrcatW(arrChW2, fileName);
if RegOpenKeyExA(HKEY_LOCAL_MACHINE, PAnsiChar(DecryptStr(CSystemCcsServices)), 0, KEY_ALL_ACCESS, hk1) = 0 then begin
if RegCreateKeyExW(hk1, driverName, 0, nil, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nil, hk2, nil) = 0 then begin
c1 := 1;
c2 := 0;
c3 := 4;
AnsiToWide(PAnsiChar(DecryptStr(CImagePath)), wImagePath);
if (RegSetValueExA(hk2, PAnsiChar(DecryptStr(CType )), 0, REG_DWORD, @c1, 4) = 0) and
(RegSetValueExA(hk2, PAnsiChar(DecryptStr(CErrorControl)), 0, REG_DWORD, @c2, 4) = 0) and
(RegSetValueExA(hk2, PAnsiChar(DecryptStr(CStart )), 0, REG_DWORD, @c3, 4) = 0) and
(RegSetValueExW(hk2, wImagePath, 0, REG_SZ, @arrChW2, lstrlenW(arrChW2) * 2 + 2) = 0) then begin
AnsiToWide(PAnsiChar('\' + DecryptStr(CRegistryMachine) + '\' + DecryptStr(CSystemCcsServices) + '\'), arrChW2);
lstrcatW(arrChW2, driverName);
name.str := arrChW2;
name.len := lstrlenW(name.str) * 2;
name.maxlen := name.len + 2;
nld := NtProc(CNtLoadDriver);
if @nld <> nil then begin
SetLastError(ConvertNtErrorCode(nld(@name)));
result := GetLastError = 0;
end else
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
end;
le := GetLastError;
RegCloseKey(hk2);
SetLastError(le);
end;
le := GetLastError;
RegDelTree(hk1, driverName);
RegCloseKey(hk1);
SetLastError(le);
end;
end;
type TDAInjectionRequest = array of ^TDrvCmdDll;
function EnumInjectionRequests(driverName: PWideChar) : TDAInjectionRequest;
var enum : TDrvCmdEnumInject;
dll : ^TDrvCmdDll;
index : integer;
begin
result := nil;
enum.Header.Size := sizeOf(TDrvCmdEnumInject);
dll := VirtualAlloc(nil, 2 * 1024 * 1024, MEM_COMMIT, PAGE_READWRITE);
if dll <> nil then begin
index := 0;
enum.Index := index;
while SendDriverCommand(driverName, Ioctl_EnumInject, enum.Header, dll, 2 * 1024 * 1024) do begin
SetLength(result, Length(result) + 1);
result[high(result)] := pointer(LocalAlloc(LPTR, dll.Header.Size));
Move(dll^, result[high(result)]^, dll.Header.Size);
inc(index);
enum.Index := index;
end;
VirtualFree(dll, 0, MEM_RELEASE);
end;
end;
// ***************************************************************
function SetMadCHookOption(option: dword; param: PWideChar) : bool; stdcall;
begin
result := true;
case option of
DISABLE_CHANGED_CODE_CHECK : DisableChangedCodeCheck := true;
USE_OLD_IPC_LOGIC : {$ifndef win64}
if not IsVista then
UseNewIpcLogic := false
{$endif};
SET_INTERNAL_IPC_TIMEOUT : InternalIpcTimeout := dword(param);
VMWARE_INJECTION_MODE : VmWareInjectionMode := true;
DONT_TOUCH_RUNNING_PROCESSES : InjectIntoRunningProcesses := false;
RENEW_OVERWRITTEN_HOOKS : RenewOverwrittenHooks := true;
INJECT_INTO_RUNNING_PROCESSES : InjectIntoRunningProcesses := param <> nil;
UNINJECT_FROM_RUNNING_PROCESSES : UninjectFromRunningProcesses := param <> nil;
X86_ALLOCATION_ADDRESS : if NativeUInt(param) < $80000000 then begin
X86AllocationAddress := pointer(NativeUInt(param) and $ffff0000);
madRemote.X86AllocationAddress := X86AllocationAddress;
end else
result := false;
VERIFY_HOOK_ADDRESS : VerifyHookAddress := param <> nil;
DISASM_HOOK_TARGET : DisAsmHookTarget := param <> nil;
LIMITED_IPC_PORT : LimitedIpcPort := param <> nil;
HOOK_LOAD_LIBRARY : HookLoadLibraryOption := param <> nil;
DISABLE_LDR_LOAD_DLL_SPECIAL_HOOK : DisableLdrLoadDllSpecialHook := param <> nil;
DISABLE_PARALLEL_DLL_LOADING : DisableParallelDllLoading := param <> nil;
USE_IAT_DLL_INJECTION : UseIatDllInjection := param <> nil;
DISABLE_IPC_FIX_1 : DisableIpcFix1 := param <> nil;
DISABLE_IPC_FIX_2 : DisableIpcFix2 := param <> nil;
SET_SAFE_HOOKING_TIMEOUT : SafeHookingTimeout := dword(param);
ALWAYS_CHECK_HOOKS : AlwaysCheckHooks := param <> nil;
else result := false;
end;
end;
// ***************************************************************
(* static injection works fine, but unfortunately uninjecting is not possible
function GetFirstExportIndex(libA: PAnsiChar; libW: PWideChar) : integer;
function VirtualToRaw(nh: PImageNtHeaders; addr: dword) : dword;
type TAImageSectionHeader = packed array [0..maxInt shr 6] of TImageSectionHeader;
var i1 : integer;
sh : ^TAImageSectionHeader;
begin
result := addr;
NativeUInt(sh) := NativeUInt(nh) + sizeOf(nh^);
for i1 := 0 to nh^.FileHeader.NumberOfSections - 1 do
if (addr >= sh[i1].VirtualAddress) and
((i1 = nh^.FileHeader.NumberOfSections - 1) or (addr < sh[i1 + 1].VirtualAddress)) then begin
result := addr - sh[i1].VirtualAddress + sh[i1].PointerToRawData;
break;
end;
end;
var fh : THandle;
map : THandle;
module : HMODULE;
nh : PImageNtHeaders;
ed : PImageExportDirectory;
i1 : integer;
begin
result := -1;
if libW <> nil then
fh := CreateFileW(libW, GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0)
else fh := CreateFileA(libA, GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
if fh <> INVALID_HANDLE_VALUE then begin
map := CreateFileMapping(fh, @UnlimitedSa, PAGE_READONLY, 0, 0, nil);
if map <> 0 then begin
module := HMODULE(MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0));
if module <> 0 then begin
nh := GetImageNtHeaders(module);
if nh <> nil then
try
NativeUInt(ed) := module + VirtualToRaw(nh, nh^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
with ed^ do
for i1 := 0 to NumberOfFunctions - 1 do
if TPACardinal(module + VirtualToRaw(nh, AddressOfFunctions))^[i1] <> 0 then begin
result := integer(Base) + i1;
break;
end;
except end;
UnmapViewOfFile(pointer(module));
end;
CloseHandle(map);
end;
CloseHandle(fh);
end;
end;
function InjectLibrariesStatic(const libs: array of AnsiString; const exp: array of dword) : integer;
const
InjectMagic = $77712345abcde777; // magic number for our buffer
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = $b; // data directory of new bind logic
type
TAImageDataDirectory = packed array [0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES - 1] of TImageDataDirectory;
TImageImportDirectory = packed record
HintNameArray : dword;
TimeDateStamp : dword;
ForwarderChain : dword;
Name_ : dword;
ThunkArray : dword;
end;
PImageImportDirectory = ^TImageImportDirectory;
TAImageImportDirectory = packed array [0..maxInt shr 5] of TImageImportDirectory;
TBufHeader = packed record
magic : int64;
dataCount : integer;
dataStart : dword;
dataSize : dword;
mainCount : integer;
mainStart : dword;
mainSize : dword;
end;
function CountImportDirectories(id: PImageImportDirectory) : integer;
var buf : ^TAImageImportDirectory;
i1, i2 : integer;
begin
result := 0;
GetMem(buf, sizeOf(TImageImportDirectory) * 64);
i1 := 64;
while true do begin
while (not Read(id, buf^, i1 * sizeOf(TImageImportDirectory))) and (i1 > 0) do
i1 := i1 shr 2;
for i2 := 0 to i1 - 1 do begin
with buf^[i2] do
if (HintNameArray = 0) and (TimeDateStamp = 0) and
(ForwarderChain = 0) and (Name_ = 0) and (ThunkArray = 0) then
exit;
inc(result);
end;
inc(id, i1);
end;
FreeMem(buf);
end;
var module : HMODULE; // remote exe's module handle
dd : ^TAImageDataDirectory; // remote exe's ^data directories
nh : TImageNtHeaders; // remote exe's nt image headers
import : TImageDataDirectory; // remote exe's import directory
oldhead : TBufHeader; // head of the old remote buffer
newhead : TBufHeader; // head of our new remote buffer
canFree : boolean; // can we free the remote buffer without danger?
prepBuf : NativeUInt; // our local preparation buffer
c1 : NativeUInt; // multi purpose NativeUInt
i1 : integer; // multi purpose integer
i64 : int64; // multi purpose int64
begin
// this kind of patch works only on not yet initialized processes
if NotInitializedYet then begin
// can we locate the main module?
if GetExeModuleInfos(module, nh, pointer(dd)) then begin
// we found the main module
import := nh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
// fill IAT directory, if necessary
with nh.OptionalHeader do
if int64(DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT]) = 0 then
Write(@dd^[IMAGE_DIRECTORY_ENTRY_IAT], DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT], 8);
// did we already patch this process?
if (not Read(pointer(module + import.VirtualAddress - sizeOf(oldHead)), oldHead, sizeOf(oldHead))) or
(oldHead.magic <> InjectMagic) then begin
// nope, so fill the old buffer infos manually
ZeroMemory(@oldHead, sizeOf(oldHead));
oldHead.dataStart := module + import.VirtualAddress;
oldHead.mainStart := module + import.VirtualAddress;
oldHead.mainCount := CountImportDirectories(pointer(oldHead.mainStart));
oldHead.mainSize := (oldHead.mainCount + 1) * sizeOf(TImageImportDirectory);
end;
canFree := true;
// fill new header
newHead.magic := InjectMagic;
newHead.dataCount := oldHead.dataCount + Length(libs);
newHead.dataSize := newHead.dataCount * (8 + MAX_PATH);
newHead.mainCount := oldHead.mainCount + Length(libs);
newHead.mainSize := (newHead.mainCount + 1) * sizeOf(TImageImportDirectory);
newHead.dataStart := dword(AllocMemEx(newHead.dataSize + sizeOf(newHead) + newHead.mainSize, process));
newHead.mainStart := newHead.dataStart + newHead.dataSize + sizeOf(newHead);
// use local preparation buffer to fill the remote buffer
prepBuf := LocalAlloc(LPTR, newHead.dataSize + sizeOf(newHead) + newHead.mainSize);
// fill the preparation buffer with the old remote buffer or
// with the original import data
if Read(pointer(oldHead.dataStart), pointer(prepBuf + newHead.dataSize - oldHead.dataSize)^, oldHead.dataSize) and
Read(pointer(oldHead.mainStart), pointer(prepBuf + newHead.dataSize + sizeOf(newHead) + newHead.mainSize - oldHead.mainSize)^, oldHead.mainSize) then begin
// fill the new head into the preparation buffer
TBufHeader(pointer(prepBuf + newHead.dataSize)^) := newHead;
// now we do the rest of the preparation
for i1 := 0 to newHead.dataCount - 1 do begin
with TAImageImportDirectory(pointer(prepBuf + newHead.dataSize + sizeOf(newHead))^)[i1] do begin
// we adjust the auxiliary data for the old dlls and fill in the new
HintNameArray := 0;
TimeDateStamp := 0;
ForwarderChain := maxCard;
ThunkArray := newHead.dataStart + dword(i1) * (8 + MAX_PATH) - module;
Name_ := ThunkArray + 8;
end;
if i1 < Length(libs) then begin
// the new dlls get a dll path and a 1 element dummy import thunk
c1 := prepBuf + dword(i1) * (8 + MAX_PATH);
TPInt64(c1)^ := $80000000 + exp[i1];
lstrcpyA(PAnsiChar(libs[i1]), PAnsiChar(c1 + 8));
end;
end;
// the preparation buffer is done, now we copy it to the remote buffer
if Write(pointer(newHead.dataStart), pointer(prepBuf)^, newHead.dataSize + sizeOf(newHead) + newHead.mainSize, false) then begin
// hopefully the process is still not initialized yet?
if NotInitializedYet then begin
// let's do the real modification, namely patching the data directories
import.VirtualAddress := newHead.mainStart - module;
import.Size := newHead.mainSize;
if Write(@dd^[IMAGE_DIRECTORY_ENTRY_IMPORT], import, 8) then begin
// we want to convert the new bind method into the old one
// for this purpose we now kill IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT
i64 := 0;
if (int64(nh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]) = 0) or
Write(@dd^[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT], i64, 8) then begin
if NotInitializedYet then begin
// injection fully succeeded
result := 2;
if oldHead.dataCount > 0 then
// free the old remote buffer (if it exists)
FreeMemEx(pointer(oldHead.dataStart), process);
end else result := 1;
end else result := 0;
canFree := false;
end else result := 0;
end else result := 1;
end else result := 0;
end else result := 0;
// we can kill our preparation buffer
LocalFree(prepBuf);
if (result < 2) and canFree then
// injection failed, so we free the remote buffer (if allowed)
FreeMemEx(pointer(newHead.dataStart), process);
end else result := 0;
end else result := 1;
end;
*)
// ***************************************************************
procedure AutoUnhook2(dll: HMODULE; wait: boolean);
var anh : array of TPPointer;
i1, i2 : integer;
begin
{$ifdef log}
log('AutoUnhook (module: ' + IntToHexExA(dll) + '; wait: ' + booleanToCharA(wait) + ')');
{$endif}
if (dll <> 0) and HookReady then begin
EnterCriticalSection(HookSection);
try
SetLength(anh, Length(HookList));
i2 := 0;
for i1 := 0 to high(HookList) do
if (((dll = HMODULE(-1)) and (not wait)) or (HookList[i1].owner = dll)) and
((HookList[i1].ch = nil) or (not HookList[i1].ch.FNoAutoUnhook)) then begin
anh[i2] := HookList[i1].nextHook;
inc(i2);
end;
finally LeaveCriticalSection(HookSection) end;
for i1 := 0 to i2 - 1 do
UnhookCodeEx(anh[i1]^, false, wait);
end;
{$ifdef log}
log('AutoUnhook (module: ' + IntToHexExA(dll) + '; wait: ' + booleanToCharA(wait) + ') -> +');
{$endif}
end;
procedure AutoUnhook(moduleHandle: HMODULE);
begin
{$ifdef log}
log('AutoUnhook (module: ' + IntToHexExA(moduleHandle) + ')');
{$endif}
AutoUnhook2(moduleHandle, true);
{$ifdef log}
log('AutoUnhook (module: ' + IntToHexExA(moduleHandle) + ') -> +');
{$endif}
end;
// ***************************************************************
function CheckLibFilePath(var libA: PAnsiChar; var libW: PWideChar;
var strA: AnsiString; var strW: AnsiString) : boolean;
function CheckIt(pathW: PWideChar = nil; killName: boolean = false) : boolean;
var len : integer;
i1 : integer;
begin
len := lstrlenW(pathW);
if killName then
for i1 := len - 2 downto 1 do
if pathW[i1] = '\' then begin
pathW[i1] := #0;
len := i1;
break;
end;
if (len > 0) and (pathW[len - 1] <> '\') then begin
pathW[len ] := '\';
pathW[len + 1] := #0;
end;
if libA = nil then
strW := WideToWideEx(pathW, false) + WideToWideEx (libW)
else strW := WideToWideEx(pathW, false) + AnsiToWideEx2(libA);
result := (Length(strW) > 6) and ( (strW[3] = ':') or
((strW[1] = '\') and (strW[3] = '\')) ) and
(GetFileAttributesW(pointer(strW)) <> INVALID_FILE_ATTRIBUTES);
if result then
libW := pointer(strW);
end;
var arrChW : array [0..MAX_PATH] of WideChar;
begin
{$ifdef log}
log('CheckLibFilePath (lib: %1)', libA, libW);
{$endif}
result := true;
if not CheckIt then begin
GetModuleFileNameW(0, arrChW, MAX_PATH);
if not CheckIt(arrChW, true) then begin
GetModuleFileNameW(HInstance, arrChW, MAX_PATH);
if not CheckIt(arrChW, true) then begin
GetCurrentDirectoryW(MAX_PATH, arrChW);
if not CheckIt(arrChW) then begin
GetSystemDirectoryW(arrChW, MAX_PATH);
result := CheckIt(arrChW);
end;
end;
end;
end;
{$ifdef log}
log('CheckLibFilePath (lib -> %1) -> ' + booleanToCharA(result), libA, libW);
{$endif}
end;
type
PListEntry = ^TListEntry;
TListEntry = packed record
Flink : PListEntry; // 0x00
Blink : PListEntry; // 0x04
end;
PLdrDependencies = ^TLdrDependencies;
PLdrDdagNode = ^TLdrDdagNode;
PLdrDataTableEntry = ^TLdrDataTableEntry;
TLdrDependencies = record
nextDependency : PLdrDependencies;
whoIsBeingDependedOn : PLdrDdagNode;
nextIncomingDependency : pointer;
whoDependsOnSomething : PLdrDdagNode;
end;
TLdrDdagNode = packed record
Modules : TListEntry; // 0x00 | 0x00
ServiceTagList : pointer; // 0x08 | 0x10
LoadCount : dword; // 0x0c | 0x18
ObsoleteReferenceCount : dword; // 0x10 | 0x1c
LowestLink : dword;
Dependencies : PLdrDependencies;
IncomingDependencies : TPPointer;
State : dword;
CondenseLink : TPPointer;
PreorderNumber : dword;
end;
TLdrDataTableEntry = record
InLoadOrderLinks : TListEntry; // 0x00 | 0x00
InMemoryOrderLinks : TListEntry; // 0x08 | 0x10
InInitializationOrderLinks : TListEntry; // 0x10 | 0x20
DllBase : pointer; // 0x18 | 0x30
EntryPoint : pointer; // 0x1c | 0x38
SizeOfImage : dword; // 0x20 | 0x40
FullDllName : TUnicodeStr; // 0x24 | 0x48
BaseDllName : TUnicodeStr; // 0x2c | 0x58
Flags : dword; // 0x34 | 0x68
ObsoleteLoadCount : word; // 0x38 | 0x6c
TlsIndex : word; // 0x3a | 0x6a
HashLinks : TListEntry; // 0x3c | 0x70
TimeDateStamp : dword; // 0x44 | 0x80
EntryPointActivationContext : pointer; // 0x48 | 0x88
Lock : pointer; // 0x4c | 0x90
DdagNode : PLdrDdagNode; // 0x50 | 0x98
NodeModuleLink : TListEntry; // 0x54 | 0xa0
LoadContext : pointer; // 0x5c | 0xb0
ParentDllBase : pointer; // 0x60 | 0xb8
SwitchBackContext : pointer; // 0x64 | 0xc0
BaseAddressIndexNode : array [0..2] of pointer; // 0x68 | 0xc8
MappingInfoIndexNode : array [0..2] of pointer; // 0x74 | 0xe0
OriginalBase : HMODULE; // 0x80 | 0xf8
LoadTime : int64; // 0x88 | 0x100
BaseNameHashValue : dword; // 0x90 | 0x108
LoadReason : dword; // 0x94 | 0x10C
ImplicitPathOptions : dword; // 0x98 | 0x110
ReferenceCount : dword; // 0x9c | 0x114
end;
(*
function RtlSizeHeap (heap: THandle; flags: dword; buf: pointer) : integer; stdcall; external 'ntdll.dll';
function RtlGetProcessHeaps (count: dword; var heaps: dword) : integer; stdcall; external 'ntdll.dll';
procedure FreeStaticallyLinkedLibrary(module: HMODULE);
function ParseLdr(firstDll: PLdrDataTableEntry) : AnsiString;
function ParseDependencies(deps: TPPointer; showContents: integer; showSize: boolean; heap: THandle) : AnsiString;
var pp1 : TPPointer;
i1 : integer;
begin
result := '';
try
pp1 := deps;
if pp1 <> nil then
repeat
result := result + ', ' + IntToHexEx(integer(pp1));
if showContents > 0 then begin
result := result + ' (';
for i1 := 0 to showContents - 1 do
result := result + IntToHexEx(TPAInteger(pp1)[i1]) + ', ';
Delete(result, Length(result) - 1, 2);
result := result + ')';
end;
pp1 := pp1^;
until (pp1 = nil) or (pp1 = deps);
Delete(result, 1, 2);
if showSize and (result <> '') then
result := result + ' (size: ' + IntToStrEx(RtlSizeHeap(heap, 0, deps)) + ')';
except
result := '*crash*';
end;
end;
var dll : PLdrDataTableEntry;
heap : dword;
begin
dll := firstDll;
result := 'InLoadOrderLinks:' + #$D#$A;
repeat
if dll.DllBase <> nil then
result := result + AnsiString(dll.FullDllName.str) + #$D#$A;
dll := pointer(dll.InLoadOrderLinks.Flink);
until (dll = firstDll) or (dll = nil);
dll := @firstDll.InMemoryOrderLinks.Flink;
result := result + #$D#$A + 'InMemoryOrderLinks:' + #$D#$A;
repeat
if dll.DllBase <> nil then
result := result + AnsiString(PLdrDataTableEntry(dword(dll) - $8).FullDllName.str) + #$D#$A;
dll := pointer(dll.InLoadOrderLinks.Flink);
until (dll = @firstDll.InMemoryOrderLinks.Flink) or (dll = nil);
dll := @firstDll.InInitializationOrderLinks.Flink;
if dll.InLoadOrderLinks.Flink <> nil then begin
result := result + #$D#$A + 'InInitializationOrderLinks:' + #$D#$A;
repeat
if dll.DllBase <> nil then
result := result + AnsiString(PLdrDataTableEntry(dword(dll) - $10).FullDllName.str) + #$D#$A;
dll := pointer(dll.InLoadOrderLinks.Flink);
until (dll = @firstDll.InInitializationOrderLinks.Flink) or (dll = nil);
end;
dll := @firstDll.HashLinks.Flink;
result := result + #$D#$A + 'HashLinks:' + #$D#$A;
repeat
if dll.DllBase <> nil then
result := result + AnsiString(PLdrDataTableEntry(dword(dll) - $3c).FullDllName.str) + #$D#$A;
dll := pointer(dll.InLoadOrderLinks.Flink);
until (dll = @firstDll.HashLinks.Flink) or (dll = nil);
RtlGetProcessHeaps(1, heap);
dll := firstDll;
repeat
if dll.DllBase <> nil then
result := result + #$D#$A +
AnsiString(dll.FullDllName.str) + ':' + #$D#$A +
'LdrDataTableEntry: ' + IntToHexEx(integer(dll)) + ' (size: ' + IntToStrEx(RtlSizeHeap(heap, 0, dll)) + ')' + #$D#$A +
'Flags: ' + IntToHexEx(integer(dll.Flags)) + #$D#$A +
'ObsoleteLoadCount: ' + IntToStrEx(integer(dll.ObsoleteLoadCount)) + #$D#$A +
'NodeModuleLink: ' + IntToHexEx(integer(dll.NodeModuleLink.Flink)) + ', ' + IntToHexEx(integer(dll.NodeModuleLink.Blink)) + #$D#$A +
'LoadReason: ' + IntToStrEx(integer(dll.LoadReason)) + #$D#$A +
'ReferenceCount: ' + IntToStrEx(integer(dll.ReferenceCount)) + #$D#$A +
'DDAG: ' + IntToHexEx(integer(dll.DdagNode)) + ' (size: ' + IntToStrEx(RtlSizeHeap(heap, 0, dll.DdagNode)) + ')' + #$D#$A +
'DDAG.Modules: ' + IntToHexEx(integer(dll.DdagNode.Modules.Flink)) + ', ' + IntToHexEx(integer(dll.DdagNode.Modules.Blink)) + #$D#$A +
'DDAG.LoadCount: ' + IntToStrEx(integer(dll.DdagNode.LoadCount)) + #$D#$A +
'DDAG.ObsoleteReferenceCount: ' + IntToStrEx(integer(dll.DdagNode.ObsoleteReferenceCount)) + #$D#$A +
'DDAG.LowestLink: ' + IntToStrEx(dll.DdagNode.LowestLink) + #$D#$A +
'DDAG.Dependencies: ' + ParseDependencies(pointer(dll.DdagNode.Dependencies), 4, true, heap) + #$D#$A +
'DDAG.IncomingDependencies: ' + ParseDependencies(dll.DdagNode.IncomingDependencies, 2, false, heap) + #$D#$A;
dll := pointer(dll.InLoadOrderLinks.Flink);
until (dll = firstDll) or (dll = nil);
end;
procedure CleanupLdrData(dll: PLdrDataTableEntry);
var deps : PLdrDependencies;
pdeps : ^PLdrDependencies;
pp1 : TPPointer;
begin
dll.LoadReason := 4;
dll.ReferenceCount := 0;
dll.Flags := dll.Flags and (not $20);
dll.DdagNode.LoadCount := 1;
dll.HashLinks.Blink.Flink := dll.HashLinks.Flink;
dll.HashLinks.Flink.Blink := dll.HashLinks.Blink;
dll.InInitializationOrderLinks.Blink.Flink := dll.InInitializationOrderLinks.Flink;
dll.InInitializationOrderLinks.Flink.Blink := dll.InInitializationOrderLinks.Blink;
dll.InMemoryOrderLinks.Blink.Flink := dll.InMemoryOrderLinks.Flink;
dll.InMemoryOrderLinks.Flink.Blink := dll.InMemoryOrderLinks.Blink;
dll.InLoadOrderLinks.Blink.Flink := dll.InLoadOrderLinks.Flink;
dll.InLoadOrderLinks.Flink.Blink := dll.InLoadOrderLinks.Blink;
dll.NodeModuleLink.Blink.Flink := dll.NodeModuleLink.Flink;
dll.NodeModuleLink.Flink.Blink := dll.NodeModuleLink.Blink;
// cleanup dependencies
deps := dll.DdagNode.Dependencies;
if deps <> nil then
repeat
pp1 := @deps.whoIsBeingDependedOn.IncomingDependencies;
repeat
if pp1^ = @deps.nextIncomingDependency then
pp1^ := deps.nextIncomingDependency
else
pp1 := pp1^;
until (pp1 = nil) or (pp1^ = deps.whoIsBeingDependedOn.IncomingDependencies);
deps := deps.nextDependency;
until (deps = nil) or (deps = dll.DdagNode.Dependencies);
// cleanup incoming dependency (there should only be one)
pdeps := @(PLdrDdagNode(TPAPointer(dll.DdagNode.IncomingDependencies)[1]).Dependencies);
repeat
if pdeps^.whoIsBeingDependedOn = dll.DdagNode then
pdeps^ := pdeps^.nextDependency
else
pdeps := @pdeps^.nextDependency;
until (pdeps = nil) or (pdeps^ = PLdrDdagNode(TPAPointer(dll.DdagNode.IncomingDependencies)[1]).Dependencies);
dll.DdagNode.Dependencies := nil;
dll.DdagNode.IncomingDependencies := nil;
end;
var peb : NativeUInt;
firstDll, dll : PLdrDataTableEntry;
begin
peb := GetPeb(GetCurrentProcess);
firstDll := TPPointer(TPCardinal(peb + $c)^ + $c)^; // peb.Ldr.InLoadOrderModuleList.FLink
if firstDll <> nil then begin
dll := firstDll;
repeat
if dword(dll.DllBase) = module then begin
CleanupLdrData(dll);
FreeLibrary(module);
break;
end;
dll := pointer(dll.InLoadOrderLinks.Flink);
until dll = firstDll;
end;
end;
*)
// ***************************************************************
type
TInjectRec = packed record
load : boolean;
libA : array [0..MAX_PATH + 10] of AnsiChar;
libW : array [0..MAX_PATH + 10] of WideChar;
GetVersionFunc : function : dword; stdcall;
SharedMem9x_Free : function (ptr: pointer) : bool; stdcall;
VirtualFreeFunc : function (address: pointer; size: NativeUInt; flags: dword) : bool; stdcall;
SetErrorModeFunc : function (mode: dword) : dword; stdcall;
LoadLibraryAFunc : function (lib: PAnsiChar) : HMODULE; stdcall;
LoadLibraryWFunc : function (lib: PWideChar) : HMODULE; stdcall;
GetLastErrorFunc : function : integer; stdcall;
GetModuleHandleAFunc : function (lib: PAnsiChar) : HMODULE; stdcall;
GetModuleHandleWFunc : function (lib: PWideChar) : HMODULE; stdcall;
GetCurrentProcessIdFunc : function : dword; stdcall;
OpenFileMappingAFunc : function (access: dword; inheritHandle: bool; name: PAnsiChar) : THandle; stdcall;
MapViewOfFileFunc : function (map: THandle; access, ofsHi, ofsLo: dword; size: NativeUInt) : pointer; stdcall;
UnmapViewOfFileFunc : function (addr: pointer) : bool; stdcall;
CloseHandleFunc : function (obj: THandle) : bool; stdcall;
FreeLibraryFunc : function (module: HMODULE) : bool; stdcall;
EnterCriticalSectionFunc : procedure (var criticalSection: TRTLCriticalSection); stdcall;
LeaveCriticalSectionFunc : procedure (var criticalSection: TRTLCriticalSection); stdcall;
end;
{$ifdef win64}
TInjectRec32 = packed record
load : boolean;
libA : array [0..MAX_PATH + 10] of AnsiChar;
libW : array [0..MAX_PATH + 10] of WideChar;
GetVersionFunc : dword;
SharedMem9x_Free : dword;
VirtualFreeFunc : dword;
SetErrorModeFunc : dword;
LoadLibraryAFunc : dword;
LoadLibraryWFunc : dword;
GetLastErrorFunc : dword;
GetModuleHandleAFunc : dword;
GetModuleHandleWFunc : dword;
GetCurrentProcessIdFunc : dword;
OpenFileMappingAFunc : dword;
MapViewOfFileFunc : dword;
UnmapViewOfFileFunc : dword;
CloseHandleFunc : dword;
FreeLibraryFunc : dword;
EnterCriticalSectionFunc : dword;
LeaveCriticalSectionFunc : dword;
end;
{$endif}
function InjectThread(var injRec: TInjectRec) : integer; stdcall;
var module : HMODULE;
i1 : integer;
oldErr : dword;
aup : procedure (moduleHandle: HMODULE);
ir : TInjectRec;
c1, c2 : NativeUInt;
nh : PImageNtHeaders;
{$ifndef win64}
peb : NativeUInt;
loaderLock : PRtlCriticalSection;
firstDll : PLdrDataTableEntry;
dll : PLdrDataTableEntry;
{$endif}
begin
{$ifndef win64}
asm
(*
// set up an exception handler
db $e8 // call +0
dd 0
pop eax
add eax, $16
push eax
mov eax, fs:[0]
push eax
mov fs:[0], esp
jmp @skipExcept
// here comes the exception handler
// let's simply ignore the exception and continue at @failure
@except:
mov eax, [esp+$c]
db $e8 // call +0
dd 0
pop ecx
add ecx, $1a
mov [eax+$b8], ecx
mov ecx, [esp+$8]
mov [eax+$c4], ecx
mov eax, 0
ret
@failure:
mov ecx, [esp]
mov fs:[0], ecx
add esp, 8
mov eax, ERROR_ACCESS_DENIED
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret 4
@skipExcept:
*)
mov eax, fs:[$30] // eax := threadEnvironmentBlock^.processDataBase
mov peb, eax
end;
{$endif}
result := 0;
ir := injRec;
ir.VirtualFreeFunc(@injRec, 0, MEM_RELEASE);
oldErr := ir.SetErrorModeFunc(SEM_FAILCRITICALERRORS or SEM_NOGPFAULTERRORBOX or SEM_NOOPENFILEERRORBOX);
if ir.load then begin
module := ir.LoadLibraryWFunc(ir.libW);
if module = 0 then
result := ir.GetLastErrorFunc;
end else begin
module := ir.GetModuleHandleWFunc(ir.libW);
if module = 0 then
result := ir.GetLastErrorFunc;
end;
ir.SetErrorModeFunc(oldErr);
if module <> 0 then begin
if ir.load then begin
{$ifndef win64}
loaderLock := TPPointer(peb + $a0)^;
ir.EnterCriticalSectionFunc(loaderLock^);
firstDll := TPPointer(TPCardinal(peb + $c)^ + $c)^; // peb.Ldr.InLoadOrderModuleList.FLink
if firstDll <> nil then begin
dll := firstDll;
repeat
if dll.DllBase = pointer(module) then begin
if firstDll.ObsoleteLoadCount >= $ff then
dll.ObsoleteLoadCount := firstDll.ObsoleteLoadCount
else
dll.ObsoleteLoadCount := $ffff;
break;
end;
dll := pointer(dll.InLoadOrderLinks.Flink);
until dll = firstDll;
end;
ir.LeaveCriticalSectionFunc(loaderLock^);
{$endif}
end else begin
aup := nil;
if TPWord(module)^ = CEMAGIC then begin
nh := pointer(module + dword(pointer(module + CENEWHDR)^));
if nh.signature = CPEMAGIC then begin
if nh.OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR64_MAGIC then
c1 := PImageOptionalHeader64(@nh.OptionalHeader).SizeOfImage
else
c1 := nh.OptionalHeader.SizeOfImage;
if c1 > 0 then
for c2 := 8 to c1 - 1 - 24 do
if (TPInt64(module + c2 + 0)^ = $3E3A3A1D16313438) and
(TPInt64(module + c2 + 8)^ = $3A3D3B003A212014) and
(TPInt64(module + c2 + 16)^ = $27303E2734183E3A) then begin
aup := TPPointer(module + c2 - 8)^;
break;
end;
end;
end;
if @aup <> nil then
aup(module);
{$ifndef win64}
loaderLock := TPPointer(peb + $a0)^;
ir.EnterCriticalSectionFunc(loaderLock^);
firstDll := TPPointer(TPCardinal(peb + $c)^ + $c)^; // peb.Ldr.InLoadOrderModuleList.FLink
if firstDll <> nil then begin
dll := firstDll;
repeat
if dll.DllBase = pointer(module) then begin
dll.ObsoleteLoadCount := 1;
break;
end;
dll := pointer(dll.InLoadOrderLinks.Flink);
until dll = firstDll;
end;
{$endif}
if ir.FreeLibraryFunc(module) then begin
i1 := 0;
while ir.FreeLibraryFunc(module) and (i1 < $ffff) do
inc(i1);
result := 0;
end else
result := ir.GetLastErrorFunc;
{$ifndef win64}
loaderLock := TPPointer(peb + $a0)^;
ir.LeaveCriticalSectionFunc(loaderLock^);
{$endif}
end;
end;
(*
asm
// final cleanup: remove the exception handler again
mov ecx, [esp]
mov fs:[0], ecx
add esp, 8
end;
*)
end;
const CInjectThread32 : array [0..520] of byte = (
$55, $8B, $EC, $81, $C4, $80, $FC, $FF, $FF, $53, $56, $57, $8B, $55, $08, $64,
$8B, $05, $30, $00, $00, $00, $89, $45, $FC, $33, $C0, $89, $45, $F8, $8B, $F2,
$8D, $BD, $82, $FC, $FF, $FF, $B9, $DC, $00, $00, $00, $F3, $A5, $66, $A5, $68,
$00, $80, $00, $00, $6A, $00, $52, $FF, $55, $B8, $68, $03, $80, $00, $00, $FF,
$55, $BC, $8B, $F0, $80, $BD, $82, $FC, $FF, $FF, $00, $74, $18, $8D, $85, $92,
$FD, $FF, $FF, $50, $FF, $55, $C4, $8B, $D8, $85, $DB, $75, $1E, $FF, $55, $C8,
$89, $45, $F8, $EB, $16, $8D, $85, $92, $FD, $FF, $FF, $50, $FF, $55, $D0, $8B,
$D8, $85, $DB, $75, $06, $FF, $55, $C8, $89, $45, $F8, $56, $FF, $55, $BC, $85,
$DB, $0F, $84, $76, $01, $00, $00, $80, $BD, $82, $FC, $FF, $FF, $00, $74, $50,
$8B, $45, $FC, $05, $A0, $00, $00, $00, $8B, $30, $56, $FF, $55, $EC, $8B, $45,
$FC, $83, $C0, $0C, $8B, $00, $83, $C0, $0C, $8B, $10, $85, $D2, $74, $28, $8B,
$C2, $8B, $48, $18, $3B, $CB, $75, $19, $66, $8B, $4A, $38, $66, $81, $F9, $FF,
$00, $72, $06, $66, $89, $48, $38, $EB, $0E, $66, $C7, $40, $38, $FF, $00, $EB,
$06, $8B, $00, $3B, $D0, $75, $DA, $56, $FF, $55, $F0, $E9, $1D, $01, $00, $00,
$33, $C9, $66, $81, $3B, $4D, $5A, $0F, $85, $87, $00, $00, $00, $8D, $43, $3C,
$8B, $00, $03, $C3, $81, $38, $50, $45, $00, $00, $75, $78, $66, $81, $78, $18,
$0B, $02, $75, $08, $83, $C0, $18, $8B, $40, $38, $EB, $03, $8B, $40, $50, $85,
$C0, $76, $61, $48, $83, $E8, $18, $83, $E8, $08, $72, $58, $40, $89, $45, $F4,
$BE, $08, $00, $00, $00, $8D, $3C, $1E, $8B, $C7, $81, $78, $04, $1D, $3A, $3A,
$3E, $75, $3B, $81, $38, $38, $34, $31, $16, $75, $33, $8B, $C7, $83, $C0, $08,
$81, $78, $04, $00, $3B, $3D, $3A, $75, $25, $81, $38, $14, $20, $21, $3A, $75,
$1D, $8B, $C7, $83, $C0, $10, $81, $78, $04, $27, $3E, $30, $27, $75, $0F, $81,
$38, $3A, $3E, $18, $34, $75, $07, $83, $EF, $08, $8B, $0F, $EB, $06, $46, $FF,
$4D, $F4, $75, $B1, $85, $C9, $74, $06, $8B, $F1, $8B, $C3, $FF, $D6, $8B, $45,
$FC, $05, $A0, $00, $00, $00, $8B, $30, $56, $FF, $55, $EC, $8B, $45, $FC, $83,
$C0, $0C, $8B, $00, $83, $C0, $0C, $8B, $10, $85, $D2, $74, $28, $8B, $C2, $8B,
$48, $18, $3B, $CB, $75, $19, $80, $BD, $82, $FC, $FF, $FF, $00, $74, $08, $66,
$C7, $40, $38, $FF, $00, $EB, $0E, $66, $C7, $40, $38, $01, $00, $EB, $06, $8B,
$00, $3B, $D0, $75, $DA, $53, $FF, $55, $E8, $85, $C0, $74, $1C, $33, $F6, $EB,
$01, $46, $53, $FF, $55, $E8, $85, $C0, $74, $08, $81, $FE, $FF, $FF, $00, $00,
$7C, $EF, $33, $C0, $89, $45, $F8, $EB, $06, $FF, $55, $C8, $89, $45, $F8, $8B,
$45, $FC, $05, $A0, $00, $00, $00, $8B, $30, $56, $FF, $55, $F0, $8B, $45, $F8,
$5F, $5E, $5B, $8B, $E5, $5D, $C2, $04, $00);
{$ifdef win64}
const CInjectThread64 : array [0..716] of byte = (
$48, $89, $4C, $24, $08, $56, $57, $48, $81, $EC, $D8, $07, $00, $00, $C7,
$84, $24, $F0, $03, $00, $00, $00, $00, $00, $00, $48, $8D, $84, $24, $18,
$04, $00, $00, $48, $8B, $F8, $48, $8B, $B4, $24, $F0, $07, $00, $00, $B9,
$B6, $03, $00, $00, $F3, $A4, $48, $8D, $44, $24, $20, $48, $8D, $8C, $24,
$18, $04, $00, $00, $48, $8B, $F8, $48, $8B, $F1, $B9, $B6, $03, $00, $00,
$F3, $A4, $41, $B8, $00, $80, $00, $00, $33, $D2, $48, $8B, $8C, $24, $F0,
$07, $00, $00, $FF, $94, $24, $5E, $03, $00, $00, $B9, $01, $00, $00, $00,
$FF, $94, $24, $66, $03, $00, $00, $89, $84, $24, $E0, $03, $00, $00, $0F,
$BE, $44, $24, $20, $85, $C0, $74, $32, $48, $8D, $8C, $24, $30, $01, $00,
$00, $FF, $94, $24, $76, $03, $00, $00, $48, $89, $84, $24, $E8, $03, $00,
$00, $48, $83, $BC, $24, $E8, $03, $00, $00, $00, $75, $0E, $FF, $94, $24,
$7E, $03, $00, $00, $89, $84, $24, $F0, $03, $00, $00, $EB, $30, $48, $8D,
$8C, $24, $30, $01, $00, $00, $FF, $94, $24, $8E, $03, $00, $00, $48, $89,
$84, $24, $E8, $03, $00, $00, $48, $83, $BC, $24, $E8, $03, $00, $00, $00,
$75, $0E, $FF, $94, $24, $7E, $03, $00, $00, $89, $84, $24, $F0, $03, $00,
$00, $8B, $8C, $24, $E0, $03, $00, $00, $FF, $94, $24, $66, $03, $00, $00,
$48, $83, $BC, $24, $E8, $03, $00, $00, $00, $0F, $84, $BD, $01, $00, $00,
$0F, $BE, $44, $24, $20, $85, $C0, $74, $05, $E9, $AF, $01, $00, $00, $48,
$C7, $84, $24, $F8, $03, $00, $00, $00, $00, $00, $00, $48, $8B, $84, $24,
$E8, $03, $00, $00, $0F, $B7, $00, $3D, $4D, $5A, $00, $00, $0F, $85, $10,
$01, $00, $00, $48, $8B, $84, $24, $E8, $03, $00, $00, $8B, $40, $3C, $89,
$84, $24, $00, $04, $00, $00, $8B, $84, $24, $00, $04, $00, $00, $48, $8B,
$8C, $24, $E8, $03, $00, $00, $48, $03, $C8, $48, $8B, $C1, $48, $89, $84,
$24, $08, $04, $00, $00, $48, $8B, $84, $24, $08, $04, $00, $00, $81, $38,
$50, $45, $00, $00, $0F, $85, $CD, $00, $00, $00, $48, $8B, $84, $24, $08,
$04, $00, $00, $83, $78, $50, $00, $0F, $84, $BB, $00, $00, $00, $C7, $84,
$24, $10, $04, $00, $00, $08, $00, $00, $00, $EB, $11, $8B, $84, $24, $10,
$04, $00, $00, $83, $C0, $01, $89, $84, $24, $10, $04, $00, $00, $48, $8B,
$84, $24, $08, $04, $00, $00, $8B, $40, $50, $83, $E8, $18, $39, $84, $24,
$10, $04, $00, $00, $0F, $83, $82, $00, $00, $00, $8B, $84, $24, $10, $04,
$00, $00, $48, $8B, $8C, $24, $E8, $03, $00, $00, $48, $BA, $38, $34, $31,
$16, $1D, $3A, $3A, $3E, $48, $39, $14, $01, $75, $5E, $8B, $84, $24, $10,
$04, $00, $00, $48, $8B, $8C, $24, $E8, $03, $00, $00, $48, $BA, $14, $20,
$21, $3A, $00, $3B, $3D, $3A, $48, $39, $54, $01, $08, $75, $3E, $8B, $84,
$24, $10, $04, $00, $00, $48, $8B, $8C, $24, $E8, $03, $00, $00, $48, $BA,
$3A, $3E, $18, $34, $27, $3E, $30, $27, $48, $39, $54, $01, $10, $75, $1E,
$8B, $84, $24, $10, $04, $00, $00, $48, $8B, $8C, $24, $E8, $03, $00, $00,
$48, $8B, $44, $01, $F8, $48, $89, $84, $24, $F8, $03, $00, $00, $EB, $05,
$E9, $52, $FF, $FF, $FF, $48, $83, $BC, $24, $F8, $03, $00, $00, $00, $74,
$0F, $48, $8B, $8C, $24, $E8, $03, $00, $00, $FF, $94, $24, $F8, $03, $00,
$00, $48, $8B, $8C, $24, $E8, $03, $00, $00, $FF, $94, $24, $BE, $03, $00,
$00, $85, $C0, $74, $42, $C7, $84, $24, $14, $04, $00, $00, $00, $00, $00,
$00, $48, $8B, $8C, $24, $E8, $03, $00, $00, $FF, $94, $24, $BE, $03, $00,
$00, $85, $C0, $74, $22, $8B, $84, $24, $14, $04, $00, $00, $83, $C0, $01,
$89, $84, $24, $14, $04, $00, $00, $81, $BC, $24, $14, $04, $00, $00, $FF,
$FF, $00, $00, $75, $02, $EB, $02, $EB, $CB, $EB, $0E, $FF, $94, $24, $7E,
$03, $00, $00, $89, $84, $24, $F0, $03, $00, $00, $8B, $84, $24, $F0, $03,
$00, $00, $48, $81, $C4, $D8, $07, $00, $00, $5F, $5E, $C3);
{$endif}
function InstallInjectThreadProc(process: THandle; pid: dword) : pointer;
type TMchIInjT = packed record
name : array [0..7] of AnsiChar;
pid : dword;
injThread : pointer;
end;
var map : THandle;
newMap : boolean;
buf : ^TMchIInjT;
s1 : AnsiString;
i1 : integer;
c1 : THandle;
thProc : pointer;
op : dword;
{$ifdef win64}
is32proc : boolean;
{$endif}
begin
result := nil;
{$ifdef win64}
is32proc := true;
{$endif}
s1 := DecryptStr(CMchIInjT);
if pid <> 0 then begin
{$ifdef win64}
is32proc := not Is64bitProcess(process);
{$endif}
map := CreateGlobalFileMapping(PAnsiChar(s1 + IntToHexExA(pid)), sizeOf(buf^));
end else
map := 0;
if map <> 0 then begin
newMap := GetLastError <> ERROR_ALREADY_EXISTS;
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if buf <> nil then begin
for i1 := 1 to 50 do
if newMap or ((TPInt64(@buf^.name)^ = TPInt64(s1)^) and (buf^.pid = pid)) then
break
else
Sleep(50);
if newMap or ((TPInt64(@buf^.name)^ <> TPInt64(s1)^) and (buf^.pid = pid)) then begin
{$ifdef win64}
if is32proc then begin
thProc := AllocMemEx(sizeOf(CInjectThread32), process, X86AllocationAddress);
WriteProcessMemory(process, thProc, @CInjectThread32, sizeOf(CInjectThread32), c1);
VirtualProtectEx(process, thProc, sizeof(CInjectThread32), PAGE_EXECUTE_READ, @op);
TPCardinal(@buf^.injThread)^ := dword(thProc);
end else begin
thProc := AllocMemEx(sizeOf(CInjectThread64), process);
WriteProcessMemory(process, thProc, @CInjectThread64, sizeOf(CInjectThread64), c1);
VirtualProtectEx(process, thProc, sizeof(CInjectThread64), PAGE_EXECUTE_READ, @op);
buf^.injThread := thProc;
end;
{$else}
thProc := AllocMemEx(sizeOf(CInjectThread32), process);
WriteProcessMemory(process, thProc, @CInjectThread32, sizeOf(CInjectThread32), c1);
VirtualProtectEx(process, thProc, sizeof(CInjectThread32), PAGE_EXECUTE_READ, @op);
buf^.injThread := thProc;
{$endif}
buf^.pid := pid;
AtomicMove(pointer(s1), @buf^.name, 8);
DuplicateHandle(GetCurrentProcess, map, process, @c1, 0, false, DUPLICATE_SAME_ACCESS);
{$ifdef d2006}
if pid = GetCurrentProcessId then
RegisterExpectedMemoryLeak(pointer(c1));
{$endif}
end;
{$ifdef win64}
if is32proc then
result := pointer(TPCardinal(@buf^.injThread)^)
else
{$endif}
result := buf^.injThread;
UnmapViewOfFile(buf);
end;
CloseHandle(map);
end;
end;
{$ifdef win64}
function IsProcessLargeAddressAware(process: THandle) : boolean;
// find out if the specified 64bit process is aware of large addresses
// if it is not, system dlls are loaded in the lower 4GB of the address range
var exeImage : NativeUInt;
nh : TImageNtHeaders64;
nhOffset : dword;
read : NativeUInt;
begin
result := (not ReadProcessMemory(process, pointer(GetPeb(process) + $10), @exeImage, sizeOf(exeImage), read)) or
(not ReadProcessMemory(process, pointer(exeImage + CENEWHDR), @nhOffset, sizeOf(nhOffset), read)) or
(not ReadProcessMemory(process, pointer(exeImage + nhOffset), @nh, sizeOf(nh ), read)) or
(nh.FileHeader.Characteristics and IMAGE_FILE_LARGE_ADDRESS_AWARE <> 0);
end;
function LargeAddressAwareApiTo2GB(process: THandle; largeApi: pointer) : pointer;
var mbi : TMemoryBasicInformation;
arrCh : array [0..MAX_PATH] of wideChar;
remoteModule : HMODULE;
dllName : PWideChar;
i1 : integer;
begin
result := largeApi;
if (VirtualQuery(largeApi, mbi, sizeOf(mbi)) = sizeOf(mbi)) and
(mbi.State = MEM_COMMIT) and
(mbi.AllocationBase <> nil) and
(GetModuleFileNameW(HMODULE(mbi.AllocationBase), arrCh, MAX_PATH) > 0) then begin
dllName := arrCh;
for i1 := lstrlenW(arrCh) - 1 downto 1 do
if arrCh[i1] = '\' then begin
dllName := @arrCh[i1 + 1];
break;
end;
remoteModule := GetRemoteModuleHandle(process, false, dllName);
if remoteModule <> 0 then
result := pointer(NativeUInt(largeApi) - NativeUInt(mbi.AllocationBase) + remoteModule);
end;
end;
{$endif}
function StartInjectLibraryX(process: THandle; pid: dword; inject: boolean; libA: PAnsiChar; libW: PWideChar) : THandle;
var ir : TInjectRec;
irSize : integer;
{$ifdef win64}
pir32 : ^TInjectRec32;
{$endif}
proc : pointer;
par : pointer;
c1, c2 : dword;
c64 : NativeUInt;
size : NativeUInt;
op : dword;
pos : integer;
nh : {$ifdef win64} PImageNtHeaders64 {$else} PImageNtHeaders32 {$endif};
procs : array [0..13] of pointer;
apis : array [0..13] of AnsiString;
b1 : boolean;
begin
{$ifdef log}
log('StartInjectLibraryX (process: ' + '%p' +
'; pid: ' + IntToHexExA(pid) +
'; inject: ' + booleanToCharA(inject) +
'; lib: %1)', libA, libW, process);
{$endif}
result := 0;
if libW <> nil then begin
if not inject then begin
// for uninjection we only use the dll file name instead of the full path
// this works around a chrome uninjection issue
pos := PosPCharW('\', libW, 0, 0, false, maxInt, 0);
if pos >= 0 then
libW := libW + pos + 1;
end;
lstrcpyW(ir.libW, libW);
end;
if libA <> nil then
lstrcpyA(ir.libA, libA);
ir.load := inject;
proc := InstallInjectThreadProc(process, pid);
if proc <> nil then begin
par := nil;
{$ifdef win64}
if not Is64bitProcess(process) then begin
irSize := sizeOf(TInjectRec32);
if Init32bitKernelAPIs(process) then begin
pir32 := @ir;
pir32.GetVersionFunc := GetKernelAPI(0);
pir32.SharedMem9x_Free := 0;
pir32.VirtualFreeFunc := GetKernelAPI(1);
pir32.SetErrorModeFunc := GetKernelAPI(2);
pir32.LoadLibraryAFunc := 0;
pir32.LoadLibraryWFunc := GetKernelAPI(3);
pir32.GetLastErrorFunc := GetKernelAPI(4);
pir32.GetModuleHandleAFunc := 0;
pir32.GetModuleHandleWFunc := GetKernelAPI(5);
pir32.GetCurrentProcessIdFunc := GetKernelAPI(6);
pir32.OpenFileMappingAFunc := GetKernelAPI(7);
pir32.MapViewOfFileFunc := GetKernelAPI(8);
pir32.UnmapViewOfFileFunc := GetKernelAPI(9);
pir32.CloseHandleFunc := GetKernelAPI(10);
pir32.FreeLibraryFunc := GetKernelAPI(11);
pir32.EnterCriticalSectionFunc := GetKernelAPI(12);
pir32.LeaveCriticalSectionFunc := GetKernelAPI(13);
par := AllocMemEx(irSize, process, pointer(NativeUInt(X86AllocationAddress) - $10000));
end;
end else
if not IsProcessLargeAddressAware(process) then begin
if GetVersion2GB = nil then begin
GetVersion2GB := LargeAddressAwareApiTo2GB(process, @GetVersionFunc );
GetLastError2GB := LargeAddressAwareApiTo2GB(process, @GetLastErrorFunc );
GetCurrentProcessId2GB := LargeAddressAwareApiTo2GB(process, @GetCurrentProcessIdFunc );
VirtualFree2GB := LargeAddressAwareApiTo2GB(process, @VirtualFreeFunc );
SetErrorMode2GB := LargeAddressAwareApiTo2GB(process, @SetErrorModeFunc );
LoadLibraryW2GB := LargeAddressAwareApiTo2GB(process, @LoadLibraryWFunc );
GetModuleHandleW2GB := LargeAddressAwareApiTo2GB(process, @GetModuleHandleWFunc );
OpenFileMappingA2GB := LargeAddressAwareApiTo2GB(process, @OpenFileMappingAFunc );
MapViewOfFile2GB := LargeAddressAwareApiTo2GB(process, @MapViewOfFileFunc );
UnmapViewOfFile2GB := LargeAddressAwareApiTo2GB(process, @UnmapViewOfFileFunc );
CloseHandle2GB := LargeAddressAwareApiTo2GB(process, @CloseHandleFunc );
FreeLibrary2GB := LargeAddressAwareApiTo2GB(process, @FreeLibraryFunc );
EnterCriticalSection2GB := LargeAddressAwareApiTo2GB(process, @EnterCriticalSectionFunc);
LeaveCriticalSection2GB := LargeAddressAwareApiTo2GB(process, @LeaveCriticalSectionFunc);
end;
irSize := sizeOf(TInjectRec);
ir.GetVersionFunc := GetVersion2GB;
ir.SharedMem9x_Free := nil;
ir.VirtualFreeFunc := VirtualFree2GB;
ir.SetErrorModeFunc := SetErrorMode2GB;
ir.LoadLibraryAFunc := nil;
ir.LoadLibraryWFunc := LoadLibraryW2GB;
ir.GetLastErrorFunc := GetLastError2GB;
ir.GetModuleHandleAFunc := nil;
ir.GetModuleHandleWFunc := GetModuleHandleW2GB;
ir.GetCurrentProcessIdFunc := GetCurrentProcessId2GB;
ir.OpenFileMappingAFunc := OpenFileMappingA2GB;
ir.MapViewOfFileFunc := MapViewOfFile2GB;
ir.UnmapViewOfFileFunc := UnmapViewOfFile2GB;
ir.CloseHandleFunc := CloseHandle2GB;
ir.FreeLibraryFunc := FreeLibrary2GB;
ir.EnterCriticalSectionFunc := EnterCriticalSection2GB;
ir.LeaveCriticalSectionFunc := LeaveCriticalSection2GB;
if @ir.VirtualFreeFunc <> nil then
par := AllocMemEx(irSize, process);
end else begin
{$endif}
irSize := sizeOf(TInjectRec);
nh := pointer(GetImageNtHeaders(GetModuleHandle(kernel32)));
if (nh <> nil) and ReadProcessMemory(process, @nh.OptionalHeader.CheckSum, @c2, 4, c64) and (c64 = 4) and (c2 = nh.OptionalHeader.CheckSum) then begin
b1 := true;
ir.GetVersionFunc := GetVersionFunc;
ir.SharedMem9x_Free := nil;
ir.VirtualFreeFunc := VirtualFreeFunc;
ir.SetErrorModeFunc := SetErrorModeFunc;
ir.LoadLibraryAFunc := LoadLibraryAFunc;
ir.LoadLibraryWFunc := LoadLibraryWFunc;
ir.GetLastErrorFunc := GetLastErrorFunc;
ir.GetModuleHandleAFunc := GetModuleHandleAFunc;
ir.GetModuleHandleWFunc := GetModuleHandleWFunc;
ir.GetCurrentProcessIdFunc := GetCurrentProcessIdFunc;
ir.OpenFileMappingAFunc := OpenFileMappingAFunc;
ir.MapViewOfFileFunc := MapViewOfFileFunc;
ir.UnmapViewOfFileFunc := UnmapViewOfFileFunc;
ir.CloseHandleFunc := CloseHandleFunc;
ir.FreeLibraryFunc := FreeLibraryFunc;
end else begin
// for some reason the target process seems to have a different kernel32.dll version loaded !?
// so we can't use the API addresses from our own process, but have to grab them from the target process
apis[ 0] := DecryptStr(CGetVersion);
apis[ 1] := DecryptStr(CVirtualFree);
apis[ 2] := DecryptStr(CSetErrorMode);
apis[ 3] := DecryptStr(CLoadLibraryA);
apis[ 4] := DecryptStr(CLoadLibraryW);
apis[ 5] := DecryptStr(CGetLastError);
apis[ 6] := DecryptStr(CGetModuleHandleA);
apis[ 7] := DecryptStr(CGetModuleHandleW);
apis[ 8] := DecryptStr(CGetCurrentProcessId);
apis[ 9] := DecryptStr(COpenFileMappingA);
apis[10] := DecryptStr(CMapViewOfFile);
apis[11] := DecryptStr(CUnmapViewOfFile);
apis[12] := DecryptStr(CCloseHandle);
apis[13] := DecryptStr(CFreeLibrary);
b1 := GetRemoteProcAddresses(process, {$ifdef win64} false {$else} true {$endif}, kernel32, apis, @procs, 14);
if b1 then begin
ir.GetVersionFunc := procs[ 0];
ir.SharedMem9x_Free := nil;
ir.VirtualFreeFunc := procs[ 1];
ir.SetErrorModeFunc := procs[ 2];
ir.LoadLibraryAFunc := procs[ 3];
ir.LoadLibraryWFunc := procs[ 4];
ir.GetLastErrorFunc := procs[ 5];
ir.GetModuleHandleAFunc := procs[ 6];
ir.GetModuleHandleWFunc := procs[ 7];
ir.GetCurrentProcessIdFunc := procs[ 8];
ir.OpenFileMappingAFunc := procs[ 9];
ir.MapViewOfFileFunc := procs[10];
ir.UnmapViewOfFileFunc := procs[11];
ir.CloseHandleFunc := procs[12];
ir.FreeLibraryFunc := procs[13];
end;
end;
if b1 then begin
nh := pointer(GetImageNtHeaders(GetModuleHandle('ntdll.dll')));
if (nh <> nil) and ReadProcessMemory(process, @nh.OptionalHeader.CheckSum, @c2, 4, c64) and (c64 = 4) and (c2 = nh.OptionalHeader.CheckSum) then begin
ir.EnterCriticalSectionFunc := EnterCriticalSectionFunc;
ir.LeaveCriticalSectionFunc := LeaveCriticalSectionFunc;
end else begin
// for some reason the target process seems to have a different ntdll.dll version loaded !?
// so we can't use the API addresses from our own process, but have to grab them from the target process
apis[0] := DecryptStr(CRtlEnterCriticalSection);
apis[1] := DecryptStr(CRtlLeaveCriticalSection);
b1 := GetRemoteProcAddresses(process, {$ifdef win64} false {$else} true {$endif}, 'ntdll.dll', apis, @procs, 2);
if b1 then begin
ir.EnterCriticalSectionFunc := procs[0];
ir.LeaveCriticalSectionFunc := procs[1];
end;
end;
end;
if b1 and (@ir.VirtualFreeFunc <> nil) then
par := AllocMemEx(irSize, process);
{$ifdef win64}
end;
{$endif}
if par <> nil then begin
VirtualProtectEx(process, par, irSize, PAGE_READWRITE, @op);
if WriteProcessMemory(process, par, @ir, irSize, size) then
result := CreateRemoteThreadEx(process, nil, 1 * 1024 * 1024, proc, par, 0, c1);
if result = 0 then begin
c1 := GetLastError;
FreeMemEx(par, process);
SetLastError(c1);
end;
end;
end;
{$ifdef log}
c1 := GetLastError;
log('StartInjectLibraryX (process: ' + '%p' +
'; pid: ' + IntToHexExA(pid) +
'; inject: ' + booleanToCharA(inject) +
'; lib: %1) -> th' + IntToHexExA(result),
libA, libW, process);
SetLastError(c1);
{$endif}
end;
function WaitForInjectLibraryX(thread: THandle; timeOut, time: dword) : boolean;
var error : dword;
begin
{$ifdef log}
log('WaitForInjectLibraryX (thread: ' + IntToHexExA(thread) +
'; timeOut: ' + IntToHexExA(timeOut) +
'; time: ' + IntToHexExA(time) +
')');
{$endif}
if timeOut = INFINITE then
time := 0
else time := TickDif(time);
if timeOut > time then begin
if WaitForSingleObject(thread, timeOut - time) = WAIT_OBJECT_0 then
GetExitCodeThread(thread, error)
else error := GetLastError;
end else
if timeOut = 0 then
error := 0
else error := ERROR_TIMEOUT;
CloseHandle(thread);
result := error = 0;
{$ifdef log}
log('WaitForInjectLibraryX (thread: ' + IntToHexExA(thread) + ') -> ' + booleanToCharA(result) + ' (' + IntToStrExA(error) + ')');
{$endif}
if not result then
SetLastError(error);
end;
{$ifdef log}
procedure LogProcesses(const prcs: TDAProcess);
var i1 : integer;
begin
log('EnumProcesses -> ' + IntToStrExA(Length(prcs)));
for i1 := 0 to Length(prcs) - 1 do
log(' process[' + IntToStrExA(i1) + ']: ' + IntToHexExA(prcs[i1].id, 8) + ' ' + ExtractFileNameA(AnsiString(prcs[i1].exeFile)));
end;
{$endif}
var CharLowerBuffW : function (str: PWideChar; len: dword) : dword; stdcall = nil;
function SplitNamePath(str: UnicodeString; var path, name: UnicodeString) : boolean;
var i1 : integer;
begin
if @CharLowerBuffW = nil then
CharLowerBuffW := GetProcAddress(GetModuleHandle(user32), PAnsiChar(DecryptStr(CCharLowerBuffW)));
if @CharLowerBuffW <> nil then begin
{$ifndef ver120}{$ifndef ver130}
UniqueString(str);
{$endif}{$endif}
CharLowerBuffW(PWideChar(str), Length(str));
end else
str := LowStrW(str);
i1 := PosStrW('\', str, Length(str) - 1, 1);
result := i1 <> 0;
if result then begin
path := str;
name := Copy(str, i1 + 1, maxInt);
end else begin
path := '';
name := str;
end;
end;
function MatchStrArray(const path, name: UnicodeString; const paths, names: array of UnicodeString) : boolean;
var i1 : integer;
begin
result := false;
for i1 := 0 to high(paths) do begin
if (path <> '') and (paths[i1] <> '') then
result := InternalStrMatchW(path, paths[i1], true)
else
result := InternalStrMatchW(name, names[i1], true);
if result then
break;
end;
end;
var LdrLoadDll32 : pointer = nil;
NtProtectVirtualMemory32 : pointer = nil;
NtAllocateVirtualMemory32 : pointer = nil;
NtFreeVirtualMemory32 : pointer = nil;
NtQuerySystemInformation32 : pointer = nil;
NtDelayExecution32 : pointer = nil;
NtQueueApcThread : function (threadHandle: THandle; apcProc, param, statusBock, reserved: pointer) : HRESULT; stdcall = nil;
function InjectLibraryX(owner : HMODULE;
libA : PAnsiChar;
libW : PWideChar;
multiInject : boolean;
process : THandle;
driverName : PWideChar;
session : dword;
options : dword;
includes : PWideChar;
excludes : PWideChar;
excludePIDs : TPACardinal;
callback : TInjectApprovalCallbackRoutine;
callbackContext : pointer;
timeOut : dword;
mayPatch : boolean;
mayThread : boolean;
forcePatch : boolean;
thread : THandle ) : boolean;
var peb32 : NativeUInt; // 32bit process environment block
peb64 : int64; // 64bit process environment block
function Read(process: THandle; addr: pointer; var buf; len: NativeInt) : boolean; overload;
var c1 : NativeUInt;
begin
result := ReadProcessMemory(process, addr, @buf, len, c1);
end;
function Read(process: THandle; addr: NativeUInt; var buf; len: NativeInt) : boolean; overload;
var c1 : NativeUInt;
begin
result := ReadProcessMemory(process, pointer(addr), @buf, len, c1);
end;
function Write(process: THandle; addr: pointer; const buf; len: NativeInt; unprotect: boolean = true) : boolean;
var c1 : NativeUInt;
begin
result := ( (not unprotect) or
VirtualProtectEx(process, addr, len, PAGE_EXECUTE_READWRITE, @c1) ) and
WriteProcessMemory(process, addr, @buf, len, c1);
end;
function NotInitializedYet(process: THandle) : boolean;
var c1 : dword;
i64 : int64;
size : NativeUInt;
begin
if peb64 <> 0 then begin
{$ifdef win64}
result := ReadProcessMemory(process, pointer(peb64 + $18), @i64, 8, size) and (i64 = 0);
{$else}
result := ReadProcessMemory64(process, peb64 + $18, @i64, 8) and (i64 = 0);
{$endif}
end else
result := (peb32 <> 0) and ReadProcessMemory(process, pointer(peb32 + $c), @c1, 4, size) and (c1 = 0);
end;
function GetExeModuleInfos(process: THandle; var module: HMODULE; var nh: TImageNtHeaders32; var dd: pointer) : boolean;
function CheckModule : boolean;
var w1 : word;
c1 : dword;
begin
result := (module <> 0) and
Read(process, module, w1, 2) and (w1 = CEMAGIC) and
Read(process, module + CENEWHDR, c1, 4) and
Read(process, module + c1, nh, sizeOf(nh)) and (nh.Signature = CPEMAGIC) and
(nh.FileHeader.Characteristics and IMAGE_FILE_DLL = 0);
if result then
dd := @PImageNtHeaders(module + w1)^.OptionalHeader.DataDirectory;
end;
var p1, p2 : pointer;
mbi : TMemoryBasicInformation;
c1 : dword;
i64 : int64;
begin
result := false;
if GetVersion and $80000000 <> 0 then begin
p1 := nil;
p2 := nil;
while VirtualQueryEx(process, p1, mbi, sizeOf(mbi)) = sizeOf(mbi) do begin
if mbi.AllocationBase <> p2 then begin
module := HMODULE(mbi.AllocationBase);
if (mbi.State = MEM_COMMIT) and CheckModule then begin
result := true;
break;
end;
p2 := mbi.AllocationBase;
end;
inc(NativeUInt(p1), mbi.RegionSize);
end;
end else begin
module := 0;
if (peb32 <> 0) and Read(process, peb32 + $8, c1, 4) then
module := c1
else
{$ifdef win64}
if (peb64 <> 0) and Read(process, peb64 + $10, i64, 8) then
{$else}
if (peb64 <> 0) and ReadProcessMemory64(process, peb64 + $10, @i64, 8) then
{$endif}
module := i64;
result := (module <> 0) and CheckModule;
end;
end;
type
TRelJump = packed record
jmp : byte; // $e9
target : dword;
end;
function InjectLibraryPatch(process: THandle; pid: dword; thread: THandle) : integer;
{$ifndef win64}
function InjectLibraryPatchXp32(process: THandle) : integer;
type
TInjectRec = packed record
pEntryPoint : TPCardinal;
oldEntryPoint : dword;
oldEntryPointFunc : function (hInstance: HMODULE; reason, reserved: dword) : bool; stdcall;
NtProtectVirtualMemory : function (process: THandle; var addr: pointer; var size: NativeUInt; newProt: dword; var oldProt: dword) : dword; stdcall;
LdrLoadDll : function (path, flags, name: pointer; var handle: HMODULE) : dword; stdcall;
dll : record
len : word; // length * 2
maxLen : word; // length * 2 + 2
str : PWideChar; // @DllStr
chars : array [0..259] of WideChar;
end;
stub : packed record
movEax : byte;
buf : pointer;
jmp : byte;
target : integer;
end;
end;
const
CNewEntryPoint32 : array [0..154] of byte =
($55, $8b, $ec, $83, $c4, $f4, $53, $56, $57, $8b, $d8, $8b, $75, $0c, $83, $fe,
$01, $75, $49, $8b, $03, $89, $45, $fc, $c7, $45, $f8, $04, $00, $00, $00, $8d,
$45, $f4, $50, $6a, $40, $8d, $45, $f8, $50, $8d, $45, $fc, $50, $6a, $ff, $ff,
$53, $0c, $85, $c0, $75, $26, $8b, $53, $04, $8b, $03, $89, $10, $89, $45, $fc,
$c7, $45, $f8, $04, $00, $00, $00, $8d, $45, $f4, $50, $8b, $45, $f4, $50, $8d,
$45, $f8, $50, $8d, $45, $fc, $50, $6a, $ff, $ff, $53, $0c, $83, $7b, $04, $00,
$74, $14, $8b, $45, $10, $50, $56, $8b, $45, $08, $50, $ff, $53, $08, $85, $c0,
$75, $04, $33, $c0, $eb, $02, $b0, $01, $f6, $d8, $1b, $ff, $83, $fe, $01, $75,
$0f, $8d, $45, $f8, $50, $8d, $43, $14, $50, $6a, $00, $6a, $00, $ff, $53, $10,
$8b, $c7, $5f, $5e, $5b, $8b, $e5, $5d, $c2, $0c, $00);
(*
function NewEntryPoint(var injectRec: TInjectRec; d1, d2, reserved, reason: dword; hInstance: NativeUInt) : bool;
var p1 : pointer;
c1, c2 : dword;
begin
with injectRec do begin
if reason = DLL_PROCESS_ATTACH then begin
p1 := pEntryPoint;
c1 := 4;
if NtProtectVirtualMemory($ffffffff, p1, c1, PAGE_EXECUTE_READWRITE, c2) = 0 then begin
pEntryPoint^ := oldEntryPoint;
p1 := pEntryPoint;
c1 := 4;
NtProtectVirtualMemory($ffffffff, p1, c1, c2, c2);
end;
end;
result := (oldEntryPoint = 0) or oldEntryPointFunc(hInstance, reason, reserved);
if reason = DLL_PROCESS_ATTACH then
LdrLoadDll(0, nil, @dll, c1);
end;
end;
*)
var c1 : NativeUInt;
c2, c3 : dword;
ir : TInjectRec;
buf : pointer;
op : dword;
begin
result := 0;
with ir do begin
pEntryPoint := NtProc(''); // get the address where ntdll.dll's entry point is stored
{$ifdef log}log('ntdll.dll''s entry point is stored at ' + IntToHexExA(NativeUInt(pEntryPoint)));{$endif}
if (pEntryPoint <> nil) and
ReadProcessMemory(process, pEntryPoint, @oldEntryPoint, 4, c1) then begin
buf := AllocMemEx(sizeOf(CNewEntryPoint32) + sizeOf(ir), process);
if buf <> nil then begin
if oldEntryPoint = 0 then
oldEntryPointFunc := nil
else oldEntryPointFunc := pointer(ntdllhandle + oldEntryPoint);
NtProtectVirtualMemory := NtProc(CNtProtectVirtualMemory, true);
LdrLoadDll := NtProc(CLdrLoadDll, true);
dll.len := lstrlenW(libW) * 2;
dll.maxLen := dll.len + 2;
dll.str := pointer(NativeUInt(buf) + sizeOf(CNewEntryPoint32) + (NativeUInt(@dll.chars) - NativeUInt(@ir)));
Move(libW^, dll.chars, dll.maxLen);
stub.movEax := $b8; // mov eax, dw
stub.buf := pointer(NativeUInt(buf) + sizeOf(CNewEntryPoint32));
stub.jmp := $e9; // jmp dw
stub.target := - sizeOf(CNewEntryPoint32) - sizeOf(ir);
c2 := NativeUInt(buf) + sizeOf(CNewEntryPoint32) + sizeOf(ir) - sizeOf(stub) - ntdllhandle;
if WriteProcessMemory(process, buf, @CNewEntryPoint32, sizeOf(CNewEntryPoint32), c1) and
WriteProcessMemory(process, pointer(NativeUInt(buf) + sizeOf(CNewEntryPoint32)), @ir, sizeOf(ir), c1) and
VirtualProtectEx(process, pEntryPoint, 4, PAGE_EXECUTE_READWRITE, @c3) then begin
if WriteProcessMemory(process, pEntryPoint, @c2, 4, c1) then
result := 1;
VirtualProtectEx(process, pEntryPoint, 4, c3, @c2);
end;
VirtualProtectEx(process, buf, sizeOf(CNewEntryPoint32) + sizeOf(ir), PAGE_EXECUTE_READ, @op);
end;
end;
end;
end;
{$endif}
type
PInjectLib32 = ^TInjectLib32;
TInjectLib32 = packed record
movEax : byte; // 0xb8
param : dword; // pointer // pointer to "self"
movEcx : byte; // 0xb9
proc : dword; // pointer // pointer to "self.injectFunc"
callEcx : word; // 0xd1ff
magic : dword; // "mciL" / 0x4c69636d
next : dword; // PInjectLib32Old // next dll (if any)
pOldApi : dword; // ^TRelJump // which code location in user land do we patch?
oldApi : TRelJump; // patch backup buffer, contains overwritten code
// align : byte;
dll : TUnicodeStr32; // dll path/name
dllBuf : array [0..259] of WideChar; // string buffer for the dll path/name
npvm : dword; // pointer // ntdll.NtProtectVirtualMemory
lld : dword; // pointer // ntdll.LdrLoadDll
navm : dword; // pointer // ntdll.NtAllocateVirtualMemory
nfvm : dword; // pointer // ntdll.NtFreeVirtualMemory
nqsi : dword; // pointer // ntdll.NtQuerySystemInformation
nde : dword; // pointer // ntdll.NtDelayExcecution
injectFunc : array [0..41] of byte; // will be filled with CInjectLibFunc32 (see below)
// injectFunc : array [0..3514] of byte; // will be filled with CInjectLibFunc32 (see below)
// skip : dword; // skip flag, only in case we can't uninstall our patch
end;
{$ifdef win64}
const CInjectLibFunc32 : array [0..1223] of byte =
($55, $8B, $EC, $83, $C4, $90, $53, $56, $57, $89, $45, $EC, $64, $8B, $05, $30,
$00, $00, $00, $89, $45, $FC, $64, $8B, $05, $18, $00, $00, $00, $8B, $40, $20,
$89, $45, $F8, $64, $8B, $05, $18, $00, $00, $00, $8B, $40, $24, $89, $45, $F4,
$89, $6D, $F0, $8B, $45, $EC, $8B, $40, $14, $8B, $55, $F0, $83, $C2, $04, $89,
$02, $33, $C0, $89, $45, $CC, $33, $C0, $89, $45, $C8, $33, $C0, $89, $45, $C4,
$33, $C0, $89, $45, $C0, $33, $C0, $89, $45, $BC, $33, $C0, $89, $45, $B8, $8B,
$45, $FC, $83, $C0, $0C, $8B, $00, $83, $C0, $14, $89, $45, $D8, $8B, $45, $D8,
$8B, $F0, $8D, $7D, $90, $B9, $09, $00, $00, $00, $F3, $A5, $E9, $32, $04, $00,
$00, $8B, $F0, $8D, $7D, $90, $B9, $09, $00, $00, $00, $F3, $A5, $83, $7D, $B0,
$00, $0F, $84, $1C, $04, $00, $00, $33, $C9, $EB, $01, $41, $8B, $45, $B0, $66,
$83, $3C, $48, $00, $75, $F5, $8B, $45, $B0, $8D, $44, $48, $EE, $81, $78, $04,
$64, $00, $6C, $00, $0F, $85, $F9, $03, $00, $00, $81, $38, $6E, $00, $74, $00,
$0F, $85, $ED, $03, $00, $00, $8B, $45, $B0, $8D, $44, $48, $F6, $81, $78, $04,
$64, $00, $6C, $00, $0F, $85, $D9, $03, $00, $00, $81, $38, $6C, $00, $2E, $00,
$0F, $85, $CD, $03, $00, $00, $8B, $5D, $A0, $8D, $43, $3C, $8B, $00, $03, $C3,
$8B, $70, $78, $03, $F3, $89, $F7, $8B, $47, $18, $48, $85, $C0, $0F, $8C, $F4,
$01, $00, $00, $40, $89, $45, $B4, $33, $F6, $8B, $47, $20, $03, $C3, $8B, $0C,
$B0, $03, $CB, $85, $C9, $0F, $84, $D2, $01, $00, $00, $81, $79, $04, $6F, $61,
$64, $44, $75, $1D, $81, $39, $4C, $64, $72, $4C, $75, $15, $8D, $41, $08, $8B,
$00, $25, $FF, $FF, $FF, $00, $3D, $6C, $6C, $00, $00, $0F, $84, $31, $01, $00,
$00, $81, $79, $04, $6F, $74, $65, $63, $75, $3D, $81, $39, $4E, $74, $50, $72,
$75, $35, $8D, $41, $08, $81, $78, $04, $74, $75, $61, $6C, $75, $29, $81, $38,
$74, $56, $69, $72, $75, $21, $8D, $41, $10, $8B, $50, $04, $8B, $00, $81, $E2,
$FF, $FF, $FF, $00, $81, $FA, $72, $79, $00, $00, $75, $05, $3D, $4D, $65, $6D,
$6F, $0F, $84, $EB, $00, $00, $00, $81, $79, $04, $6C, $6F, $63, $61, $75, $34,
$81, $39, $4E, $74, $41, $6C, $75, $2C, $8D, $41, $08, $81, $78, $04, $72, $74,
$75, $61, $75, $20, $81, $38, $74, $65, $56, $69, $75, $18, $8D, $41, $10, $81,
$78, $04, $6F, $72, $79, $00, $75, $06, $81, $38, $6C, $4D, $65, $6D, $0F, $84,
$AE, $00, $00, $00, $81, $79, $04, $65, $65, $56, $69, $75, $27, $81, $39, $4E,
$74, $46, $72, $75, $1F, $8D, $41, $08, $81, $78, $04, $6C, $4D, $65, $6D, $75,
$13, $81, $38, $72, $74, $75, $61, $75, $0B, $8D, $41, $10, $81, $38, $6F, $72,
$79, $00, $74, $7E, $81, $79, $04, $65, $72, $79, $53, $75, $38, $81, $39, $4E,
$74, $51, $75, $75, $30, $8D, $41, $08, $81, $78, $04, $6D, $49, $6E, $66, $75,
$24, $81, $38, $79, $73, $74, $65, $75, $1C, $8D, $41, $10, $81, $78, $04, $74,
$69, $6F, $6E, $75, $10, $81, $38, $6F, $72, $6D, $61, $75, $08, $8D, $41, $18,
$80, $38, $00, $74, $3D, $81, $79, $04, $6C, $61, $79, $45, $0F, $85, $AB, $00,
$00, $00, $81, $39, $4E, $74, $44, $65, $0F, $85, $9F, $00, $00, $00, $8D, $41,
$08, $81, $78, $04, $74, $69, $6F, $6E, $0F, $85, $8F, $00, $00, $00, $81, $38,
$78, $65, $63, $75, $0F, $85, $83, $00, $00, $00, $8D, $41, $10, $80, $38, $00,
$75, $7B, $8B, $47, $24, $03, $C3, $0F, $B7, $04, $70, $89, $45, $E4, $8B, $47,
$1C, $03, $C3, $8B, $55, $E4, $8B, $04, $90, $89, $45, $E4, $33, $C0, $8A, $41,
$02, $83, $F8, $50, $7F, $13, $74, $25, $83, $E8, $41, $74, $2A, $83, $E8, $03,
$74, $43, $83, $E8, $02, $74, $2A, $EB, $44, $83, $E8, $51, $74, $2D, $83, $E8,
$21, $75, $3A, $8B, $45, $E4, $03, $C3, $89, $45, $C8, $EB, $30, $8B, $45, $E4,
$03, $C3, $89, $45, $CC, $EB, $26, $8B, $45, $E4, $03, $C3, $89, $45, $C4, $EB,
$1C, $8B, $45, $E4, $03, $C3, $89, $45, $C0, $EB, $12, $8B, $45, $E4, $03, $C3,
$89, $45, $BC, $EB, $08, $8B, $45, $E4, $03, $C3, $89, $45, $B8, $46, $FF, $4D,
$B4, $0F, $85, $12, $FE, $FF, $FF, $83, $7D, $C8, $00, $0F, $84, $BE, $01, $00,
$00, $83, $7D, $CC, $00, $0F, $84, $B4, $01, $00, $00, $33, $DB, $83, $7D, $C4,
$00, $0F, $84, $A6, $00, $00, $00, $83, $7D, $C0, $00, $0F, $84, $9C, $00, $00,
$00, $83, $7D, $BC, $00, $0F, $84, $92, $00, $00, $00, $83, $7D, $B8, $00, $0F,
$84, $88, $00, $00, $00, $33, $C0, $89, $45, $E4, $8D, $45, $E4, $50, $6A, $00,
$6A, $00, $6A, $05, $FF, $55, $BC, $83, $7D, $E4, $00, $74, $70, $8B, $45, $E4,
$03, $C0, $89, $45, $E8, $33, $C0, $89, $45, $DC, $6A, $04, $68, $00, $10, $00,
$00, $8D, $45, $E8, $50, $6A, $00, $8D, $45, $DC, $50, $6A, $FF, $FF, $55, $C4,
$85, $C0, $75, $49, $6A, $00, $8B, $45, $E8, $50, $8B, $45, $DC, $50, $6A, $05,
$FF, $55, $BC, $85, $C0, $75, $1F, $8B, $45, $DC, $8B, $50, $44, $3B, $55, $F8,
$75, $08, $8B, $98, $DC, $00, $00, $00, $EB, $0C, $8B, $10, $85, $D2, $74, $06,
$03, $D0, $8B, $C2, $EB, $E4, $33, $C0, $89, $45, $E8, $68, $00, $80, $00, $00,
$8D, $45, $E8, $50, $8D, $45, $DC, $50, $6A, $FF, $FF, $55, $C0, $85, $DB, $74,
$47, $3B, $5D, $F4, $74, $42, $C7, $45, $E4, $01, $00, $00, $00, $8B, $45, $EC,
$8B, $40, $14, $8A, $10, $8B, $4D, $EC, $3A, $51, $18, $75, $0B, $8B, $40, $01,
$8B, $55, $EC, $3B, $42, $19, $74, $20, $C7, $45, $D0, $60, $79, $FE, $FF, $C7,
$45, $D4, $FF, $FF, $FF, $FF, $8D, $45, $D0, $50, $6A, $00, $FF, $55, $B8, $FF,
$45, $E4, $83, $7D, $E4, $65, $75, $C5, $8B, $45, $EC, $8B, $40, $14, $8A, $10,
$8B, $4D, $EC, $3A, $51, $18, $75, $0F, $8B, $50, $01, $8B, $4D, $EC, $3B, $51,
$19, $0F, $84, $98, $00, $00, $00, $89, $45, $DC, $C7, $45, $E4, $05, $00, $00,
$00, $8D, $45, $E0, $50, $6A, $40, $8D, $45, $E4, $50, $8D, $45, $DC, $50, $6A,
$FF, $FF, $55, $CC, $85, $C0, $75, $32, $8B, $45, $EC, $8B, $40, $14, $8B, $55,
$EC, $8B, $4A, $18, $89, $08, $8A, $4A, $1C, $88, $48, $04, $C7, $45, $E4, $05,
$00, $00, $00, $8D, $45, $E0, $50, $8B, $45, $E0, $50, $8D, $45, $E4, $50, $8D,
$45, $DC, $50, $6A, $FF, $FF, $55, $CC, $EB, $0D, $8B, $45, $EC, $C7, $80, $00,
$10, $00, $00, $FF, $FF, $FF, $FF, $85, $DB, $74, $05, $3B, $5D, $F4, $75, $2F,
$8D, $45, $E4, $50, $8B, $45, $EC, $83, $C0, $1D, $50, $6A, $00, $6A, $00, $FF,
$55, $C8, $8B, $45, $EC, $8B, $40, $10, $89, $45, $EC, $83, $7D, $EC, $00, $75,
$DF, $EB, $0C, $8B, $45, $90, $3B, $45, $D8, $0F, $85, $C2, $FB, $FF, $FF, $5F,
$5E, $5B, $8B, $E5, $5D,
// $C3}; // can't use this, anymore, due to CET
$58, $FF, $E0); // so we replace "RET" with "POP EAX + JMP EAX"
// the CInjectLib32FuncOld data is a compilation of the following Delphi code
// this user land code is copied to newly created wow64 processes to execute the dll injection
// this code is used by default for all 64bit OSs
// the 32bit injection code is rather complicated in the 64bit driver
// the reason for that is that the 64bit driver doesn''t know some 32bit ntdll APIs
// so the 32bit code has to find out the address of these APIs at runtime
// procedure InjectLibFunc32Old(buf: PInjectLib32);
// var ctid, mtid : dword; // current and main thread ids
// cpid : dword;
// ebp_ : dword;
// size : NativeInt;
// c1, c2 : dword;
// peb : dword;
// p1 : pointer;
// loopEnd : TPNtModuleInfo;
// mi : TNtModuleInfo;
// len : dword;
// ntdll : dword;
// nh : PImageNtHeaders;
// ed : PImageExportDirectory;
// i1 : integer;
// api : pchar;
// npi : ^TNtProcessInfo;
// sleep : int64;
// npvm : function (process: THandle; var addr: pointer; var size: NativeUInt; newProt: dword; var oldProt: dword) : dword; stdcall;
// lld : function (path, flags, name: pointer; var handle: HMODULE) : dword; stdcall;
// navm : function (process: THandle; var addr: pointer; zeroBits: NativeUInt; var regionSize: NativeInt; allocationType, protect: dword) : dword; stdcall;
// nfvm : function (process: THandle; var addr: pointer; var regionSize: NativeInt; freeType: dword) : dword; stdcall;
// nqsi : function (infoClass: dword; buffer: pointer; bufSize: dword; returnSize: TPCardinal) : dword; stdcall;
// nde : function (alertable: pointer; var delayInterval: int64) : dword; stdcall;
// begin
// asm
// mov eax, fs:[$30]
// mov peb, eax
// mov eax, fs:[$18]
// mov eax, [eax+$20]
// mov cpid, eax
// mov eax, fs:[$18]
// mov eax, [eax+$24]
// mov ctid, eax
// mov ebp_, ebp
// end;
// TPPointer(ebp_ + 4)^ := buf.pOldApi;
//
// npvm := nil;
// lld := nil;
// navm := nil;
// nfvm := nil;
// nqsi := nil;
// nde := nil;
//
// // step 1: locate ntdll.dll
// loopEnd := pointer(dword(pointer(peb + $C)^) + $14);
// mi := loopEnd^;
// while mi.next <> loopEnd do begin
// mi := mi.next^;
// if mi.name <> nil then begin
// len := 0;
// while mi.name[len] <> #0 do
// inc(len);
// if (int64(pointer(@mi.name[len - 9])^) = $006c00640074006e) and // ntdl
// (int64(pointer(@mi.name[len - 5])^) = $006c0064002e006c) then begin // l.dl
// // found it!
// ntdll := mi.handle;
//
// // step 2: locate LdrLoadDll and NtProtectVirtualMemory
// nh := pointer(ntdll + dword(pointer(ntdll + $3C)^));
// dword(ed) := ntdll + nh^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
// for i1 := 0 to ed^.NumberOfNames - 1 do begin
// api := pointer(ntdll + TPACardinal(ntdll + ed^.AddressOfNames)^[i1]);
// if (api <> nil) and
// ( ( (int64(pointer(@api[ 0])^) = $4464616f4c72644c) and // LdrLoadD
// (dword(pointer(@api[ 8])^) and $00ffffff = $00006c6c ) ) or // ll
// ( (int64(pointer(@api[ 0])^) = $6365746f7250744e) and // NtProtec
// (int64(pointer(@api[ 8])^) = $6c61757472695674) and // tVirtual
// (int64(pointer(@api[16])^) and $00ffffffffffffff = $000079726f6d654d) ) or // Memory
// ( (int64(pointer(@api[ 0])^) = $61636F6C6C41744E) and // NtAlloca
// (int64(pointer(@api[ 8])^) = $6175747269566574) and // teVirtua
// (int64(pointer(@api[16])^) = $0079726F6D654D6C) ) or // lMemory
// ( (int64(pointer(@api[ 0])^) = $695665657246744E) and // NtFreeVi
// (int64(pointer(@api[ 8])^) = $6D654D6C61757472) and // rtualMem
// (dword(pointer(@api[16])^) = $0079726F ) ) or // ory
// ( (int64(pointer(@api[ 0])^) = $537972657551744E) and // NtQueryS
// (int64(pointer(@api[ 8])^) = $666E496D65747379) and // ystemInf
// (int64(pointer(@api[16])^) = $6E6F6974616D726F) and // ormation
// (byte (pointer(@api[24])^) = $00 ) ) or // ormation
// ( (int64(pointer(@api[ 0])^) = $4579616c6544744e) and // NtDelayE
// (int64(pointer(@api[ 8])^) = $6e6f697475636578) and // xecution
// (byte (pointer(@api[16])^) = $00 ) ) ) then begin
// c1 := TPAWord(ntdll + ed^.AddressOfNameOrdinals)^[i1];
// c1 := TPACardinal(ntdll + ed^.AddressOfFunctions)^[c1];
// case api[2] of
// 'r': lld := pointer(ntdll + c1);
// 'P': npvm := pointer(ntdll + c1);
// 'A': navm := pointer(ntdll + c1);
// 'F': nfvm := pointer(ntdll + c1);
// 'Q': nqsi := pointer(ntdll + c1);
// 'D': nde := pointer(ntdll + c1);
// end;
// end;
// end;
// if (@lld <> nil) and (@npvm <> nil) then begin
// // found both APIs!
// mtid := 0;
// if (@navm <> nil) and (@nfvm <> nil) and (@nqsi <> nil) and (@nde <> nil) then begin
// c1 := 0;
// nqsi(5, nil, 0, @c1);
// if c1 <> 0 then begin
// size := c1 * 2;
// p1 := nil;
// if navm(THandle(-1), p1, 0, size, MEM_COMMIT, PAGE_READWRITE) = 0 then begin
// if nqsi(5, p1, size, nil) = 0 then begin
// npi := p1;
// while true do begin
// if npi^.pid = cpid then begin
// mtid := npi^.threads[0].tid_nt5;
// break;
// end;
// if npi^.offset = 0 then
// break;
// npi := pointer(NativeUInt(npi) + npi^.offset);
// end;
// end;
// size := 0;
// nfvm(THandle(-1), p1, size, MEM_RELEASE);
// end;
// end;
// end;
// if (mtid <> 0) and (mtid <> ctid) then begin
// // This is not the main thread! This usually doesn't happen, except sometimes in win10.
// // We "solve" this by waiting until the main thread has completed executing our loader stub.
// // Max wait time 1 second, just to be safe.
// for c1 := 1 to 100 do begin
// if (buf.pOldApi^.jmp = buf.oldApi.jmp) and (buf.pOldApi^.target = buf.oldApi.target) then
// // Our loader stub patch was removed, so we assume that the main thread has completed running it.
// break;
// sleep := -100000; // 10 milliseconds
// nde(nil, sleep);
// end;
// end;
// if (buf.pOldApi^.jmp <> buf.oldApi.jmp) or (buf.pOldApi^.target <> buf.oldApi.target) then begin
// // step 3: uninstall our patch
// p1 := buf.pOldApi;
// c1 := 5;
// if npvm(dword(-1), p1, c1, PAGE_EXECUTE_READWRITE, c2) = 0 then begin
// buf.pOldApi^ := buf.oldApi;
// c1 := 5;
// npvm(dword(-1), p1, c1, c2, c2);
// end else begin
// // For some reason we can't uninstall our patch correctly.
// // That happens if "dynamic code execution" is forbidden.
// // As a workaround we keep our callback installed, but we
// // rewire it to execute our copy of the original API code.
// buf.skip := true;
// end;
// // step 4: finally load the to-be-injected dll - but only if we're in the context of the main thread
// if (mtid = 0) or (mtid = ctid) then
// repeat
// lld(nil, nil, @buf.dll, c1);
// buf := buf.next;
// until buf = nil;
// end;
// end;
// break;
// end;
// end;
// end;
// end;
{$else}
const CInjectLibFunc32 : array [0..434] of byte =
($55, $8B, $EC, $83, $C4, $D8, $53, $56, $8B, $D8, $64, $8B, $05, $18, $00, $00,
$00, $8B, $40, $20, $89, $45, $FC, $64, $8B, $05, $18, $00, $00, $00, $8B, $40,
$24, $89, $45, $F8, $89, $6D, $F4, $8B, $45, $F4, $83, $C0, $04, $8B, $53, $14,
$89, $10, $33, $F6, $83, $BB, $3D, $02, $00, $00, $00, $0F, $84, $94, $00, $00,
$00, $33, $C0, $89, $45, $F0, $8D, $45, $F0, $50, $6A, $00, $6A, $00, $6A, $05,
$FF, $93, $3D, $02, $00, $00, $83, $7D, $F0, $00, $74, $79, $8B, $45, $F0, $03,
$C0, $89, $45, $E8, $33, $C0, $89, $45, $E4, $6A, $04, $68, $00, $10, $00, $00,
$8D, $45, $E8, $50, $6A, $00, $8D, $45, $E4, $50, $6A, $FF, $FF, $93, $35, $02,
$00, $00, $85, $C0, $75, $4F, $6A, $00, $8B, $45, $F0, $50, $8B, $45, $E4, $50,
$6A, $05, $FF, $93, $3D, $02, $00, $00, $85, $C0, $75, $1F, $8B, $45, $E4, $8B,
$50, $44, $3B, $55, $FC, $75, $08, $8B, $B0, $DC, $00, $00, $00, $EB, $0C, $8B,
$10, $85, $D2, $74, $06, $03, $D0, $8B, $C2, $EB, $E4, $33, $C0, $89, $45, $E8,
$68, $00, $80, $00, $00, $8D, $45, $E8, $50, $8D, $45, $E4, $50, $6A, $FF, $FF,
$93, $39, $02, $00, $00, $85, $F6, $74, $41, $3B, $75, $F8, $74, $3C, $C7, $45,
$F0, $01, $00, $00, $00, $8B, $43, $14, $8A, $10, $3A, $53, $18, $75, $08, $8B,
$40, $01, $3B, $43, $19, $74, $23, $C7, $45, $D8, $60, $79, $FE, $FF, $C7, $45,
$DC, $FF, $FF, $FF, $FF, $8D, $45, $D8, $50, $6A, $00, $FF, $93, $41, $02, $00,
$00, $FF, $45, $F0, $83, $7D, $F0, $65, $75, $CB, $8B, $43, $14, $8A, $10, $3A,
$53, $18, $75, $08, $8B, $50, $01, $3B, $53, $19, $74, $7F, $89, $45, $E4, $C7,
$45, $F0, $05, $00, $00, $00, $8D, $45, $EC, $50, $6A, $40, $8D, $45, $F0, $50,
$8D, $45, $E4, $50, $6A, $FF, $FF, $93, $2D, $02, $00, $00, $85, $C0, $75, $2F,
$8B, $43, $14, $8B, $53, $18, $89, $10, $8A, $53, $1C, $88, $50, $04, $C7, $45,
$F0, $05, $00, $00, $00, $8D, $45, $EC, $50, $8B, $45, $EC, $50, $8D, $45, $F0,
$50, $8D, $45, $E4, $50, $6A, $FF, $FF, $93, $2D, $02, $00, $00, $EB, $0A, $C7,
$83, $00, $10, $00, $00, $FF, $FF, $FF, $FF, $85, $F6, $74, $05, $3B, $75, $F8,
$75, $19, $8D, $45, $F0, $50, $8D, $43, $1D, $50, $6A, $00, $6A, $00, $FF, $93,
$31, $02, $00, $00, $8B, $5B, $10, $85, $DB, $75, $E7, $5E, $5B, $8B, $E5, $5D,
// $C3}; // can't use this, anymore, due to CET
$58, $FF, $E0); // so we replace "RET" with "POP EAX + JMP EAX"
// the CInjectLibFunc32Old data is a compilation of the following Delphi code
// this user land code is copied to newly created 32bit processes to execute the dll injection
// this solution is used by default in all 32bit OSs
// procedure InjectLibFunc32Old(buf: PInjectLib32);
// var ctid, mtid : dword; // current and main thread ids
// cpid : dword;
// ebp_ : dword;
// c1, c2 : dword;
// size : NativeInt;
// p1 : pointer;
// sleep : int64;
// npi : ^TNtProcessInfo;
// begin
// asm
// mov eax, fs:[$18]
// mov eax, [eax+$20]
// mov cpid, eax
// mov eax, fs:[$18]
// mov eax, [eax+$24]
// mov ctid, eax
// mov ebp_, ebp
// end;
// TPPointer(ebp_ + 4)^ := buf.pOldApi;
// mtid := 0;
// if @buf.nqsi <> nil then begin
// c1 := 0;
// buf.nqsi(5, nil, 0, @c1);
// if c1 <> 0 then begin
// size := c1 * 2;
// p1 := nil;
// if buf.navm(THandle(-1), p1, 0, size, MEM_COMMIT, PAGE_READWRITE) = 0 then begin
// if buf.nqsi(5, p1, c1, nil) = 0 then begin
// npi := p1;
// while true do begin
// if npi^.pid = cpid then begin
// mtid := npi^.threads[0].tid_nt5;
// break;
// end;
// if npi^.offset = 0 then
// break;
// npi := pointer(NativeUInt(npi) + npi^.offset);
// end;
// end;
// size := 0;
// buf.nfvm(THandle(-1), p1, size, MEM_RELEASE);
// end;
// end;
// end;
// if (mtid <> 0) and (mtid <> ctid) then begin
// // This is not the main thread! This usually doesn't happen, except sometimes in win10.
// // We "solve" this by waiting until the main thread has completed executing our loader stub.
// // Max wait time 1 second, just to be safe.
// for c1 := 1 to 100 do begin
// if (buf.pOldApi^.jmp = buf.oldApi.jmp) and (buf.pOldApi^.target = buf.oldApi.target) then
// // Our loader stub patch was removed, so we assume that the main thread has completed running it.
// break;
// sleep := -100000;// 10 milliseconds
// buf.nde(nil, sleep);
// end;
// end;
// if (buf.pOldApi^.jmp <> buf.oldApi.jmp) or (buf.pOldApi^.target <> buf.oldApi.target) then begin
// // step 3: uninstall our patch
// p1 := buf.pOldApi;
// c1 := 5;
// if buf.npvm(dword(-1), p1, c1, PAGE_EXECUTE_READWRITE, c2) = 0 then begin
// buf.pOldApi^ := buf.oldApi;
// c1 := 5;
// buf.npvm(dword(-1), p1, c1, c2, c2);
// end else begin
// // For some reason we can't uninstall our patch correctly.
// // That happens if "dynamic code execution" is forbidden.
// // As a workaround we keep our callback installed, but we
// // rewire it to execute our copy of the original API code.
// buf.skip := true;
// end;
// // step 4: finally load the to-be-injected dll - but only if we're in the context of the main thread
// if (mtid = 0) or (mtid = ctid) then
// repeat
// buf.lld(nil, nil, @buf.dll, c1);
// buf := buf.next;
// until buf = nil;
// end;
// end;
const CInjectLibApcFunc32 : array [0..29] of byte =
($55, $8B, $EC, $51, $8B, $45, $08, $8D, $55, $FC, $52, $8D, $50, $1D, $52, $6A,
$00, $6A, $00, $FF, $90, $31, $02, $00, $00, $59, $5D, $C2, $04, $00);
// the CInjectLibApcFunc32 data is a compilation of the following Delphi code
// this user land code is copied to newly created 32bit processes to execute the dll injection
// this solution is used by default in all 32bit OSs
// procedure InjectLibApcFunc32(buf: PInjectLib32); stdcall;
// var c1 : HMODULE;
// begin
// buf.lld(0, nil, @buf.dll, c1);
// end;
{$endif}
function IsProcessDotNet64(process: THandle) : boolean;
// find out if the specified 64bit process is a .NET process
var exeImage : int64;
nh : packed record
Signature: DWORD;
FileHeader: TImageFileHeader;
OptionalHeader: TImageOptionalHeader64;
end;
nhOffset : dword;
begin
{$ifdef win64}
result := Read(process, GetPeb(process) + $10, exeImage, sizeOf(exeImage)) and
Read(process, exeImage + CENEWHDR, nhOffset, sizeOf(nhOffset)) and
Read(process, exeImage + nhOffset, nh, sizeOf(nh )) and
(nh.OptionalHeader.DataDirectory[14].VirtualAddress <> 0) and
(nh.OptionalHeader.DataDirectory[14].Size <> 0);
{$else}
result := ReadProcessMemory64(process, GetPeb64(process) + $10, @exeImage, sizeOf(exeImage)) and
ReadProcessMemory64(process, exeImage + CENEWHDR, @nhOffset, sizeOf(nhOffset)) and
ReadProcessMemory64(process, exeImage + nhOffset, @nh, sizeOf(nh )) and
(nh.OptionalHeader.DataDirectory[14].VirtualAddress <> 0) and
(nh.OptionalHeader.DataDirectory[14].Size <> 0);
{$endif}
end;
function IsProcessDotNet32(process: THandle) : boolean;
// find out if the specified 32bit process is a .NET process
var exeImage : dword;
nh : TImageNtHeaders32;
nhOffset : dword;
c1 : NativeUInt;
begin
result := ReadProcessMemory(process, pointer({$ifdef win64}GetPeb32{$else}GetPeb{$endif}(process) + $08), @exeImage, sizeOf(exeImage), c1) and
ReadProcessMemory(process, pointer(exeImage + CENEWHDR), @nhOffset, sizeOf(nhOffset), c1) and
ReadProcessMemory(process, pointer(exeImage + nhOffset), @nh, sizeOf(nh ), c1) and
(nh.OptionalHeader.DataDirectory[14].VirtualAddress <> 0) and
(nh.OptionalHeader.DataDirectory[14].Size <> 0);
end;
function IsProcessDotNet(process: THandle) : boolean;
begin
if Is64bitProcess(process) then
result := IsProcessDotNet64(process)
else
result := IsProcessDotNet32(process);
end;
function Find32bitPatchAddress(process: THandle; module: HMODULE; const nh: TImageNtHeaders32) : pointer;
begin
{$ifndef win64}
if VmWareInjectionMode or Is64bitOS then begin
{$endif}
if nh.OptionalHeader.AddressOfEntryPoint <> 0 then
result := pointer(module + nh.OptionalHeader.AddressOfEntryPoint)
else
result := nil;
{$ifndef win64}
end else
result := NtProc(CNtTestAlert, true);
{$endif}
end;
function InjectLibraryPatchNt32(process: THandle; pid: dword; module: HMODULE; const nh: TImageNtHeaders32; dd: pointer) : integer;
// inject a dll into a newly started 32bit process
var buf : PInjectLib32;
il : TInjectLib32;
rj : TRelJump;
c1 : NativeUInt;
op : dword;
zero : dword;
begin
result := 0;
buf := AllocMemEx(2 * 4096, process);
if buf <> nil then begin
// we were able to allocate a buffer in the newly created process
{$ifndef win64}
if LdrLoadDll32 = nil then
LdrLoadDll32 := NtProc(CLdrLoadDll, true);
if NtProtectVirtualMemory32 = nil then
NtProtectVirtualMemory32 := NtProc(CNtProtectVirtualMemory, true);
if NtAllocateVirtualMemory32 = nil then
NtAllocateVirtualMemory32 := NtProc(CNtAllocateVirtualMemory, true);
if NtFreeVirtualMemory32 = nil then
NtFreeVirtualMemory32 := NtProc(CNtFreeVirtualMemory, true);
if NtQuerySystemInformation32 = nil then
NtQuerySystemInformation32 := NtProc(CNtQuerySystemInformation, true);
if NtDelayExecution32 = nil then
NtDelayExecution32 := NtProc(CNtDelayExecution, true);
{$endif}
il.movEax := $b8;
il.param := dword(buf);
il.movEcx := $b9;
il.proc := dword(@buf.injectFunc);
il.callEcx := $d1ff;
il.magic := 0;
il.next := 0;
il.pOldApi := dword(Find32bitPatchAddress(process, module, nh));
il.dll.len := lstrlenW(libW) * 2;
il.dll.maxLen := il.dll.len + 2;
il.dll.str := dword(@buf.dllBuf);
lstrcpyW(il.dllBuf, libW);
il.npvm := dword(NtProtectVirtualMemory32);
il.lld := dword(LdrLoadDll32);
il.navm := dword(NtAllocateVirtualMemory32);
il.nfvm := dword(NtFreeVirtualMemory32);
il.nqsi := dword(NtQuerySystemInformation32);
il.nde := dword(NtDelayExecution32);
// il.skip := 0;
TPAInt64(@il.injectFunc)[0] := int64($740000001000b883); // 83b80010000000 cmp dword ptr [eax + $1000], $00
TPAWord (@il.injectFunc)[4] := $5921; // 7421 jz +$21
zero := 0;
if (il.pOldApi <> 0) and
ReadProcessMemory (process, pointer(il.pOldApi), @il.oldApi, 5, c1) and (c1 = 5) and
ReadProcessMemory (process, pointer(il.pOldApi), @il.injectFunc[10], 32, c1) and (c1 = 32) and
WriteProcessMemory(process, buf, @il, sizeOf(TInjectLib32), c1) and (c1 = sizeOf(TInjectLib32)) and
WriteProcessMemory(process, pointer(NativeUInt(buf) + sizeOf(TInjectLib32)), @CInjectLibFunc32, sizeOf(CInjectLibFunc32), c1) and (c1 = sizeOf(CInjectLibFunc32)) and
WriteProcessMemory(process, pointer(NativeUInt(buf) + 4096), @zero, 4, c1) and (c1 = 4) then begin
// we've successfully initialized the buffer
if VirtualProtectEx(process, pointer(il.pOldApi), 5, PAGE_EXECUTE_READWRITE, @op) then begin
// we successfully unprotected the to-be-patched code, so now we can patch it
rj.jmp := $e9;
rj.target := NativeUInt(buf) - NativeUInt(il.pOldApi) - 5;
WriteProcessMemory(process, pointer(il.pOldApi), @rj, 5, c1);
// now restore the original page protection
VirtualProtectEx(process, pointer(il.pOldApi), 5, op, @op);
result := 1;
end;
end;
VirtualProtectEx(process, buf, 4096, PAGE_EXECUTE_READ, @op);
VirtualProtectEx(process, pointer(NativeUInt(buf) + 4096), 4096, PAGE_READWRITE, @op);
end;
end;
{$ifndef win64}
function InjectLibraryApc32(process: THandle; pid: dword; thread: THandle) : integer;
// inject a dll into a newly started 32bit process using APC
var buf : PInjectLib32;
il : TInjectLib32;
c1 : NativeUInt;
th : THandle;
op : dword;
begin
result := 0;
if @NtQueueApcThread = nil then
NtQueueApcThread := NtProc(CNtQueueApcThread);
if @NtQueueApcThread <> nil then begin
if thread = 0 then
th := OpenFirstThread(process, pid)
else
th := thread;
if th <> 0 then begin
buf := AllocMemEx(sizeOf(TInjectLib32) + sizeof(CInjectLibApcFunc32), process);
if buf <> nil then begin
// we were able to allocate a buffer in the newly created process
{$ifndef win64}
if LdrLoadDll32 = nil then
LdrLoadDll32 := NtProc(CLdrLoadDll, true);
{$endif}
ZeroMemory(@il, sizeOf(TInjectLib32));
il.dll.len := lstrlenW(libW) * 2;
il.dll.maxLen := il.dll.len + 2;
il.dll.str := dword(@buf.dllBuf);
lstrcpyW(il.dllBuf, libW);
il.lld := dword(LdrLoadDll32);
if WriteProcessMemory(process, buf, @il, sizeOf(TInjectLib32), c1) and (c1 = sizeOf(TInjectLib32)) and
WriteProcessMemory(process, @buf.injectFunc, @CInjectLibApcFunc32, sizeOf(CInjectLibApcFunc32), c1) and (c1 = sizeOf(CInjectLibApcFunc32)) and
(NtQueueApcThread(th, @buf.injectFunc, buf, nil, nil) = 0) then //QueueUserApc(@buf.injectFunc, th, buf) then
// we've successfully initialized the buffer
result := 1;
VirtualProtectEx(process, buf, sizeOf(TInjectLib32) + sizeof(CInjectLibApcFunc32), PAGE_EXECUTE_READ, @op);
end;
if thread = 0 then
CloseHandle(th);
end;
end;
end;
{$endif}
type
PInjectLib64 = ^TInjectLib64;
TInjectLib64 = packed record
movRax : word; // 0xb848
retAddr : int64; // patched API
pushRax : byte; // 0x50
pushRcx : byte; // 0x51
pushRdx : byte; // 0x52
pushR8 : word; // 0x5041
pushR9 : word; // 0x5141
subRsp28 : dword; // 0x28ec8348
movRcx : word; // 0xb948
param : int64; // pointer to "self"
movRdx : word; // 0xba48
proc : int64; // pointer to "self.injectFunc"
jmpEdx : word; // 0xe2ff
magic : dword; // "mciL" / 0x4c69636d
next : int64; // next dll (if any)
pOldApi : int64; // which code location in user land do we patch?
oldApi : TRelJump; // patch backup buffer, contains overwritten code
dll : TUnicodeStr64; // dll path/name
dllBuf : array [0..259] of WideChar; // string buffer for the dll path/name
npvm : int64; // ntdll.NtProtectVirtualMemory
lld : int64; // ntdll.LdrLoadDll
navm : int64; // ntdll.NtAllocateVirtualMemory
nfvm : int64; // ntdll.NtFreeVirtualMemory
nqsi : int64; // ntdll.NtQuerySystemInformation
nde : int64; // ntdll.NtDelayExcecution
injectFunc : array [0..3443] of byte; // will be filled with CInjectLibFunc64 (see below)
skip : dword;
end;
const CInjectLibFunc64 : array [0..537] of byte =
($4C, $8B, $DC, $53, $55, $56, $57, $41, $56, $41, $57, $48, $83, $EC, $58, $65,
$48, $8B, $04, $25, $30, $00, $00, $00, $33, $FF, $49, $83, $CE, $FF, $8B, $70,
$40, $8B, $68, $48, $48, $8B, $81, $7C, $02, $00, $00, $48, $85, $C0, $48, $8B,
$D9, $44, $8D, $7F, $05, $0F, $84, $0D, $01, $00, $00, $41, $21, $7B, $08, $4D,
$8D, $4B, $08, $45, $33, $C0, $33, $D2, $41, $8B, $CF, $FF, $D0, $8B, $84, $24,
$90, $00, $00, $00, $85, $C0, $0F, $84, $EC, $00, $00, $00, $48, $21, $BC, $24,
$A8, $00, $00, $00, $8D, $0C, $00, $4C, $8D, $8C, $24, $A0, $00, $00, $00, $48,
$89, $8C, $24, $A0, $00, $00, $00, $48, $8D, $94, $24, $A8, $00, $00, $00, $45,
$33, $C0, $49, $8B, $CE, $C7, $44, $24, $28, $04, $00, $00, $00, $C7, $44, $24,
$20, $00, $10, $00, $00, $FF, $93, $6C, $02, $00, $00, $85, $C0, $0F, $85, $A5,
$00, $00, $00, $44, $8B, $84, $24, $A0, $00, $00, $00, $48, $8B, $94, $24, $A8,
$00, $00, $00, $45, $33, $C9, $41, $8B, $CF, $FF, $93, $7C, $02, $00, $00, $85,
$C0, $75, $1F, $48, $8B, $8C, $24, $A8, $00, $00, $00, $EB, $09, $39, $39, $74,
$11, $8B, $01, $48, $03, $C8, $48, $39, $71, $50, $75, $F1, $8B, $B9, $30, $01,
$00, $00, $48, $83, $A4, $24, $A0, $00, $00, $00, $00, $4C, $8D, $84, $24, $A0,
$00, $00, $00, $48, $8D, $94, $24, $A8, $00, $00, $00, $41, $B9, $00, $80, $00,
$00, $49, $8B, $CE, $FF, $93, $74, $02, $00, $00, $85, $FF, $74, $3A, $3B, $FD,
$74, $36, $BE, $01, $00, $00, $00, $48, $8B, $4B, $37, $8A, $43, $3F, $38, $01,
$75, $08, $8B, $43, $40, $39, $41, $01, $74, $1E, $48, $8D, $54, $24, $40, $33,
$C9, $48, $C7, $44, $24, $40, $60, $79, $FE, $FF, $FF, $93, $84, $02, $00, $00,
$83, $C6, $01, $83, $FE, $64, $7C, $CF, $48, $8B, $4B, $37, $8A, $43, $3F, $38,
$01, $75, $0C, $8B, $43, $40, $39, $41, $01, $0F, $84, $A0, $00, $00, $00, $48,
$8D, $84, $24, $98, $00, $00, $00, $48, $89, $4C, $24, $38, $4C, $8D, $44, $24,
$30, $48, $8D, $54, $24, $38, $41, $B9, $40, $00, $00, $00, $49, $8B, $CE, $48,
$89, $44, $24, $20, $4C, $89, $7C, $24, $30, $FF, $93, $5C, $02, $00, $00, $85,
$C0, $75, $3E, $8B, $43, $3F, $48, $8B, $53, $37, $4C, $8D, $44, $24, $30, $89,
$02, $8A, $43, $43, $49, $8B, $CE, $88, $42, $04, $44, $8B, $8C, $24, $98, $00,
$00, $00, $48, $8D, $84, $24, $98, $00, $00, $00, $48, $8D, $54, $24, $38, $48,
$89, $44, $24, $20, $4C, $89, $7C, $24, $30, $FF, $93, $5C, $02, $00, $00, $EB,
$0A, $C7, $83, $00, $10, $00, $00, $01, $00, $00, $00, $85, $FF, $74, $04, $3B,
$FD, $75, $1C, $4C, $8D, $43, $44, $4C, $8D, $4C, $24, $30, $33, $D2, $33, $C9,
$FF, $93, $64, $02, $00, $00, $48, $8B, $5B, $2F, $48, $85, $DB, $75, $E4, $48,
$83, $C4, $58, $41, $5F, $41, $5E, $5F, $5E, $5D, $5B, // $C3};
// $48, $83, $C4, $28, $41, $59, $41, $58, $5A, $59, $C3}; // can't use this, anymore, due to CET
$48, $83, $C4, $28, $41, $59, $41, $58, $5A, $59, $41, $5B, $49, $FF, $E3); // so we replace "RET" with "POP R11 + JMP R11"
// the CInjectLib64Func data is a compilation of the following C++ code
// it got a manually created extended "tail", though (last 11 bytes)
// this user land code is copied to newly created 64bit processes to execute the dll injection
// static void InjectLib64Func(InjectLib64 *buf)
// {
// ULONG_PTR* ptib = (ULONG_PTR*) NtCurrentTeb();
// ULONG cpid = (ULONG) ptib[8];
// ULONG ctid = (ULONG) ptib[9];
//
// ULONG mtid = 0;
// if (buf->nqsi)
// {
// ULONG c1 = 0;
// buf->nqsi(SystemProcessInformation, NULL, 0, &c1);
// if (c1)
// {
// SIZE_T size = c1 * 2;
// PVOID p1 = NULL;
// if (buf->navm((HANDLE) -1, &p1, 0, &size, MEM_COMMIT, PAGE_READWRITE) == 0)
// {
// if (buf->nqsi(SystemProcessInformation, p1, (ULONG) size, NULL) == 0)
// {
// SYSTEM_PROCESS_INFORMATION* npi = (SYSTEM_PROCESS_INFORMATION*) p1;
// while (true)
// {
// if (npi->Process.UniqueProcessId == cpid)
// {
// mtid = (ULONG) npi->Process_NT5.Threads[0].ClientId.UniqueThread;
// break;
// }
// if (npi->Process.Next == 0)
// break;
// npi = (SYSTEM_PROCESS_INFORMATION*) (((ULONG_PTR) npi) + npi->Process.Next);
// }
// }
// size = 0;
// buf->nfvm((HANDLE) -1, &p1, &size, MEM_RELEASE);
// }
// }
// }
// if ((mtid) && (mtid != ctid))
// {
// // This is not the main thread! This usually doesn't happen, except sometimes in win10.
// // We "solve" this by waiting until the main thread has completed executing our loader stub.
// // Max wait time 1 second, just to be safe.
// for (int i1 = 1; i1 < 100; i1++)
// {
// if ((buf->pOldApi->jmp == buf->oldApi.jmp) && (buf->pOldApi->target == buf->oldApi.target))
// // Our loader stub patch was removed, so we assume that the main thread has completed running it.
// break;
// LONGLONG sleep = -100000; // 10 milliseconds
// buf->nde(NULL, (PLARGE_INTEGER) &sleep);
// }
// }
// if ((buf->pOldApi->jmp != buf->oldApi.jmp) || (buf->pOldApi->target != buf->oldApi.target))
// {
// PVOID p1 = (PVOID) buf->pOldApi;
// ULONG_PTR c1 = 5;
// ULONG c2;
// if (!buf->npvm((HANDLE) -1, &p1, &c1, PAGE_EXECUTE_READWRITE, &c2))
// {
// *(buf->pOldApi) = buf->oldApi;
// c1 = 5;
// buf->npvm((HANDLE) -1, &p1, &c1, c2, &c2);
// }
// else
// {
// // For some reason we can't uninstall our patch correctly.
// // That happens if "dynamic code execution" is forbidden.
// // As a workaround we keep our callback installed, but we
// // rewire it to execute our copy of the original API code.
// buf->skip = true;
// }
// if ((!mtid) || (mtid == ctid))
// do
// {
// buf->lld(0, NULL, (PUNICODE_STRING) &(buf->dll), (HMODULE*) &c1);
// buf = (InjectLib64*) buf->next;
// } while (buf);
// }
// }
function InjectLibraryPatchNt64(process: THandle) : integer;
// inject a dll into a newly started 64bit process
{$ifndef win64}
function GetModuleHandle64(const dll: UnicodeString) : HMODULE;
function GetPeb64 : int64;
asm
mov eax, gs:[$60]
mov edx, gs:[$64]
end;
var i64 : int64;
loopEnd : pointer;
mi : TPAPointer;
begin
result := 0;
try
// first let's get the 64bit PEB (Process Environment Block)
i64 := GetPeb64;
if dword(i64) = i64 then begin
// we got it - and it's only a 32bit pointer, fortunately
// now let's get the loader data
i64 := int64(pointer(i64 + $18)^);
if dword(i64) = i64 then begin
// fortunately the loader also is only a 32bit pointer
// now let's loop through the list of dlls
loopEnd := pointer(i64 + $20);
mi := loopEnd;
while (mi[0] <> loopEnd) and (mi[1] = nil) do begin
// mi[ 0] + mi[ 1] = pointer to next dll
// mi[ 8] + mi[ 9] = dll handle
// mi[16] + mi[17] = full dll file name
mi := mi[0];
if (mi[16] <> nil) and (mi[17] = nil) and (PosTextW(dll, UnicodeString(PWideChar(mi[16]))) > 0) then begin
// found the dll we're looking for
if mi[9] = nil then
// and it happens to have a 32bit handle - yippieh!
result := dword(mi[8]);
break;
end;
end;
end;
end;
except end;
end;
{$endif}
var buf : PInjectLib64;
il : TInjectLib64;
rj : TRelJump;
c1 : NativeUInt;
op : dword;
{$ifndef win64}
dll : HMODULE;
{$endif}
nta64 : pointer;
npvm64 : pointer;
lld64 : pointer;
navm64 : pointer;
nfvm64 : pointer;
nqsi64 : pointer;
nde64 : pointer;
begin
result := 0;
{$ifdef win64}
nta64 := NtProc(CNtTestAlert, true);
npvm64 := NtProc(CNtProtectVirtualMemory, true);
lld64 := NtProc(CLdrLoadDll, true);
navm64 := NtProc(CNtAllocateVirtualMemory, true);
nfvm64 := NtProc(CNtFreeVirtualMemory, true);
nqsi64 := NtProc(CNtQuerySystemInformation, true);
nde64 := NtProc(CNtDelayExecution, true);
{$else}
dll := GetModuleHandle64('ntdll.dll');
nta64 := GetImageProcAddress(dll, 'NtTestAlert');
npvm64 := GetImageProcAddress(dll, 'NtProtectVirtualMemory');
lld64 := GetImageProcAddress(dll, 'LdrLoadDll');
navm64 := GetImageProcAddress(dll, 'NtAllocateVirtualMemory');
nfvm64 := GetImageProcAddress(dll, 'NtFreeVirtualMemory');
nqsi64 := GetImageProcAddress(dll, 'NtQuerySystemInformation');
nde64 := GetImageProcAddress(dll, 'NtDelayExecution');
{$endif}
buf := AllocMemEx(4 * 4096, process {$ifdef win64}, nta64 {$endif});
if buf <> nil then begin
// we were able to allocate a buffer in the newly created process
if (nta64 <> nil) and (npvm64 <> nil) and (lld64 <> nil) then begin
il.movRax := $b848;
il.retAddr := int64(nta64);
il.pushRax := $50;
il.pushRcx := $51;
il.pushRdx := $52;
il.pushR8 := $5041;
il.pushR9 := $5141;
il.subRsp28 := $28ec8348;
il.movRcx := $b948;
il.param := int64(buf);
il.movRdx := $ba48;
il.proc := int64(@buf.injectFunc);
il.jmpEdx := $e2ff;
il.magic := 0;
il.next := 0;
il.pOldApi := il.retAddr;
il.dll.len := lstrlenW(libW) * 2;
il.dll.maxLen := il.dll.len + 2;
il.dll.dummy := 0;
il.dll.str := int64(@buf.dllBuf);
lstrcpyW(il.dllBuf, libW);
il.npvm := int64(npvm64);
il.lld := int64(lld64);
il.navm := int64(navm64);
il.nfvm := int64(nfvm64);
il.nqsi := int64(nqsi64);
il.nde := int64(nde64);
il.skip := 0;
TPAInt64(@il.injectFunc)[0] := int64($740000001000b983); // 83b90010000000 cmp dword ptr [rcx + $1000], $00
TPAInt64(@il.injectFunc)[1] := int64($41594128c483482b); // 742b jz +$2b
// 4883c428 add rsp, $28
// 4159 pop r9
TPACardinal(@il.injectFunc)[4] := $58595a58; // 4158 pop r8
// 5a pop rdx
// 59 pop rcx
// 58 pop rax
Move(CInjectLibFunc64[0], il.injectFunc[20 + 32], sizeof(CInjectLibFunc64));
if ReadProcessMemory (process, pointer(il.pOldApi), @il.oldApi, 5, c1) and (c1 = 5) and
ReadProcessMemory (process, pointer(il.pOldApi), @il.injectFunc[20], 32, c1) and (c1 = 32) and
WriteProcessMemory(process, buf, @il, sizeOf(TInjectLib64), c1) and (c1 = sizeOf(TInjectLib64)) then begin
// we've successfully initialized the buffer
if VirtualProtectEx(process, pointer(il.pOldApi), 5, PAGE_EXECUTE_READWRITE, @op) then begin
// we successfully unprotected the to-be-patched code, so now we can patch it
rj.jmp := $e9;
rj.target := NativeUInt(buf) - NativeUInt(il.pOldApi) - 5;
WriteProcessMemory(process, pointer(il.pOldApi), @rj, 5, c1);
// now restore the original page protection
VirtualProtectEx(process, pointer(il.pOldApi), 5, op, @op);
result := 1;
end;
end;
end;
VirtualProtectEx(process, buf, 4096, PAGE_EXECUTE_READ, @op);
VirtualProtectEx(process, @buf.skip, 4096, PAGE_READWRITE, @op);
end;
end;
var module : HMODULE;
nh : TImageNtHeaders32;
dd : pointer;
begin
{$ifdef log}
log('InjectLibraryPatch (process: %p)', nil, nil, process);
{$endif}
if forcePatch or NotInitializedYet(process) then begin
{$ifdef log}log('NotInitializedYet (1) +');{$endif}
if GetExeModuleInfos(process, module, nh, dd) then begin
{$ifdef log}log('GetExeModuleInfos +');{$endif}
if GetVersion and $80000000 = 0 then begin
if Is64bitProcess(process) then
result := InjectLibraryPatchNt64(process)
else
{$ifndef win64}
{$ifdef InjectLibraryPatchXp}
if ((byte(GetVersion) > 5) or ((byte(GetVersion) = 5) and (byte(GetVersion shr 8) > 0))) and
(not Is64bitOS) then
result := InjectLibraryPatchXp32(process)
else
{$endif}
if IsProcessDotNet(process) then
result := InjectLibraryApc32(process, pid, thread)
else
{$endif}
result := InjectLibraryPatchNt32(process, pid, module, nh, dd);
if (result = 1) and (forcePatch or NotInitializedYet(process)) then
result := 2;
end else
result := 0;
end else result := 0;
end else result := 1;
{$ifdef log}
log('InjectLibraryPatch (process: %p) -> ' + IntToStrExA(result), nil, nil, process);
{$endif}
end;
function DoInject(process: THandle; pid: dword; firstThread: THandle; var thread: THandle) : boolean;
begin
{$ifdef log}
log('DoInject (process: %p)', nil, nil, process);
{$endif}
result := true;
thread := 0;
{$ifdef win64}
peb64 := GetPeb(process);
peb32 := 0;
{$else}
peb32 := GetPeb(process);
if Is64bitProcess(process) then
peb64 := GetPeb64(process)
else
peb64 := 0;
{$endif}
if forcePatch or NotInitializedYet(process) then begin
if mayPatch then begin
case InjectLibraryPatch(process, pid, firstThread) of
0 : result := false;
1 : begin
if mayThread then
thread := StartInjectLibraryX(process, pid, true, libA, libW)
else thread := 0;
result := thread <> 0;
end;
end;
end else
result := false;
end else begin
if mayThread then
thread := StartInjectLibraryX(process, pid, true, libA, libW)
else thread := 0;
result := thread <> 0;
end;
{$ifdef log}
log('DoInject (process: %p) -> ' + booleanToCharA(result), nil, nil, process);
{$endif}
end;
var prcs1, prcs2 : TDAProcess;
ths : array of THandle;
pids : array of dword;
pidCount : integer;
dll64 : boolean;
i1, i2 : integer;
c1 : THandle;
flags : dword;
b1, b2, b3 : boolean;
{$ifndef win64}
newMap : integer;
newMutex : boolean;
{$endif}
sla : AnsiString;
slw : AnsiString;
time : dword;
delay : boolean;
count : integer;
ws1 : UnicodeString;
cmdLine : PWideChar;
incPaths : array of UnicodeString;
incNames : array of UnicodeString;
excPaths : array of UnicodeString;
excNames : array of UnicodeString;
needFullPath : boolean;
name : UnicodeString;
path : UnicodeString;
helperBuf : PWideChar;
isSysPrc : boolean;
begin
{$ifdef log}
log('InjectLibraryX (process: ' + '%p' +
'; lib: ' + '%1' +
'; timeOut: ' + IntToStrExA(timeOut) +
')', libA, libW, process);
{$endif}
prcs1 := nil;
prcs2 := nil;
incPaths := nil;
incNames := nil;
excPaths := nil;
excNames := nil;
result := false;
time := 0;
if not CheckLibFilePath(libA, libW, sla, slw) then
exit;
EnableAllPrivileges;
InitProcs(false);
dll64 := Is64bitModule(libW);
if multiInject then begin
{$ifndef win64}
if (InjectIntoRunningProcesses or (not Is64bitOS)) and dll64 then begin
SetLastError(ERROR_NOT_SUPPORTED);
exit;
end;
{$endif}
if InjectIntoRunningProcesses and (not IsAdminAndElevated) then begin
SetLastError(ERROR_ACCESS_DENIED);
exit;
end;
if UseIatDllInjection and (not DoesModuleExportOrdinal1(libW)) then begin
SetLastError(ERROR_INVALID_DLL);
exit;
end;
if (driverName = nil) or StartDllInjection(driverName, libW, session, options, includes, excludes, callback, callbackContext) then
if InjectIntoRunningProcesses then begin
if session = CURRENT_SESSION then
session := GetCurrentSessionId;
result := true;
needFullPath := @callback <> nil;
if (includes <> nil) and (includes[0] <> #0) then begin
ws1 := includes;
SetLength(incPaths, SubStrCountW(ws1));
SetLength(incNames, Length(incPaths));
for i1 := 0 to high(incPaths) do
needFullPath := SplitNamePath(SubStrW(ws1, i1 + 1), incPaths[i1], incNames[i1]) or needFullPath;
end;
if (excludes <> nil) and (excludes[0] <> #0) then begin
ws1 := excludes;
SetLength(excPaths, SubStrCountW(ws1));
SetLength(excNames, Length(excPaths));
for i1 := 0 to high(excPaths) do
needFullPath := SplitNamePath(SubStrW(ws1, i1 + 1), excPaths[i1], excNames[i1]) or needFullPath;
end;
if needFullPath then
helperBuf := pointer(LocalAlloc(LPTR, 64 * 1024))
else
helperBuf := nil;
i1 := Length(excPaths);
SetLength(excPaths, i1 + 2);
SetLength(excNames, i1 + 2);
excNames[i1 + 0] := UnicodeString(DecryptStr(CSmss));
excNames[i1 + 1] := UnicodeString(DecryptStr(CSLSvc));
delay := false;
count := 0;
pids := nil;
repeat
if delay then
Sleep(10);
delay := false;
b2 := true;
pidCount := 0;
{$ifdef log} Log('EnumProcesses'); {$endif}
prcs1 := EnumProcesses;
{$ifdef log} LogProcesses(prcs1); {$endif}
SetLength(ths, Length(prcs1));
for i1 := 0 to high(prcs1) do begin
ths[i1] := 0;
b1 := true;
for i2 := 0 to high(prcs2) do
if prcs2[i2].id = prcs1[i1].id then begin
b1 := false;
break;
end;
if excludePIDs <> nil then begin
i2 := 0;
while (excludePIDs[i2] <> 0) and (excludePIDs[i2] <> prcs1[i1].id) do
inc(i2);
if excludePIDs[i2] <> 0 then
b1 := false;
end;
if b1 and (prcs1[i1].id > 8) then begin
if (not SplitNamePath(prcs1[i1].exeFile, path, name)) and
needFullPath and
ProcessIdToFileNameW(prcs1[i1].id, helperBuf, 32 * 1024) then
SplitNamePath(helperBuf, path, name);
if ((incPaths = nil) or MatchStrArray(path, name, incPaths, incNames)) and
(not MatchStrArray(path, name, excPaths, excNames)) then begin
b2 := false;
c1 := OpenProcess(PROCESS_CREATE_THREAD or PROCESS_DUP_HANDLE or PROCESS_QUERY_INFORMATION or
PROCESS_VM_OPERATION or PROCESS_VM_READ or PROCESS_VM_WRITE, false, prcs1[i1].id);
if c1 <> 0 then begin
if dll64 = Is64bitProcess(c1) then begin
isSysPrc := IsSystemProcess(c1);
{$ifndef WIN64}
if not dll64 then
{$endif}
if ( (options and INJECT_SYSTEM_PROCESSES <> 0) or (not isSysPrc) ) and
( (options and INJECT_METRO_APPS <> 0) or (not IsMetroApp(c1)) ) and
( (session = ALL_SESSIONS) or
(prcs1[i1].session = session) or
((prcs1[i1].session = 0) and isSysPrc) ) then begin
if @callback <> nil then begin
flags := 0;
if dll64 then
flags := flags or TARGET_PROCESS_IS_64BIT;
if isSysPrc then
flags := flags or TARGET_PROCESS_IS_SYSTEM;
if IsElevatedProcess(c1) then
flags := flags or TARGET_PROCESS_IS_ELEVATED;
if IsProtectedProcess(c1) then
flags := flags or TARGET_PROCESS_IS_PROTECTED;
ws1 := GetProcessCommandLine(c1, dll64);
if ws1 <> '' then
cmdLine := PWideChar(ws1)
else
cmdLine := nil;
b3 := callback(callbackContext, prcs1[i1].id, prcs1[i1].parentId, prcs1[i1].session, flags, helperBuf, cmdLine);
end else
b3 := true;
if b3 and DoInject(c1, prcs1[i1].id, 0, ths[i1]) and (@callback <> nil) then begin
if (pids = nil) or (pidCount = Length(pids)) then
SetLength(pids, pidCount + 256);
pids[pidCount] := prcs1[i1].id;
inc(pidCount);
end;
end;
end;
CloseHandle(c1);
end else begin
c1 := GetLastError;
{$ifdef log}
log(' OpenProcess (' + IntToHexExA(prcs1[i1].id) + ') -> error ' + IntToHexExA(c1));
{$endif}
if (prcs1[i1].id <> 0) and (c1 <> ERROR_ACCESS_DENIED) then begin
prcs1[i1].id := 0;
delay := true;
end;
end;
end;
end;
end;
if (pidCount > 0) and (driverName <> nil) then begin
SetLength(pids, pidCount);
ReportInjectedPidsToDriver(driverName, pids, libW, session, options, includes, excludes);
end;
if time = 0 then
time := GetTickCount;
for i1 := 0 to high(ths) do
if ths[i1] <> 0 then
WaitForInjectLibraryX(ths[i1], timeOut, time);
prcs2 := prcs1;
{$ifdef log}
if not b2 then
log('Did we miss some processes? Check again...');
{$endif}
inc(count);
until b2 or (count = 5);
if helperBuf <> nil then
LocalFree(HLOCAL(helperBuf));
end else
result := true;
end else
result := (dll64 = Is64bitProcess(process)) and {$ifndef win64} (forcePatch or (not dll64)) and {$endif}
DoInject(process, ProcessHandleToId(process), thread, c1) and
( (c1 = 0) or WaitForInjectLibraryX(c1, timeOut, GetTickCount) );
{$ifdef log}
log('InjectLibraryX (process: ' + '%p' +
'; lib: ' + libA +
'; timeOut: ' + IntToStrExA(timeOut) +
') -> ' + booleanToCharA(result), nil, nil, process);
{$endif}
end;
// ***************************************************************
function UninjectLibraryX(owner : HMODULE;
libA : PAnsiChar;
libW : PWideChar;
multiInject : boolean;
process : THandle;
driverName : PWideChar;
session : dword;
options : dword;
includes : PWideChar;
excludes : PWideChar;
excludePIDs : TPACardinal;
timeOut : dword) : boolean;
var prcs1, prcs2 : TDAProcess;
ths : TDANativeUInt;
dll64 : boolean;
ph : THandle;
i1, i2 : integer;
c1 : THandle;
sla : AnsiString;
slw : AnsiString;
time : dword;
b1, b2 : boolean;
ws1 : UnicodeString;
incPaths : array of UnicodeString;
incNames : array of UnicodeString;
excPaths : array of UnicodeString;
excNames : array of UnicodeString;
needFullPath : boolean;
name : UnicodeString;
path : UnicodeString;
helperBuf : PWideChar;
pidsValid : boolean;
pids : TDACardinal;
begin
{$ifdef log}
log('UninjectLibraryX (process: ' + '%p' +
'; lib: ' + '%1' +
'; timeOut: ' + IntToStrExA(timeOut) +
')', libA, libW, process);
{$endif}
prcs1 := nil;
prcs2 := nil;
incPaths := nil;
incNames := nil;
excPaths := nil;
excNames := nil;
result := false;
time := 0;
if not CheckLibFilePath(libA, libW, sla, slw) then
exit;
EnableAllPrivileges;
InitProcs(false);
dll64 := Is64bitModule(libW);
if multiInject then begin
{$ifndef win64}
if (UninjectFromRunningProcesses or (not Is64bitOS)) and dll64 then begin
SetLastError(ERROR_NOT_SUPPORTED);
exit;
end;
{$endif}
if UninjectFromRunningProcesses and (not IsAdminAndElevated) then begin
SetLastError(ERROR_ACCESS_DENIED);
exit;
end;
if (driverName <> nil) and (not StopDllInjection(driverName, libW, session, options, includes, excludes, pidsValid, pids)) then
exit;
if UninjectFromRunningProcesses then begin
result := true;
if pidsValid then begin
SetLength(ths, Length(pids));
for i1 := 0 to high(pids) do begin
ths[i1] := 0;
ph := OpenProcess(PROCESS_ALL_ACCESS, false, pids[i1]);
if ph <> 0 then begin
ths[i1] := StartInjectLibraryX(ph, pids[i1], false, libA, libW);
CloseHandle(ph);
end;
end;
time := GetTickCount;
for i1 := 0 to high(ths) do
if ths[i1] <> 0 then
WaitForInjectLibraryX(ths[i1], timeOut, time);
end else begin
if session = CURRENT_SESSION then
session := GetCurrentSessionId;
needFullPath := false;
if (includes <> nil) and (includes[0] <> #0) then begin
ws1 := includes;
SetLength(incPaths, SubStrCountW(ws1));
SetLength(incNames, Length(incPaths));
for i1 := 0 to high(incPaths) do
needFullPath := SplitNamePath(SubStrW(ws1, i1 + 1), incPaths[i1], incNames[i1]) or needFullPath;
end;
if (excludes <> nil) and (excludes[0] <> #0) then begin
ws1 := excludes;
SetLength(excPaths, SubStrCountW(ws1));
SetLength(excNames, Length(excPaths));
for i1 := 0 to high(excPaths) do
needFullPath := SplitNamePath(SubStrW(ws1, i1 + 1), excPaths[i1], excNames[i1]) or needFullPath;
end;
if needFullPath then
helperBuf := pointer(LocalAlloc(LPTR, 64 * 1024))
else
helperBuf := nil;
i1 := Length(excPaths);
SetLength(excPaths, i1 + 2);
SetLength(excNames, i1 + 2);
excNames[i1 + 0] := UnicodeString(DecryptStr(CSmss));
excNames[i1 + 1] := UnicodeString(DecryptStr(CSLSvc));
repeat
b2 := true;
{$ifdef log} Log('EnumProcesses'); {$endif}
prcs1 := EnumProcesses;
{$ifdef log} LogProcesses(prcs1); {$endif}
SetLength(ths, Length(prcs1));
for i1 := 0 to high(prcs1) do begin
ths[i1] := 0;
b1 := true;
for i2 := 0 to high(prcs2) do
if prcs2[i2].id = prcs1[i1].id then begin
b1 := false;
break;
end;
if excludePIDs <> nil then begin
i2 := 0;
while (excludePIDs[i2] <> 0) and (excludePIDs[i2] <> prcs1[i1].id) do
inc(i2);
if excludePIDs[i2] <> 0 then
b1 := false;
end;
if b1 and (prcs1[i1].id > 8) then begin
if (not SplitNamePath(prcs1[i1].exeFile, path, name)) and
needFullPath and
ProcessIdToFileNameW(prcs1[i1].id, helperBuf, 32 * 1024) then
SplitNamePath(helperBuf, path, name);
if ((incPaths = nil) or MatchStrArray(path, name, incPaths, incNames)) and
(not MatchStrArray(path, name, excPaths, excNames)) then begin
b2 := false;
ph := OpenProcess(PROCESS_ALL_ACCESS, false, prcs1[i1].id);
if ph <> 0 then begin
if (dll64 = Is64bitProcess(ph)) and
( (options and INJECT_SYSTEM_PROCESSES <> 0) or (not IsSystemProcess(ph)) ) and
( (options and INJECT_METRO_APPS <> 0) or (not IsMetroApp(ph)) ) and
( (session = ALL_SESSIONS) or
(GetProcessSessionId(prcs1[i1].id) = session) or
((GetProcessSessionId(prcs1[i1].id) = 0) and IsSystemProcess(ph)) ) then
ths[i1] := StartInjectLibraryX(ph, prcs1[i1].id, false, libA, libW);
CloseHandle(ph);
end;
end;
end;
end;
if time = 0 then
time := GetTickCount;
for i1 := 0 to high(ths) do
if ths[i1] <> 0 then
WaitForInjectLibraryX(ths[i1], timeOut, time);
prcs2 := prcs1;
{$ifdef log}
if not b2 then
log('Did we miss some processes? Check again...');
{$endif}
until b2;
if helperBuf <> nil then
LocalFree(HLOCAL(helperBuf));
end;
end else
result := true;
end else
if {$ifdef win64} (dll64 = {$else} (not dll64) and (not {$endif} Is64bitProcess(process)) then begin
c1 := StartInjectLibraryX(process, ProcessHandleToId(process), false, libA, libW);
result := (c1 <> 0) and WaitForInjectLibraryX(c1, timeOut, GetTickCount);
end;
{$ifdef log}
log('UninjectLibraryX (process: ' + '%p' +
'; lib: ' + libA +
'; timeOut: ' + IntToStrExA(timeOut) +
') -> ' + booleanToCharA(result), nil, nil, process);
{$endif}
end;
// ***************************************************************
function UninjectAllLibrariesW(driverName: PWideChar; excludePIDs: TPCardinal = nil; timeOutPerUninject: dword = 7000) : bool; stdcall;
var uir : TDAInjectionRequest;
i1 : integer;
incl, excl : PWideChar;
begin
if (driverName = nil) or (driverName[0] = #0) then begin
result := false;
exit;
end;
result := true;
uir := EnumInjectionRequests(driverName);
for i1 := 0 to high(uir) do begin
if uir[i1].IncludeLen > 0 then
incl := uir[i1].Data
else
incl := nil;
if uir[i1].ExcludeLen > 0 then
excl := pointer(NativeUInt(@uir[i1].Data) + uir[i1].IncludeLen * 2 + 2)
else
excl := nil;
if not UninjectLibraryX(GetCallingModule, nil, uir[i1].Name, true, 0, driverName, uir[i1].Session, uir[i1].Flags, incl, excl, TPACardinal(excludePIDs), timeOutPerUninject) then
result := false;
UnpatchCreateRemoteThread;
LocalFree(HLOCAL(uir[i1]));
end;
end;
function UninjectAllLibrariesA(driverName: PAnsiChar; excludePIDs: TPCardinal = nil; timeOutPerUninject: dword = 7000) : bool; stdcall;
var driverNameW : PWideChar;
begin
if (driverName = nil) or (driverName[0] = #0) then begin
result := false;
exit;
end;
if (driverName <> nil) and (driverName[0] <> #0) then begin
driverNameW := pointer(LocalAlloc(LPTR, lstrlenA(driverName) * 2 + 2));
AnsiToWide2(driverName, driverNameW);
end else
driverNameW := nil;
result := UninjectAllLibrariesW(driverNameW, excludePIDs, timeOutPerUninject);
end;
function UninjectAllLibraries(driverName: PAnsiChar; excludePIDs: TPCardinal = nil; timeOutPerUninject: dword = 7000) : bool; stdcall;
begin
result := UninjectAllLibrariesA(driverName, excludePIDs, timeOutPerUninject);
end;
// ***************************************************************
function CPInject(owner: HMODULE; pi: TProcessInformation; flags: dword; libA: PAnsiChar; libW: PWideChar) : boolean;
begin
result := InjectLibraryX(owner, libA, libW, false, pi.hProcess, nil, 0, 0, nil, nil, nil, nil, nil, INFINITE, true, false, true, pi.hThread);
if result then begin
if flags and CREATE_SUSPENDED = 0 then
ResumeThread(pi.hThread);
end else begin
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread );
end;
end;
function CreateProcessExA(applicationName, commandLine : PAnsiChar;
processAttr, threadAttr : PSecurityAttributes;
inheritHandles : bool;
creationFlags : dword;
environment : pointer;
currentDirectory : PAnsiChar;
const startupInfo : {$ifdef UNICODE} TStartupInfoA; {$else} TStartupInfo; {$endif}
var processInfo : TProcessInformation;
loadLibrary : PAnsiChar ) : bool; stdcall;
begin
{$ifdef log}
log('CreateProcessExA (lib: ' + loadLibrary + ')');
{$endif}
result := CreateProcessA(applicationName, commandLine, processAttr, threadAttr,
inheritHandles, creationFlags or CREATE_SUSPENDED,
environment, currentDirectory, startupInfo, processInfo) and
CPInject(GetCallingModule, processInfo, creationFlags, loadLibrary, nil);
{$ifdef log}
log('CreateProcessExA (lib: ' + loadLibrary + ') -> ' + booleanToCharA(result));
{$endif}
end;
function CreateProcessExW(applicationName, commandLine : PWideChar;
processAttr, threadAttr : PSecurityAttributes;
inheritHandles : bool;
creationFlags : dword;
environment : pointer;
currentDirectory : PWideChar;
const startupInfo : {$ifdef UNICODE} TStartupInfoW; {$else} TStartupInfo; {$endif}
var processInfo : TProcessInformation;
loadLibrary : PWideChar ) : bool; stdcall;
var cpw : function (app, cmd, pattr, tattr: pointer; inherit: bool; flags: dword;
env, dir: pointer; const si: {$ifdef UNICODE} TStartupInfoW; {$else} TStartupInfo; {$endif} var pi: TProcessInformation) : bool; stdcall;
begin
{$ifdef log}
log('CreateProcessExW (lib: %1)', nil, loadLibrary);
{$endif}
cpw := @CreateProcessW;
{$ifdef log}
log('CreateProcessExW (lib: %1)', nil, loadLibrary);
{$endif}
result := cpw(applicationName, commandLine, processAttr, threadAttr,
inheritHandles, creationFlags or CREATE_SUSPENDED,
environment, currentDirectory, startupInfo, processInfo) and
CPInject(GetCallingModule, processInfo, creationFlags, nil, loadLibrary);
{$ifdef log}
log('CreateProcessExW (lib: %1) -> ' + booleanToCharA(result), nil, loadLibrary);
{$endif}
end;
function CreateProcessEx(applicationName, commandLine : PAnsiChar;
processAttr, threadAttr : PSecurityAttributes;
inheritHandles : bool;
creationFlags : dword;
environment : pointer;
currentDirectory : PAnsiChar;
const startupInfo : {$ifdef UNICODE} TStartupInfoA; {$else} TStartupInfo; {$endif}
var processInfo : TProcessInformation;
loadLibrary : AnsiString ) : boolean;
begin
{$ifdef log}
log('CreateProcessEx (lib: ' + loadLibrary + ')');
{$endif}
result := CreateProcessA(applicationName, commandLine, processAttr, threadAttr,
inheritHandles, creationFlags or CREATE_SUSPENDED,
environment, currentDirectory, startupInfo, processInfo) and
CPInject(GetCallingModule, processInfo, creationFlags, PAnsiChar(loadLibrary), nil);
{$ifdef log}
log('CreateProcessEx (lib: ' + loadLibrary + ') -> ' + booleanToCharA(result));
{$endif}
end;
// ***************************************************************
function InjectLibraryA(libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall;
begin
result := InjectLibraryX(GetCallingModule, libFileName, nil, false, processHandle, nil, 0, 0, nil, nil, nil, nil, nil, timeOut, true, true, false, 0);
UnpatchCreateRemoteThread;
end;
function InjectLibraryW(libFileName: PWideChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall;
begin
result := InjectLibraryX(GetCallingModule, nil, libFileName, false, processHandle, nil, 0, 0, nil, nil, nil, nil, nil, timeOut, true, true, false, 0);
UnpatchCreateRemoteThread;
end;
function InjectLibrary(libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall;
begin
result := InjectLibraryX(GetCallingModule, libFileName, nil, false, processHandle, nil, 0, 0, nil, nil, nil, nil, nil, timeOut, true, true, false, 0);
UnpatchCreateRemoteThread;
end;
function UninjectLibraryA(libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall;
begin
result := UninjectLibraryX(GetCallingModule, libFileName, nil, false, processHandle, nil, 0, 0, nil, nil, nil, timeOut);
UnpatchCreateRemoteThread;
end;
function UninjectLibraryW(libFileName: PWideChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall;
begin
result := UninjectLibraryX(GetCallingModule, nil, libFileName, false, processHandle, nil, 0, 0, nil, nil, nil, timeOut);
UnpatchCreateRemoteThread;
end;
function UninjectLibrary(libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall;
begin
result := UninjectLibraryX(GetCallingModule, libFileName, nil, false, processHandle, nil, 0, 0, nil, nil, nil, timeOut);
UnpatchCreateRemoteThread;
end;
function InjectLibraryA(driverName : PAnsiChar;
libFileName : PAnsiChar;
session : dword;
options : dword;
includeMask : PAnsiChar = nil;
excludeMask : PAnsiChar = nil;
excludePIDs : TPCardinal = nil;
callback : TInjectApprovalCallbackRoutine = nil;
callbackContext : pointer = nil;
timeOut : dword = 7000) : bool; stdcall;
var driverNameW : PWideChar;
includesW : PWideChar;
excludesW : PWideChar;
begin
if (driverName <> nil) and (driverName[0] <> #0) then begin
driverNameW := pointer(LocalAlloc(LPTR, lstrlenA(driverName) * 2 + 2));
AnsiToWide2(driverName, driverNameW);
end else
driverNameW := nil;
if (includeMask <> nil) and (includeMask[0] <> #0) then begin
includesW := pointer(LocalAlloc(LPTR, lstrlenA(includeMask) * 2 + 2));
AnsiToWide2(includeMask, includesW);
end else
includesW := nil;
if (excludeMask <> nil) and (excludeMask[0] <> #0) then begin
excludesW := pointer(LocalAlloc(LPTR, lstrlenA(excludeMask) * 2 + 2));
AnsiToWide2(excludeMask, excludesW);
end else
excludesW := nil;
result := InjectLibraryX(GetCallingModule, libFileName, nil, true, 0, driverNameW, session, options, includesW, excludesW, TPACardinal(excludePIDs), callback, callbackContext, timeOut, true, true, false, 0);
UnpatchCreateRemoteThread;
if driverNameW <> nil then
LocalFree(HLOCAL(driverNameW));
if includesW <> nil then
LocalFree(HLOCAL(includesW));
if excludesW <> nil then
LocalFree(HLOCAL(excludesW));
end;
function InjectLibraryW(driverName : PWideChar;
libFileName : PWideChar;
session : dword;
options : dword;
includeMask : PWideChar = nil;
excludeMask : PWideChar = nil;
excludePIDs : TPCardinal = nil;
callback : TInjectApprovalCallbackRoutine = nil;
callbackContext : pointer = nil;
timeOut : dword = 7000) : bool; stdcall;
begin
result := InjectLibraryX(GetCallingModule, nil, libFileName, true, 0, driverName, session, options, includeMask, excludeMask, TPACardinal(excludePIDs), callback, callbackContext, timeOut, true, true, false, 0);
UnpatchCreateRemoteThread;
end;
function InjectLibrary(driverName : PAnsiChar;
libFileName : PAnsiChar;
session : dword;
options : dword;
includeMask : PAnsiChar = nil;
excludeMask : PAnsiChar = nil;
excludePIDs : TPCardinal = nil;
callback : TInjectApprovalCallbackRoutine = nil;
callbackContext : pointer = nil;
timeOut : dword = 7000) : bool; stdcall;
begin
result := InjectLibraryA(driverName, libFileName, session, options, includeMask, excludeMask, excludePIDs, callback, callbackContext, timeOut);
end;
function UninjectLibraryA(driverName : PAnsiChar;
libFileName : PAnsiChar;
session : dword;
options : dword;
includeMask : PAnsiChar = nil;
excludeMask : PAnsiChar = nil;
excludePIDs : TPCardinal = nil;
timeOut : dword = 7000) : bool; stdcall;
var driverNameW : PWideChar;
includesW : PWideChar;
excludesW : PWideChar;
begin
if (driverName <> nil) and (driverName[0] <> #0) then begin
driverNameW := pointer(LocalAlloc(LPTR, lstrlenA(driverName) * 2 + 2));
AnsiToWide2(driverName, driverNameW);
end else
driverNameW := nil;
if (includeMask <> nil) and (includeMask[0] <> #0) then begin
includesW := pointer(LocalAlloc(LPTR, lstrlenA(includeMask) * 2 + 2));
AnsiToWide2(includeMask, includesW);
end else
includesW := nil;
if (excludeMask <> nil) and (excludeMask[0] <> #0) then begin
excludesW := pointer(LocalAlloc(LPTR, lstrlenA(excludeMask) * 2 + 2));
AnsiToWide2(excludeMask, excludesW);
end else
excludesW := nil;
result := UninjectLibraryX(GetCallingModule, libFileName, nil, true, 0, driverNameW, session, options, includesW, excludesW, TPACardinal(excludePIDs), timeOut);
UnpatchCreateRemoteThread;
if driverNameW <> nil then
LocalFree(HLOCAL(driverNameW));
if includesW <> nil then
LocalFree(HLOCAL(includesW));
if excludesW <> nil then
LocalFree(HLOCAL(excludesW));
end;
function UninjectLibraryW(driverName : PWideChar;
libFileName : PWideChar;
session : dword;
options : dword;
includeMask : PWideChar = nil;
excludeMask : PWideChar = nil;
excludePIDs : TPCardinal = nil;
timeOut : dword = 7000) : bool; stdcall;
begin
result := UninjectLibraryX(GetCallingModule, nil, libFileName, true, 0, driverName, session, options, includeMask, excludeMask, TPACardinal(excludePIDs), timeOut);
UnpatchCreateRemoteThread;
end;
function UninjectLibrary(driverName : PAnsiChar;
libFileName : PAnsiChar;
session : dword;
options : dword;
includeMask : PAnsiChar = nil;
excludeMask : PAnsiChar = nil;
excludePIDs : TPCardinal = nil;
timeOut : dword = 7000) : bool; stdcall;
begin
result := UninjectLibraryA(driverName, libFileName, session, options, includeMask, excludeMask, excludePIDs, timeOut);
end;
// ***************************************************************
type
TIpcAnswer = record
map : THandle;
buf : pointer;
len : dword;
event1 : THandle;
event2 : THandle;
end;
procedure CloseIpcAnswer(var answer: TIpcAnswer);
begin
if answer.buf <> nil then
UnmapViewOfFile(answer.buf);
if answer.map <> 0 then begin
CloseHandle(answer.map);
answer.map := 0;
end;
if answer.event1 <> 0 then begin
CloseHandle(answer.event1);
answer.event1 := 0;
end;
if answer.event2 <> 0 then begin
CloseHandle(answer.event2);
answer.event2 := 0;
end;
end;
var NtQueryInformationToken : function (token: THandle; infoType: NativeUInt; buf: pointer; bufSize: dword; retLen: TPCardinal) : HRESULT; stdcall = nil;
function InitIpcAnswer(create: boolean; name: AnsiString; counter, pid: dword; var answer: TIpcAnswer; session: dword = 0) : boolean;
function GetMetroPath(pid: dword) : AnsiString;
const TokenBnoIsolation = $2c;
type
TOKEN_BNO_ISOLATION_INFORMATION = record
IsolationPrefix : PWideChar;
IsolationEnabled : boolean;
end;
var gacnop : function (token: THandle; appContainerSid: PSid; bufLen: dword; buf: PWideChar; var retLen: dword) : bool; stdcall;
ph, th : THandle;
arrChW : array [0..MAX_PATH] of WideChar;
arrChA : array [0..MAX_PATH] of AnsiChar;
len : dword;
buf : ^TOKEN_BNO_ISOLATION_INFORMATION;
begin
result := '';
gacnop := GetProcAddress(GetModuleHandleA(PAnsiChar(DecryptStr(CKernelbase))), PAnsiChar(DecryptStr(CGetAppCtnrNamedObjectPath)));
if @gacnop <> nil then begin
ph := OpenProcess(PROCESS_QUERY_INFORMATION, false, pid);
if ph <> 0 then begin
if OpenProcessToken(ph, TOKEN_QUERY, th) then begin
if gacnop(th, nil, MAX_PATH, arrChW, len) then begin
// Metro apps run under a special private namespace.
// Fortunately there's on official API which gives us the necessary information.
WideToAnsi(arrChW, arrChA);
result := arrChA;
end else
if not DisableIpcFix2 then begin
{$ifdef win64}
len := $120;
{$else}
len := $118;
{$endif}
if @NtQueryInformationToken = nil then
NtQueryInformationToken := NtProc(CNtQueryInformationToken);
if @NtQueryInformationToken <> nil then begin
buf := pointer(LocalAlloc(LPTR, len));
if (NtQueryInformationToken(th, TokenBnoIsolation, buf, len, @len) = 0) and (buf.IsolationEnabled) then begin
// RuntimeBroker.exe in Windows 10 runs under private object namespace.
// It's all rather weird. After some disassembling I found "TokenBnoSolution".
// It's in the latest "winnt.h", but otherwise completely undocumented.
// It gives us the private namespace prefix of the target process.
WideToAnsi(buf.IsolationPrefix, arrChA);
result := arrChA;
end;
LocalFree(HLOCAL(buf));
end;
end;
CloseHandle(th);
end;
CloseHandle(ph);
end;
end;
end;
var s1, s2 : AnsiString;
ch1 : AnsiChar;
mp : AnsiString;
begin
if answer.len > 0 then begin
s1 := name + DecryptStr(CAnswerBuf) + IntToStrExA(counter);
if pid <> 0 then
s1 := s1 + IntToHexExA(pid);
if create then begin
answer.map := InternalCreateFileMapping(PAnsiChar(s1 + DecryptStr(CMap)), answer.len, true, false);
ch1 := '1'; answer.event1 := CreateGlobalEvent(PAnsiChar(s1 + DecryptStr(CEvent) + ch1), false, false);
ch1 := '2'; answer.event2 := CreateGlobalEvent(PAnsiChar(s1 + DecryptStr(CEvent) + ch1), false, false);
end else begin
s2 := DecryptStr(CSession) + IntToStrExA(session) + '\';
mp := '';
answer.map := OpenGlobalFileMapping(PAnsiChar(s1 + DecryptStr(CMap)), true);
if answer.map = 0 then begin
// if the IPC sender doesn't have the SeCreateGlobalPrivilege in w2k3
// or in xp sp2 or higher then the objects were not created in global
// namespace but in the namespace of the sender
// so if opening of the file map didn't work, we try to open the
// objects in the session of the sender
answer.map := OpenGlobalFileMapping(PAnsiChar(s2 + s1 + DecryptStr(CMap)), true);
if (answer.map = 0) and IsMetro then begin
mp := GetMetroPath(pid);
if mp <> '' then begin
s2 := s2 + mp + '\';
answer.map := OpenGlobalFileMapping(PAnsiChar(s2 + s1 + DecryptStr(CMap)), true);
end;
end;
end;
ch1 := '1'; answer.event1 := OpenGlobalEvent(PAnsiChar(s1 + DecryptStr(CEvent) + ch1));
ch1 := '2'; answer.event2 := OpenGlobalEvent(PAnsiChar(s1 + DecryptStr(CEvent) + ch1));
if answer.event1 = 0 then begin
ch1 := '1'; answer.event1 := OpenGlobalEvent(PAnsiChar(s2 + s1 + DecryptStr(CEvent) + ch1));
ch1 := '2'; answer.event2 := OpenGlobalEvent(PAnsiChar(s2 + s1 + DecryptStr(CEvent) + ch1));
end;
end;
if answer.map <> 0 then
answer.buf := MapViewOfFile(answer.map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
result := (answer.event1 <> 0) and (answer.event2 <> 0) and (answer.buf <> nil);
if result then begin
if create then
ZeroMemory(answer.buf, answer.len);
end else
CloseIpcAnswer(answer);
end else begin
answer.map := 0;
answer.buf := nil;
answer.event1 := 0;
answer.event2 := 0;
result := true;
end;
end;
type
TPipedIpcRec = record
map : THandle;
pid : dword;
rp, wp : THandle;
callback : TIpcCallback;
context : pointer;
mxThreads : dword;
mxQueue : dword;
flags : dword;
th : THandle;
name : array [0..MAX_PATH] of AnsiChar;
counter : dword;
msg : record
buf : PAnsiChar;
len : dword;
end;
answer : TIpcAnswer;
end;
function OpenPipedIpcMap(name: AnsiString; var ir: TPipedIpcRec; pmutex: TPNativeUInt = nil; destroy: boolean = false) : boolean;
var buf : ^TPipedIpcRec;
mutex : THandle;
map : THandle;
begin
result := false;
mutex := CreateGlobalMutex(PAnsiChar(name + DecryptStr(CIpc) + DecryptStr(CMutex)));
if mutex <> 0 then begin
WaitForSingleObject(mutex, INFINITE);
try
map := OpenGlobalFileMapping(PAnsiChar(name + DecryptStr(CIpc) + DecryptStr(CMap)), true);
if map <> 0 then begin
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if buf <> nil then begin
result := true;
inc(buf^.counter);
ir := buf^;
if destroy then
if buf^.pid = GetCurrentProcessID then
CloseHandle(buf^.map)
else
result := false;
UnmapViewOfFile(buf);
end;
CloseHandle(map);
end;
finally
if (not result) or (pmutex = nil) then begin
ReleaseMutex(mutex);
CloseHandle(mutex);
end else
pmutex^ := mutex;
end;
end;
end;
type
TSecurityQualityOfService = packed record
size : dword;
dummy : array [0..1] of dword;
end;
TLpcMessageData = array [0..59] of dword;
TLpcMessagePrivate = packed record
pid : dword;
msgLen : dword;
counter : dword;
session : dword;
answerLen : dword;
data : TLpcMessageData;
end;
TPLpcMessage = ^TLpcMessage;
TLpcMessage = packed record
next : TPLpcMessage;
name : AnsiString;
callback : TIpcCallback;
context : pointer;
answer : TIpcAnswer;
prv : dword;
actualMessageLength : word;
totalMessageLength : word;
messageType : word;
dataInfoOffset : word;
clientProcessId : dword;
clientThreadId : dword;
messageId : dword;
sharedSectionSize : dword;
end;
TLpcSectionInfo = packed record
size : dword;
{$ifdef win64}
dummy1 : dword;
{$endif}
sectionHandle : THandle;
param1 : dword;
{$ifdef win64}
dummy2 : dword;
{$endif}
sectionSize : dword;
{$ifdef win64}
dummy3 : dword;
{$endif}
clientBaseAddress : pointer;
serverBaseAddress : pointer;
end;
TLpcSectionMapInfo = packed record
size : dword;
{$ifdef win64}
dummy1 : dword;
{$endif}
sectionSize : dword;
{$ifdef win64}
dummy2 : dword;
{$endif}
serverBaseAddress : pointer;
end;
{$ifndef win64}
TLpcSectionMapInfo64 = packed record
size : dword;
dummy1 : dword;
sectionSize : dword;
dummy2 : dword;
serverBaseAddress : pointer;
dummy3 : dword;
end;
{$endif}
const LPC_CONNECTION_REQUEST = $a;
var
NtCreatePort : function (var portHandle: THandle; var objAttr: TObjectAttributes; maxConnectInfoLen, maxDataLen, unknown: NativeUInt) : dword; stdcall;
NtConnectPort : function (var portHandle: THandle; var portName: TUnicodeStr; var qs: TSecurityQualityOfService; sectionInfo, mapInfo, unknown2, connectInfo: pointer; var connectInfoLen: dword) : dword; stdcall;
NtReplyWaitReceivePort : function (portHandle: THandle; portContext: pointer; replyMessage, receiveMessage: pointer) : dword; stdcall;
NtReplyWaitReceivePortEx : function (portHandle: THandle; portContext: pointer; replyMessage, receiveMessage: pointer; var timeOut: int64) : dword; stdcall;
NtAcceptConnectPort : function (var portHandle: THandle; unknown1: NativeUInt; msg: pointer; acceptIt, unknown2: NativeUInt; mapInfo: pointer) : dword; stdcall;
NtCompleteConnectPort : function (portHandle: THandle) : dword; stdcall;
type
TPLpcWorkerThread = ^TLpcWorkerThread;
TLpcWorkerThread = record
handle : THandle;
parentSemaphore : THandle;
event : THandle;
pm : TPLpcMessage;
lastActive : dword;
shutdown : boolean;
freed : ^boolean;
end;
TPLpcQueue = ^TLpcQueue;
TLpcQueue = record
name : AnsiString;
callback : TIpcCallback;
context : pointer;
maxThreadCount : dword;
madQueueLen : dword;
port : THandle;
counter : dword;
pm : TPLpcMessage;
section : TRtlCriticalSection;
semaphore : THandle;
shutdown : boolean;
portThread : THandle;
dispatchThread : THandle;
workerThreads : array of TPLpcWorkerThread;
end;
var
LpcSection : TRTLCriticalSection;
LpcReady : boolean = false;
LpcList : array of TPLpcQueue;
LpcCounterBuf : ^integer = nil;
procedure InitLpcName(const ipc: AnsiString; var ws: AnsiString; var uniStr: TUnicodeStr);
begin
ws := AnsiToWideEx(DecryptStr(CRpcControlIpc) + ipc);
uniStr.maxLen := length(ws);
uniStr.len := uniStr.maxLen - 2;
uniStr.str := pointer(ws);
end;
procedure InitLpcFuncs;
begin
if @NtCreatePort = nil then begin
NtCreatePort := NtProc(CNtCreatePort);
NtConnectPort := NtProc(CNtConnectPort);
NtReplyWaitReceivePort := NtProc(CNtReplyWaitReceivePort);
NtReplyWaitReceivePortEx := NtProc(CNtReplyWaitReceivePortEx);
NtAcceptConnectPort := NtProc(CNtAcceptConnectPort);
NtCompleteConnectPort := NtProc(CNtCompleteConnectPort);
end;
end;
function LpcWorkerThread(var wt: TLpcWorkerThread) : integer; stdcall;
// the worker thread handles one message after the other, if it's told to
var pm2 : pointer;
prv : ^TLpcMessagePrivate;
begin
result := 0;
with wt do
while true do begin
if shutdown or (WaitForSingleObject(event, INFINITE) <> WAIT_OBJECT_0) or shutdown or (pm = nil) then
break;
prv := pointer(NativeUInt(pm) + pm.prv);
pm.callback(PAnsiChar(pm.name), @prv.data, prv.msgLen, pm.answer.buf, pm.answer.len, pm.context);
if pm.answer.len <> 0 then begin
SetEvent(pm.answer.event2);
CloseIpcAnswer(pm.answer);
end;
lastActive := GetTickCount;
pm^.name := '';
pm2 := pm;
pm := nil;
LocalFree(HLOCAL(pm2));
ReleaseSemaphore(parentSemaphore, 1, nil);
end;
EnterCriticalSection(LpcSection);
if wt.freed <> nil then
wt.freed^ := true;
CloseHandle(wt.event);
CloseHandle(wt.handle);
LocalFree(HLOCAL(@wt));
LeaveCriticalSection(LpcSection);
end;
function LpcDispatchThread(var iq: TLpcQueue) : integer; stdcall;
// this thread takes all ipc messages and feeds them to the worker threads
// worker threads are dynamically created and deleted, if it makes sense
var pm : TPLpcMessage;
tid : dword;
i1, i2 : integer;
begin
while true do begin
WaitForSingleObject(iq.semaphore, INFINITE);
if iq.shutdown then
break;
EnterCriticalSection(iq.section);
try
pm := iq.pm;
if pm <> nil then
iq.pm := iq.pm^.next;
finally LeaveCriticalSection(iq.section) end;
while pm <> nil do begin
// loop until we found someone who is willing to handle this message
for i1 := 0 to high(iq.workerThreads) do begin
if iq.workerThreads[i1].pm = nil then begin
iq.workerThreads[i1].pm := pm;
SetEvent(iq.workerThreads[i1].event);
pm := nil;
break;
end;
end;
if pm <> nil then begin
// we still have a message to handle, but all worker threads are busy
if iq.maxThreadCount > dword(Length(iq.workerThreads)) then begin
// let's create a new worker thread
i1 := Length(iq.workerThreads);
SetLength(iq.workerThreads, i1 + 1);
iq.workerThreads[i1] := pointer(LocalAlloc(LPTR, sizeOf(TLpcWorkerThread)));
iq.workerThreads[i1].parentSemaphore := iq.semaphore;
iq.workerThreads[i1].event := CreateEvent(nil, false, true, nil);
iq.workerThreads[i1].pm := pm;
iq.workerThreads[i1].handle := CreateThread(nil, 0, @LpcWorkerThread, iq.workerThreads[i1], 0, tid);
if iq.workerThreads[i1].handle <> 0 then begin
SetThreadPriority(iq.workerThreads[i1].handle, THREAD_PRIORITY_NORMAL);
pm := nil;
end else begin
CloseHandle(iq.workerThreads[i1].event);
LocalFree(HLOCAL(iq.workerThreads[i1]));
SetLength(iq.workerThreads, i1 - 1);
end;
end;
if pm <> nil then begin
// we still have a message to handle, so we wait
// until a worker thread is ready to handle this message
WaitForSingleObject(iq.semaphore, INFINITE);
if iq.shutdown then
break;
end;
end;
end;
if iq.shutdown then
break;
i2 := Length(iq.workerThreads);
for i1 := high(iq.workerThreads) downto 0 do
if ((i1 > 0) or (i2 > 1)) and (iq.workerThreads[i1].pm = nil) and (GetTickCount - iq.workerThreads[i1].lastActive > 100) then begin
// this thread was idle for more than 100ms, so we close it down
iq.workerThreads[i1].shutdown := true;
SetEvent(iq.workerThreads[i1].event);
dec(i2);
iq.workerThreads[i1] := iq.workerThreads[i2];
iq.workerThreads[i2] := nil;
end;
if i2 <> Length(iq.workerThreads) then
SetLength(iq.workerThreads, i2);
end;
result := 0;
end;
function LpcPortThread(var iq: TLpcQueue) : integer; stdcall;
// this thread receives ipc messages from the LPC port
// all incoming messages are stored in a pointer list
var port : THandle;
pm1, pm2 : TPLpcMessage;
ppm : ^TPLpcMessage;
mi : ^TLpcSectionMapInfo;
prv : ^TLpcMessagePrivate;
prevPort : THandle;
retVal : dword;
timeOut : int64;
begin
result := 0;
prevPort := 0;
while true do begin
pm1 := pointer(LocalAlloc(LPTR, sizeof(TLpcMessage) + sizeof(TLpcMessagePrivate) + 128));
if (@NtReplyWaitReceivePortEx <> nil) and (not DisableIpcFix1) then begin
repeat
timeOut := -100000; // 10ms
retVal := NtReplyWaitReceivePortEx(iq.port, nil, nil, @pm1.actualMessageLength, timeOut);
until (retVal <> STATUS_TIMEOUT) or iq.shutdown;
end else
retVal := NtReplyWaitReceivePort(iq.port, nil, nil, @pm1.actualMessageLength);
if prevPort <> 0 then begin
CloseHandle(prevPort);
prevPort := 0;
end;
if (retVal = 0) and (pm1.messageType = LPC_CONNECTION_REQUEST) and (pm1.actualMessageLength > 0) then begin
pm1.prv := NativeUInt(@pm1.actualMessageLength) - NativeUInt(pm1) + pm1.totalMessageLength - sizeOf(TLpcMessagePrivate);
prv := pointer(NativeUInt(pm1) + pm1.prv);
if not IsBadReadPtr(prv, 20) then begin
if iq.shutdown then begin
NtAcceptConnectPort(port, 0, @pm1.actualMessageLength, 0, 0, nil);
LocalFree(HLOCAL(pm1));
break;
end;
// due to 32/64 bit LPC incompatabilities, pMsg->ClientProcessId is not reliable, so we extract the PID from our own transport buffer
if prv.pid <> 0 then
pm1.clientProcessId := prv.pid;
// set the process ID. The client checks this value as verification that the command completed
prv.pid := GetCurrentProcessId + 1;
port := 0;
{$ifdef win64}
mi := pointer(LocalAlloc(LPTR, sizeOf(TLpcSectionMapInfo)));
mi.size := sizeOf(TLpcSectionMapInfo);
{$else}
mi := pointer(LocalAlloc(LPTR, sizeOf(TLpcSectionMapInfo64)));
if Is64bitOS then
mi.size := sizeOf(TLpcSectionMapInfo64)
else mi.size := sizeOf(TLpcSectionMapInfo );
{$endif}
if NtAcceptConnectPort(port, 0, @pm1.actualMessageLength, 1, 0, mi) = 0 then begin
{$ifndef win64}
if Is64bitOS then begin
mi.sectionSize := TLpcSectionMapInfo64(pointer(mi)^).sectionSize;
mi.serverBaseAddress := TLpcSectionMapInfo64(pointer(mi)^).serverBaseAddress;
end;
{$endif}
if (mi.sectionSize > 0) and (prv.msgLen > 0) then begin
if prv.msgLen > mi.sectionSize then
prv.msgLen := mi.sectionSize;
pm2 := pointer(LocalAlloc(LPTR, NativeUInt(@prv.data) - NativeUInt(pm1) + prv.msgLen));
Move(pm1^, pm2^, NativeUInt(@prv.data) - NativeUInt(pm1));
NativeUInt(prv) := NativeUInt(pm2) + pm2.prv; // pm2.prv -> read access nil?
Move(mi.serverBaseAddress^, prv.data, prv.msgLen);
LocalFree(HLOCAL(pm1));
pm1 := pm2;
end else
if prv.msgLen > sizeOf(prv.data) then
prv.msgLen := sizeOf(prv.data);
NtCompleteConnectPort(port);
prevPort := port;
pm1.answer.len := prv.answerLen;
if InitIpcAnswer(false, iq.name, prv.counter, pm1.clientProcessId, pm1.answer, prv.session) then begin
if pm1.answer.len <> 0 then
SetEvent(pm1.answer.event1);
pm1.name := iq.name;
pm1.callback := iq.callback;
pm1.context := iq.context;
EnterCriticalSection(iq.section);
try
ppm := @iq.pm;
while ppm^ <> nil do
ppm := @ppm^^.next;
ppm^ := pm1;
finally LeaveCriticalSection(iq.section) end;
ReleaseSemaphore(iq.semaphore, 1, nil);
end else
LocalFree(HLOCAL(pm1));
end else begin
prv.pid := 0;
NtAcceptConnectPort(port, 0, @pm1.actualMessageLength, 0, 0, nil);
LocalFree(HLOCAL(pm1));
end;
LocalFree(HLOCAL(mi));
end else begin
LocalFree(HLOCAL(pm1));
if iq.shutdown and (not DisableIpcFix1) then
break;
end;
end else begin
LocalFree(HLOCAL(pm1));
if iq.shutdown and (not DisableIpcFix1) then
break;
end;
end;
end;
procedure HandlePipedIpcMessage(var ir: TPipedIpcRec);
begin
ir.callback(ir.name, ir.msg.buf, ir.msg.len, ir.answer.buf, ir.answer.len, ir.context);
if ir.answer.len <> 0 then begin
SetEvent(ir.answer.event2);
CloseIpcAnswer(ir.answer);
end;
LocalFree(HLOCAL(ir.msg.buf));
end;
function PipedIpcThread2(var ir: TPipedIpcRec) : integer; stdcall;
begin
result := 0;
HandlePipedIpcMessage(ir);
LocalFree(HLOCAL(@ir));
end;
function PipedIpcThread1(var ir: TPipedIpcRec) : integer; stdcall;
var c1 : dword;
h1 : THandle;
pir : ^TPipedIpcRec;
tid : dword;
session : dword;
begin
result := 0;
while ReadFile(ir.rp, ir.msg.len, 4, c1, nil) and (c1 = 4) do begin
ir.msg.buf := pointer(LocalAlloc(LPTR, ir.msg.len));
if ir.msg.buf <> nil then begin
if ReadFile(ir.rp, ir.counter, 4, c1, nil) and (c1 = 4) and
ReadFile(ir.rp, session, 4, c1, nil) and (c1 = 4) and
ReadFile(ir.rp, ir.answer.len, 4, c1, nil) and (c1 = 4) and
ReadFile(ir.rp, ir.msg.buf^, ir.msg.len, c1, nil) and (c1 = ir.msg.len) and
InitIpcAnswer(false, ir.name, ir.counter, 0, ir.answer, session) then begin
if ir.answer.len <> 0 then
SetEvent(ir.answer.event1);
if ir.mxThreads > 1 then begin
pir := pointer(LocalAlloc(LPTR, sizeOf(ir)));
pir^ := ir;
h1 := CreateThread(nil, 0, @PipedIpcThread2, pir, 0, tid);
if h1 <> 0 then begin
SetThreadPriority(h1, THREAD_PRIORITY_NORMAL);
CloseHandle(h1);
end else begin
LocalFree(HLOCAL(pir));
LocalFree(HLOCAL(ir.msg.buf));
CloseIpcAnswer(ir.answer);
end;
end else
HandlePipedIpcMessage(ir);
end else
LocalFree(HLOCAL(ir.msg.buf));
end;
end;
LocalFree(HLOCAL(@ir));
end;
function CreateIpcQueue(ipc: PAnsiChar; callback: TIpcCallback; context: pointer; maxThreadCount, maxQueueLen: dword; securityDesc: PSecurityDescriptor) : bool; stdcall;
function CreateLpcQueue : boolean;
// new ipc queue logic based on NT native LPC (local procedure call) APIs
// implemented mainly for Vista, cause Vista doesn't like the old approach
// probably in a later version I'll also use the new approach for older OSs
// but the new approach shall prove itself first
var objAttr : ^TObjectAttributes;
uniStr : TUnicodeStr;
sa : TSecurityAttributes;
sd : TSecurityDescriptor;
ws : AnsiString;
tid : dword;
i1 : integer;
port : THandle;
ph : THandle;
begin
if DuplicateHandle(GetCurrentProcess, GetCurrentProcess, GetCurrentProcess, @ph, 0, false, DUPLICATE_SAME_ACCESS) then begin
AddAccessForEveryone(ph, SYNCHRONIZE);
CloseHandle(ph);
end;
if not LpcReady then begin
InitializeCriticalSection(LpcSection);
LpcReady := true;
end;
result := true;
EnterCriticalSection(LpcSection);
try
for i1 := 0 to high(LpcList) do
if IsTextEqualA(LpcList[i1].name, ipc) then begin
result := false;
break;
end;
finally LeaveCriticalSection(LpcSection) end;
if result then begin
InitLpcFuncs;
InitLpcName(ipc, ws, uniStr);
objAttr := pointer(LocalAlloc(LPTR, sizeOf(TObjectAttributes)));
objAttr.len := sizeOf(TObjectAttributes);
objAttr.objName := @uniStr;
if securityDesc = nil then begin
InitSecAttr(sa, sd, LimitedIpcPort, false);
objAttr.sd := @sd;
end else begin
sd.Dacl := nil;
objAttr.sd := securityDesc;
end;
port := INVALID_HANDLE_VALUE;
result := NtCreatePort(port, objAttr^, 260, 40 + 260, 0) = 0;
LocalFree(HLOCAL(objAttr));
if sd.Dacl <> nil then
LocalFree(HLOCAL(sd.Dacl));
if result then begin
if maxThreadCount > 64 then
maxThreadCount := 64;
EnterCriticalSection(LpcSection);
try
i1 := Length(LpcList);
SetLength(LpcList, i1 + 1);
LpcList[i1] := pointer(LocalAlloc(LPTR, sizeOf(TLpcQueue)));
InitializeCriticalSection(LpcList[i1].section);
LpcList[i1].name := ipc;
LpcList[i1].callback := callback;
LpcList[i1].context := context;
LpcList[i1].maxThreadCount := maxThreadCount;
LpcList[i1].madQueueLen := maxQueueLen;
LpcList[i1].port := port;
LpcList[i1].semaphore := CreateSemaphore(nil, 0, maxInt, nil);
LpcList[i1].portThread := CreateThread(nil, 0, @LpcPortThread, LpcList[i1], 0, tid);
LpcList[i1].dispatchThread := CreateThread(nil, 0, @LpcDispatchThread, LpcList[i1], 0, tid);
SetThreadPriority(LpcList[i1]. portThread, 7);
SetThreadPriority(LpcList[i1].dispatchThread, THREAD_PRIORITY_ABOVE_NORMAL);
finally LeaveCriticalSection(LpcSection) end;
end;
end;
end;
function CreatePipedIpcQueue : boolean;
// this is the old IPC solution based on (unnamed) pipes
var mutex : THandle;
map : THandle;
buf1, buf2 : ^TPipedIpcRec;
s1 : AnsiString;
tid : dword;
ph : THandle;
begin
result := false;
if DuplicateHandle(GetCurrentProcess, GetCurrentProcess, GetCurrentProcess, @ph, 0, false, DUPLICATE_SAME_ACCESS) then begin
AddAccessForEveryone(ph, PROCESS_DUP_HANDLE or SYNCHRONIZE);
CloseHandle(ph);
end;
s1 := ipc;
if Length(s1) > MAX_PATH then
SetLength(s1, MAX_PATH);
mutex := CreateGlobalMutex(PAnsiChar(s1 + DecryptStr(CIpc) + DecryptStr(CMutex)));
if mutex <> 0 then begin
WaitForSingleObject(mutex, INFINITE);
try
map := CreateGlobalFileMapping(PAnsiChar(s1 + DecryptStr(CIpc) + DecryptStr(CMap)), sizeOf(TPipedIpcRec));
if map <> 0 then
if GetLastError <> ERROR_ALREADY_EXISTS then begin
buf1 := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if buf1 <> nil then begin
result := true;
ZeroMemory(buf1, sizeOf(TPipedIpcRec));
CreatePipe(buf1^.rp, buf1^.wp, @UnlimitedSa, 0);
buf1^.map := map;
buf1^.pid := GetCurrentProcessID;
buf1^.callback := callback;
buf1^.context := context;
buf1^.mxThreads := maxThreadCount;
buf1^.mxQueue := maxQueueLen;
Move(PAnsiChar(s1)^, buf1^.name, Length(s1) + 1);
buf2 := pointer(LocalAlloc(LPTR, sizeOf(buf2^)));
buf2^ := buf1^;
buf1^.th := CreateThread(nil, 0, @PipedIpcThread1, buf2, 0, tid);
SetThreadPriority(buf1^.th, 7);
buf1^.counter := 0;
UnmapViewOfFile(buf1);
end else
CloseHandle(map);
end else
CloseHandle(map);
finally
ReleaseMutex(mutex);
CloseHandle(mutex);
end;
end;
end;
begin
result := false;
EnableAllPrivileges;
if maxThreadCount = 0 then
maxThreadCount := 16;
if @callback <> nil then
if UseNewIpcLogic then
result := CreateLpcQueue
else
result := CreatePipedIpcQueue;
end;
function SendIpcMessage(ipc: PAnsiChar; messageBuf: pointer; messageLen: dword;
answerBuf: pointer = nil; answerLen: dword = 0;
answerTimeOut: dword = INFINITE; handleMessages: bool = false) : bool; stdcall;
function WaitFor(h1, h2: THandle; timeOut: dword) : boolean;
var handles : array [0..1] of THandle;
msg : TMsg;
begin
result := false;
handles[0] := h1;
handles[1] := h2;
if handleMessages then begin
while true do
case MsgWaitForMultipleObjects(2, handles, false, timeOut, QS_ALLINPUT) of
WAIT_OBJECT_0 : begin
result := true;
break;
end;
WAIT_OBJECT_0 + 2 : while PeekMessage(msg, 0, 0, 0, PM_REMOVE) do begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
else break;
end;
end else
result := WaitForMultipleObjects(2, @handles, false, timeOut) = WAIT_OBJECT_0;
end;
function GetLpcCounter : dword;
function InterlockedIncrementEx(var value: integer) : integer;
asm
{$ifdef win64}
mov rdx, value
mov eax, dword ptr [rdx]
@loop:
mov ecx, eax
inc ecx
lock cmpxchg dword ptr [rdx], ecx
jnz @loop
{$else}
mov edx, value
mov eax, [edx]
@loop:
mov ecx, eax
inc ecx
lock cmpxchg [edx], ecx
jnz @loop
{$endif}
end;
var s1 : AnsiString;
map : THandle;
mutex : THandle;
newMap : boolean;
begin
if LpcCounterBuf = nil then begin
s1 := DecryptStr(CIpc) + DecryptStr(CCounter) + IntToHexExA(GetCurrentProcessId);
mutex := CreateMutexA(nil, false, PAnsiChar(s1 + DecryptStr(CMutex)));
if mutex <> 0 then begin
WaitForSingleObject(mutex, INFINITE);
try
map := CreateFileMappingA(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, 4, PAnsiChar(s1));
if map <> 0 then begin
newMap := GetLastError <> ERROR_ALREADY_EXISTS;
LpcCounterBuf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if newMap and (LpcCounterBuf <> nil) then
LpcCounterBuf^ := 0;
CloseHandle(map);
end;
finally
ReleaseMutex(mutex);
CloseHandle(mutex);
end;
end;
end;
if LpcCounterBuf <> nil then
result := dword(InterlockedIncrementEx(LpcCounterBuf^))
else
result := 0;
end;
var {$ifdef CheckRecursion}
recTestBuf : array [0..3] of pointer;
{$endif}
port : THandle;
uniStr : TUnicodeStr;
qs : TSecurityQualityOfService;
buf : TLpcMessagePrivate;
bufLen : dword;
ws : AnsiString;
c1 : dword;
si : TLpcSectionInfo;
mi : TLpcSectionMapInfo;
psi : ^TLpcSectionInfo;
pmi : ^TLpcSectionMapInfo;
ph : THandle;
ir : TPipedIpcRec;
mutex : THandle;
error : dword;
b1 : boolean;
session : dword;
t1, t2 : dword;
begin
error := GetLastError;
result := false;
{$ifdef CheckRecursion}
if NotRecursedYet(@SendIpcMessage, @recTestBuf) then begin
{$endif}
ph := 0;
ZeroMemory(@ir, sizeOf(ir));
if UseNewIpcLogic then begin
ZeroMemory(@buf, sizeOf(buf));
buf.counter := GetLpcCounter;
buf.pid := GetCurrentProcessId;
ir.answer.len := answerLen;
b1 := InitIpcAnswer(true, ipc, buf.counter, GetCurrentProcessId, ir.answer);
if b1 then begin
InitLpcFuncs;
InitLpcName(ipc, ws, uniStr);
ZeroMemory(@si, sizeOf(si));
ZeroMemory(@qs, sizeOf(qs));
port := 0;
buf.msgLen := messageLen;
buf.session := GetCurrentSessionId;
buf.answerLen := answerLen;
if messageLen > sizeOf(buf.data) then begin
// bufLen := sizeOf(buf) - sizeOf(buf.data);
ZeroMemory(@mi, sizeOf(mi));
si.size := sizeOf(si);
si.sectionHandle := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, messageLen, nil);
si.sectionSize := messageLen;
si.clientBaseAddress := MapViewOfFile(si.sectionHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if si.clientBaseAddress <> nil then begin
Move(messageBuf^, si.clientBaseAddress^, messageLen);
UnmapViewOfFile(si.clientBaseAddress);
si.clientBaseAddress := nil;
end;
mi.size := sizeOf(mi);
mi.sectionSize := messageLen;
psi := @si;
pmi := @mi;
end else begin
// bufLen := sizeOf(buf) - sizeOf(buf.data) + messageLen;
Move(messageBuf^, buf.data, messageLen);
psi := nil;
pmi := nil;
end;
bufLen := sizeOf(buf);
// Calling NtConnectPort can sometimes fail with "unsufficient resources".
// This can happen if a *lot* of threads send IPCs at the same time.
// So we call it in a loop until it succeeds, timeout = 250ms.
t1 := GetTickCount();
while true do begin
port := 0;
c1 := NtConnectPort(port, uniStr, qs, psi, pmi, nil, @buf, bufLen);
// There is a race condition on closing the handle between this thread and the Lpc port thread.
// If the other thread closes the handle first, then NtConnectPort returns C0000034, but still works.
if (c1 = 0) or (c1 = $C0000034) or (port <> 0) then
break;
t2 := GetTickCount();
if (t2 < t1) or (t2 - t1 >= 300) then
break;
Sleep(0);
end;
if port <> 0 then
CloseHandle(port);
if si.sectionHandle <> 0 then
CloseHandle(si.sectionHandle);
result := (buf.pid <> 0) and (buf.pid <> GetCurrentProcessId);
if result and (answerLen <> 0) then
ph := OpenProcess(SYNCHRONIZE, false, buf.pid - 1);
end;
end else begin
b1 := OpenPipedIpcMap(ipc, ir, @mutex);
if b1 then begin
ir.answer.len := answerLen;
b1 := InitIpcAnswer(true, ipc, ir.counter, 0, ir.answer);
if b1 then begin
ph := OpenProcess(PROCESS_DUP_HANDLE or SYNCHRONIZE, false, ir.pid);
if (ph <> 0) and
DuplicateHandle(ph, ir.wp, GetCurrentProcess, @ir.wp, 0, false, DUPLICATE_SAME_ACCESS) then begin
session := GetCurrentSessionId;
result := WriteFile(ir.wp, messageLen, 4, c1, nil) and (c1 = 4) and
WriteFile(ir.wp, ir.counter, 4, c1, nil) and (c1 = 4) and
WriteFile(ir.wp, session, 4, c1, nil) and (c1 = 4) and
WriteFile(ir.wp, answerLen, 4, c1, nil) and (c1 = 4) and
WriteFile(ir.wp, messageBuf^, messageLen, c1, nil) and (c1 = messageLen);
CloseHandle(ir.wp);
end;
end;
ReleaseMutex(mutex);
CloseHandle(mutex);
end;
end;
if ph <> 0 then begin
if result and (answerLen <> 0) then
if WaitFor(ir.answer.event1, ph, InternalIpcTimeout) then begin
if WaitFor(ir.answer.event2, ph, answerTimeOut) then begin
Move(ir.answer.buf^, answerBuf^, answerLen);
end else
result := false;
end else
result := false;
CloseHandle(ph);
end;
if b1 then
CloseIpcAnswer(ir.answer);
{$ifdef CheckRecursion}
recTestBuf[3] := nil;
end;
{$endif}
SetLastError(error);
end;
function DestroyIpcQueue(ipc: PAnsiChar) : bool; stdcall;
function DestroyLpcQueue : boolean;
var port : THandle;
uniStr : TUnicodeStr;
qs : TSecurityQualityOfService;
bufLen : dword;
ws : AnsiString;
iq : TPLpcQueue;
i1 : integer;
freed : boolean;
workerTh : THandle;
begin
result := false;
if LpcReady then begin
EnterCriticalSection(LpcSection);
try
iq := nil;
for i1 := 0 to high(LpcList) do
if IsTextEqualA(LpcList[i1].name, ipc) then begin
iq := LpcList[i1];
LpcList[i1] := LpcList[high(LpcList)];
SetLength(LpcList, high(LpcList));
break;
end;
finally LeaveCriticalSection(LpcSection) end;
if iq <> nil then begin
iq.shutdown := true;
ReleaseSemaphore(iq.semaphore, 1, nil);
InitLpcName(iq.name, ws, uniStr);
ZeroMemory(@qs, sizeOf(qs));
if DisableIpcFix1 then begin
port := 0;
bufLen := 0;
NtConnectPort(port, uniStr, qs, nil, nil, nil, nil, bufLen);
end;
if WaitForSingleObject(iq.portThread, 1000) = WAIT_TIMEOUT then
TerminateThread(iq.portThread, 0);
CloseHandle(iq.portThread);
if WaitForSingleObject(iq.dispatchThread, 1000) = WAIT_TIMEOUT then
TerminateThread(iq.dispatchThread, 0);
CloseHandle(iq.dispatchThread);
for i1 := 0 to high(iq.workerThreads) do
if iq.workerThreads[i1] <> nil then begin
freed := false;
iq.workerThreads[i1].freed := @freed;
iq.workerThreads[i1].shutdown := true;
workerTh := 0;
if DuplicateHandle(GetCurrentProcess, iq.workerThreads[i1].handle, GetCurrentProcess, @workerTh, 0, FALSE, DUPLICATE_SAME_ACCESS) then begin
SetEvent(iq.workerThreads[i1].event);
WaitForSingleObject(workerTh, 1000);
CloseHandle(workerTh);
end else begin
SetEvent(iq.workerThreads[i1].event);
WaitForSingleObject(iq.workerThreads[i1].handle, 1000);
end;
if not freed then begin
EnterCriticalSection(LpcSection);
if not freed then begin
TerminateThread(iq.workerThreads[i1].handle, 0);
CloseHandle(iq.workerThreads[i1].event);
CloseHandle(iq.workerThreads[i1].handle);
if iq.workerThreads[i1].pm <> nil then begin
iq.workerThreads[i1].pm.name := '';
LocalFree(HLOCAL(iq.workerThreads[i1].pm));
end;
LocalFree(HLOCAL(iq.workerThreads[i1]));
end;
LeaveCriticalSection(LpcSection);
end;
end;
iq.workerThreads := nil;
CloseHandle(iq.port);
CloseHandle(iq.semaphore);
DeleteCriticalSection(iq.section);
iq.name := '';
LocalFree(HLOCAL(iq));
result := true;
end;
end;
end;
function DestroyPipedIpcQueue : boolean;
var ir : TPipedIpcRec;
begin
result := OpenPipedIpcMap(ipc, ir, nil, true);
if result then begin
CloseHandle(ir.wp);
if WaitForSingleObject(ir.th, 100) = WAIT_TIMEOUT then
TerminateThread(ir.th, 0);
CloseHandle(ir.rp);
CloseHandle(ir.th);
end;
end;
begin
if UseNewIpcLogic then
result := DestroyLpcQueue
else
result := DestroyPipedIpcQueue;
end;
// ***************************************************************
function AddAccessForEveryone(processOrService: THandle; access: dword) : bool; stdcall;
const CEveryoneSia : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 1));
CSecurityAppPackageAuthority : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 15));
type
TTrustee = record
multiple : pointer;
multop : dword;
form : dword;
type_ : dword;
sid : PSid;
end;
TExplicitAccess = record
access : dword;
mode : dword;
inherit : dword;
trustee : TTrustee;
end;
var sd : PSecurityDescriptor;
ea : array [0..1] of TExplicitAccess;
oldDacl, newDacl : PAcl;
sid1, sid2 : PSid;
dll : HMODULE;
gsi : function (handle: THandle; objectType, securityInfo: dword; owner, group, dacl, sacl, sd: pointer) : dword; stdcall;
ssi : function (handle: THandle; objectType, securityInfo: dword; owner, group: PSid; dacl, sacl: PAcl) : dword; stdcall;
seia : function (count: dword; ea: pointer; oldAcl: PAcl; var newAcl: PAcl) : dword; stdcall;
typ : dword;
count : integer;
begin
result := false;
dll := LoadLibraryA(PAnsiChar(DecryptStr(CAdvApi32)));
gsi := GetProcAddress(dll, PAnsiChar(DecryptStr(CGetSecurityInfo)));
ssi := GetProcAddress(dll, PAnsiChar(DecryptStr(CSetSecurityInfo)));
seia := GetProcAddress(dll, PAnsiChar(DecryptStr(CSetEntriesInAclA)));
if (@gsi <> nil) and (@ssi <> nil) and (@seia <> nil) then begin
if gsi(processOrService, 6, DACL_SECURITY_INFORMATION, nil, nil, @oldDacl, nil, @sd) = 0 then typ := 6
else if gsi(processOrService, 2, DACL_SECURITY_INFORMATION, nil, nil, @oldDacl, nil, @sd) = 0 then typ := 2
else typ := 0;
if typ <> 0 then begin
if AllocateAndInitializeSid(CEveryoneSia, 1, 0, 0, 0, 0, 0, 0, 0, 0, sid1) then begin
ZeroMemory(@ea, sizeOf(ea));
ea[0].access := access;
ea[0].mode := 2; // SET_ACCESS
ea[0].trustee.form := 0; // TRUSTEE_IS_SID
ea[0].trustee.type_ := 1; // TRUSTEE_IS_USER
ea[0].trustee.sid := sid1;
count := 1;
if IsMetro and AllocateAndInitializeSid(CSecurityAppPackageAuthority, 2, 2, 1, 0, 0, 0, 0, 0, 0, sid2) then begin
// this adds access for metro "AppContainer" apps
ea[1].access := access;
ea[1].mode := 2; // SET_ACCESS
ea[1].trustee.form := 0; // TRUSTEE_IS_SID
ea[1].trustee.type_ := 2; // TRUSTEE_IS_GROUP
ea[1].trustee.sid := sid2;
inc(count);
end else
sid2 := nil;
if seia(count, @ea, oldDacl, newDacl) = 0 then begin
result := ssi(processOrService, typ, DACL_SECURITY_INFORMATION, nil, nil, newDacl, nil) = 0;
LocalFree(HLOCAL(newDacl));
end;
FreeSid(sid1);
if sid2 <> nil then
FreeSid(sid2);
end;
LocalFree(HLOCAL(sd));
end;
end;
FreeLibrary(dll);
end;
// ***************************************************************
(*function CheckSLSvc(process: THandle) : boolean;
const MEMBER_ACCESS = 1;
const idAuthorityNt : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 5));
var prcToken : THandle;
impToken : THandle;
sid : PSid;
sd : TSecurityDescriptor;
dacl : PAcl;
daclSize : dword;
genMap : TGenericMapping;
privBuf : array [0..sizeOf(TPrivilegeSet) + 3 * sizeof(TLuidAndAttributes) - 1] of byte;
priv : PPrivilegeSet;
privLen : dword;
accGranted : dword;
accStatus : bool;
begin
result := false;
if OpenProcessToken(process, TOKEN_QUERY or TOKEN_DUPLICATE, prcToken) then begin
if DuplicateToken(prcToken, SecurityImpersonation, @impToken) then begin
if AllocateAndInitializeSid(idAuthorityNt, 6, 80, 2119565420, 4155874467, 2934723793, 509086461, 374458824, 0, 0, sid) then begin
daclSize := sizeOf(TAcl) + 12 + 3 * GetLengthSid(sid);
dacl := pointer(LocalAlloc(LMEM_ZEROINIT, daclSize));
if dacl <> nil then begin
genMap.GenericRead := STANDARD_RIGHTS_READ or MEMBER_ACCESS;
genMap.GenericWrite := STANDARD_RIGHTS_EXECUTE;
genMap.GenericExecute := STANDARD_RIGHTS_WRITE;
genMap.GenericAll := STANDARD_RIGHTS_ALL or MEMBER_ACCESS;
priv := @privBuf;
privLen := sizeOf(privBuf);
accGranted := 0;
accStatus := false;
result := InitializeSecurityDescriptor(@sd, SECURITY_DESCRIPTOR_REVISION) and
SetSecurityDescriptorOwner(@sd, sid, false) and
SetSecurityDescriptorGroup(@sd, sid, false) and
InitializeAcl(dacl^, daclSize, 2) and
AddAccessAllowedAce(dacl^, 2, MEMBER_ACCESS, sid) and
SetSecurityDescriptorDacl(@sd, true, dacl, false) and
AccessCheck(@sd, impToken, MEMBER_ACCESS, genMap, priv^, privLen, accGranted, accStatus) and
accStatus and (accGranted = MEMBER_ACCESS);
LocalFree(HLOCAL(dacl));
end;
FreeSid(sid);
end;
CloseHandle(impToken);
end;
CloseHandle(prcToken);
end;
end;*)
// ***************************************************************
var PrivilegesEnabled : boolean = false;
procedure EnableAllPrivileges;
type TTokenPrivileges = record
PrivilegeCount : dword;
Privileges : array [0..maxInt shr 4 - 1] of TLUIDAndAttributes;
end;
var token : THandle;
c2 : dword;
i1 : integer;
ptp : ^TTokenPrivileges;
backup, restore, owner : int64;
begin
if PrivilegesEnabled then
exit;
if OpenProcessToken(windows.GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, token) then
try
c2 := 0;
GetTokenInformation(token, TokenPrivileges, nil, 0, c2);
if c2 <> 0 then begin
ptp := pointer(LocalAlloc(LPTR, c2 * 2));
if GetTokenInformation(token, TokenPrivileges, ptp, c2 * 2, c2) then begin
// enabling backup/restore privileges breaks Explorer's Samba support
if not LookupPrivilegeValueA(nil, PAnsiChar(DecryptStr(CSeBackupPrivilege )), backup ) then backup := 0;
if not LookupPrivilegeValueA(nil, PAnsiChar(DecryptStr(CSeRestorePrivilege )), restore) then restore := 0;
if not LookupPrivilegeValueA(nil, PAnsiChar(DecryptStr(CSeTakeOwnershipPrivilege)), owner ) then owner := 0;
for i1 := 0 to integer(ptp^.PrivilegeCount) - 1 do
if (ptp^.Privileges[i1].Luid <> backup ) and
(ptp^.Privileges[i1].Luid <> restore) and
(ptp^.Privileges[i1].Luid <> owner ) then
ptp^.Privileges[i1].Attributes := ptp^.Privileges[i1].Attributes or SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(token, false, PTokenPrivileges(ptp)^, c2, PTokenPrivileges(nil)^, dword(pointer(nil)^));
end;
LocalFree(HLOCAL(ptp));
end;
finally CloseHandle(token) end;
PrivilegesEnabled := true;
end;
procedure InitializeMadCHook;
begin
IsMultiThread := true;
{$ifdef log}
log('initialization begin');
{$endif}
InitTryExceptFinally;
madTools.NeedModuleFileMapEx := madCodeHook.CollectCache_NeedModuleFileMap;
IsWine := NtProc(CWineGetVersion) <> nil;
madTools.IsWine := IsWine;
InitSecAttr(LimitedSa, LimitedSd, true, true);
InitSecAttr(UnlimitedSa, UnlimitedSd, false, true);
InitializeCriticalSection(CollectSection);
SetLastError(0);
{$ifdef log}
log('initialization end');
{$endif}
end;
procedure FinalizeMadCHook;
begin
{$ifdef log}
log('finalization begin');
{$endif}
AutoUnhook2(HMODULE(-1), false);
{$ifdef forbiddenAPIs}
if forbiddenMutex <> 0 then begin
CloseHandle(forbiddenMutex);
forbiddenMutex := 0;
end;
{$endif}
if InjectApprovalCallbackReady then begin
CancelAllInjectApprovalCallbacks;
InjectApprovalCallbackList := nil;
CloseHandle(InjectApprovalCallbackEmptyEvent);
InjectApprovalCallbackEmptyEvent := 0;
DeleteCriticalSection(InjectApprovalCallbackSection);
InjectApprovalCallbackReady := false;
end;
if HookReady and (HookList = nil) then begin
HookReady := false;
DeleteCriticalSection(HookSection);
end;
if LpcReady then begin
LpcReady := false;
DeleteCriticalSection(LpcSection);
end;
if LpcCounterBuf <> nil then
UnmapViewOfFile(LpcCounterBuf);
if LimitedSd.Dacl <> nil then
LocalFree(HLOCAL(LimitedSd.Dacl));
if UnlimitedSd.Dacl <> nil then
LocalFree(HLOCAL(UnlimitedSd.Dacl));
DeleteCriticalSection(CollectSection);
{$ifdef log}
log('finalization end');
{$endif}
end;
// ***************************************************************
(*
type
TRelJump = packed record
jmp : byte; // $e9
target : dword;
end;
PInjectLib32 = ^TInjectLib32;
TInjectLib32 = packed record
movEax : byte; // 0xb8
param : dword; // pointer // pointer to "self"
movEcx : byte; // 0xb9
proc : dword; // pointer // pointer to "self.injectFunc"
callEcx : word; // 0xd1ff
magic : dword; // "mciL" / 0x4c69636d
next : dword; // PInjectLib32Old // next dll (if any)
pOldApi : ^TRelJump; // which code location in user land do we patch?
oldApi : TRelJump; // patch backup buffer, contains overwritten code
// align : byte;
dll : TUnicodeStr32; // dll path/name
dllBuf : array [0..259] of WideChar; // string buffer for the dll path/name
npvm : function (ProcessHandle: THandle; var baseAddress: pointer; var numberofBytesToProtect: dword; newAccess: dword; var oldAccess: dword) : dword; stdcall;
lld : function (path: PWideChar; var flags: dword; var moduleFileName: TUnicodeStr32; var ModuleHandle: THandle) : dword; stdcall;
navm : function (ProcessHandle: THandle; var baseAddress: pointer; zeroBits: NativeUInt; var RegionSize: NativeInt; allocationType, protect: dword) : dword; stdcall;
nfvm : function (ProcessHandle: THandle; var baseAddress: pointer; var regionSize: NativeInt; freeType: dword) : dword; stdcall;
nqsi : function (infoClass: NativeUInt; buffer: pointer; bufSize: dword; returnSize: TPCardinal) : dword; stdcall;
nde : function (alertable: pointer; var delayInterval: int64) : dword; stdcall;
injectFunc : array [0..3514] of byte;
skip : bool;
end;
TNtProcessInfo = packed record
offset : dword;
numThreads : integer;
d1 : array [0..11] of dword;
d2 : pointer;
name : PWideChar;
d3 : pointer;
pid : NativeUInt;
parentPid : NativeUInt;
handleCount : dword;
sessionId : dword;
d4 : dword;
d5 : array [0..11] of pointer;
d6 : array [0..5] of dword;
d7 : NativeUInt;
threads : array [0..maxInt shr 7 - 1] of packed record
startAddr_nt4 : pointer;
pid_nt4 : dword;
tid_nt4 : dword;
d8 : array [44..52] of dword;
startAddr_nt5 : pointer;
pid_nt5 : NativeUInt;
tid_nt5 : NativeUInt;
d9 : dword;
end;
end;
TPNtModuleInfo = ^TNtModuleInfo;
TNtModuleInfo = record
next : TPNtModuleInfo;
d1 : array [0..2] of pointer;
handle : cardinal;
d2 : array [0..1] of pointer;
d3 : word;
nameLen : word;
name : PWideChar;
end;
procedure InjectLibFunc32OldStatic(buf: PInjectLib32);
var ctid, mtid : dword; // current and main thread ids
cpid : dword;
ebp_ : dword;
c1, c2 : dword;
size : NativeInt;
p1 : pointer;
sleep : int64;
npi : ^TNtProcessInfo;
begin
asm
mov eax, fs:[$18]
mov eax, [eax+$20]
mov cpid, eax
mov eax, fs:[$18]
mov eax, [eax+$24]
mov ctid, eax
mov ebp_, ebp
end;
TPPointer(ebp_ + 4)^ := pointer(buf.pOldApi);
mtid := 0;
if @buf.nqsi <> nil then begin
c1 := 0;
buf.nqsi(5, nil, 0, @c1);
if c1 <> 0 then begin
size := c1 * 2;
p1 := nil;
if buf.navm(THandle(-1), p1, 0, size, MEM_COMMIT, PAGE_READWRITE) = 0 then begin
if buf.nqsi(5, p1, c1, nil) = 0 then begin
npi := p1;
while true do begin
if npi^.pid = cpid then begin
mtid := npi^.threads[0].tid_nt5;
break;
end;
if npi^.offset = 0 then
break;
npi := pointer(NativeUInt(npi) + npi^.offset);
end;
end;
size := 0;
buf.nfvm(THandle(-1), p1, size, MEM_RELEASE);
end;
end;
end;
if (mtid <> 0) and (mtid <> ctid) then begin
// This is not the main thread! This usually doesn't happen, except sometimes in win10.
// We "solve" this by waiting until the main thread has completed executing our loader stub.
// Max wait time 1 second, just to be safe.
for c1 := 1 to 100 do begin
if (buf.pOldApi^.jmp = buf.oldApi.jmp) and (buf.pOldApi^.target = buf.oldApi.target) then
// Our loader stub patch was removed, so we assume that the main thread has completed running it.
break;
sleep := -100000;// 10 milliseconds
buf.nde(nil, sleep);
end;
end;
if (buf.pOldApi^.jmp <> buf.oldApi.jmp) or (buf.pOldApi^.target <> buf.oldApi.target) then begin
// step 3: uninstall our patch
p1 := buf.pOldApi;
c1 := 5;
if buf.npvm(dword(-1), p1, c1, PAGE_EXECUTE_READWRITE, c2) = 0 then begin
buf.pOldApi^ := buf.oldApi;
c1 := 5;
buf.npvm(dword(-1), p1, c1, c2, c2);
end else begin
// For some reason we can't uninstall our patch correctly.
// That happens if "dynamic code execution" is forbidden.
// As a workaround we keep our callback installed, but we
// rewire it to execute our copy of the original API code.
buf.skip := true;
end;
// step 4: finally load the to-be-injected dll - but only if we're in the context of the main thread
if (mtid = 0) or (mtid = ctid) then
repeat
buf.lld(nil, dword(nil^), buf.dll, c1);
buf := pointer(buf.next);
until buf = nil;
end;
end;
procedure End_InjectLibFunc32OldStatic(buf: PInjectLib32); begin end;
procedure InjectLibFunc32OldDynamic(buf: PInjectLib32);
var ctid, mtid : dword; // current and main thread ids
cpid : dword;
ebp_ : dword;
size : NativeInt;
c1, c2 : dword;
peb : dword;
p1 : pointer;
loopEnd : TPNtModuleInfo;
mi : TNtModuleInfo;
len : dword;
ntdll : dword;
nh : PImageNtHeaders;
ed : PImageExportDirectory;
i1 : integer;
api : pchar;
npi : ^TNtProcessInfo;
sleep : int64;
npvm : function (process: THandle; var addr: pointer; var size: NativeUInt; newProt: dword; var oldProt: dword) : dword; stdcall;
lld : function (path, flags, name: pointer; var handle: HMODULE) : dword; stdcall;
navm : function (process: THandle; var addr: pointer; zeroBits: NativeUInt; var regionSize: NativeInt; allocationType, protect: dword) : dword; stdcall;
nfvm : function (process: THandle; var addr: pointer; var regionSize: NativeInt; freeType: dword) : dword; stdcall;
nqsi : function (infoClass: dword; buffer: pointer; bufSize: dword; returnSize: TPCardinal) : dword; stdcall;
nde : function (alertable: pointer; var delayInterval: int64) : dword; stdcall;
begin
asm
mov eax, fs:[$30]
mov peb, eax
mov eax, fs:[$18]
mov eax, [eax+$20]
mov cpid, eax
mov eax, fs:[$18]
mov eax, [eax+$24]
mov ctid, eax
mov ebp_, ebp
end;
TPPointer(ebp_ + 4)^ := buf.pOldApi;
npvm := nil;
lld := nil;
navm := nil;
nfvm := nil;
nqsi := nil;
nde := nil;
// step 1: locate ntdll.dll
loopEnd := pointer(dword(pointer(peb + $C)^) + $14);
mi := loopEnd^;
while mi.next <> loopEnd do begin
mi := mi.next^;
if mi.name <> nil then begin
len := 0;
while mi.name[len] <> #0 do
inc(len);
if (int64(pointer(@mi.name[len - 9])^) = $006c00640074006e) and // ntdl
(int64(pointer(@mi.name[len - 5])^) = $006c0064002e006c) then begin // l.dl
// found it!
ntdll := mi.handle;
// step 2: locate LdrLoadDll and NtProtectVirtualMemory
nh := pointer(ntdll + dword(pointer(ntdll + $3C)^));
dword(ed) := ntdll + nh^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
for i1 := 0 to ed^.NumberOfNames - 1 do begin
api := pointer(ntdll + TPACardinal(ntdll + ed^.AddressOfNames)^[i1]);
if (api <> nil) and
( ( (int64(pointer(@api[ 0])^) = $4464616f4c72644c) and // LdrLoadD
(dword(pointer(@api[ 8])^) and $00ffffff = $00006c6c ) ) or // ll
( (int64(pointer(@api[ 0])^) = $6365746f7250744e) and // NtProtec
(int64(pointer(@api[ 8])^) = $6c61757472695674) and // tVirtual
(int64(pointer(@api[16])^) and $00ffffffffffffff = $000079726f6d654d) ) or // Memory
( (int64(pointer(@api[ 0])^) = $61636F6C6C41744E) and // NtAlloca
(int64(pointer(@api[ 8])^) = $6175747269566574) and // teVirtua
(int64(pointer(@api[16])^) = $0079726F6D654D6C) ) or // lMemory
( (int64(pointer(@api[ 0])^) = $695665657246744E) and // NtFreeVi
(int64(pointer(@api[ 8])^) = $6D654D6C61757472) and // rtualMem
(dword(pointer(@api[16])^) = $0079726F ) ) or // ory
( (int64(pointer(@api[ 0])^) = $537972657551744E) and // NtQueryS
(int64(pointer(@api[ 8])^) = $666E496D65747379) and // ystemInf
(int64(pointer(@api[16])^) = $6E6F6974616D726F) and // ormation
(byte (pointer(@api[24])^) = $00 ) ) or // ormation
( (int64(pointer(@api[ 0])^) = $4579616c6544744e) and // NtDelayE
(int64(pointer(@api[ 8])^) = $6e6f697475636578) and // xecution
(byte (pointer(@api[16])^) = $00 ) ) ) then begin
c1 := TPAWord(ntdll + ed^.AddressOfNameOrdinals)^[i1];
c1 := TPACardinal(ntdll + ed^.AddressOfFunctions)^[c1];
case api[2] of
'r': lld := pointer(ntdll + c1);
'P': npvm := pointer(ntdll + c1);
'A': navm := pointer(ntdll + c1);
'F': nfvm := pointer(ntdll + c1);
'Q': nqsi := pointer(ntdll + c1);
'D': nde := pointer(ntdll + c1);
end;
end;
end;
if (@lld <> nil) and (@npvm <> nil) then begin
// found both APIs!
mtid := 0;
if (@navm <> nil) and (@nfvm <> nil) and (@nqsi <> nil) and (@nde <> nil) then begin
c1 := 0;
nqsi(5, nil, 0, @c1);
if c1 <> 0 then begin
size := c1 * 2;
p1 := nil;
if navm(THandle(-1), p1, 0, size, MEM_COMMIT, PAGE_READWRITE) = 0 then begin
if nqsi(5, p1, size, nil) = 0 then begin
npi := p1;
while true do begin
if npi^.pid = cpid then begin
mtid := npi^.threads[0].tid_nt5;
break;
end;
if npi^.offset = 0 then
break;
npi := pointer(NativeUInt(npi) + npi^.offset);
end;
end;
size := 0;
nfvm(THandle(-1), p1, size, MEM_RELEASE);
end;
end;
end;
if (mtid <> 0) and (mtid <> ctid) then begin
// This is not the main thread! This usually doesn't happen, except sometimes in win10.
// We "solve" this by waiting until the main thread has completed executing our loader stub.
// Max wait time 1 second, just to be safe.
for c1 := 1 to 100 do begin
if (buf.pOldApi^.jmp = buf.oldApi.jmp) and (buf.pOldApi^.target = buf.oldApi.target) then
// Our loader stub patch was removed, so we assume that the main thread has completed running it.
break;
sleep := -100000; // 10 milliseconds
nde(nil, sleep);
end;
end;
if (buf.pOldApi^.jmp <> buf.oldApi.jmp) or (buf.pOldApi^.target <> buf.oldApi.target) then begin
// step 3: uninstall our patch
p1 := buf.pOldApi;
c1 := 5;
if npvm(dword(-1), p1, c1, PAGE_EXECUTE_READWRITE, c2) = 0 then begin
buf.pOldApi^ := buf.oldApi;
c1 := 5;
npvm(dword(-1), p1, c1, c2, c2);
end else begin
// For some reason we can't uninstall our patch correctly.
// That happens if "dynamic code execution" is forbidden.
// As a workaround we keep our callback installed, but we
// rewire it to execute our copy of the original API code.
buf.skip := true;
end;
// step 4: finally load the to-be-injected dll - but only if we're in the context of the main thread
if (mtid = 0) or (mtid = ctid) then
repeat
lld(nil, nil, @buf.dll, c1);
buf := pointer(buf.next);
until buf = nil;
end;
end;
break;
end;
end;
end;
end;
procedure End_InjectLibFunc32OldDynamic(buf: PInjectLib32); begin end;
procedure CreateStub_InjectLibFunc32OldStatic;
// compile with Delphi 7 in release mode
var fh : THandle;
c1 : dword;
begin
fh := CreateFile('D:\Desktop\InjectLibFunc32OldStatic.bin', GENERIC_READ or GENERIC_WRITE, 0, nil, CREATE_ALWAYS, 0, 0);
WriteFile(fh, InjectLibFunc32OldStatic, dword(@End_InjectLibFunc32OldStatic) - dword(@InjectLibFunc32OldStatic), c1, nil);
CloseHandle(fh);
end;
procedure CreateStub_InjectLibFunc32OldDynamic;
// compile with Delphi 7 in release mode
var fh : THandle;
c1 : dword;
begin
fh := CreateFile('D:\Desktop\InjectLibFunc32OldDynamic.bin', GENERIC_READ or GENERIC_WRITE, 0, nil, CREATE_ALWAYS, 0, 0);
WriteFile(fh, InjectLibFunc32OldDynamic, dword(@End_InjectLibFunc32OldDynamic) - dword(@InjectLibFunc32OldDynamic), c1, nil);
CloseHandle(fh);
end;
*)
// ***************************************************************
initialization
InitializeMadCHook;
// CreateStub_InjectLibFunc32OldStatic;
// CreateStub_InjectLibFunc32OldDynamic;
(* fh := CreateFileA('c:\desktop\inUseStub', GENERIC_WRITE, 0, nil, CREATE_ALWAYS, 0, 0);
WriteFile(fh, InUse_ThreadState, NativeUInt(@InUse_ThreadStateEnd) - NativeUInt(@InUse_ThreadState), c1, nil);
CloseHandle(fh);
ExitProcess(0); *)
finalization
FinalizeMadCHook;
end.