// *************************************************************** // 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 // 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. // 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.