12546 lines
547 KiB
Plaintext
12546 lines
547 KiB
Plaintext
// ***************************************************************
|
|
// madCodeHook.pas version: 4.2.2 · date: 2023-03-08
|
|
// -------------------------------------------------------------
|
|
// API/code hooking, DLL injection
|
|
// -------------------------------------------------------------
|
|
// Copyright (C) 1999 - 2023 www.madshi.net, All Rights Reserved
|
|
// ***************************************************************
|
|
|
|
// 2023-03-08 4.2.2 updated user mode DLL injection to match latest driver
|
|
// 2021-12-27 4.2.1 (1) fixed a CET bug
|
|
// (2) fixed rare crash when installing API hook
|
|
// (3) fixed rare issue with user mode injection into DotNet
|
|
// (4) fixed rare crash with weird docker configurations
|
|
// (5) added INJECT_ALLOW_THREAD flag
|
|
// 2021-05-12 4.2.0 (1) rewrite of many assembler stubs to make Intel CET happy
|
|
// (2) fixed: GetStoredThreadState() sometimes failed
|
|
// (3) fixed an IPC vulnerability
|
|
// (4) improved IPC reliability under heavy stress
|
|
// 2020-07-16 4.1.2 (1) re-added VMWARE_INJECTION_MODE option
|
|
// (2) added SET_SAFE_HOOKING_TIMEOUT option
|
|
// (3) added ALWAYS_CHECK_HOOKS option
|
|
// (4) SendIpcMessage now defaults to not handle messages
|
|
// (5) fixed potential thread timing bug in DestroyIpcQueue
|
|
// (6) return to trusted "old" user mode injection method
|
|
// 2018-11-17 4.1.1 (1) added ex/including Metro app injection functionality
|
|
// (2) added support for selectively activating IAT injection
|
|
// 2018-07-28 4.1.0 (1) added USE_IAT_DLL_INJECTION option
|
|
// (2) DLLs with no export are rejected for USE_IAT_DLL_INJECTION
|
|
// (3) little change to make latest Application Verifier happy
|
|
// (4) added "USE_ABSOLUTE_JMP" HookAPI/Code() flag
|
|
// 2018-05-17 4.0.5 (1) added support for driver DLL inject approval callback
|
|
// (2) added "callback" parameters to InjectLibraryA/W
|
|
// (3) avoid crash when uninstalling API hooks in Edge
|
|
// (4) improved LoadLibrary hook thread safety
|
|
// (5) avoid deadlock while checking for new/removed DLLs
|
|
// (6) improved ProcessIdToFileName for wow64 processes
|
|
// (7) added DISABLE_LDR_LOAD_DLL_SPECIAL_HOOK option
|
|
// (8) added DISABLE_PARALLEL_DLL_LOADING option
|
|
// 2017-12-22 4.0.4 (1) fixed: sending 32bit IPC from system to user failed
|
|
// (2) fixed: sending IPC from RuntimeBroker.exe could fail
|
|
// (3) fixed: memory leak in ProcessIdToFileName
|
|
// 2017-07-12 4.0.3 (1) fixed: initialization could eventually (rarely) crash
|
|
// (2) improved DestroyIpcQueue to avoid leaks and freezes
|
|
// (3) improved Chrome sandbox uninjection
|
|
// (4) improved "FOLLOW_JMP" to work with Bitdefender x64
|
|
// (5) CreateIpcQueue supports a custom security descriptor
|
|
// 2017-03-29 4.0.2 added "HOOK_LOAD_LIBRARY" option
|
|
// 2017-03-21 4.0.1 (1) fixed: uninject callback failed if no API was hooked
|
|
// (2) fixed: injecting dlls from within rundll failed
|
|
// (3) fixed: IPC answer didn't always arrive
|
|
// (4) fixed: dll injection handle leak
|
|
// (5) improved chrome sandbox uninjection
|
|
// (6) improved GetCallingModule reliability
|
|
// (7) faster performance when checking newly loaded dlls
|
|
// (8) added new "LIMITED_IPC_PORT" option
|
|
// 2016-08-24 4.0.0 (1) added option to do "permanent" DLL injection
|
|
// (2) added "STORE_THREAD_STATE" HookAPI flag
|
|
// (3) added "GetStoredThreadState()" API
|
|
// (4) added "RegisterUninjectCallback()" API
|
|
// (5) added extended support for hooking "ordinal" APIs
|
|
// (6) added "VERIFY_HOOK_ADDRESS" performance option
|
|
// (7) added "DISASM_HOOK_TARGET" performance options
|
|
// (8) hooks use 5-byte JMP now to improve compatability
|
|
// (9) improved injection method for newly created processes
|
|
// (a) improved safe unhooking counter logic
|
|
// (b) "CreateIpcQueueEx" renamed to "CreateIpcQueue"
|
|
// (c) added IPC "context" parameter
|
|
// (d) former "SECURE_MEMORY_MAPS" option is always on now
|
|
// (e) former "USE_NEW_IPC_LOGIC" option is always on now
|
|
// (f) former "VMWARE_INJECTION_MODE" option no longer needed
|
|
// (g) some small performance improvements and bugfixes
|
|
// (h) dropped win9x and NT4 support
|
|
// 2016-05-19 3.1.13 (1) fixed: regression could cause finalization crashes
|
|
// (2) fixed: SendIpcMessage blocked if queue didn't exist
|
|
// 2016-05-17 3.1.12 (1) fixed: some chrome shutdown crashes (when debugging)
|
|
// (2) fixed: hook uninstall could crash (when debugging)
|
|
// (3) fixed: SAFE_HOOKING could crash after uninjection
|
|
// (4) fixed: IPC reply sometimes didn't arrive (missing PID)
|
|
// (5) fixed: hook stub was allocated at wrong address (x64)
|
|
// 2016-03-16 3.1.11 (1) fixed: x64 jmp/call relocation miscalculation
|
|
// (2) added hook to detect delay loaded dlls
|
|
// (3) new process dll inject now always done in main thread
|
|
// (4) small performance improvements
|
|
// (5) fixed some PAGE_EXECUTE_READWRITE security issues
|
|
// (6) fixed rare crash when calling HookAPI
|
|
// (7) dll injection loader lock improvement
|
|
// 2015-09-10 3.1.10 (1) fixed: threading issue when to-be-hooked dll is loaded
|
|
// (2) fixed: some conflicts with other hook libraries (x64)
|
|
// (3) improved thread protection for multiple injections
|
|
// 2015-04-20 3.1.9 (1) fixed: rare injection/hook instability bug
|
|
// (2) fixed: rare IPC stability bug
|
|
// 2014-10-26 3.1.8 (1) fixed: RestoreCode sometimes produced incorrect code
|
|
// (2) fixed: hooking ntdll in non-large-address-aware x64
|
|
// processes crashed
|
|
// (3) FOLLOW_JMP now follows up to 10 JMPs in a row
|
|
// 2013-12-03 3.1.6 (1) fixed: CreateProcessEx failed for .Net processes
|
|
// (2) fixed: two rare crashes
|
|
// 2013-10-01 3.1.5 (1) added XE5 support
|
|
// (2) added support for Windows 8.1
|
|
// (3) improved FOLLOW_JMP implementation
|
|
// (4) revert aligned UNICODE_STRING (compatability problems)
|
|
// 2013-05-13 3.1.4 (1) fixed: FOLLOW_JMP eventually modified export tables
|
|
// (2) fixed: win9x hooking eventually crashed
|
|
// (3) aligned UNICODE_STRING in internal structure
|
|
// (4) "driver only" injection now works without admin rights
|
|
// 2013-03-13 3.1.3 (1) fixed: IPC messages sometimes contained wrong session id
|
|
// (2) fixed: uninjecting DLL twice at the same time crashed
|
|
// (3) fixed: injecting multiple 32bit dlls in x64 OS crashed
|
|
// (4) added new FOLLOW_JMP flag for HookAPI/Code
|
|
// 2012-09-05 3.1.2 (1) added support for Metro (AppContainer integrity) apps
|
|
// (2) fixed: uninjection crash in w2k3 error reporting service
|
|
// 2012-08-20 3.1.1 fixed: crash in CreateProcessEx (32bit)
|
|
// 2012-08-02 3.1.0 (1) added support for XE2 x64
|
|
// (2) improved internal LoadLibrary hook reliability
|
|
// 2012-06-12 3.0.4 fixed: CreateProcessEx didn't always work in XP64
|
|
// 2012-05-21 3.0.3 (1) fixed uninjection memory leak
|
|
// (2) improved 64bit injection into already running processes
|
|
// (3) RENEW_OVERWRITTEN_HOOKS can now also affect new hooks
|
|
// (4) fixed chrome sandbox uninjection problem
|
|
// 2011-09-08 3.0.2 make HookLoadLibrary work even if LoadLibraryExW is hooked
|
|
// 2011-03-27 3.0.1 (1) CreateGlobalFileMapping: LastError value is now restored
|
|
// (2) (Un)InjectLibraryA: improved ansi string conversion
|
|
// (3) added IsInjectionDriverInstalled/Running
|
|
// (4) added UninjectAllLibraries
|
|
// (5) added SetInjectionMethod
|
|
// (6) improved injection driver APIs' GetLastError handling
|
|
// (7) small change to make (un)injection slightly more stable
|
|
// (8) fixed win9x injection crash
|
|
// (9) sped up first HookAPI call
|
|
// (a) modified internal LoadLibrary hook (win7) once again
|
|
// (b) added SetInjectionMethod API
|
|
// (c) added UninjectAllLibrariesA/W API
|
|
// (d) added special handling for "wine"
|
|
// (e) increased default internal IPC timeout from 2s to 7s
|
|
// 2010-01-10 3.0.0 (1) added full support for 64bit OSs
|
|
// (2) DLL injection APIs changed
|
|
// (3) injection driver control APIs added
|
|
// (4) process black & white lists for DLL injection added
|
|
// (5) (Un)InstallMadCHook replaced by driver injection APIs
|
|
// (6) Is64bitOS added + Is64bitProcess exported
|
|
// (7) CreateProcessEx now works for 64bit processes
|
|
// (8) ProcessIdToFileName modified and unicode version added
|
|
// (9) added "INJECT_INTO/UNINJECT_FROM_RUNNING_PROCESSES"
|
|
// 2010-01-07 2.2l (1) privileges stay untouched in hook dlls (win 7 stability)
|
|
// (2) fixed: some allocation was still done at 0x5f040000
|
|
// (3) fixed: internal bug in CollectCache
|
|
// (4) modified LoadLibrary hook for Windows 7
|
|
// 2009-09-15 2.2k (1) fixed: DLL inj failed when some native APIs were hooked
|
|
// (2) fixed: SendIpcMessage sometimes failed in Windows 7
|
|
// (3) moved allocation from $71700000 to $71b00000 (msys bug)
|
|
// (4) fixed: LoadLibrary hook eventually crashed in x64 OSs
|
|
// (5) DONT_TOUCH_RUNNING_PROCESSES now only affects injection
|
|
// 2009-07-22 2.2j (1) other libs' hooks are not overwritten by default, anymore
|
|
// (2) DONT_RENEW_OVERWRITTEN_HOOKS -> RENEW_OVERWRITTEN_HOOKS
|
|
// (3) RestoreCode added
|
|
// (4) DLL injection thread now always uses 1MB stack size
|
|
// 2009-02-09 2.2i (1) Delphi 2009 support
|
|
// (2) added new option "DONT_TOUCH_RUNNING_PROCESSES"
|
|
// (3) added new option "DONT_RENEW_OVERWRITTEN_HOOKS"
|
|
// (4) fixed "USE_EXTERNAL_DRIVER_FILE": driver got deleted
|
|
// 2008-02-18 2.2h fixed DEP issue introduced with version 2.2f <sigh>
|
|
// 2008-01-19 2.2g fixed exception introduced with version 2.2f
|
|
// 2008-01-14 2.2f (1) new function "SetMadCHookOption" added
|
|
// (2) stability check was too careful with UPXed module
|
|
// (3) SeTakeOwnershipPrivilege is not enabled, anymore
|
|
// (4) improved cooperation with other hooking libraries
|
|
// - internal LoadLibrary hook modified to avoid conflicts
|
|
// - when unhooking fails, stability is still maintained
|
|
// (5) hooking unprotected (writable) code sometimes failed
|
|
// 2007-05-28 2.2e IPC works in Vista64 now, too
|
|
// 2007-03-28 2.2d (1) incompatability with ZoneAlarm AV / Kaspersky fixed
|
|
// (2) ALLOW_WINSOCK2_MIXTURE_MODE flag added
|
|
// (3) Vista ASLR incorrectly triggered mixture mode
|
|
// 2006-11-28 2.2c (1) endless recursion protection for tool functions
|
|
// (2) some first changes for 64bit OS support
|
|
// (3) Get/SetLastError value for LoadLibrary was destroyed
|
|
// (4) "MixtureCache" renamed to "CollectCache"
|
|
// (5) CollectHooks improves performance even more now
|
|
// (6) improved performance in internal LoadLibrary hook
|
|
// (7) little bug in dll injection fixed
|
|
// (8) official Vista (32bit) support added
|
|
// 2006-01-30 2.2b (1) Windows Vista (32bit) support added
|
|
// (2) Injection driver allocates at $71700000 now (winNT)
|
|
// 2005-11-28 2.2a (1) fixed deletion of invalid crit. section in finalization
|
|
// (2) auto unhooking bug fixed
|
|
// (3) GetCallingModule crash in non-com. Delphi edition fixed
|
|
// 2005-07-23 2.2 (1) fixed a little resource leak (1 critical section per dll)
|
|
// (2) NextHook variable stays usable after unhooking now
|
|
// (3) flag "DONT_COUNT" renamed to "NO_SAFE_UNHOOKING"
|
|
// (4) safe unhooking slightly improved (winNT)
|
|
// (5) "SAFE_HOOKING" -> thread safe hook installation (winNT)
|
|
// (6) turning "compiler -> optimization off" made problems
|
|
// (7) reduced double hooking problems with other hook libaries
|
|
// (8) mchInjDrv doesn't get listed in device manager, anymore
|
|
// (9) you can now permanently install the injection driver
|
|
// 2005-01-02 2.1e patch 6 bytes before page boundary: hook installation failed
|
|
// 2004-09-26 2.1d (1) memory leak in CreateIpcQueue fixed
|
|
// (2) win9x: injection skips newly created 16bit processes now
|
|
// 2004-08-05 2.1c (1) CURRENT_PROCESS is not ignored by UninjectLibrary anymore
|
|
// (2) enabling Backup/Restore privileges broke Samba support
|
|
// (3) some tweaks for win98, when IPC functions are stressed
|
|
// (4) xp sp2: IPC sending from under privileged account failed
|
|
// (5) IsHookInUse added
|
|
// 2004-04-25 2.1b (1) VirtualAlloc now allocates >= $5f040000 (winNT)
|
|
// (2) new injection driver version embedded
|
|
// 2004-04-12 2.1a (1) hook finalization: "nextHook" may be zeroed too early
|
|
// (2) safe unhooking improved (16 -> 280 thread capacity)
|
|
// (3) AMD64 NX: LocalAlloc -> VirtualAlloc (TCodeHook.Create)
|
|
// (4) GetTickCount doesn't overrun now, anymore
|
|
// 2004-03-07 2.1 (1) dll inject into new processes done by kernel driver (nt)
|
|
// (2) w2k3 support improved
|
|
// (3) AutoCAD debugger detection doesn't trigger anymore
|
|
// (4) ProtectMemory added where UnprotectMemory was used
|
|
// (5) PatchExportTable sometimes froze IE in win2k
|
|
// (6) special fix for a "Process Explorer" bug (see CheckMap)
|
|
// (7) InjectLibrary wide string handling bug fixed
|
|
// (8) bug in library path checking functions fixed
|
|
// (9) NO_MIXTURE_MODE flag added
|
|
// (a) the mixture mode can't be used for ws_32.dll
|
|
// (b) workaround for 2k/XP DuplicateHandle bug in AcLayers.dll
|
|
// (c) initialization ends with SetLastError(0), just in case
|
|
// (d) HookCode/API calls SetLastError(0) when it succeeds
|
|
// (e) CheckHooks doesn't modify the LastError value anymore
|
|
// (f) dummy win9x API detection improved
|
|
// (g) virtual memory allocation now >= $5f000000 (winNT)
|
|
// (h) CreateIpcQueueEx added with additional parameters
|
|
// 2003-11-10 2.0a (1) memory leak in API hooking code fixed
|
|
// (2) some bugs in Ipc functionality fixed
|
|
// (3) memory leak in madCHook.dll/lib fixed
|
|
// (4) call CloseHandle/ReleaseMutex/etc only for valid handles
|
|
// (5) error handling improved a bit
|
|
// 2003-08-10 2.0 (1) most code is totally rewritten/changed
|
|
// (2) HookCode parameters changed -> only one flags parameter
|
|
// (3) automatic mixture mode detection mightily improved
|
|
// (4) process wide hooking of system APIs in win9x more stable
|
|
// (5) shared PE sections are fully supported now (win9x)
|
|
// (6) dummy 9x wide APIs (e.g. CreateFileW) are not hooked
|
|
// (7) (Un)InjectLibrary: user/session/system wide injection!
|
|
// (8) InjectLibrary2 replaced by InjectLibrary (auto detect)
|
|
// (9) hook dlls protected from unauthorized FreeLibrary calls
|
|
// (a) fully automatic unhooking before dll unloading
|
|
// (b) safe unhooking implemented
|
|
// (c) static lib for Microsoft C++ added
|
|
// (d) CreateIpcQueue + SendIpcMessage + DestroyIpcQueue added
|
|
// (e) AmSystemProcess + AmUsingInputDesktop added
|
|
// (f) GetCurrentSessionId + GetInputSessionId added
|
|
// (g) GetCallingModule function added
|
|
// (h) ProcessIdToFileName added
|
|
// (i) Create/OpenGlobalMutex + Event + FileMapping added
|
|
// (j) WideToAnsi + AnsiToWide functions added
|
|
// (k) IAT patching doesn't stumble about compressed modules
|
|
// (l) try..except/try..finally works without SysUtils now
|
|
// (m) RenewHook function added
|
|
// (n) madCodeHook.dll -> madCHook.dll (8.3 dos name logic)
|
|
// (o) madIsBadModule tool added
|
|
// (p) several nice new demos added
|
|
// (q) UnhookAPI added (= UnhookCode, added just for the look)
|
|
// (r) AddAccessForEveryone added
|
|
// 2002-11-13 1.3k (1) using GetMem was a bad idea for dlls, now LocalAlloc
|
|
// (2) mixture mode hooks get uninstalled now, too
|
|
// (3) InjectLibraryW didn't allocate enough memory for dll path
|
|
// (4) InjectLibrary2(W) doesn't ask GetProcessVersion, anymore
|
|
// 2002-10-17 1.3j (1) InjectLibrary2(W) was not stdcall (ouch)
|
|
// (2) AtomicMove improves stability when (un)installing Hook
|
|
// 2002-10-06 1.3i improved reliability of CreateProcessEx in 9x (hopefully)
|
|
// 2002-10-03 1.3h (1) 1.3g introduced a bug in CreateProcessEx
|
|
// (2) InjectLibraryW added
|
|
// (3) InjectLibrary2(W) added for use in CreateProcess(W) hooks
|
|
// 2002-09-22 1.3g (1) added a FreeMem to TCodeHook.Destroy to fix a memory leak
|
|
// (2) CreateProcessExW added
|
|
// (3) GetProcAddressEx avoids IAT patching problems
|
|
// 2002-04-06 1.3f InstallMixturePatcher only did a part of what it should
|
|
// 2002-03-24 1.3e (1) mixture mode initialization changed again
|
|
// (2) CollectHooks/FlushHooks speed up mixture initialization
|
|
// (3) bug in PatchMyImportTables made HookCode crash sometimes
|
|
// 2002-03-15 1.3d CreateProcessEx now works with .net programs in NT family
|
|
// 2002-03-10 1.3c when using system wide hooking with mixture mode in win9x
|
|
// each process now patches its own import tables
|
|
// this gets rid of any (noticable) initialization delays
|
|
// 2002-02-23 1.3b mutexes were not entered, if they already existed
|
|
// 2001-07-28 1.3a serious bug (-> crash!) in CreateProcessEx fixed
|
|
// 2001-07-08 1.3 InjectLibrary added
|
|
// 2001-05-25 1.2b mixture: automatic IAT patching of new processes/modules (9x)
|
|
// normally not needed, because we also patch the export table
|
|
// but in rare cases the import table is hard coded
|
|
// 2001-04-20 1.2a you can now force HookCode to use the mixture mode
|
|
// 2000-12-23 1.2 new function CreateProcessEx -> dll injecting
|
|
// 2000-11-28 1.1c bug in PatchImportTable fixed (2nd missing VirtualProtectEx)
|
|
// 2000-11-26 1.1b (1) bug in PatchImportTable fixed (missing VirtualProtectEx)
|
|
// (2) support for w9x debug mode
|
|
// 2000-11-25 1.1a two bugs in the new hooking mode fixed
|
|
// 2000-11-23 1.1 (1) integrated enhanced import/export table patching added
|
|
// so now we should be able to hook really *every* API
|
|
// (2) no interfaces anymore to get rid of madBasic
|
|
// 2000-07-25 1.0d minor changes in order to get rid of SysUtils
|
|
|
|
unit madCodeHook;
|
|
|
|
{$I mad.inc} {$W-}
|
|
|
|
{$ifdef win64}{$define unlocked}{$endif}
|
|
|
|
// use ntdll entry point patching in XP and higher?
|
|
// advantage: dll gets initialized even before the statically linked dlls are
|
|
// disadvantage: dll can't be uninjected anymore
|
|
{ $define InjectLibraryPatchXp}
|
|
|
|
// enable logging?
|
|
{ $define log}
|
|
|
|
// enable automatic infinite recursion protection?
|
|
{$define CheckRecursion}
|
|
|
|
interface
|
|
|
|
uses Windows, madTypes, madDisAsm;
|
|
|
|
// ***************************************************************
|
|
|
|
const
|
|
// before installing an API hook madCodeHook does some security checks
|
|
// one check is verifying whether the to be hooked code was already modified
|
|
// in this case madCodeHook does not tempt to modify the code another time
|
|
// otherwise there would be a danger to run into stability issues
|
|
// with protected/compressed modules there may be false alarms, though
|
|
// so you can turn this check off
|
|
// param: unsused
|
|
DISABLE_CHANGED_CODE_CHECK = $00000003;
|
|
|
|
// when calling SendIpcMessage you can specify a timeout value
|
|
// this value only applies to how long madCodeHook waits for the reply
|
|
// there's an additional internal timeout value which specifies how long
|
|
// madCodeHook waits for the IPC message to be accepted by the queue owner
|
|
// the default value is 7000ms
|
|
// param: internal timeout value in ms
|
|
// example: SetMadCHookOption(SET_INTERNAL_IPC_TIMEOUT, PWideChar(5000));
|
|
SET_INTERNAL_IPC_TIMEOUT = $00000005;
|
|
|
|
// VMware: when disabling acceleration dll injection sometimes is delayed
|
|
// to work around this issue you can activate this special option
|
|
// it will result in a slightly modified dll injection logic
|
|
// as a side effect injection into DotNet applications may not work properly
|
|
// param: unused
|
|
VMWARE_INJECTION_MODE = $00000006;
|
|
|
|
// system wide dll injection normally injects the hook dll into both:
|
|
// (1) currently running processes
|
|
// (2) newly created processes
|
|
// this flag disables injection into already running processes
|
|
// param: unused
|
|
DONT_TOUCH_RUNNING_PROCESSES = $00000007;
|
|
|
|
// normally madCodeHook renews hooks only when they were removed
|
|
// hooks that were overwritten with some other code aren't renewed by default
|
|
// this behaviour allows other hooking libraries to co-exist with madCodeHook
|
|
// use this flag to force madCodeHook to always renew hooks
|
|
// this may result in other hooking libraries stopping to work correctly
|
|
// param: unused
|
|
RENEW_OVERWRITTEN_HOOKS = $00000009;
|
|
|
|
// system wide dll injection normally injects the hook dll into both:
|
|
// (1) currently running processes
|
|
// (2) newly created processes
|
|
// this option enabled/disables injection into already running processes
|
|
// param: bool
|
|
// default: true
|
|
INJECT_INTO_RUNNING_PROCESSES = $0000000a;
|
|
|
|
// system wide dll uninjection normally does two things:
|
|
// (1) uninjecting from all running processes
|
|
// (2) stop automatic injection into newly created processes
|
|
// this option controls if uninjection from running processes is performed
|
|
// param: bool
|
|
// default: true
|
|
UNINJECT_FROM_RUNNING_PROCESSES = $0000000b;
|
|
|
|
// by default madCodeHook allocates at <= $71af0000
|
|
// you can tell madCodeHook to allocate at the address you prefer
|
|
// param: LPVOID (must be < $80000000)
|
|
// default: $71af0000
|
|
X86_ALLOCATION_ADDRESS = $0000000c;
|
|
|
|
// By default madCodeHook performs several hook address verifications.
|
|
// You can disable that to save performance, but that means it's your
|
|
// responsibility then to provide proper addresses.
|
|
// Hook address verification includes things such as bypassing existing
|
|
// import/export table manipulations etc.
|
|
// param: bool
|
|
// default: true
|
|
VERIFY_HOOK_ADDRESS = $0000000d;
|
|
|
|
// By default madCodeHook disassembles the whole to-be-hooked API/code,
|
|
// to make sure it can be hooked without stability issues. You can disable
|
|
// this check, but if that produces stability issues, that's on your head.
|
|
// param: bool
|
|
// default: true
|
|
DISASM_HOOK_TARGET = $0000000e;
|
|
|
|
// madCodeHook has two different IPC solutions built in
|
|
// in Vista+ and in all 64 bit OSs the "old" IPC solution doesn't work
|
|
// so in these OSs the new IPC solution is always used
|
|
// in older OSs the old solution works, but the new one is used by default
|
|
// the new solution is based on undocumented internal Windows LPC APIs
|
|
// the old solution is based on pipes and memory mapped files
|
|
// you can optionally force the old IPC solution for the older OSs
|
|
// param: unused
|
|
USE_OLD_IPC_LOGIC = $0000000f;
|
|
|
|
// the IPC port is usually accessible by Everyone
|
|
// if you prefer, you can assign limited access rights to the port
|
|
// only SYSTEM and the creator then have access rights
|
|
// this is not recommended because it may block communication
|
|
// so don't use it, unless you know exactly what you're doing
|
|
// param: bool
|
|
// default: false
|
|
LIMITED_IPC_PORT = $00000010;
|
|
|
|
// By default, madCodeHook internally hooks LoadLibrary, so that it can
|
|
// automatically install your API hooks when the target DLL is loaded.
|
|
// This allows you to call HookAPI() in DllMain even for DLLs that are not
|
|
// loaded yet. You can disable this internal LoadLibrary hook, but it's not
|
|
// recommended. Set this option before you do the first HookAPI() call.
|
|
// param: bool
|
|
// default: true
|
|
HOOK_LOAD_LIBRARY = $00000011;
|
|
|
|
// By default, madCodeHook tries to hook LoadLibrary by modifying the code
|
|
// in LoadLibraryExW which internally calls LdrLoadDll. The purpose of this
|
|
// approach is that many many people like to hook LoadLibraryExW, so by
|
|
// hooking it in a different way there's a chance we can minimize conflicts
|
|
// with other hook libraries.
|
|
// However, you can disable this solution, for whatever reason. If you do,
|
|
// madCodeHook will use "conventional" LoadLibraryExW hooking instead.
|
|
// param: bool
|
|
// default: false
|
|
DISABLE_LDR_LOAD_DLL_SPECIAL_HOOK = $00000012;
|
|
|
|
// Starting with Windows 10, when a process is initialized, the OS likes to
|
|
// load DLLs in multiple threads. This might give a small speed improvement,
|
|
// but it can cause problems with our DLL injection technique. So madCodeHook
|
|
// by default disables "parallel loading" for processes your hook DLL gets
|
|
// injected into.
|
|
// param: bool
|
|
// default: true
|
|
DISABLE_PARALLEL_DLL_LOADING = $00000013;
|
|
|
|
// By default, the madCodeHook injection driver injects your DLL into newly
|
|
// created processes by hooking "NtTestAlert()". Which the OS loader usually
|
|
// calls the first time right before executing the EXE's "main()" entry
|
|
// point.
|
|
// Optionally, you can activate the new IAT patching logic, which will
|
|
// modify the EXE's import table in such a way that your hook DLL appears to
|
|
// be statically linked to by the EXE. This way the OS loader will actually
|
|
// do the dirty work for us and load your hook DLL together with all the
|
|
// other statically linked DLLs. Unfortunately this also means that the OS
|
|
// considers your hook DLL essential to the new process, so we can't ever
|
|
// uninject it again.
|
|
// param: bool
|
|
// default: false
|
|
USE_IAT_DLL_INJECTION = $00000014;
|
|
|
|
// When calling HookAPI/Code with the SAFE_HOOKING flag, madCodeHook
|
|
// by default waits forever (in a blocking way), until it's sure that
|
|
// installing the API hook can be done without stability issues.
|
|
// Optionally, you can tell madCodeHook to abort waiting after a specific
|
|
// timeout. This of course defeats the purpose of the SAFE_HOOKING flag
|
|
// to some extent. But it's your decision, of course.
|
|
// param: internal timeout value in ms (0 = no timeout)
|
|
// example: SetMadCHookOption(SET_SAFE_HOOKING_TIMEOUT, (LPCWSTR) 5000);
|
|
SET_SAFE_HOOKING_TIMEOUT = $00000015;
|
|
|
|
// Normally, madCodeHook only checks if API hooks need to be installed, if
|
|
// a *new* DLL is loaded into the current process. However, the check if a
|
|
// loaded DLL is new or not consumes time on its own. So this option allows
|
|
// you to skip the check if a loaded DLL is new or not, which may save some
|
|
// time. But then that also results in madCodeHook always checking if API
|
|
// hooks need to be installed, which also consumes time. Not really sure
|
|
// which way is faster overall. <sigh>
|
|
// param: bool
|
|
// default: false
|
|
ALWAYS_CHECK_HOOKS = $00000016;
|
|
|
|
// temp: Disable IPC fix 1.
|
|
// param: bool
|
|
// default: false
|
|
DISABLE_IPC_FIX_1 = $10000001;
|
|
|
|
// temp: Disable IPC fix 2.
|
|
// param: bool
|
|
// default: false
|
|
DISABLE_IPC_FIX_2 = $10000002;
|
|
|
|
// with this API you can configure some aspects of madCodeHook
|
|
// available options see constants above
|
|
function SetMadCHookOption (option: dword; param: PWideChar) : bool; stdcall;
|
|
|
|
// ***************************************************************
|
|
|
|
// When your DLL gets uninjected, your "uninject callback" will be called
|
|
// right before FreeLibrary(yourHook.dll) is executed, so your callback runs
|
|
// outside of DllMain.
|
|
// If your DLL gets unloaded by other means than uninjection, your callback
|
|
// will *not* be called at all.
|
|
|
|
type TUninjectCallback = procedure (context: pointer); stdcall;
|
|
|
|
procedure RegisterUninjectCallback (callback: TUninjectCallback; context: pointer); stdcall;
|
|
|
|
// ***************************************************************
|
|
|
|
const
|
|
// by default madCodeHook counts how many times any thread is currently
|
|
// running inside of your callback function
|
|
// this way unhooking can be safely synchronized to that counter
|
|
// sometimes you don't need/want this counting to happen, e.g.
|
|
// (1) if you don't plan to ever unhook, anyway
|
|
// (2) if the counting performance drop is too high for your taste
|
|
// (3) if you want to unhook from inside the hook callback function
|
|
// in those cases you can set the flag "NO_SAFE_UNHOOKING"
|
|
DONT_COUNT = $00000001; // old name - kept for compatability
|
|
NO_SAFE_UNHOOKING = $00000001; // new name
|
|
|
|
// optionally madCodeHook can use a special technique to make sure that
|
|
// hooking in multi threaded situations won't result in crashing threads
|
|
// this technique is not tested too well right now, so it's optional for now
|
|
// you can turn this feature on by setting the flag "SAFE_HOOKING"
|
|
// without this technique crashes can happen, if a thread is calling the API
|
|
// which we want to hook in exactly the moment when the hook is installed
|
|
SAFE_HOOKING = $00000020;
|
|
|
|
// by default madCodeHook will auto unhook everything in finalization
|
|
// you can disable that - do it on your own risk!
|
|
NO_AUTO_UNHOOKING = $00000100;
|
|
|
|
// madCodeHook implements two different API hooking methods
|
|
// the mixture mode is the second best method, it's only used if the main
|
|
// hooking method doesn't work for whatever reason (e.g. API code structure)
|
|
// normally madCodeHook chooses automatically which mode to use
|
|
// you can force madCodeHook to use the mixture mode by specifying the flag:
|
|
MIXTURE_MODE = $00000002;
|
|
|
|
// if you don't want madCodeHook to use the mixture mode, you can say so
|
|
// however, if the main hooking mode can't be used, hooking then simply fails
|
|
NO_MIXTURE_MODE = $00000010;
|
|
|
|
// winsock2 normally doesn't like the mixture mode
|
|
// however, I've found a way to convince winsock2 to accept mixture hooks
|
|
// this is a somewhat experimental feature, though
|
|
// so it must be turned on explicitly
|
|
ALLOW_WINSOCK2_MIXTURE_MODE = $00000080;
|
|
|
|
// By default, if the target API was already hooked by other hook library,
|
|
// madCodeHook switches to mixture mode (if possible).
|
|
// If the other hook library used code overwriting with a simple JMP,
|
|
// using the flag FOLLOW_JMP will instead make madCodeHook hook the callback
|
|
// function of the other hook library. This should work just fine. However,
|
|
// your hook will stop working in this case in the moment when the other
|
|
// hook library uninstalls its hook.
|
|
FOLLOW_JMP = $00000200;
|
|
|
|
// Use this flag to have madCodeHook store the thread state, every time the
|
|
// hooked function/API is called. The thread state is stored on the stack.
|
|
// You can retrieve it in your hook callback function by using the API
|
|
// "GetStoredThreadState".
|
|
STORE_THREAD_STATE = $00000400;
|
|
|
|
// By default, madCodeHook v4 uses a 5-byte relative JMP to hook an API.
|
|
// You can force madCodeHook to use the old v3 6-byte absolute JMP instead.
|
|
USE_ABSOLUTE_JMP = $00000800;
|
|
|
|
// Hook any code or a specific API.
|
|
// api:
|
|
// Name of the to-be-hooked API. Or a typecasted ordinal number.
|
|
// flags:
|
|
// See flags defined above.
|
|
// numStackParams:
|
|
// The parameter "numStackParams" is ignored, unless you specify the flag
|
|
// STORE_THREAD_STATE at the same time. In that case "numStackParams" should
|
|
// be set to the number of parameters that are stored on the stack. If you
|
|
// set this number too low, your callback function will receive random param
|
|
// values. Setting this number too high is not a problem. So if you're lazy,
|
|
// you can just stick with the default of 32, which should be high enough
|
|
// for all but the rarest situations. However, such a high value consumes
|
|
// additional stack space and CPU performance. So in order to optimize RAM
|
|
// and CPU consumption, it'd still be useful to set this parameter to the
|
|
// correct value, or at least near to the correct value. You can estimate
|
|
// high, just to be safe. A good estimate would be the number of parameters
|
|
// plus one, that should work for all calling conventions, and also for
|
|
// class methods.
|
|
function HookCode (code : pointer;
|
|
callbackFunc : pointer;
|
|
out nextHook : pointer;
|
|
flags : dword = 0;
|
|
numStackParams : dword = 32) : bool; stdcall;
|
|
function HookAPI (module, api : PAnsiChar;
|
|
callbackFunc : pointer;
|
|
out nextHook : pointer;
|
|
flags : dword = 0;
|
|
numStackParams : dword = 32) : bool; stdcall;
|
|
|
|
// some firewall/antivirus programs kill our hooks, so we need to renew them
|
|
function RenewHook (var nextHook: pointer) : bool; stdcall;
|
|
|
|
// is the hook callback function of the specified hook currently in use?
|
|
// 0: the hook callback function is not in use
|
|
// x: the hook callback function is in use x times
|
|
function IsHookInUse (var nextHook: pointer) : integer; stdcall;
|
|
|
|
// unhook again
|
|
function UnhookCode (var nextHook: pointer) : bool; stdcall;
|
|
function UnhookAPI (var nextHook: pointer) : bool; stdcall;
|
|
|
|
// putting all your "HookCode" calls into a "CollectHooks".."FlushHooks" frame
|
|
// can eventually speed up the installation of the hooks
|
|
procedure CollectHooks;
|
|
procedure FlushHooks;
|
|
|
|
// restores the original code of the API/function (only first 6 bytes)
|
|
// the original code is read from the dll file on harddisk
|
|
// you can use this function e.g. to remove the hook of another hook library
|
|
// don't use this to uninstall your own hooks, use UnhookCode for that purpose
|
|
function RestoreCode (code: pointer) : bool; stdcall;
|
|
|
|
// ***************************************************************
|
|
|
|
// same as CreateProcess
|
|
// additionally the dll "loadLibrary" is injected into the newly created process
|
|
// the dll gets loaded before the entry point of the exe module is called
|
|
function CreateProcessExA (applicationName, commandLine : PAnsiChar;
|
|
processAttr, threadAttr : PSecurityAttributes;
|
|
inheritHandles : bool;
|
|
creationFlags : dword;
|
|
environment : pointer;
|
|
currentDirectory : PAnsiChar;
|
|
const startupInfo : {$ifdef UNICODE} TStartupInfoA; {$else} TStartupInfo; {$endif}
|
|
var processInfo : TProcessInformation;
|
|
loadLibrary : PAnsiChar ) : bool; stdcall;
|
|
function CreateProcessExW (applicationName, commandLine : PWideChar;
|
|
processAttr, threadAttr : PSecurityAttributes;
|
|
inheritHandles : bool;
|
|
creationFlags : dword;
|
|
environment : pointer;
|
|
currentDirectory : PWideChar;
|
|
const startupInfo : {$ifdef UNICODE} TStartupInfoW; {$else} TStartupInfo; {$endif}
|
|
var processInfo : TProcessInformation;
|
|
loadLibrary : PWideChar ) : bool; stdcall;
|
|
function CreateProcessEx (applicationName, commandLine : PAnsiChar;
|
|
processAttr, threadAttr : PSecurityAttributes;
|
|
inheritHandles : bool;
|
|
creationFlags : dword;
|
|
environment : pointer;
|
|
currentDirectory : PAnsiChar;
|
|
const startupInfo : {$ifdef UNICODE} TStartupInfoA; {$else} TStartupInfo; {$endif}
|
|
var processInfo : TProcessInformation;
|
|
loadLibrary : AnsiString ) : boolean;
|
|
|
|
// ***************************************************************
|
|
|
|
const
|
|
// flags for injection/uninjection "session" parameter
|
|
ALL_SESSIONS : dword = dword(-1);
|
|
CURRENT_SESSION : dword = dword(-2);
|
|
|
|
// flags for inject approval callbacks (see below)
|
|
TARGET_PROCESS_IS_64BIT : dword = $00000001;
|
|
TARGET_PROCESS_IS_SYSTEM : dword = $00000002;
|
|
TARGET_PROCESS_IS_ELEVATED : dword = $00000004;
|
|
TARGET_PROCESS_IS_PROTECTED : dword = $00000008;
|
|
TARGET_PROCESS_IS_NEWLY_CREATED : dword = $00000010;
|
|
|
|
type
|
|
// callback approval definition for system/user wide dll injection
|
|
TInjectApprovalCallbackRoutine = function (
|
|
context : pointer; // you can use this for whatever purpose you like
|
|
processId : dword; // injection target process ID
|
|
parentId : dword; // which process started the target process?
|
|
sessionId : dword; // to which session does the process belong?
|
|
flags : dword; // see TARGET_PROCESS_IS_XXX flags (defined above)
|
|
imagePath : PWideChar; // exe file name/path
|
|
commandLine : PWideChar // full command line
|
|
) : bool; stdcall;
|
|
|
|
// same as Load/FreeLibrary, but is able to load/free the library in any process
|
|
function InjectLibraryA (libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall; overload;
|
|
function InjectLibraryW (libFileName: PWideChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall; overload;
|
|
function InjectLibrary (libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall; overload;
|
|
function UninjectLibraryA (libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall; overload;
|
|
function UninjectLibraryW (libFileName: PWideChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall; overload;
|
|
function UninjectLibrary (libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall; overload;
|
|
|
|
const
|
|
// options for Inject/UninjectLibrary
|
|
INJECT_SYSTEM_PROCESSES = $0001;
|
|
INJECT_PERMANENTLY = $0002;
|
|
INJECT_METRO_APPS = $0004;
|
|
INJECT_VIA_IAT_PATCHING = $0008;
|
|
INJECT_ALLOW_THREAD = $0010;
|
|
|
|
INJECT_GLOBAL_ENABLE_PARALLEL_DLL_LOADING = $10000000;
|
|
INJECT_GLOBAL_USE_IAT_DLL_INJECTION = $20000000;
|
|
|
|
// (un)injects a library to/from all processes of the specified session(s)
|
|
// driverName: name of the driver to use
|
|
// libFileName: full file path/name of the hook dll
|
|
// session: session id into which you want the dll to be injected
|
|
// "-1" or "ALL_SESSIONS" means all sessions
|
|
// "-2" or "CURRENT_SESSION" means the current session
|
|
// options: various options, see INJECT_xxx flags
|
|
// includeMask: list of exe file name/path masks into which the hook dll shall be injected
|
|
// you can use multiple masks separated by a "|" char
|
|
// you can either use a full path, or a file name, only
|
|
// leaving this parameter empty means that all processes are "included"
|
|
// Example: "c:\program files\*.exe|calc.exe|*.scr"
|
|
// excludeMask: list of exe file name/path masks which shall be excluded from dll injection
|
|
// the excludeMask has priority over the includeMask
|
|
// leaving this parameter empty means that no processes are "excluded"
|
|
// excludePIDs: list of process IDs which shall not be touched
|
|
// when used, the list must be terminated with a "0" PID item
|
|
// callback: will be called for each potential to-be-injected-into process
|
|
// callback decides whether to perform injection or not
|
|
// if callback doesn't reply within 5 sec timeout, a "true" reply is inferred
|
|
// caution: slow callback results in new processes starting delayed
|
|
// callbackContext: this pointer is forwarded to your callback function
|
|
// use for any purpose you like
|
|
function InjectLibraryA (driverName : PAnsiChar;
|
|
libFileName : PAnsiChar;
|
|
session : dword;
|
|
options : dword;
|
|
includeMask : PAnsiChar = nil;
|
|
excludeMask : PAnsiChar = nil;
|
|
excludePIDs : TPCardinal = nil;
|
|
callback : TInjectApprovalCallbackRoutine = nil;
|
|
callbackContext : pointer = nil;
|
|
timeOut : dword = 7000) : bool; stdcall; overload;
|
|
function InjectLibraryW (driverName : PWideChar;
|
|
libFileName : PWideChar;
|
|
session : dword;
|
|
options : dword;
|
|
includeMask : PWideChar = nil;
|
|
excludeMask : PWideChar = nil;
|
|
excludePIDs : TPCardinal = nil;
|
|
callback : TInjectApprovalCallbackRoutine = nil;
|
|
callbackContext : pointer = nil;
|
|
timeOut : dword = 7000) : bool; stdcall; overload;
|
|
function InjectLibrary (driverName : PAnsiChar;
|
|
libFileName : PAnsiChar;
|
|
session : dword;
|
|
options : dword;
|
|
includeMask : PAnsiChar = nil;
|
|
excludeMask : PAnsiChar = nil;
|
|
excludePIDs : TPCardinal = nil;
|
|
callback : TInjectApprovalCallbackRoutine = nil;
|
|
callbackContext : pointer = nil;
|
|
timeOut : dword = 7000) : bool; stdcall; overload;
|
|
function UninjectLibraryA (driverName : PAnsiChar;
|
|
libFileName : PAnsiChar;
|
|
session : dword;
|
|
options : dword;
|
|
includeMask : PAnsiChar = nil;
|
|
excludeMask : PAnsiChar = nil;
|
|
excludePIDs : TPCardinal = nil;
|
|
timeOut : dword = 7000) : bool; stdcall; overload;
|
|
function UninjectLibraryW (driverName : PWideChar;
|
|
libFileName : PWideChar;
|
|
session : dword;
|
|
options : dword;
|
|
includeMask : PWideChar = nil;
|
|
excludeMask : PWideChar = nil;
|
|
excludePIDs : TPCardinal = nil;
|
|
timeOut : dword = 7000) : bool; stdcall; overload;
|
|
function UninjectLibrary (driverName : PAnsiChar;
|
|
libFileName : PAnsiChar;
|
|
session : dword;
|
|
options : dword;
|
|
includeMask : PAnsiChar = nil;
|
|
excludeMask : PAnsiChar = nil;
|
|
excludePIDs : TPCardinal = nil;
|
|
timeOut : dword = 7000) : bool; stdcall; overload;
|
|
|
|
// uninjects every currently active system/session wide injection
|
|
function UninjectAllLibrariesW (driverName: PWideChar; excludePIDs: TPCardinal = nil; timeOutPerUninject: dword = 7000) : bool; stdcall;
|
|
function UninjectAllLibrariesA (driverName: PAnsiChar; excludePIDs: TPCardinal = nil; timeOutPerUninject: dword = 7000) : bool; stdcall;
|
|
function UninjectAllLibraries (driverName: PAnsiChar; excludePIDs: TPCardinal = nil; timeOutPerUninject: dword = 7000) : bool; stdcall;
|
|
|
|
// ***************************************************************
|
|
|
|
// you can use these APIs to permanently (un)install your injection driver
|
|
// it will survive reboots, it will stay installed until you uninstall it
|
|
// installing the same driver twice fails succeeds and updates all parameters
|
|
function InstallInjectionDriver (driverName, fileName32bit, fileName64bit, description: PWideChar) : bool; stdcall;
|
|
function UninstallInjectionDriver (driverName: PWideChar) : bool; stdcall;
|
|
|
|
// this will load and activate your injection driver
|
|
// it will stay active only until the next reboot
|
|
// you may prefer this technique if you want your product to not require any
|
|
// installation and uninstallation procedures
|
|
// loading the same driver twice fails with ERROR_SERVICE_ALREADY_RUNNING
|
|
function LoadInjectionDriver (driverName, fileName32bit, fileName64bit: PWideChar) : bool; stdcall;
|
|
|
|
// stopping the injection driver will only work:
|
|
// (1) if the driver was configured at build time to be stoppable
|
|
// (2) if you call StopInjectionDriver
|
|
// (3) if there are no dll injections currently running
|
|
// stopping the injection driver with the device manager or "sc" is blocked
|
|
// call Stop + Start to update an installed driver to a newer version
|
|
// call Stop + Load to update a loaded driver to a newer version
|
|
function StopInjectionDriver (driverName: PWideChar) : bool; stdcall;
|
|
function StartInjectionDriver (driverName: PWideChar) : bool; stdcall;
|
|
|
|
// checks whether the specific injection driver is installed
|
|
// returns true if you used InstallInjectionDriver
|
|
// returns false if you used LoadInjectionDriver
|
|
function IsInjectionDriverInstalled (driverName: PWideChar) : bool; stdcall;
|
|
|
|
// checks whether the specific injection driver is running
|
|
// returns true if you used InstallInjectionDriver and
|
|
// if the driver is running (not paused / stopped)
|
|
// returns true if you used LoadInjectionDriver
|
|
function IsInjectionDriverRunning (driverName: PWideChar) : bool; stdcall;
|
|
|
|
// ***************************************************************
|
|
|
|
// is the current process a service/system process?
|
|
function AmSystemProcess : bool; stdcall;
|
|
|
|
// is the current thread's desktop the input desktop?
|
|
// only in that case you should show messages boxes or other GUI stuff
|
|
// but please note that in XP fast user switching AmUsingInputDesktop may
|
|
// return true, although the current session is currently not visible
|
|
// XP fast user switching is implemented by using terminal server logic
|
|
// so each fast user session has its own window station and input desktop
|
|
function AmUsingInputDesktop : bool; stdcall;
|
|
|
|
// the following two functions can be used to get the session id of the
|
|
// current session and of the input session
|
|
// each terminal server (or XP fast user switching) session has its own id
|
|
// the "input session" is the one currently shown on the physical screen
|
|
function GetCurrentSessionId : dword; stdcall;
|
|
function GetInputSessionId : dword; stdcall;
|
|
|
|
// ***************************************************************
|
|
|
|
// which module called me? works only if your function has a stack frame
|
|
function GetCallingModule (returnAddress: pointer = nil) : HMODULE; stdcall;
|
|
|
|
type
|
|
TPThreadState = ^TThreadState;
|
|
TThreadState = record
|
|
{$ifdef win64}
|
|
rflags : NativeUInt;
|
|
r15 : NativeUInt;
|
|
r14 : NativeUInt;
|
|
r13 : NativeUInt;
|
|
r12 : NativeUInt;
|
|
r11 : NativeUInt;
|
|
r10 : NativeUInt;
|
|
r9 : NativeUInt;
|
|
r8 : NativeUInt;
|
|
rdi : NativeUInt;
|
|
rsi : NativeUInt;
|
|
rbp : NativeUInt;
|
|
rbx : NativeUInt;
|
|
rdx : NativeUInt;
|
|
rcx : NativeUInt;
|
|
rax : NativeUInt;
|
|
rsp : NativeUInt;
|
|
{$else}
|
|
rflags : NativeUInt;
|
|
edi : NativeUInt;
|
|
esi : NativeUInt;
|
|
ebp : NativeUInt;
|
|
esp : NativeUInt;
|
|
ebx : NativeUInt;
|
|
edx : NativeUInt;
|
|
ecx : NativeUInt;
|
|
eax : NativeUInt;
|
|
{$endif}
|
|
end;
|
|
|
|
// which state was the thread in when the hooked API/function was called
|
|
function GetStoredThreadState (var threadState: TPThreadState) : bool; stdcall;
|
|
|
|
// ***************************************************************
|
|
|
|
// find out what file the specified process was executed from
|
|
function ProcessIdToFileNameA (processId: dword; fileName: PAnsiChar; bufLenInChars: word) : bool; stdcall;
|
|
function ProcessIdToFileNameW (processId: dword; fileName: PWideChar; bufLenInChars: word) : bool; stdcall;
|
|
function ProcessIdToFileName (processId: dword; var fileName: AnsiString) : boolean;
|
|
|
|
// ***************************************************************
|
|
// global = normal + "access for everyone" + "non session specific"
|
|
|
|
function CreateGlobalMutex (name: PAnsiChar) : THandle; stdcall;
|
|
function OpenGlobalMutex (name: PAnsiChar) : THandle; stdcall;
|
|
|
|
function CreateGlobalEvent (name: PAnsiChar; manual, initialState: bool) : THandle; stdcall;
|
|
function OpenGlobalEvent (name: PAnsiChar ) : THandle; stdcall;
|
|
|
|
function CreateGlobalFileMapping (name: PAnsiChar; size: dword) : THandle; stdcall;
|
|
function OpenGlobalFileMapping (name: PAnsiChar; write: bool) : THandle; stdcall;
|
|
|
|
// ***************************************************************
|
|
|
|
// convert strings ansi <-> wide
|
|
// the result buffer must have a size of MAX_PATH characters (or more)
|
|
// please use these functions in nt wide API hook callback functions
|
|
// because the OS' own functions seem to confuse nt in hook callback functions
|
|
procedure AnsiToWide (ansi: PAnsiChar; wide: PWideChar); stdcall;
|
|
procedure WideToAnsi (wide: PWideChar; ansi: PAnsiChar); stdcall;
|
|
|
|
// ***************************************************************
|
|
// ipc (inter process communication) message services
|
|
// in contrast to SendMessage the following functions don't crash NT services
|
|
|
|
type
|
|
// this is how you get notified about incoming ipc messages
|
|
// you have to write a function which fits to this type definition
|
|
// and then you give it into "CreateIpcQueue"
|
|
// your callback function will then be called for each incoming message
|
|
// CAUTION: each ipc message is handled by a seperate thread, as a result
|
|
// your callback will be called by a different thread each time
|
|
TIpcCallback = procedure (name : PAnsiChar;
|
|
messageBuf : pointer;
|
|
messageLen : dword;
|
|
answerBuf : pointer;
|
|
answerLen : dword;
|
|
context : pointer); stdcall;
|
|
|
|
// create an ipc queue
|
|
// please choose a unique ipc name to avoid conflicts with other programs
|
|
// only one ipc queue with the same name can be open at the same time
|
|
// so if 2 programs try to create the same ipc queue, the second call will fail
|
|
// you can specify how many threads may be created to handle incoming messages
|
|
// if the order of the messages is crucial for you, set "maxThreadCount" to "1"
|
|
// in its current implementation "maxThreadCount" only supports "1" or unlimited
|
|
// the parameter "maxQueueLen" is not yet implemented at all
|
|
// the "context" is a simple pointer value that is forwarded to your callback
|
|
function CreateIpcQueue (ipc : PAnsiChar;
|
|
callback : TIpcCallback;
|
|
context : pointer = nil;
|
|
maxThreadCount : dword = 16;
|
|
maxQueueLen : dword = $1000;
|
|
securityDesc : PSecurityDescriptor = nil) : bool; stdcall;
|
|
|
|
// send an ipc message to whomever has created the ipc queue (doesn't matter)
|
|
// if you only fill the first 3 parameters, SendIpcMessage returns at once
|
|
// if you fill the next two parameters, too, SendIpcMessage will
|
|
// wait for an answer of the ipc queue owner
|
|
// you can further specify how long you're willing to wait for the answer
|
|
// and whether you want SendIpcMessage to handle messages while waiting
|
|
function SendIpcMessage(ipc : PAnsiChar;
|
|
messageBuf : pointer;
|
|
messageLen : dword;
|
|
answerBuf : pointer = nil;
|
|
answerLen : dword = 0;
|
|
answerTimeOut : dword = INFINITE;
|
|
handleMessages : bool = false ) : bool; stdcall;
|
|
|
|
// destroy the ipc queue again
|
|
// when the queue owning process quits, the ipc queue is automatically deleted
|
|
// only the queue owning process can destroy the queue
|
|
function DestroyIpcQueue (ipc: PAnsiChar) : bool; stdcall;
|
|
|
|
// ***************************************************************
|
|
|
|
// this function adds some access rights to the specified target
|
|
// the target can either be a process handle or a service handle
|
|
function AddAccessForEveryone (processOrService: THandle; access: dword) : bool; stdcall;
|
|
|
|
// ***************************************************************
|
|
|
|
// internal stuff, please do not use
|
|
function FindRealCode (code: pointer) : pointer;
|
|
function CheckLibFilePath (var libA: PAnsiChar; var libW: PWideChar;
|
|
var strA: AnsiString; var strW: AnsiString) : boolean;
|
|
procedure InitializeMadCHook;
|
|
procedure FinalizeMadCHook;
|
|
procedure AutoUnhook (moduleHandle: HMODULE);
|
|
procedure EnableAllPrivileges;
|
|
var amMchDll : boolean = false;
|
|
|
|
{$ifdef log}
|
|
procedure log(str: AnsiString; paramA: PAnsiChar = nil; paramW: PWideChar = nil; ph: THandle = 0);
|
|
{$endif}
|
|
|
|
{$ifdef protect}
|
|
procedure InitSpecialProcs;
|
|
{$endif}
|
|
|
|
implementation
|
|
|
|
uses madStrings, madTools, madRemote, madRipeMD;
|
|
|
|
// ***************************************************************
|
|
|
|
{$ifdef log}
|
|
function ExtractFileNameA(path: AnsiString) : AnsiString;
|
|
var i1 : integer;
|
|
begin
|
|
result := path;
|
|
for i1 := Length(result) downto 1 do
|
|
if result[i1] = '\' then begin
|
|
Delete(result, 1, i1);
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
var firstLog : boolean = true;
|
|
doLog : boolean = false;
|
|
procedure log(str: AnsiString; paramA: PAnsiChar = nil; paramW: PWideChar = nil; ph: THandle = 0);
|
|
var fh : THandle;
|
|
c1 : dword;
|
|
st : TSystemTime;
|
|
time : AnsiString;
|
|
mutex : THandle;
|
|
s1 : AnsiString;
|
|
ws1 : array [0..MAX_PATH] of WideChar;
|
|
begin
|
|
if firstLog then begin
|
|
doLog := GetFileAttributesW('c:\madCodeHook.txt') <> maxCard;
|
|
firstLog := false;
|
|
end;
|
|
if doLog then begin
|
|
GetLocalTime(st);
|
|
time := IntToStrExA(dword(st.wHour), 2) + ':' +
|
|
IntToStrExA(dword(st.wMinute), 2) + ':' +
|
|
IntToStrExA(dword(st.wSecond), 2) + '-' +
|
|
IntToStrExA(dword(st.wMilliseconds), 3) + ' ';
|
|
GetModuleFileNameW(0, ws1, MAX_PATH);
|
|
s1 := WideToAnsiEx(ws1);
|
|
s1 := ExtractFileNameA(s1);
|
|
s1 := time + IntToHexExA(GetCurrentProcessID, 8) + ' ' + s1 + ' ';
|
|
ReplaceStrA(str, #$D#$A, #$D#$A + s1);
|
|
str := s1 + str + #$D#$A;
|
|
if paramW <> nil then
|
|
s1 := WideToAnsiEx(paramW)
|
|
else s1 := paramA;
|
|
ReplaceStrA(str, '%1', s1);
|
|
if ph <> 0 then begin
|
|
SetLength(s1, MAX_PATH + 1);
|
|
if not ProcessIdToFileNameA(ProcessHandleToId(ph), PAnsiChar(s1)) then
|
|
s1 := '';
|
|
ReplaceStrA(str, '%p', 'ph:' + IntToHexExA(ph) + ';pid:' + PAnsiChar(s1));
|
|
end;
|
|
mutex := CreateMutexW(nil, false, 'mAHLogMutex');
|
|
if mutex <> 0 then begin
|
|
WaitForSingleObject(mutex, INFINITE);
|
|
try
|
|
fh := CreateFileW('c:\madCodeHook.txt', GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, 0, 0);
|
|
if fh <> INVALID_HANDLE_VALUE then begin
|
|
SetFilePointer(fh, 0, nil, FILE_END);
|
|
WriteFile(fh, pointer(str)^, length(str), c1, nil);
|
|
CloseHandle(fh);
|
|
end;
|
|
finally ReleaseMutex(mutex) end;
|
|
CloseHandle(mutex);
|
|
end;
|
|
end;
|
|
end;
|
|
{$endif}
|
|
|
|
// ***************************************************************
|
|
|
|
{$ifdef protect}
|
|
{$include protect.inc}
|
|
{$endif}
|
|
|
|
// ***************************************************************
|
|
// SetMadCHookOption options
|
|
|
|
var
|
|
DisableChangedCodeCheck : boolean = false;
|
|
UseNewIpcLogic : boolean = true;
|
|
InternalIpcTimeout : dword = 7000;
|
|
VmWareInjectionMode : boolean = false;
|
|
RenewOverwrittenHooks : boolean = false;
|
|
InjectIntoRunningProcesses : boolean = true;
|
|
UninjectFromRunningProcesses : boolean = true;
|
|
X86AllocationAddress : pointer = pointer($71af0000);
|
|
VerifyHookAddress : boolean = true;
|
|
DisAsmHookTarget : boolean = true;
|
|
LimitedIpcPort : boolean = false;
|
|
HookLoadLibraryOption : boolean = true;
|
|
DisableLdrLoadDllSpecialHook : boolean = false;
|
|
DisableParallelDllLoading : boolean = true;
|
|
UseIatDllInjection : boolean = false;
|
|
DisableIpcFix1 : boolean = false;
|
|
DisableIpcFix2 : boolean = false;
|
|
SafeHookingTimeout : dword = 0;
|
|
AlwaysCheckHooks : boolean = false;
|
|
|
|
// ***************************************************************
|
|
// encrypted strings
|
|
|
|
const
|
|
// ntdll.dll
|
|
CLdrLoadDll : AnsiString = (* LdrLoadDll *) #$19#$31#$27#$19#$3A#$34#$31#$11#$39#$39;
|
|
CLdrGetDllHandle : AnsiString = (* LdrGetDllHandle *) #$19#$31#$27#$12#$30#$21#$11#$39#$39#$1D#$34#$3B#$31#$39#$30;
|
|
CLdrRegisterDllNotification : AnsiString = (* LdrRegisterDllNotification *) #$19#$31#$27#$07#$30#$32#$3C#$26#$21#$30#$27#$11#$39#$39#$1B#$3A#$21#$3C#$33#$3C#$36#$34#$21#$3C#$3A#$3B;
|
|
CLdrUnregisterDllNotification : AnsiString = (* LdrUnregisterDllNotification *) #$19#$31#$27#$00#$3B#$27#$30#$32#$3C#$26#$21#$30#$27#$11#$39#$39#$1B#$3A#$21#$3C#$33#$3C#$36#$34#$21#$3C#$3A#$3B;
|
|
CLdrResolveDelayLoadedAPI : AnsiString = (* LdrResolveDelayLoadedAPI *) #$19#$31#$27#$07#$30#$26#$3A#$39#$23#$30#$11#$30#$39#$34#$2C#$19#$3A#$34#$31#$30#$31#$14#$05#$1C;
|
|
CNtLoadDriver : AnsiString = (* NtLoadDriver *) #$1B#$21#$19#$3A#$34#$31#$11#$27#$3C#$23#$30#$27;
|
|
CNtUnloadDriver : AnsiString = (* NtUnloadDriver *) #$1B#$21#$00#$3B#$39#$3A#$34#$31#$11#$27#$3C#$23#$30#$27;
|
|
CNtTestAlert : AnsiString = (* NtTestAlert *) #$1B#$21#$01#$30#$26#$21#$14#$39#$30#$27#$21;
|
|
CNtAllocateVirtualMemory : AnsiString = (* NtAllocateVirtualMemory *) #$1B#$21#$14#$39#$39#$3A#$36#$34#$21#$30#$03#$3C#$27#$21#$20#$34#$39#$18#$30#$38#$3A#$27#$2C;
|
|
CNtFreeVirtualMemory : AnsiString = (* NtFreeVirtualMemory *) #$1B#$21#$13#$27#$30#$30#$03#$3C#$27#$21#$20#$34#$39#$18#$30#$38#$3A#$27#$2C;
|
|
CNtProtectVirtualMemory : AnsiString = (* NtProtectVirtualMemory *) #$1B#$21#$05#$27#$3A#$21#$30#$36#$21#$03#$3C#$27#$21#$20#$34#$39#$18#$30#$38#$3A#$27#$2C;
|
|
CNtDelayExecution : AnsiString = (* NtDelayExecution *) #$1B#$21#$11#$30#$39#$34#$2C#$10#$2D#$30#$36#$20#$21#$3C#$3A#$3B;
|
|
CKiUserExceptionDispatcher : AnsiString = (* KiUserExceptionDispatcher *) #$1E#$3C#$00#$26#$30#$27#$10#$2D#$36#$30#$25#$21#$3C#$3A#$3B#$11#$3C#$26#$25#$34#$21#$36#$3D#$30#$27;
|
|
CNtCreatePort : AnsiString = (* NtCreatePort *) #$1B#$21#$16#$27#$30#$34#$21#$30#$05#$3A#$27#$21;
|
|
CNtConnectPort : AnsiString = (* NtConnectPort *) #$1B#$21#$16#$3A#$3B#$3B#$30#$36#$21#$05#$3A#$27#$21;
|
|
CNtReplyWaitReceivePort : AnsiString = (* NtReplyWaitReceivePort *) #$1B#$21#$07#$30#$25#$39#$2C#$02#$34#$3C#$21#$07#$30#$36#$30#$3C#$23#$30#$05#$3A#$27#$21;
|
|
CNtReplyWaitReceivePortEx : AnsiString = (* NtReplyWaitReceivePortEx *) #$1B#$21#$07#$30#$25#$39#$2C#$02#$34#$3C#$21#$07#$30#$36#$30#$3C#$23#$30#$05#$3A#$27#$21#$10#$2D;
|
|
CNtAcceptConnectPort : AnsiString = (* NtAcceptConnectPort *) #$1B#$21#$14#$36#$36#$30#$25#$21#$16#$3A#$3B#$3B#$30#$36#$21#$05#$3A#$27#$21;
|
|
CNtCompleteConnectPort : AnsiString = (* NtCompleteConnectPort *) #$1B#$21#$16#$3A#$38#$25#$39#$30#$21#$30#$16#$3A#$3B#$3B#$30#$36#$21#$05#$3A#$27#$21;
|
|
CWineGetVersion : AnsiString = (* wine_get_version *) #$22#$3C#$3B#$30#$0A#$32#$30#$21#$0A#$23#$30#$27#$26#$3C#$3A#$3B;
|
|
CNtQueryObject : AnsiString = (* NtQueryObject *) #$1B#$21#$04#$20#$30#$27#$2C#$1A#$37#$3F#$30#$36#$21;
|
|
CNtQueryInformationToken : AnsiString = (* NtQueryInformationToken *) #$1B#$21#$04#$20#$30#$27#$2C#$1C#$3B#$33#$3A#$27#$38#$34#$21#$3C#$3A#$3B#$01#$3A#$3E#$30#$3B;
|
|
CNtQueueApcThread : AnsiString = (* NtQueueApcThread *) #$1B#$21#$04#$20#$30#$20#$30#$14#$25#$36#$01#$3D#$27#$30#$34#$31;
|
|
|
|
// kernel32.dll
|
|
CGetModuleHandleA : AnsiString = (* GetModuleHandleA *) #$12#$30#$21#$18#$3A#$31#$20#$39#$30#$1D#$34#$3B#$31#$39#$30#$14;
|
|
CLoadLibraryA : AnsiString = (* LoadLibraryA *) #$19#$3A#$34#$31#$19#$3C#$37#$27#$34#$27#$2C#$14;
|
|
CLoadLibraryExW : AnsiString = (* LoadLibraryExW *) #$19#$3A#$34#$31#$19#$3C#$37#$27#$34#$27#$2C#$10#$2D#$02;
|
|
CVirtualFree : AnsiString = (* VirtualFree *) #$03#$3C#$27#$21#$20#$34#$39#$13#$27#$30#$30;
|
|
CCreateMutexA : AnsiString = (* CreateMutexA *) #$16#$27#$30#$34#$21#$30#$18#$20#$21#$30#$2D#$14;
|
|
CGetModuleFileNameA : AnsiString = (* GetModuleFileNameA *) #$12#$30#$21#$18#$3A#$31#$20#$39#$30#$13#$3C#$39#$30#$1B#$34#$38#$30#$14;
|
|
CReleaseMutex : AnsiString = (* ReleaseMutex *) #$07#$30#$39#$30#$34#$26#$30#$18#$20#$21#$30#$2D;
|
|
CSetLastError : AnsiString = (* SetLastError *) #$06#$30#$21#$19#$34#$26#$21#$10#$27#$27#$3A#$27;
|
|
CLocalAlloc : AnsiString = (* LocalAlloc *) #$19#$3A#$36#$34#$39#$14#$39#$39#$3A#$36;
|
|
CLocalFree : AnsiString = (* LocalFree *) #$19#$3A#$36#$34#$39#$13#$27#$30#$30;
|
|
CVirtualQuery : AnsiString = (* VirtualQuery *) #$03#$3C#$27#$21#$20#$34#$39#$04#$20#$30#$27#$2C;
|
|
CWaitForMultipleObjects : AnsiString = (* WaitForMultipleObjects *) #$02#$34#$3C#$21#$13#$3A#$27#$18#$20#$39#$21#$3C#$25#$39#$30#$1A#$37#$3F#$30#$36#$21#$26;
|
|
CGetInputSessionId : AnsiString = (* WTSGetActiveConsoleSessionId *) #$02#$01#$06#$12#$30#$21#$14#$36#$21#$3C#$23#$30#$16#$3A#$3B#$26#$3A#$39#$30#$06#$30#$26#$26#$3C#$3A#$3B#$1C#$31;
|
|
CFlushInstructionCache : AnsiString = (* FlushInstructionCache *) #$13#$39#$20#$26#$3D#$1C#$3B#$26#$21#$27#$20#$36#$21#$3C#$3A#$3B#$16#$34#$36#$3D#$30;
|
|
CAddVecExceptHandler : AnsiString = (* AddVectoredExceptionHandler *) #$14#$31#$31#$03#$30#$36#$21#$3A#$27#$30#$31#$10#$2D#$36#$30#$25#$21#$3C#$3A#$3B#$1D#$34#$3B#$31#$39#$30#$27;
|
|
CRemoveVecExceptHandler : AnsiString = (* RemoveVectoredExceptionHandler *) #$07#$30#$38#$3A#$23#$30#$03#$30#$36#$21#$3A#$27#$30#$31#$10#$2D#$36#$30#$25#$21#$3C#$3A#$3B#$1D#$34#$3B#$31#$39#$30#$27;
|
|
CQueryFullProcessImageName : AnsiString = (* QueryFullProcessImageNameW *) #$04#$20#$30#$27#$2C#$13#$20#$39#$39#$05#$27#$3A#$36#$30#$26#$26#$1C#$38#$34#$32#$30#$1B#$34#$38#$30#$02;
|
|
CNtWow64QueryInfoProcess64 : AnsiString = (* NtWow64QueryInformationProcess64 *) #$1B#$21#$02#$3A#$22#$63#$61#$04#$20#$30#$27#$2C#$1C#$3B#$33#$3A#$27#$38#$34#$21#$3C#$3A#$3B#$05#$27#$3A#$36#$30#$26#$26#$63#$61;
|
|
CNtWow64ReadVirtualMemory64 : AnsiString = (* NtWow64ReadVirtualMemory64 *) #$1B#$21#$02#$3A#$22#$63#$61#$07#$30#$34#$31#$03#$3C#$27#$21#$20#$34#$39#$18#$30#$38#$3A#$27#$2C#$63#$61;
|
|
CQueueUserAPC : AnsiString = (* QueueUserAPC *) #$04#$20#$30#$20#$30#$00#$26#$30#$27#$14#$05#$16;
|
|
|
|
// advapi32.dll
|
|
CAdvApi32 : AnsiString = (* advapi32.dll *) #$34#$31#$23#$34#$25#$3C#$66#$67#$7B#$31#$39#$39;
|
|
CGetSecurityInfo : AnsiString = (* GetSecurityInfo *) #$12#$30#$21#$06#$30#$36#$20#$27#$3C#$21#$2C#$1C#$3B#$33#$3A;
|
|
CSetSecurityInfo : AnsiString = (* SetSecurityInfo *) #$06#$30#$21#$06#$30#$36#$20#$27#$3C#$21#$2C#$1C#$3B#$33#$3A;
|
|
CSetEntriesInAclA : AnsiString = (* SetEntriesInAclA *) #$06#$30#$21#$10#$3B#$21#$27#$3C#$30#$26#$1C#$3B#$14#$36#$39#$14;
|
|
CCloseServiceHandle : AnsiString = (* CloseServiceHandle *) #$16#$39#$3A#$26#$30#$06#$30#$27#$23#$3C#$36#$30#$1D#$34#$3B#$31#$39#$30;
|
|
CControlService : AnsiString = (* ControlService *) #$16#$3A#$3B#$21#$27#$3A#$39#$06#$30#$27#$23#$3C#$36#$30;
|
|
CQueryServiceStatus : AnsiString = (* QueryServiceStatus *) #$04#$20#$30#$27#$2C#$06#$30#$27#$23#$3C#$36#$30#$06#$21#$34#$21#$20#$26;
|
|
CDeleteService : AnsiString = (* DeleteService *) #$11#$30#$39#$30#$21#$30#$06#$30#$27#$23#$3C#$36#$30;
|
|
COpenSCManagerW : AnsiString = (* OpenSCManagerW *) #$1A#$25#$30#$3B#$06#$16#$18#$34#$3B#$34#$32#$30#$27#$02;
|
|
COpenServiceW : AnsiString = (* OpenServiceW *) #$1A#$25#$30#$3B#$06#$30#$27#$23#$3C#$36#$30#$02;
|
|
CStartServiceW : AnsiString = (* StartServiceW *) #$06#$21#$34#$27#$21#$06#$30#$27#$23#$3C#$36#$30#$02;
|
|
CCreateServiceW : AnsiString = (* CreateServiceW *) #$16#$27#$30#$34#$21#$30#$06#$30#$27#$23#$3C#$36#$30#$02;
|
|
CChangeServiceConfigW : AnsiString = (* ChangeServiceConfigW *) #$16#$3D#$34#$3B#$32#$30#$06#$30#$27#$23#$3C#$36#$30#$16#$3A#$3B#$33#$3C#$32#$02;
|
|
CQueryServiceConfigW : AnsiString = (* QueryServiceConfigW *) #$04#$20#$30#$27#$2C#$06#$30#$27#$23#$3C#$36#$30#$16#$3A#$3B#$33#$3C#$32#$02;
|
|
|
|
// user32.dll
|
|
CCharLowerBuffW : AnsiString = (* CharLowerBuffW *) #$16#$3D#$34#$27#$19#$3A#$22#$30#$27#$17#$20#$33#$33#$02;
|
|
|
|
// kernelbase.dll
|
|
CGetAppCtnrNamedObjectPath : AnsiString = (* GetAppContainerNamedObjectPath *) #$12#$30#$21#$14#$25#$25#$16#$3A#$3B#$21#$34#$3C#$3B#$30#$27#$1B#$34#$38#$30#$31#$1A#$37#$3F#$30#$36#$21#$05#$34#$21#$3D;
|
|
|
|
// dll names
|
|
CWs2_32 : AnsiString = (* ws2_32.dll *) #$22#$26#$67#$0A#$66#$67#$7B#$31#$39#$39;
|
|
|
|
// other strings
|
|
CSLSvc : AnsiString = (* slsvc.exe *) #$26#$39#$26#$23#$36#$7B#$30#$2D#$30;
|
|
CSeBackupPrivilege : AnsiString = (* SeBackupPrivilege *) #$06#$30#$17#$34#$36#$3E#$20#$25#$05#$27#$3C#$23#$3C#$39#$30#$32#$30;
|
|
CSeRestorePrivilege : AnsiString = (* SeRestorePrivilege *) #$06#$30#$07#$30#$26#$21#$3A#$27#$30#$05#$27#$3C#$23#$3C#$39#$30#$32#$30;
|
|
CSeTakeOwnershipPrivilege : AnsiString = (* SeTakeOwnershipPrivilege *) #$06#$30#$01#$34#$3E#$30#$1A#$22#$3B#$30#$27#$26#$3D#$3C#$25#$05#$27#$3C#$23#$3C#$39#$30#$32#$30;
|
|
CGlobal : AnsiString = (* Global\ *) #$12#$39#$3A#$37#$34#$39#$09;
|
|
CSession : AnsiString = (* Session\ *) #$06#$30#$26#$26#$3C#$3A#$3B#$09;
|
|
CMutex : AnsiString = (* Mutex *) #$18#$20#$21#$30#$2D;
|
|
CEvent : AnsiString = (* Event *) #$10#$23#$30#$3B#$21;
|
|
CMap : AnsiString = (* Map *) #$18#$34#$25;
|
|
CProcess : AnsiString = (* Process *) #$05#$27#$3A#$36#$30#$26#$26;
|
|
CApi : AnsiString = (* API *) #$14#$05#$1C;
|
|
CNamedBuffer : AnsiString = (* NamedBuffer *) #$1B#$34#$38#$30#$31#$17#$20#$33#$33#$30#$27;
|
|
CMixPrefix : AnsiString = (* mix *) #$38#$3C#$2D;
|
|
CMAHPrefix : AnsiString = (* mAH *) #$38#$14#$1D;
|
|
CMchLLEW2 : AnsiString = (* mchLLEW2 *) #$38#$36#$3D#$19#$19#$10#$02#$67;
|
|
CMchIInjT : AnsiString = (* mc5IInjT *) #$38#$36#$60#$1C#$1C#$3B#$3F#$01;
|
|
CType : AnsiString = (* Type *) #$01#$2C#$25#$30;
|
|
CErrorControl : AnsiString = (* ErrorControl *) #$10#$27#$27#$3A#$27#$16#$3A#$3B#$21#$27#$3A#$39;
|
|
CStart : AnsiString = (* Start *) #$06#$21#$34#$27#$21;
|
|
CImagePath : AnsiString = (* ImagePath *) #$1C#$38#$34#$32#$30#$05#$34#$21#$3D;
|
|
CSystemCcsServices : AnsiString = (* SYSTEM\CurrentControlSet\Services *) #$06#$0C#$06#$01#$10#$18#$09#$16#$20#$27#$27#$30#$3B#$21#$16#$3A#$3B#$21#$27#$3A#$39#$06#$30#$21#$09#$06#$30#$27#$23#$3C#$36#$30#$26;
|
|
CRegistryMachine : AnsiString = (* registry\machine *) #$27#$30#$32#$3C#$26#$21#$27#$2C#$09#$38#$34#$36#$3D#$3C#$3B#$30;
|
|
CIpc : AnsiString = (* Ipc2 *) #$1C#$25#$36#$67;
|
|
CAnswerBuf : AnsiString = (* AnswerBuf2 *) #$14#$3B#$26#$22#$30#$27#$17#$20#$33#$67;
|
|
CCounter : AnsiString = (* Cnt *) #$16#$3B#$21;
|
|
CRpcControlIpc : AnsiString = (* \RPC Control\mchIpc *) #$09#$07#$05#$16#$75#$16#$3A#$3B#$21#$27#$3A#$39#$09#$38#$36#$3D#$1C#$25#$36;
|
|
CSysWOW64 : AnsiString = (* SysWOW64 *) #$06#$2C#$26#$02#$1A#$02#$63#$61;
|
|
{$ifndef unlocked}
|
|
CEnumServicesStatusA : AnsiString = (* EnumServicesStatusA *) #$10#$3B#$20#$38#$06#$30#$27#$23#$3C#$36#$30#$26#$06#$21#$34#$21#$20#$26#$14;
|
|
CEnumServicesStatusW : AnsiString = (* EnumServicesStatusW *) #$10#$3B#$20#$38#$06#$30#$27#$23#$3C#$36#$30#$26#$06#$21#$34#$21#$20#$26#$02;
|
|
CNtQuerySystemInfo2 : AnsiString = (* NtQuerySystemInformation *) #$1B#$21#$04#$20#$30#$27#$2C#$06#$2C#$26#$21#$30#$38#$1C#$3B#$33#$3A#$27#$38#$34#$21#$3C#$3A#$3B;
|
|
CProcess32Next2 : AnsiString = (* Process32Next *) #$05#$27#$3A#$36#$30#$26#$26#$66#$67#$1B#$30#$2D#$21;
|
|
CEnumServicesStatusA2 : AnsiString = (* EnumServicesStatusA *) #$10#$3B#$20#$38#$06#$30#$27#$23#$3C#$36#$30#$26#$06#$21#$34#$21#$20#$26#$14;
|
|
CEnumServicesStatusW2 : AnsiString = (* EnumServicesStatusW *) #$10#$3B#$20#$38#$06#$30#$27#$23#$3C#$36#$30#$26#$06#$21#$34#$21#$20#$26#$02;
|
|
{$endif}
|
|
|
|
// ***************************************************************
|
|
|
|
var
|
|
UninjectCallbacks : array [0..15] of TUninjectCallback = (nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil);
|
|
UninjectCallbackContexts : array [0..15] of pointer = (nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil);
|
|
UninjectCallbackCount : integer = 0;
|
|
|
|
procedure InitAutoUnhook; forward;
|
|
|
|
procedure RegisterUninjectCallback(callback: TUninjectCallback; context: pointer); stdcall;
|
|
var i1 : integer;
|
|
begin
|
|
i1 := InterlockedIncrement(UninjectCallbackCount) - 1;
|
|
if (i1 >= 0) and (i1 < 16) then begin
|
|
InitAutoUnhook;
|
|
UninjectCallbacks[i1] := callback;
|
|
UninjectCallbackContexts[i1] := context;
|
|
end;
|
|
end;
|
|
|
|
procedure FireUninjectCallbacks;
|
|
var i1 : integer;
|
|
begin
|
|
for i1 := 0 to 15 do
|
|
if @UninjectCallbacks[i1] <> nil then
|
|
UninjectCallbacks[i1](UninjectCallbackContexts[i1]);
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
{$ifdef CheckRecursion}
|
|
function NotRecursedYet(caller, buf: pointer) : boolean;
|
|
|
|
function GetStackAddr : NativeUInt;
|
|
// ask the current thread's current stack pointer
|
|
asm
|
|
{$ifdef win64}
|
|
mov rax, rbp
|
|
{$else}
|
|
mov eax, ebp
|
|
{$endif}
|
|
end;
|
|
|
|
function GetStackTop : NativeUInt;
|
|
// ask the current thread's stack starting address
|
|
asm
|
|
{$ifdef win64}
|
|
mov rax, gs:[abs 8]
|
|
{$else}
|
|
mov eax, fs:[4]
|
|
{$endif}
|
|
end;
|
|
|
|
var stackAddr : NativeUInt;
|
|
stackTop : NativeUInt;
|
|
count : integer;
|
|
begin
|
|
stackAddr := GetStackAddr;
|
|
stackTop := GetStackTop;
|
|
count := 0;
|
|
result := true;
|
|
if (stackAddr + sizeOf(pointer) * 4 < stackTop) and (not IsBadReadPtrEx(pointer(stackAddr), stackTop - stackAddr)) then
|
|
while stackAddr + sizeOf(pointer) * 4 < stackTop do begin
|
|
if (TPAPointer(stackAddr)[0] = pointer({$ifdef win64} $7777777777777777 {$else} $77777777 {$endif})) and
|
|
(TPAPointer(stackAddr)[1] = pointer(stackAddr)) and
|
|
(TPAPointer(stackAddr)[2] = caller) and
|
|
(TPAPointer(stackAddr)[3] = pointer(stackAddr xor NativeUInt(caller))) then begin
|
|
inc(count);
|
|
if count = 7 then begin
|
|
result := false;
|
|
break;
|
|
end;
|
|
end;
|
|
inc(stackAddr, sizeOf(pointer));
|
|
end;
|
|
if result then begin
|
|
TPAPointer(buf)[0] := pointer({$ifdef win64} $7777777777777777 {$else} $77777777 {$endif});
|
|
TPAPointer(buf)[1] := buf;
|
|
TPAPointer(buf)[2] := caller;
|
|
TPAPointer(buf)[3] := pointer(TPANativeUInt(buf)[1] xor TPANativeUInt(buf)[2]);
|
|
end;
|
|
end;
|
|
{$endif}
|
|
|
|
// ***************************************************************
|
|
|
|
type
|
|
TUnicodeStr = record
|
|
len, maxLen : word;
|
|
str : PWideChar;
|
|
end;
|
|
TUnicodeStr32 = record
|
|
len, maxLen : word;
|
|
str : dword;
|
|
end;
|
|
TUnicodeStr64 = packed record
|
|
len, maxLen : word;
|
|
dummy : dword;
|
|
str : int64;
|
|
end;
|
|
TObjectAttributes = record
|
|
len : dword;
|
|
rootDir : THandle;
|
|
objName : ^TUnicodeStr;
|
|
attr : dword;
|
|
sd : pointer;
|
|
qos : pointer;
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
procedure AnsiToWide(ansi: PAnsiChar; wide: PWideChar); stdcall;
|
|
var {$ifdef CheckRecursion}
|
|
recTestBuf : array [0..3] of pointer;
|
|
{$endif}
|
|
s1 : AnsiString;
|
|
error : dword;
|
|
begin
|
|
error := GetLastError;
|
|
{$ifdef CheckRecursion}
|
|
if NotRecursedYet(@AnsiToWide, @recTestBuf) then begin
|
|
{$endif}
|
|
s1 := AnsiToWideEx(ansi);
|
|
Move(PAnsiChar(s1)^, wide^, Length(s1));
|
|
{$ifdef CheckRecursion}
|
|
recTestBuf[3] := nil;
|
|
end;
|
|
{$endif}
|
|
SetLastError(error);
|
|
end;
|
|
|
|
procedure WideToAnsi(wide: PWideChar; ansi: PAnsiChar); stdcall;
|
|
var {$ifdef CheckRecursion}
|
|
recTestBuf : array [0..3] of pointer;
|
|
{$endif}
|
|
s1 : AnsiString;
|
|
error : dword;
|
|
begin
|
|
error := GetLastError;
|
|
{$ifdef CheckRecursion}
|
|
if NotRecursedYet(@WideToAnsi, @recTestBuf) then begin
|
|
{$endif}
|
|
s1 := WideToAnsiEx(wide);
|
|
Move(PAnsiChar(s1)^, ansi^, Length(s1) + 1);
|
|
{$ifdef CheckRecursion}
|
|
recTestBuf[3] := nil;
|
|
end;
|
|
{$endif}
|
|
SetLastError(error);
|
|
end;
|
|
|
|
function WideToWideEx(wide: PWideChar; addTerminatingNull: boolean = true) : AnsiString;
|
|
begin
|
|
SetLength(result, lstrlenW(wide) * 2);
|
|
if result <> '' then
|
|
Move(wide^, pointer(result)^, Length(result));
|
|
if addTerminatingNull then
|
|
result := result + #0#0;
|
|
end;
|
|
|
|
function AnsiToWideEx2(const ansi: AnsiString; addTerminatingZero: boolean = true) : AnsiString;
|
|
var ws : UnicodeString;
|
|
begin
|
|
ws := UnicodeString(ansi);
|
|
SetLength(result, Length(ws) * 2);
|
|
if ws <> '' then
|
|
Move(pointer(ws)^, pointer(result)^, Length(result));
|
|
if addTerminatingZero then
|
|
result := result + #0#0;
|
|
end;
|
|
|
|
procedure AnsiToWide2(ansi: PAnsiChar; wide: PWideChar);
|
|
var s1 : AnsiString;
|
|
begin
|
|
s1 := AnsiToWideEx2(ansi);
|
|
Move(PAnsiChar(s1)^, wide^, Length(s1));
|
|
end;
|
|
|
|
function WideToAnsiEx2(wide: PWideChar) : AnsiString;
|
|
begin
|
|
result := AnsiString(UnicodeString(wide));
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
var SetErrorModeFunc : function (mode: dword) : dword stdcall = nil;
|
|
LoadLibraryAFunc : function (lib: PAnsiChar) : HMODULE stdcall = nil;
|
|
LoadLibraryWFunc : function (lib: PWideChar) : HMODULE stdcall = nil;
|
|
FreeLibraryFunc : function (module: HMODULE) : bool stdcall = nil;
|
|
GetModuleHandleAFunc : function (lib: PAnsiChar) : HMODULE stdcall = nil;
|
|
GetModuleHandleWFunc : function (lib: PWideChar) : HMODULE stdcall = nil;
|
|
GetLastErrorFunc : function : integer stdcall = nil;
|
|
VirtualFreeFunc : function (address: pointer; size: NativeUInt; flags: dword) : bool stdcall = nil;
|
|
GetVersionFunc : function : dword stdcall = nil;
|
|
CreateMutexAFunc : function (attr: PSecurityAttributes; initialOwner: bool; name: PAnsiChar) : THandle stdcall = nil;
|
|
GetModuleFileNameAFunc : function (module: HMODULE; fileName: PAnsiChar; size: dword) : dword stdcall = nil;
|
|
WaitForSingleObjectFunc : function (handle: THandle; timeOut: dword) : dword stdcall = nil;
|
|
GetCurrentProcessIdFunc : function : dword stdcall = nil;
|
|
OpenFileMappingAFunc : function (access: dword; inheritHandle: bool; name: PAnsiChar) : THandle stdcall = nil;
|
|
MapViewOfFileFunc : function (map: THandle; access, ofsHi, ofsLo: dword; size: NativeUInt) : pointer stdcall = nil;
|
|
UnmapViewOfFileFunc : function (addr: pointer) : bool stdcall = nil;
|
|
CloseHandleFunc : function (obj: THandle) : bool stdcall = nil;
|
|
ReleaseMutexFunc : function (mutex: THandle) : bool stdcall = nil;
|
|
SetLastErrorProc : procedure (error: dword) stdcall = nil;
|
|
LocalAllocFunc : function (flags: dword; size: NativeUInt) : pointer stdcall = nil;
|
|
LocalFreeFunc : function (addr: pointer) : NativeUInt stdcall = nil;
|
|
VirtualQueryFunc : function (addr: pointer; var mbi: TMemoryBasicInformation; size: NativeUInt) : NativeUInt stdcall = nil;
|
|
SleepProc : procedure (time: dword) stdcall = nil;
|
|
WaitForMultipleObjectsFunc : function (count: dword; handles: PWOHandleArray; waitAll: bool; timeOut: dword) : dword stdcall = nil;
|
|
EnterCriticalSectionFunc : procedure (var criticalSection: TRTLCriticalSection) stdcall = nil;
|
|
LeaveCriticalSectionFunc : procedure (var criticalSection: TRTLCriticalSection) stdcall = nil;
|
|
|
|
{$ifdef win64}
|
|
GetVersion2GB : pointer = nil;
|
|
GetLastError2GB : pointer = nil;
|
|
GetCurrentProcessId2GB : pointer = nil;
|
|
VirtualFree2GB : pointer = nil;
|
|
SetErrorMode2GB : pointer = nil;
|
|
LoadLibraryW2GB : pointer = nil;
|
|
GetModuleHandleW2GB : pointer = nil;
|
|
OpenFileMappingA2GB : pointer = nil;
|
|
MapViewOfFile2GB : pointer = nil;
|
|
UnmapViewOfFile2GB : pointer = nil;
|
|
CloseHandle2GB : pointer = nil;
|
|
FreeLibrary2GB : pointer = nil;
|
|
EnterCriticalSection2GB : pointer = nil;
|
|
LeaveCriticalSection2GB : pointer = nil;
|
|
{$endif}
|
|
|
|
procedure InitProcs(hookOnly: boolean);
|
|
begin
|
|
// todo: add critical section to make this totally thread safe (C++, too)
|
|
if @GetModuleHandleAFunc = nil then
|
|
if hookOnly then begin
|
|
if @GetModuleFileNameAFunc = nil then
|
|
GetModuleFileNameAFunc := KernelProc(CGetModuleFileNameA, true);
|
|
if @VirtualQueryFunc = nil then
|
|
VirtualQueryFunc := KernelProc(CVirtualQuery, true);
|
|
end else begin
|
|
GetModuleHandleWFunc := KernelProc(CGetModuleHandleW, true);
|
|
SetErrorModeFunc := KernelProc(CSetErrorMode, true);
|
|
LoadLibraryAFunc := KernelProc(CLoadLibraryA, true);
|
|
LoadLibraryWFunc := KernelProc(CLoadLibraryW, true);
|
|
FreeLibraryFunc := KernelProc(CFreeLibrary, true);
|
|
GetLastErrorFunc := KernelProc(CGetLastError, true);
|
|
VirtualFreeFunc := KernelProc(CVirtualFree, true);
|
|
GetVersionFunc := KernelProc(CGetVersion, true);
|
|
CreateMutexAFunc := KernelProc(CCreateMutexA, true);
|
|
GetModuleFileNameAFunc := KernelProc(CGetModuleFileNameA, true);
|
|
WaitForSingleObjectFunc := KernelProc(CWaitForSingleObject, true);
|
|
GetCurrentProcessIdFunc := KernelProc(CGetCurrentProcessId, true);
|
|
OpenFileMappingAFunc := KernelProc(COpenFileMappingA, true);
|
|
MapViewOfFileFunc := KernelProc(CMapViewOfFile, true);
|
|
UnmapViewOfFileFunc := KernelProc(CUnmapViewOfFile, true);
|
|
CloseHandleFunc := KernelProc(CCloseHandle, true);
|
|
ReleaseMutexFunc := KernelProc(CReleaseMutex, true);
|
|
SetLastErrorProc := KernelProc(CSetLastError, true);
|
|
LocalAllocFunc := KernelProc(CLocalAlloc, true);
|
|
LocalFreeFunc := KernelProc(CLocalFree, true);
|
|
VirtualQueryFunc := KernelProc(CVirtualQuery, true);
|
|
SleepProc := KernelProc(CSleep, true);
|
|
WaitForMultipleObjectsFunc := KernelProc(CWaitForMultipleObjects, true);
|
|
EnterCriticalSectionFunc := NtProc(CRtlEnterCriticalSection, true);
|
|
LeaveCriticalSectionFunc := NtProc(CRtlLeaveCriticalSection, true);
|
|
GetModuleHandleAFunc := KernelProc(CGetModuleHandleA, true);
|
|
end;
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
function AtomicMove(src, dst: pointer; len: integer) : boolean; // len = 1..8
|
|
|
|
function IsCmpxchg8bSupported : boolean;
|
|
{$ifdef win64}
|
|
begin
|
|
result := true;
|
|
end;
|
|
{$else}
|
|
asm
|
|
pushfd
|
|
pop eax
|
|
mov ecx, eax
|
|
xor eax, 00200000h
|
|
push eax
|
|
popfd
|
|
pushfd
|
|
pop eax
|
|
cmp eax, ecx
|
|
mov eax, 0
|
|
jz @quit
|
|
mov eax, 1
|
|
push ebx
|
|
dw $A20F // D4/D5 doesn't know the assembler instruction "cpuid"
|
|
pop ebx
|
|
test edx, $100
|
|
mov eax, 0
|
|
jz @quit
|
|
mov eax, 1
|
|
@quit:
|
|
end;
|
|
{$endif}
|
|
|
|
procedure cmpxchg8b;//(src, dst: pointer; len: integer); stdcall;
|
|
asm
|
|
{$ifdef win64}
|
|
|
|
.noframe
|
|
push rsi // 18 24 rsi
|
|
push rdi // 10 16 rdi
|
|
push rbx // 08 8 rbx
|
|
push rbp // 00 0 rbp
|
|
|
|
mov [rsp+28h], rcx // fill parameters in
|
|
mov [rsp+30h], rdx
|
|
mov [rsp+38h], r8
|
|
|
|
mov rbp, [rsp+30h] // move address of destination to rbp
|
|
mov rax, rbp // rax gets address of destination
|
|
mov edx, [rax+04h] // edx gets value at destination+4, HIGH DWORD
|
|
mov eax, [rax] // eax gets value at destination, LOW DWORD
|
|
// EDX:EAX is 8 bytes that destination points to
|
|
|
|
@tryAgain:
|
|
mov [rsp+40h], eax // local2 gets EAX, LOW DWORD
|
|
mov [rsp+44h], edx // local1 gets EDX, HIGH DWORD
|
|
|
|
mov rsi, [rsp+28h] // rsi gets source address parameter
|
|
lea rdi, [rsp+40h] // rdi gets address of first byte of local 2
|
|
mov rcx, [rsp+38h] // rcx gets length
|
|
cld // clear the direction flag
|
|
// thus, string instructions increment esi/edi
|
|
rep movsb // repeat cx times a move of a byte from [esi] to [edi] (cx will be 1-8)
|
|
// thus, the locals are populated with SOURCE
|
|
|
|
mov ebx, [rsp+40h] // ebx = LOW DWORD of Source value
|
|
mov ecx, [rsp+44h] // ecx = HIGH DWORD of Source value
|
|
|
|
db $F0 // lock ...
|
|
dd $004DC70F // ... cmpxchg8b qword ptr [ebp+$00]
|
|
|
|
jnz @tryAgain
|
|
|
|
pop rbp
|
|
pop rbx
|
|
pop rdi
|
|
pop rsi
|
|
ret
|
|
|
|
{$else}
|
|
|
|
add esp, -8
|
|
|
|
push esi
|
|
push edi
|
|
push ebx
|
|
push ebp
|
|
|
|
mov ebp, [esp+$20]
|
|
|
|
mov eax, ebp
|
|
mov edx, [eax+4]
|
|
mov eax, [eax]
|
|
|
|
@tryAgain:
|
|
mov [esp+$10], eax
|
|
mov [esp+$14], edx
|
|
|
|
mov esi, [esp+$1c]
|
|
lea edi, [esp+$10]
|
|
mov ecx, [esp+$24]
|
|
cld
|
|
rep movsb
|
|
|
|
mov ebx, [esp+$10]
|
|
mov ecx, [esp+$14]
|
|
|
|
db $F0 // lock ...
|
|
dd $004DC70F // ... cmpxchg8b qword ptr [ebp+$00]
|
|
|
|
jnz @tryAgain
|
|
|
|
pop ebp
|
|
pop ebx
|
|
pop edi
|
|
pop esi
|
|
|
|
add esp, 8
|
|
ret $c
|
|
|
|
{$endif}
|
|
end;
|
|
|
|
type TCmpxchg8b = procedure (src, dst: pointer; len: integer); stdcall;
|
|
begin
|
|
result := (len > 0) and (len <= 8) and IsCmpxchg8bSupported;
|
|
if result then
|
|
TCmpxchg8b(@cmpxchg8b)(src, dst, len);
|
|
end;
|
|
|
|
var FVista : integer = -1;
|
|
function IsVista : boolean;
|
|
begin
|
|
if FVista = -1 then
|
|
FVista := ord(byte(GetVersion) >= 6);
|
|
result := FVista = 1;
|
|
end;
|
|
|
|
var FMetro : integer = -1;
|
|
function IsMetro : boolean;
|
|
begin
|
|
if FMetro = -1 then
|
|
FMetro := ord((byte(GetVersion) > 6) or ((byte(GetVersion) = 6) and (byte(GetVersion shr 8) > 1)));
|
|
result := FMetro = 1;
|
|
end;
|
|
|
|
function GetProcessSid(processHandle: THandle; var saa: PSidAndAttributes) : boolean;
|
|
var token : THandle;
|
|
size : dword;
|
|
begin
|
|
result := false;
|
|
if OpenProcessToken(processHandle, TOKEN_QUERY, token) then begin
|
|
size := 0;
|
|
GetTokenInformation(token, TokenUser, nil, 0, size);
|
|
saa := pointer(LocalAlloc(LPTR, size * 2));
|
|
if GetTokenInformation(token, TokenUser, saa, size * 2, size) then
|
|
result := true
|
|
else LocalFree(HLOCAL(saa));
|
|
CloseHandle(token);
|
|
end;
|
|
end;
|
|
|
|
function CreateMetroSd(var sd: TSecurityDescriptor) : boolean;
|
|
// create a security descriptor which includes metro "AppContainer" apps
|
|
const CEveryoneSia : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 1));
|
|
CSecurityAppPackageAuthority : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 15));
|
|
type
|
|
TTrustee = record
|
|
multiple : pointer;
|
|
multop : dword;
|
|
form : dword;
|
|
type_ : dword;
|
|
sid : PSid;
|
|
end;
|
|
TExplicitAccess = record
|
|
access : dword;
|
|
mode : dword;
|
|
inherit : dword;
|
|
trustee : TTrustee;
|
|
end;
|
|
var ea : array [0..1] of TExplicitAccess;
|
|
dacl : PAcl;
|
|
sid1, sid2 : PSid;
|
|
seia : function (count: dword; ea: pointer; oldAcl: PAcl; var newAcl: PAcl) : dword; stdcall;
|
|
begin
|
|
result := false;
|
|
seia := GetProcAddress(LoadLibraryA(PAnsiChar(DecryptStr(CAdvApi32))), PAnsiChar(DecryptStr(CSetEntriesInAclA)));
|
|
if @seia <> nil then begin
|
|
sid1 := nil;
|
|
sid2 := nil;
|
|
if AllocateAndInitializeSid(CEveryoneSia, 1, 0, 0, 0, 0, 0, 0, 0, 0, sid1) and
|
|
AllocateAndInitializeSid(CSecurityAppPackageAuthority, 2, 2, 1, 0, 0, 0, 0, 0, 0, sid2) then begin
|
|
ZeroMemory(@ea, sizeOf(ea));
|
|
ea[0].access := STANDARD_RIGHTS_ALL or SPECIFIC_RIGHTS_ALL;
|
|
ea[0].mode := 2; // SET_ACCESS
|
|
ea[0].trustee.form := 0; // TRUSTEE_IS_SID
|
|
ea[0].trustee.type_ := 1; // TRUSTEE_IS_USER
|
|
ea[0].trustee.sid := sid1;
|
|
ea[1].access := STANDARD_RIGHTS_ALL or SPECIFIC_RIGHTS_ALL;
|
|
ea[1].mode := 2; // SET_ACCESS
|
|
ea[1].trustee.form := 0; // TRUSTEE_IS_SID
|
|
ea[1].trustee.type_ := 2; // TRUSTEE_IS_GROUP
|
|
ea[1].trustee.sid := sid2;
|
|
if seia(2, @ea, nil, dacl) = 0 then begin
|
|
result := true;
|
|
InitializeSecurityDescriptor(@sd, SECURITY_DESCRIPTOR_REVISION);
|
|
SetSecurityDescriptorDacl(@sd, true, dacl, false);
|
|
end;
|
|
end;
|
|
if sid1 <> nil then
|
|
FreeSid(sid1);
|
|
if sid2 <> nil then
|
|
FreeSid(sid2);
|
|
end;
|
|
end;
|
|
|
|
var LimitedSa : TSecurityAttributes;
|
|
LimitedSd : TSecurityDescriptor;
|
|
UnlimitedSa : TSecurityAttributes;
|
|
UnlimitedSd : TSecurityDescriptor;
|
|
|
|
procedure InitSecAttr(var sa: TSecurityAttributes; var sd: TSecurityDescriptor; limited, allowEveryone: boolean);
|
|
const
|
|
CEveryoneSia : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 1));
|
|
CSystemSia : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 5));
|
|
CSecurityAppPackageAuthority : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 15));
|
|
type
|
|
TTrustee = record
|
|
multiple : pointer;
|
|
multop : dword;
|
|
form : dword;
|
|
type_ : dword;
|
|
sid : PSid;
|
|
end;
|
|
TExplicitAccess = record
|
|
access : dword;
|
|
mode : dword;
|
|
inherit : dword;
|
|
trustee : TTrustee;
|
|
end;
|
|
var seia : function (count: dword; ea: pointer; oldAcl: PAcl; var newAcl: PAcl) : dword; stdcall;
|
|
sid1, sid2, sid3 : PSid;
|
|
dacl : PAcl;
|
|
saa : PSidAndAttributes;
|
|
ea : array [0..3] of TExplicitAccess;
|
|
count : integer;
|
|
begin
|
|
ZeroMemory(@sa, sizeOf(sa));
|
|
ZeroMemory(@sd, sizeOf(sd));
|
|
if limited then begin
|
|
sid1 := nil;
|
|
sid2 := nil;
|
|
sid3 := nil;
|
|
saa := nil;
|
|
dacl := nil;
|
|
seia := GetProcAddress(GetModuleHandleA(PAnsiChar(DecryptStr(CAdvApi32))), PAnsiChar(DecryptStr(CSetEntriesInAclA)));
|
|
if @seia <> nil then begin
|
|
if AllocateAndInitializeSid(CEveryoneSia, 1, 0, 0, 0, 0, 0, 0, 0, 0, sid1) and
|
|
AllocateAndInitializeSid(CSystemSia, 1, 18, 0, 0, 0, 0, 0, 0, 0, sid2) and
|
|
GetProcessSid(GetCurrentProcess, saa) and (saa <> nil) then begin
|
|
ZeroMemory(@ea, sizeOf(ea));
|
|
ea[0].mode := 1; // GRANT_ACCESS
|
|
ea[0].trustee.form := 0; // TRUSTEE_IS_SID
|
|
ea[0].trustee.type_ := 1; // TRUSTEE_IS_USER
|
|
ea[0].access := SECTION_MAP_READ;
|
|
ea[0].trustee.sid := sid1;
|
|
ea[1].mode := 1; // GRANT_ACCESS
|
|
ea[1].trustee.form := 0; // TRUSTEE_IS_SID
|
|
ea[1].trustee.type_ := 1; // TRUSTEE_IS_USER
|
|
ea[1].access := STANDARD_RIGHTS_ALL or SPECIFIC_RIGHTS_ALL;
|
|
ea[1].trustee.sid := sid2;
|
|
ea[2].mode := 1; // GRANT_ACCESS
|
|
ea[2].trustee.form := 0; // TRUSTEE_IS_SID
|
|
ea[2].trustee.type_ := 1; // TRUSTEE_IS_USER
|
|
ea[2].access := STANDARD_RIGHTS_ALL or SPECIFIC_RIGHTS_ALL;
|
|
ea[2].trustee.sid := saa.Sid;
|
|
count := 3;
|
|
if IsMetro and AllocateAndInitializeSid(CSecurityAppPackageAuthority, 2, 2, 1, 0, 0, 0, 0, 0, 0, sid3) then begin
|
|
// this adds access for metro "AppContainer" apps
|
|
ea[3].access := SECTION_MAP_READ;
|
|
ea[3].mode := 1; // GRANT_ACCESS
|
|
ea[3].trustee.form := 0; // TRUSTEE_IS_SID
|
|
ea[3].trustee.type_ := 2; // TRUSTEE_IS_GROUP
|
|
ea[3].trustee.sid := sid3;
|
|
inc(count);
|
|
end else
|
|
sid3 := nil;
|
|
if allowEveryone then begin
|
|
if seia(count, @ea, nil, dacl) <> 0 then
|
|
dacl := nil;
|
|
end else
|
|
if seia(count - 1, @(ea[1]), nil, dacl) <> 0 then
|
|
dacl := nil;
|
|
end;
|
|
end;
|
|
InitializeSecurityDescriptor(@sd, SECURITY_DESCRIPTOR_REVISION);
|
|
SetSecurityDescriptorDacl(@sd, true, dacl, false);
|
|
if sid1 <> nil then
|
|
FreeSid(sid1);
|
|
if sid2 <> nil then
|
|
FreeSid(sid2);
|
|
if sid3 <> nil then
|
|
FreeSid(sid3);
|
|
if saa <> nil then
|
|
LocalFree(HLOCAL(saa));
|
|
end else
|
|
if IsMetro then
|
|
CreateMetroSd(sd)
|
|
else begin
|
|
InitializeSecurityDescriptor(@sd, SECURITY_DESCRIPTOR_REVISION);
|
|
SetSecurityDescriptorDacl(@sd, true, nil, false);
|
|
end;
|
|
sa.nLength := sizeOf(sa);
|
|
sa.lpSecurityDescriptor := @sd;
|
|
sa.bInheritHandle := false;
|
|
end;
|
|
|
|
function InternalCreateMutex(name: PAnsiChar; global: boolean) : THandle;
|
|
begin
|
|
if global then
|
|
result := CreateMutexW(@UnlimitedSa, false, pointer(AnsiToWideEx(DecryptStr(CGlobal) + name)))
|
|
else
|
|
result := 0;
|
|
if result = 0 then
|
|
result := CreateMutexW(@UnlimitedSa, false, pointer(AnsiToWideEx(name)));
|
|
end;
|
|
|
|
function CreateGlobalMutex(name: PAnsiChar) : THandle; stdcall;
|
|
begin
|
|
result := InternalCreateMutex(name, true);
|
|
end;
|
|
|
|
function CreateLocalMutex(name: PAnsiChar) : THandle; stdcall;
|
|
begin
|
|
result := InternalCreateMutex(name, false);
|
|
end;
|
|
|
|
function OpenGlobalMutex(name: PAnsiChar) : THandle; stdcall;
|
|
begin
|
|
result := OpenMutexW(SYNCHRONIZE, false, pointer(AnsiToWideEx(DecryptStr(CGlobal) + name)));
|
|
if result = 0 then
|
|
result := OpenMutexW(SYNCHRONIZE, false, pointer(AnsiToWideEx(name)));
|
|
end;
|
|
|
|
var BaseGetNamedObjectDirectory : function (var handle: THandle) : dword; stdcall = nil;
|
|
function InitBaseGetNamedObjectDirectory : boolean;
|
|
begin
|
|
if @BaseGetNamedObjectDirectory = nil then begin
|
|
BaseGetNamedObjectDirectory := GetProcAddress(GetModuleHandle(kernel32), 'BaseGetNamedObjectDirectory');
|
|
if @BaseGetNamedObjectDirectory = nil then
|
|
BaseGetNamedObjectDirectory := pointer(1);
|
|
end;
|
|
result := NativeUInt(@BaseGetNamedObjectDirectory) > 1;
|
|
end;
|
|
|
|
function ZwCreateSection(var sectionHandle: THandle; desiredAccess: ACCESS_MASK; var objectAttr: TObjectAttributes; var maximumSize: int64; sectionPageProtection, allocationAttributes: dword; fileHandle: THandle) : dword; stdcall; external 'ntdll.dll';
|
|
|
|
function InternalCreateFileMapping(name: PAnsiChar; size: dword; global: boolean; limited: boolean = true) : THandle;
|
|
var sa : PSecurityAttributes;
|
|
objAttr : TObjectAttributes;
|
|
uniStr : TUnicodeStr;
|
|
s1 : AnsiString;
|
|
size64 : int64;
|
|
begin
|
|
result := 0;
|
|
if limited then
|
|
sa := @LimitedSa
|
|
else
|
|
sa := @UnlimitedSa;
|
|
if InitBaseGetNamedObjectDirectory then begin
|
|
objAttr.len := sizeOf(objAttr);
|
|
BaseGetNamedObjectDirectory(objAttr.rootDir);
|
|
objAttr.objName := @uniStr;
|
|
objAttr.attr := $80; // OBJ_OPENIF
|
|
objAttr.sd := sa.lpSecurityDescriptor;
|
|
objAttr.qos := nil;
|
|
size64 := size;
|
|
if global then begin
|
|
s1 := AnsiToWideEx(DecryptStr(CGlobal) + name);
|
|
uniStr.len := Length(s1) - 2;
|
|
uniStr.maxLen := Length(s1);
|
|
uniStr.str := pointer(s1);
|
|
ZwCreateSection(result, STANDARD_RIGHTS_REQUIRED or SECTION_QUERY or SECTION_MAP_WRITE or SECTION_MAP_READ, objAttr, size64, PAGE_READWRITE, SEC_COMMIT, 0);
|
|
end;
|
|
if result = 0 then begin
|
|
s1 := AnsiToWideEx(name);
|
|
uniStr.len := Length(s1) - 2;
|
|
uniStr.maxLen := Length(s1);
|
|
uniStr.str := pointer(s1);
|
|
ZwCreateSection(result, STANDARD_RIGHTS_REQUIRED or SECTION_QUERY or SECTION_MAP_WRITE or SECTION_MAP_READ, objAttr, size64, PAGE_READWRITE, SEC_COMMIT, 0);
|
|
end;
|
|
end;
|
|
if result = 0 then begin
|
|
if global then
|
|
result := CreateFileMappingW(INVALID_HANDLE_VALUE, sa, PAGE_READWRITE, 0, size, pointer(AnsiToWideEx(DecryptStr(CGlobal) + name)));
|
|
if result = 0 then
|
|
result := CreateFileMappingW(INVALID_HANDLE_VALUE, sa, PAGE_READWRITE, 0, size, pointer(AnsiToWideEx(name)));
|
|
end;
|
|
end;
|
|
|
|
function CreateGlobalFileMapping(name: PAnsiChar; size: dword) : THandle; stdcall;
|
|
begin
|
|
EnableAllPrivileges;
|
|
result := InternalCreateFileMapping(name, size, true);
|
|
end;
|
|
|
|
function CreateLocalFileMapping(name: PAnsiChar; size: dword) : THandle; stdcall;
|
|
begin
|
|
result := InternalCreateFileMapping(name, size, false);
|
|
end;
|
|
|
|
var NtQueryObject : function (handle: THandle; infoClass: NativeUInt; buf: pointer; bufSize: NativeUInt; returnSize: TPCardinal) : HRESULT; stdcall = nil;
|
|
|
|
function InternalOpenFileMapping(name: PAnsiChar; write: bool; global: boolean) : THandle;
|
|
|
|
function FindFileMappingHandle(const name: AnsiString) : THandle;
|
|
// browse through the process handles to find a specific memory mapped file
|
|
var buf : TPAPointer;
|
|
|
|
function GetKernelObjectTypeName(handle: THandle) : AnsiString;
|
|
begin
|
|
if SUCCEEDED(NtQueryObject(handle, 2, buf, 2048, nil)) and (buf[1] <> nil) then
|
|
result := WideToAnsiEx(PWideChar(buf[1]))
|
|
else
|
|
result := '';
|
|
end;
|
|
|
|
function GetKernelObjectName(handle: THandle) : AnsiString;
|
|
begin
|
|
if SUCCEEDED(NtQueryObject(handle, 1, buf, 2048, nil)) and (buf[1] <> nil) then begin
|
|
result := WideToAnsiEx(PWideChar(buf[1]))
|
|
end else
|
|
result := '';
|
|
end;
|
|
|
|
var i1 : integer;
|
|
s1 : AnsiString;
|
|
begin
|
|
result := 0;
|
|
if @NtQueryObject = nil then
|
|
NtQueryObject := NtProc(CNtQueryObject);
|
|
if @NtQueryObject <> nil then begin
|
|
buf := VirtualAlloc(nil, 2048, MEM_COMMIT, PAGE_READWRITE);
|
|
for i1 := 1 to $1000 do
|
|
try
|
|
if IsTextEqualA(GetKernelObjectTypeName(i1 * 4), 'Section') then begin
|
|
s1 := GetKernelObjectName(i1 * 4);
|
|
if Length(s1) > Length(name) then begin
|
|
Delete(s1, 1, Length(s1) - Length(name));
|
|
if IsTextEqualA(s1, name) and DuplicateHandle(GetCurrentProcess, i1 * 4, GetCurrentProcess, @result, 0, false, DUPLICATE_SAME_ACCESS) then
|
|
break;
|
|
end;
|
|
end;
|
|
except end;
|
|
end;
|
|
VirtualFree(buf, 0, MEM_RELEASE);
|
|
end;
|
|
|
|
var access : dword;
|
|
le : dword;
|
|
begin
|
|
if write then
|
|
access := FILE_MAP_ALL_ACCESS
|
|
else access := FILE_MAP_READ;
|
|
if global then
|
|
result := OpenFileMappingW(access, false, pointer(AnsiToWideEx(DecryptStr(CGlobal) + name)))
|
|
else
|
|
result := 0;
|
|
if result = 0 then begin
|
|
le := GetLastError;
|
|
result := OpenFileMappingW(access, false, pointer(AnsiToWideEx(name)));
|
|
if result = 0 then
|
|
SetLastError(le);
|
|
end;
|
|
if (result = 0) and (GetLastError in [5, 6]) then begin
|
|
// chrome sandbox processes don't seem to be allowed to open memory mapped files, ouch
|
|
// so we do some special processing to locate already open handles which fit our needs
|
|
le := GetLastError;
|
|
result := FindFileMappingHandle(name);
|
|
if result <> 0 then
|
|
SetLastError(0)
|
|
else
|
|
SetLastError(le);
|
|
end;
|
|
end;
|
|
|
|
function OpenGlobalFileMapping(name: PAnsiChar; write: bool) : THandle; stdcall;
|
|
begin
|
|
result := InternalOpenFileMapping(name, write, true);
|
|
end;
|
|
|
|
(*function OpenLocalFileMapping(name: PAnsiChar; write: bool) : THandle; stdcall;
|
|
begin
|
|
result := InternalOpenFileMapping(name, write, false);
|
|
end;*)
|
|
|
|
function ZwCreateEvent(var eventHandle: THandle; desiredAccess: ACCESS_MASK; var objAttr: TObjectAttributes; eventType: NativeUInt; initialState: BOOL) : dword; stdcall; external 'ntdll.dll';
|
|
|
|
function CreateGlobalEvent(name: PAnsiChar; manual, initialState: bool) : THandle; stdcall;
|
|
var objAttr : TObjectAttributes;
|
|
uniStr : TUnicodeStr;
|
|
s1 : AnsiString;
|
|
evType : NativeUInt;
|
|
begin
|
|
result := 0;
|
|
if InitBaseGetNamedObjectDirectory then begin
|
|
objAttr.len := sizeOf(objAttr);
|
|
BaseGetNamedObjectDirectory(objAttr.rootDir);
|
|
objAttr.objName := @uniStr;
|
|
objAttr.attr := $80; // OBJ_OPENIF
|
|
objAttr.sd := UnlimitedSa.lpSecurityDescriptor;
|
|
objAttr.qos := nil;
|
|
if manual then
|
|
evType := 0
|
|
else
|
|
evType := 1;
|
|
s1 := AnsiToWideEx(DecryptStr(CGlobal) + name);
|
|
uniStr.len := Length(s1) - 2;
|
|
uniStr.maxLen := Length(s1);
|
|
uniStr.str := pointer(s1);
|
|
ZwCreateEvent(result, EVENT_ALL_ACCESS, objAttr, evType, initialState);
|
|
if result = 0 then begin
|
|
s1 := AnsiToWideEx(name);
|
|
uniStr.len := Length(s1) - 2;
|
|
uniStr.maxLen := Length(s1);
|
|
uniStr.str := pointer(s1);
|
|
ZwCreateEvent(result, EVENT_ALL_ACCESS, objAttr, evType, initialState);
|
|
end;
|
|
end;
|
|
if result = 0 then begin
|
|
result := CreateEventW(@UnlimitedSa, manual, initialState, pointer(AnsiToWideEx(DecryptStr(CGlobal) + name)));
|
|
if result = 0 then
|
|
result := CreateEventW(@UnlimitedSa, manual, initialState, pointer(AnsiToWideEx(name)));
|
|
end;
|
|
end;
|
|
|
|
function OpenGlobalEvent(name: PAnsiChar) : THandle; stdcall;
|
|
begin
|
|
result := OpenEventW(EVENT_ALL_ACCESS, false, pointer(AnsiToWideEx(DecryptStr(CGlobal) + name)));
|
|
if result = 0 then
|
|
result := OpenEventW(EVENT_ALL_ACCESS, false, pointer(AnsiToWideEx(name)));
|
|
end;
|
|
|
|
function ApiSpecialName(prefix: AnsiString; api: pointer) : AnsiString;
|
|
begin
|
|
result := DecryptStr(CProcess) + ' ' + IntToHexExA(GetCurrentProcessID, 8);
|
|
result := DecryptStr(prefix) + ', ' + result + ', ' + DecryptStr(CApi) + ' ' + IntToHexExA(NativeUInt(api), {$ifdef win64} 16 {$else} 8 {$endif});
|
|
end;
|
|
|
|
function ResolveMixtureMode(var code: pointer) : boolean;
|
|
var map : THandle;
|
|
buf : pointer;
|
|
s1 : AnsiString;
|
|
begin
|
|
result := false;
|
|
s1 := DecryptStr(CNamedBuffer) + ', ' + ApiSpecialName(CMixPrefix, code);
|
|
map := OpenGlobalFileMapping(PAnsiChar(s1), true);
|
|
if map <> 0 then begin
|
|
buf := MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
|
|
if buf <> nil then begin
|
|
code := pointer(buf^);
|
|
UnmapViewOfFile(buf);
|
|
result := true;
|
|
end;
|
|
CloseHandle(map);
|
|
end;
|
|
end;
|
|
|
|
function FindRealCode(code: pointer) : pointer;
|
|
|
|
function IsAlreadyKnown : boolean;
|
|
var s1 : AnsiString;
|
|
c1 : THandle;
|
|
begin
|
|
s1 := DecryptStr(CNamedBuffer) + ', ' + ApiSpecialName(CMAHPrefix, code);
|
|
c1 := OpenGlobalFileMapping(PAnsiChar(s1), false);
|
|
result := c1 <> 0;
|
|
if result then
|
|
CloseHandle(c1);
|
|
end;
|
|
|
|
function CheckCode : boolean;
|
|
var module : HMODULE;
|
|
pexp : PImageExportDirectory;
|
|
i1 : integer;
|
|
c1 : dword;
|
|
begin
|
|
result := ResolveMixtureMode(code) or IsAlreadyKnown;
|
|
if (not result) and GetExportDirectory(code, module, pexp) then
|
|
with pexp^ do
|
|
for i1 := 0 to NumberOfFunctions - 1 do begin
|
|
c1 := TPACardinal(module + AddressOfFunctions)^[i1];
|
|
if pointer(module + c1) = code then begin
|
|
result := true;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
{$ifdef log}
|
|
log('FindRealCode (code: ' + IntToHexExA(NativeUInt(code)) + ')');
|
|
{$endif}
|
|
result := code;
|
|
if code <> nil then
|
|
if CheckCode then
|
|
result := code
|
|
else
|
|
if word(code^) = $25FF then begin
|
|
{$ifdef win64}
|
|
// RIP relative addressing
|
|
code := TPPointer(NativeUInt(code) + 6 + TPCardinal(NativeUInt(code) + 2)^)^;
|
|
{$else}
|
|
code := TPPointer(TPPointer(NativeUInt(code) + 2)^)^;
|
|
{$endif}
|
|
if CheckCode then
|
|
result := code
|
|
else
|
|
if byte(code^) = $68 then begin // w9x debug mode?
|
|
code := TPPointer(NativeUInt(code) + 1)^;
|
|
if CheckCode then
|
|
result := code;
|
|
end;
|
|
end;
|
|
{$ifdef log}
|
|
log('FindRealCode -> ' + IntToHexExA(NativeUInt(result)));
|
|
{$endif}
|
|
end;
|
|
|
|
function VirtualAlloc2(size: integer; preferredAddr: pointer) : pointer;
|
|
begin
|
|
{$ifdef win64}
|
|
if (NativeUInt(preferredAddr) >= $70000000) and (NativeUInt(preferredAddr) < $80000000) then
|
|
// let's skip the area which is usually used by windows system dlls
|
|
// in 64 bit we have enough memory address range available above of 0x80000000
|
|
// so there's no need to allocate where we could collide with the system dlls
|
|
preferredAddr := pointer($80000000);
|
|
result := AllocMemEx(size, 0, preferredAddr);
|
|
{$else}
|
|
result := AllocMemEx(size);
|
|
{$endif}
|
|
end;
|
|
|
|
{$ifndef win64}
|
|
function FindWs2InternalProcList(module: HMODULE) : pointer;
|
|
// Winsock2 internally has an additional list of some exported APIs
|
|
// we need to find this list and modify it, too
|
|
// or else Winsock2 will refuse to initialize, when we change the export table
|
|
var nh : PImageNtHeaders;
|
|
pexp : PImageExportDirectory;
|
|
ish : PImageSectionHeader;
|
|
i1, i2 : integer;
|
|
buf : array [0..9] of NativeUInt;
|
|
begin
|
|
result := nil;
|
|
if module <> 0 then begin
|
|
nh := pointer(GetImageNtHeaders(module));
|
|
pexp := GetImageExportDirectory(module);
|
|
if (nh <> nil) and (pexp <> nil) then begin
|
|
NativeUInt(ish) := NativeUInt(nh) + sizeOf(TImageNtHeaders);
|
|
for i1 := 0 to integer(nh^.FileHeader.NumberOfSections) - 1 do begin
|
|
if (ish.Name[0] = ord('.')) and
|
|
((ish.Name[1] = ord('d')) or (ish.Name[1] = ord('D'))) and
|
|
((ish.Name[2] = ord('a')) or (ish.Name[2] = ord('A'))) and
|
|
((ish.Name[3] = ord('t')) or (ish.Name[3] = ord('T'))) and
|
|
((ish.Name[4] = ord('a')) or (ish.Name[4] = ord('A'))) and
|
|
(ish.Name[5] = 0) then begin
|
|
for i2 := 0 to high(buf) do
|
|
buf[i2] := module + TPACardinal(module + pexp^.AddressOfFunctions)[i2];
|
|
i2 := PosPCharA(@buf, PAnsiChar(module + ish.VirtualAddress), sizeOf(buf), ish.SizeOfRawData);
|
|
if i2 >= 0 then
|
|
NativeUInt(result) := module + ish.VirtualAddress + dword(i2);
|
|
break;
|
|
end;
|
|
inc(ish);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
{$endif}
|
|
|
|
function PatchExportTable(module: HMODULE; old, new: pointer; ws2procList: pointer) : boolean;
|
|
|
|
{$ifndef win64}
|
|
function HackWinsock2IfNecessary(ws2procList: TPAPointer; old, new: pointer) : boolean;
|
|
var i1 : integer;
|
|
op : dword;
|
|
begin
|
|
result := ws2procList = nil;
|
|
if not result then begin
|
|
i1 := 0;
|
|
while ws2procList[i1] <> nil do
|
|
if ws2procList[i1] = old then begin
|
|
if VirtualProtect(ws2procList[i1], 4, PAGE_EXECUTE_READWRITE, @op) then begin
|
|
ws2procList[i1] := new;
|
|
result := true;
|
|
VirtualProtect(ws2procList[i1], 4, op, @op);
|
|
end;
|
|
break;
|
|
end else
|
|
inc(i1);
|
|
end;
|
|
end;
|
|
{$endif}
|
|
|
|
var pexp : PImageExportDirectory;
|
|
i1 : integer;
|
|
c1 : dword;
|
|
p1 : TPCardinal;
|
|
op : dword;
|
|
nh : PImageNtHeaders32;
|
|
begin
|
|
{$ifdef log}
|
|
log('PatchExportTable (old: ' + IntToHexExA(NativeUInt(old)) +
|
|
'; new: ' + IntToHexExA(NativeUInt(new)) + ')');
|
|
{$endif}
|
|
result := false;
|
|
try
|
|
pexp := nil;
|
|
try
|
|
if (module <> 0) and (TPWord(module)^ = CEMAGIC) then begin
|
|
nh := pointer(module + dword(pointer(module + CENEWHDR)^));
|
|
if nh^.signature = CPEMAGIC then
|
|
if nh^.OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR64_MAGIC then
|
|
pexp := pointer(module + PImageOptionalHeader64(@nh^.OptionalHeader).DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
|
|
else pexp := pointer(module + nh^.OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
|
|
end;
|
|
except end;
|
|
if pexp <> nil then
|
|
with pexp^ do
|
|
for i1 := 0 to NumberOfFunctions - 1 do begin
|
|
c1 := TPACardinal(module + AddressOfFunctions)^[i1];
|
|
if pointer(module + c1) = old then begin
|
|
{$ifndef win64} if HackWinsock2IfNecessary(ws2procList, old, new) then {$endif} begin
|
|
p1 := @TPACardinal(module + AddressOfFunctions)^[i1];
|
|
if VirtualProtect(p1, 4, PAGE_EXECUTE_READWRITE, @op) then begin
|
|
p1^ := NativeUInt(new) - module;
|
|
result := true;
|
|
VirtualProtect(p1, 4, op, @op);
|
|
end {$ifndef win64} else
|
|
HackWinsock2IfNecessary(ws2procList, new, old) {$endif};
|
|
end;
|
|
break;
|
|
end;
|
|
end;
|
|
except end;
|
|
{$ifdef log}
|
|
log('PatchExportTable (old: ' + IntToHexExA(NativeUInt(old)) +
|
|
'; new: ' + IntToHexExA(NativeUInt(new)) + ') -> ' + booleanToCharA(result));
|
|
{$endif}
|
|
end;
|
|
|
|
procedure PatchImportTable(module: HMODULE; old, new: pointer);
|
|
var nh : {$ifdef win64} PImageNtHeaders64 {$else} PImageNtHeaders {$endif};
|
|
id : PImageImportDirectory;
|
|
thunk : ^pointer;
|
|
op : dword;
|
|
begin
|
|
nh := pointer(GetImageNtHeaders(module));
|
|
if (nh <> nil) and
|
|
(nh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress <> 0) and
|
|
(nh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress < nh.OptionalHeader.SizeOfImage) then begin
|
|
id := pointer(module + nh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
|
|
if id <> nil then
|
|
while (id.Name_ <> 0) and (id.ThunkArray <> 0) and (id.ThunkArray < nh.OptionalHeader.SizeOfImage) do begin
|
|
thunk := pointer(module + id.ThunkArray);
|
|
while thunk^ <> nil do begin
|
|
if (thunk^ = old) and VirtualProtect(thunk, sizeOf(pointer), PAGE_EXECUTE_READWRITE, @op) then begin
|
|
thunk^ := new;
|
|
VirtualProtect(thunk, sizeOf(pointer), op, @op);
|
|
end;
|
|
thunk := pointer(NativeUInt(thunk) + sizeOf(pointer));
|
|
end;
|
|
id := pointer(NativeUInt(id) + sizeOf(TImageImportDirectory));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure PatchMyImportTables(old, new: pointer);
|
|
var p1 : pointer;
|
|
mbi : TMemoryBasicInformation;
|
|
module : HMODULE;
|
|
arrCh : array [0..7] of wideChar;
|
|
begin
|
|
p1 := nil;
|
|
module := 0;
|
|
while VirtualQuery(p1, mbi, sizeOf(mbi)) = sizeOf(mbi) do begin
|
|
if (mbi.State = MEM_COMMIT) and (mbi.AllocationBase <> nil) and (HMODULE(mbi.AllocationBase) <> module) then begin
|
|
module := HMODULE(mbi.AllocationBase);
|
|
if GetModuleFileNameW(module, arrCh, 2) <> 0 then
|
|
PatchImportTable(module, old, new);
|
|
end;
|
|
p1 := pointer(NativeUInt(p1) + mbi.RegionSize);
|
|
end;
|
|
end;
|
|
|
|
(* Caution: currently only 32bit support
|
|
|
|
function HookReloc(module: HMODULE; old, new: pointer; var next: pointer) : boolean;
|
|
type TImageBaseRelocation = record
|
|
VirtualAddress : dword;
|
|
SizeOfBlock : dword;
|
|
end;
|
|
var
|
|
nh : PImageNtHeaders;
|
|
rel1 : ^TImageBaseRelocation;
|
|
rel2 : NativeUInt;
|
|
i1 : integer;
|
|
item : TPWord;
|
|
data : ^TPPointer;
|
|
opcode : TPWord;
|
|
cb, ce : NativeUInt;
|
|
p1 : pointer;
|
|
c1 : dword;
|
|
begin
|
|
result := false;
|
|
nh := GetImageNtHeaders(module);
|
|
if nh <> nil then
|
|
with nh^.OptionalHeader do begin
|
|
cb := module + BaseOfCode + 2;
|
|
ce := module + BaseOfCode + SizeOfCode;
|
|
with DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC] do begin
|
|
NativeUInt(rel1) := module + VirtualAddress;
|
|
rel2 := NativeUInt(rel1) + Size;
|
|
end;
|
|
while (NativeUInt(rel1) < rel2) and (rel1^.VirtualAddress <> 0) do begin
|
|
NativeUInt(item) := NativeUInt(rel1) + sizeOf(rel1^);
|
|
for i1 := 1 to (rel1.SizeOfBlock - sizeOf(rel1^)) div 2 do begin
|
|
if item^ and $f000 = $3000 then begin
|
|
NativeUInt(data) := module + rel1.VirtualAddress + item^ and $0fff;
|
|
NativeUInt(opcode) := NativeUInt(data) - 2;
|
|
if (NativeUInt(data) >= cb) and (NativeUInt(data) < ce) and
|
|
(data^ <> @next) and
|
|
( (opcode^ = $25ff) or (opcode^ = $15ff) ) and
|
|
TryRead(data^, @p1, 4) and
|
|
(p1 = old) and
|
|
VirtualProtect(data^, 4, PAGE_EXECUTE_READWRITE, c1) then begin
|
|
result := true;
|
|
data^^ := new;
|
|
VirtualProtect(data^, 4, c1, c1);
|
|
end;
|
|
end;
|
|
inc(item);
|
|
end;
|
|
NativeUInt(rel1) := NativeUInt(rel1) + rel1^.SizeOfBlock;
|
|
end;
|
|
end;
|
|
end; *)
|
|
|
|
function HookCodeX(owner : HMODULE;
|
|
moduleH : HMODULE;
|
|
module : AnsiString;
|
|
api : AnsiString;
|
|
ordinal : dword;
|
|
code : pointer;
|
|
callbackFunc : pointer;
|
|
out nextHook : pointer;
|
|
flags : dword;
|
|
numStackParams : dword) : boolean; forward;
|
|
|
|
// ***************************************************************
|
|
|
|
var IsWine : boolean = false;
|
|
|
|
var
|
|
CollectSection : TRTLCriticalSection;
|
|
CollectCache : array of packed record
|
|
thread : dword;
|
|
refCount : integer;
|
|
|
|
// in RAM a lot of patching can happen
|
|
// sometimes madCodeHook has to make sure that it can get original unpatched information
|
|
// for such purposes it opens the original exe/dll files on harddisk
|
|
// in order to speed things up, we're caching the last opened file here
|
|
moduleOrg : HMODULE; // which loaded exe/dll module is currently open?
|
|
moduleFile : pointer; // where is the file mapped into memory?
|
|
end;
|
|
|
|
function CollectCache_Open(force: boolean) : integer;
|
|
var i1 : integer;
|
|
begin
|
|
EnterCriticalSection(CollectSection);
|
|
result := -1;
|
|
for i1 := 0 to high(CollectCache) do
|
|
if CollectCache[i1].thread = GetCurrentThreadID then begin
|
|
result := i1;
|
|
break;
|
|
end;
|
|
if (result = -1) and force then begin
|
|
result := Length(CollectCache);
|
|
SetLength(CollectCache, result + 1);
|
|
CollectCache[result].thread := GetCurrentThreadID;
|
|
end;
|
|
end;
|
|
|
|
procedure CollectCache_Close;
|
|
begin
|
|
LeaveCriticalSection(CollectSection);
|
|
end;
|
|
|
|
procedure CollectCache_AddRef;
|
|
var i1 : integer;
|
|
begin
|
|
i1 := CollectCache_Open(true);
|
|
try
|
|
inc(CollectCache[i1].refCount);
|
|
finally CollectCache_Close end;
|
|
end;
|
|
|
|
function CollectCache_NeedModuleFileMap(module: HMODULE) : pointer;
|
|
var i1 : integer;
|
|
buf : pointer;
|
|
begin
|
|
result := nil;
|
|
if (not IsWine) and (CollectCache <> nil) then begin
|
|
buf := nil;
|
|
i1 := CollectCache_Open(false);
|
|
try
|
|
if i1 <> -1 then begin
|
|
if CollectCache[i1].moduleOrg <> module then begin
|
|
if CollectCache[i1].moduleOrg <> 0 then begin
|
|
buf := CollectCache[i1].moduleFile;
|
|
CollectCache[i1].moduleOrg := 0;
|
|
CollectCache[i1].moduleFile := nil;
|
|
end;
|
|
end else
|
|
result := CollectCache[i1].moduleFile;
|
|
end;
|
|
finally CollectCache_Close end;
|
|
if i1 <> -1 then begin
|
|
if buf <> nil then
|
|
UnmapViewOfFile(buf);
|
|
if result = nil then begin
|
|
buf := NeedModuleFileMap(module);
|
|
if buf <> nil then begin
|
|
i1 := CollectCache_Open(false);
|
|
try
|
|
if i1 <> -1 then begin
|
|
CollectCache[i1].moduleOrg := module;
|
|
CollectCache[i1].moduleFile := buf;
|
|
result := buf;
|
|
end else
|
|
UnmapViewOfFile(buf);
|
|
finally CollectCache_Close end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure CollectCache_Release;
|
|
var buf : pointer;
|
|
i1 : integer;
|
|
begin
|
|
buf := nil;
|
|
i1 := CollectCache_Open(false);
|
|
try
|
|
if i1 <> -1 then begin
|
|
dec(CollectCache[i1].refCount);
|
|
if CollectCache[i1].refCount = 0 then begin
|
|
buf := CollectCache[i1].moduleFile;
|
|
CollectCache[i1] := CollectCache[high(CollectCache)];
|
|
SetLength(CollectCache, high(CollectCache));
|
|
end;
|
|
end;
|
|
finally CollectCache_Close end;
|
|
if buf <> nil then
|
|
UnmapViewOfFile(buf);
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
type
|
|
// type for absolute jmp calls
|
|
TAbsoluteJmp = packed record // jmp [target]
|
|
opcode : byte; // $ff
|
|
modRm : byte; // $25, 00101001, Mod = 00, Reg = 100, R/M = 101 : 32 bit displacement follows
|
|
target : dword; // @(absolute target) which is pointer to an address (32 bits)
|
|
end;
|
|
|
|
// type for absolute jumps
|
|
THookStub = packed record
|
|
jmpNextHook : TAbsoluteJmp; // $FF / $25 / @absTarget
|
|
absTarget : pointer; // absolute target
|
|
end;
|
|
|
|
THookEntry = record
|
|
HookProc : pointer;
|
|
PNextHook : TPPointer;
|
|
end;
|
|
THookQueueItems = array [0..maxInt shr 5 - 1] of THookEntry;
|
|
THookQueue = record
|
|
ItemCount : integer;
|
|
Capacity : integer;
|
|
MapHandle : THandle;
|
|
PatchAddr : ^TAbsoluteJmp;
|
|
OldCode : TAbsoluteJmp;
|
|
NewCode : TAbsoluteJmp;
|
|
Items : THookQueueItems;
|
|
end;
|
|
|
|
const
|
|
CHookEntrySize = sizeOf(THookEntry);
|
|
CHookQueueSize = sizeOf(THookQueue) - sizeOf(THookQueueItems);
|
|
|
|
// ***************************************************************
|
|
|
|
const
|
|
PAGE_SIZE = 4096;
|
|
|
|
// CInUseCount = min((PAGE_SIZE * 2) / sizeOf(TInUseItem), (PAGE_SIZE * 2 - sizeOf(pointer) * 2) / (sizeOf(dword) + sizeOf(pointer)));
|
|
CInUseCount = 546;
|
|
|
|
type
|
|
{$ifdef win64}
|
|
TInUseItem = packed record
|
|
popReturn : dword; // $08c48348
|
|
call : word; // $15ff
|
|
callOffset : integer; // rip relative displacement
|
|
jmp : byte; // $e9
|
|
jmpOffset : integer; // relative target
|
|
end;
|
|
{$else}
|
|
TInUseItem = packed record
|
|
popReturn : array [0..2] of byte; // $04c483
|
|
call : word; // $15ff
|
|
callTarget : pointer; // address of call target
|
|
jmp : byte; // $e9
|
|
jmpOffset : integer; // relative target
|
|
end;
|
|
{$endif}
|
|
TInUseCodeArr = packed array [0..CInUseCount - 1] of TInUseItem;
|
|
TInUseThreadArr = packed array [0..CInUseCount - 1] of dword;
|
|
TInUseTargetArr = packed array [0..CInUseCount - 1] of pointer;
|
|
|
|
TCodeHook = class
|
|
public
|
|
FValid : boolean;
|
|
FNewHook : boolean;
|
|
FHookedFunc : pointer;
|
|
FMapHandle : THandle;
|
|
FCallbackFunc : pointer;
|
|
FPNextHook : TPPointer;
|
|
FPHookStub : ^THookStub;
|
|
FPHookStubTarget : ^pointer;
|
|
FInUseCodeArr : ^TInUseCodeArr;
|
|
FInUseThreadArr : ^TInUseThreadArr;
|
|
FInUseTargetArr : ^TInUseTargetArr;
|
|
FDestroying : boolean;
|
|
FLeakUnhook : boolean;
|
|
FDoubleHook : boolean;
|
|
FModuleH : HMODULE;
|
|
FStoreThreadState : boolean;
|
|
FSafeHooking : boolean;
|
|
FNoAutoUnhook : boolean;
|
|
FIsWinsock2 : boolean;
|
|
FPatchExport : boolean;
|
|
FTramp : pointer;
|
|
FPatchAddr : ^TAbsoluteJmp;
|
|
FNewCode : TAbsoluteJmp;
|
|
FOldCode : TAbsoluteJmp;
|
|
|
|
constructor Create (moduleH: HMODULE; hookThisFunc, callbackFunc: pointer; out nextHook: pointer; flags, numParams: dword);
|
|
destructor Destroy; override;
|
|
procedure WritePatch (mutex: THandle; buf: pointer);
|
|
function IsHookInUse : integer;
|
|
end;
|
|
|
|
(*
|
|
procedure InUseEnter;
|
|
asm
|
|
{$ifdef win64}
|
|
|
|
.noframe
|
|
push rdi
|
|
push rsi
|
|
push rdx
|
|
push rcx
|
|
push rax
|
|
push rbx
|
|
mov rbx, $1111111111111111
|
|
mov rdi, $2222222222222222
|
|
mov rsi, $3333333333333333
|
|
mov rdx, [rsp+30h]
|
|
mov rcx, 546 // CInUseCount
|
|
xor rax, rax
|
|
@loop:
|
|
lock cmpxchg [rdi], rdx
|
|
jz @success
|
|
add rbx, 15
|
|
add rdi, 8
|
|
add rsi, 4
|
|
xor rax, rax
|
|
loop @loop
|
|
jmp @fail
|
|
@success:
|
|
mov [rsp+30h], rbx
|
|
mov rax, gs:[$30]
|
|
mov rax, [rax+$48]
|
|
mov dword ptr [rsi], eax
|
|
pop rbx
|
|
pop rax
|
|
pop rcx
|
|
pop rdx
|
|
pop rsi
|
|
pop rdi
|
|
jmp [rsp]
|
|
@fail:
|
|
pop rbx
|
|
pop rax
|
|
pop rcx
|
|
pop rdx
|
|
pop rsi
|
|
pop rdi
|
|
db 0ffh
|
|
db 025h
|
|
dd 0
|
|
dq 0
|
|
|
|
{$else}
|
|
|
|
push edi
|
|
push esi
|
|
push edx
|
|
push ecx
|
|
push eax
|
|
push ebx
|
|
mov ebx, $11111111
|
|
mov edi, $22222222
|
|
mov esi, $33333333
|
|
mov edx, [esp+$18]
|
|
mov ecx, 546 // CInUseCount
|
|
xor eax, eax
|
|
@loop:
|
|
lock cmpxchg [edi], edx
|
|
jz @success
|
|
add ebx, 14
|
|
add edi, 4
|
|
add esi, 4
|
|
xor eax, eax
|
|
loop @loop
|
|
jmp @fail
|
|
@success:
|
|
mov [esp+$18], ebx
|
|
mov eax, fs:[$18];
|
|
mov eax, [eax+$24]
|
|
mov [esi], eax
|
|
pop ebx
|
|
pop eax
|
|
pop ecx
|
|
pop edx
|
|
pop esi
|
|
pop edi
|
|
jmp [esp]
|
|
@fail:
|
|
pop ebx
|
|
pop eax
|
|
pop ecx
|
|
pop edx
|
|
pop esi
|
|
pop edi
|
|
db $e9
|
|
dd $44444444
|
|
|
|
{$endif}
|
|
end;
|
|
*)
|
|
|
|
const
|
|
// We use fixed compiled code for x86 and x64 targets to make sure the code
|
|
// is the same for all compilers. This helps with dynamic code replacements.
|
|
{$ifdef win64}
|
|
CInUseEnter : array[0..128] of Byte = (
|
|
$57, $56, $52, $51, $50, $53, $48, $BB, $11, $11, $11, $11, $11, $11, $11, $11,
|
|
$48, $BF, $22, $22, $22, $22, $22, $22, $22, $22, $48, $BE, $33, $33, $33, $33,
|
|
$33, $33, $33, $33, $48, $8B, $54, $24, $30, $48, $B9, $22, $02, $00, $00, $00,
|
|
$00, $00, $00, $48, $31, $C0, $F0, $48, $0F, $B1, $17, $74, $13, $48, $83, $C3,
|
|
$0F, $48, $83, $C7, $08, $48, $83, $C6, $04, $48, $31, $C0, $E2, $E8, $EB, $1D,
|
|
$48, $89, $5C, $24, $30, $65, $48, $8B, $04, $25, $30, $00, $00, $00, $48, $8B,
|
|
$40, $48, $89, $06, $5B, $58, $59, $5A, $5E, $5F, $FF, $24, $24, $5B, $58, $59,
|
|
$5A, $5E, $5F, $FF, $25, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
|
|
$00
|
|
);
|
|
{$else}
|
|
CInUseEnter : array [0..88] of byte = (
|
|
$57, $56, $52, $51, $50, $53, $BB, $11, $11, $11, $11, $BF, $22, $22, $22, $22,
|
|
$BE, $33, $33, $33, $33, $8B, $54, $24, $18, $B9, $22, $02, $00, $00, $31, $C0,
|
|
$F0, $0F, $B1, $17, $74, $0F, $83, $C3, $0E, $83, $C7, $04, $83, $C6, $04, $31,
|
|
$C0, $E2, $ED, $EB, $19, $89, $5C, $24, $18, $64, $8B, $05, $18, $00, $00, $00,
|
|
$8B, $40, $24, $89, $06, $5B, $58, $59, $5A, $5E, $5F, $FF, $24, $24, $5B, $58,
|
|
$59, $5A, $5E, $5F, $E9, $44, $44, $44, $44
|
|
);
|
|
{$endif}
|
|
|
|
(*
|
|
procedure InUseLeave;
|
|
asm
|
|
{$ifdef win64}
|
|
|
|
.noframe
|
|
push rax
|
|
push rcx
|
|
push rdx
|
|
// eax := index
|
|
mov rax, [rsp+$18]
|
|
mov rdx, $1111111111111111
|
|
sub rax, rdx
|
|
xor rdx, rdx
|
|
mov rcx, 15
|
|
div rcx
|
|
dec rax
|
|
// edx := @InUseTargetArr[index]
|
|
mov rcx, $2222222222222222
|
|
lea rdx, [rcx + rax * 8]
|
|
// retAddr := InUseTargetArr[index];
|
|
mov rax, [rdx]
|
|
mov [rsp+$18], rax
|
|
// InUseTargetArr[index] := 0;
|
|
lock and qword ptr [rdx], 0
|
|
pop rdx
|
|
pop rcx
|
|
pop rax
|
|
ret
|
|
|
|
{$else}
|
|
|
|
push eax
|
|
push ecx
|
|
push edx
|
|
// eax := index
|
|
mov eax, [esp+$c]
|
|
mov edx, $11111111
|
|
sub eax, edx
|
|
xor edx, edx
|
|
mov ecx, 14
|
|
div ecx
|
|
dec eax
|
|
// edx := @InUseTargetArr[index]
|
|
lea edx, [eax * 4 + $22222222]
|
|
// retAddr := InUseTargetArr[index];
|
|
mov eax, [edx]
|
|
mov [esp+$c], eax
|
|
// InUseTargetArr[index] := 0;
|
|
lock and [edx], 0
|
|
pop edx
|
|
pop ecx
|
|
pop eax
|
|
ret
|
|
|
|
{$endif}
|
|
end;
|
|
*)
|
|
|
|
const
|
|
{$ifdef win64}
|
|
CInUseLeave : array [0..73] of byte = (
|
|
$50, $51, $52, $48, $8B, $44, $24, $18, $48, $BA, $11, $11, $11, $11, $11, $11,
|
|
$11, $11, $48, $29, $D0, $48, $31, $D2, $48, $B9, $0F, $00, $00, $00, $00, $00,
|
|
$00, $00, $48, $F7, $F1, $48, $FF, $C8, $48, $B9, $22, $22, $22, $22, $22, $22,
|
|
$22, $22, $48, $8D, $14, $C1, $48, $8B, $02, $48, $89, $44, $24, $18, $F0, $48,
|
|
$81, $22, $00, $00, $00, $00, $5A, $59, $58, $C3
|
|
);
|
|
{$else}
|
|
CInUseLeave : array [0..44] of byte = (
|
|
$50, $51, $52, $8B, $44, $24, $0C, $BA, $11, $11, $11, $11, $29, $D0, $31, $D2,
|
|
$B9, $0E, $00, $00, $00, $F7, $F1, $48, $8D, $14, $85, $22, $22, $22, $22, $8B,
|
|
$02, $89, $44, $24, $0C, $F0, $83, $22, $00, $5A, $59, $58, $C2
|
|
);
|
|
{$endif}
|
|
|
|
(*
|
|
procedure InUse_ThreadState;
|
|
asm
|
|
{$ifdef win64}
|
|
|
|
.noframe
|
|
// keep 4 stack items untouched
|
|
sub rsp, 4*8
|
|
// store current thread state to stack
|
|
push rsp
|
|
push rax
|
|
push rcx
|
|
push rdx
|
|
push rbx
|
|
push rbp
|
|
push rsi
|
|
push rdi
|
|
push r8
|
|
push r9
|
|
push r10
|
|
push r11
|
|
push r12
|
|
push r13
|
|
push r14
|
|
push r15
|
|
pushfq
|
|
// add a signature and checksum
|
|
mov rax, 6D63683474687374h // 'mch4thst'
|
|
push rax
|
|
xor rax, rsp
|
|
push rax
|
|
// let's reserve some space here for private use
|
|
sub rsp, 20h
|
|
// copy the parameters and return address
|
|
lea rsi, [rsp+472] // (numParams + 23) * 8 + 4*8
|
|
mov rcx, 32 // (numParams + 0)
|
|
@copyLoop:
|
|
mov rax, [rsi]
|
|
sub rsi, 8
|
|
push rax
|
|
loop @copyLoop
|
|
// here comes (mostly) the usual InUseEnter code
|
|
mov rdi, $2222222222222222 // InUseTargetArr
|
|
mov rsi, $3333333333333333 // InUseThreadArr
|
|
mov rcx, 546 // CInUseCount
|
|
xor rax, rax
|
|
@inUseLoop:
|
|
// search for an empty (zero) InUseTargetArr array index
|
|
lock cmpxchg [rdi], rdi
|
|
jz @success
|
|
add rdi, 8
|
|
add rsi, 4
|
|
xor rax, rax
|
|
loop @inUseLoop
|
|
xor rdi, rdi
|
|
jmp @finish
|
|
@success:
|
|
// store GetCurrentThreadId in InUseThreadArr
|
|
mov rax, gs:[$30]
|
|
mov rax, [rax+$48]
|
|
mov dword ptr [rsi], eax
|
|
@finish:
|
|
// store the InUseTargetArr pointer in the private storage area
|
|
mov [rsp+280], rdi // (numParams + 3) * 8
|
|
// restore the registers by using the stored thread state
|
|
mov rax, [rsp+424] // (numParams + 21) * 8
|
|
mov rcx, [rsp+416] // (numParams + 20) * 8
|
|
mov rdx, [rsp+408] // (numParams + 19) * 8
|
|
mov rbx, [rsp+400] // (numParams + 18) * 8
|
|
mov rsi, [rsp+384] // (numParams + 16) * 8
|
|
mov rdi, [rsp+376] // (numParams + 15) * 8
|
|
// correct rsp to account for the 4 untouched stack items
|
|
add qword ptr [rsp+432], 4*8 // (numParams + 22) * 8
|
|
|
|
// here we call the callback function
|
|
db 0ffh
|
|
db 015h
|
|
dd 002h
|
|
jmp @skipTarget
|
|
dq 0
|
|
@skipTarget:
|
|
|
|
// the callback function returned
|
|
// we don't know how many stack items the callback function removed from the stack
|
|
// we "measure" that here by locating our magic stack signature
|
|
push rax
|
|
push rcx
|
|
push rdx
|
|
mov rdx, 6D63683474687374h // 'mch4thst'
|
|
lea rax, [rsp+320] // (numParams + 8) * 8
|
|
@searchLoop:
|
|
mov rcx, [rax]
|
|
cmp rcx, rdx
|
|
jz @searchMaybeComplete
|
|
sub rax, 8
|
|
jmp @searchLoop
|
|
@searchMaybeComplete:
|
|
// we found the stack signature, now let's verify the checksum
|
|
mov rcx, [rax-8]
|
|
xor rcx, rax
|
|
cmp rcx, rdx
|
|
jz @searchDefinitelyComplete
|
|
sub rax, 8
|
|
jmp @searchLoop
|
|
@searchDefinitelyComplete:
|
|
// ok, we found it, clear the marker
|
|
xor qword ptr [rax], rdx
|
|
// now let's do some math
|
|
lea rcx, [rsp+320] // (numParams + 8) * 8
|
|
sub rax, rcx
|
|
// eax now contains the negative number of stack items which the callback function removed from the stack
|
|
// for example, if the callback function has a "ret 16", then eax is now -16
|
|
// now we fetch InUseTargetArr pointer from the private storage area and clear it
|
|
mov rcx, [rsp+rax+304] // (numParams + 6) * 8
|
|
cmp rcx, 0
|
|
jz @skipClear
|
|
lock and qword ptr [rcx], 0
|
|
@skipClear:
|
|
// finally let's clean up
|
|
// unfortunately thanks to stupid CET this is rather complicated
|
|
// basically we have to perform a "ret x" jump which matches the one of the callback function
|
|
neg rax
|
|
mov rcx, rax
|
|
shr rax, 2
|
|
add rcx, rax
|
|
shr rax, 1
|
|
add rcx, rax
|
|
add rcx, 9
|
|
call @myself
|
|
@myself:
|
|
pop rax
|
|
add rax, rcx
|
|
pop rdx
|
|
pop rcx
|
|
jmp rax
|
|
// return table, max supported number of parameters is 64
|
|
pop rax; add rsp, 472; ret $000 // (numParams + 23) * 8 + 4*8
|
|
pop rax; add rsp, 464; ret $008 // (numParams + 22) * 8 + 4*8
|
|
pop rax; add rsp, 456; ret $010 // (numParams + 21) * 8 + 4*8
|
|
pop rax; add rsp, 448; ret $018 // (numParams + 20) * 8 + 4*8
|
|
pop rax; add rsp, 440; ret $020 // (numParams + 19) * 8 + 4*8
|
|
pop rax; add rsp, 432; ret $028 // (numParams + 18) * 8 + 4*8
|
|
pop rax; add rsp, 424; ret $030 // (numParams + 17) * 8 + 4*8
|
|
pop rax; add rsp, 416; ret $038 // (numParams + 16) * 8 + 4*8
|
|
pop rax; add rsp, 408; ret $040 // (numParams + 15) * 8 + 4*8
|
|
pop rax; add rsp, 400; ret $048 // (numParams + 14) * 8 + 4*8
|
|
pop rax; add rsp, 392; ret $050 // (numParams + 13) * 8 + 4*8
|
|
pop rax; add rsp, 384; ret $058 // (numParams + 12) * 8 + 4*8
|
|
pop rax; add rsp, 376; ret $060 // (numParams + 11) * 8 + 4*8
|
|
pop rax; add rsp, 368; ret $068 // (numParams + 10) * 8 + 4*8
|
|
pop rax; add rsp, 360; ret $070 // (numParams + 9) * 8 + 4*8
|
|
pop rax; add rsp, 352; ret $078 // (numParams + 8) * 8 + 4*8
|
|
pop rax; add rsp, 344; ret $080 // (numParams + 7) * 8 + 4*8
|
|
pop rax; add rsp, 336; ret $088 // (numParams + 6) * 8 + 4*8
|
|
pop rax; add rsp, 328; ret $090 // (numParams + 5) * 8 + 4*8
|
|
pop rax; add rsp, 320; ret $098 // (numParams + 4) * 8 + 4*8
|
|
pop rax; add rsp, 312; ret $0a0 // (numParams + 3) * 8 + 4*8
|
|
pop rax; add rsp, 304; ret $0a8 // (numParams + 2) * 8 + 4*8
|
|
pop rax; add rsp, 296; ret $0b0 // (numParams + 1) * 8 + 4*8
|
|
pop rax; add rsp, 288; ret $0b8 // (numParams + 0) * 8 + 4*8
|
|
pop rax; add rsp, 280; ret $0c0 // (numParams - 1) * 8 + 4*8
|
|
pop rax; add rsp, 272; ret $0c8 // (numParams - 2) * 8 + 4*8
|
|
pop rax; add rsp, 264; ret $0d0 // (numParams - 3) * 8 + 4*8
|
|
pop rax; add rsp, 256; ret $0d8 // (numParams - 4) * 8 + 4*8
|
|
pop rax; add rsp, 248; ret $0e0 // (numParams - 5) * 8 + 4*8
|
|
pop rax; add rsp, 240; ret $0e8 // (numParams - 6) * 8 + 4*8
|
|
pop rax; add rsp, 232; ret $0f0 // (numParams - 7) * 8 + 4*8
|
|
pop rax; add rsp, 224; ret $0f8 // (numParams - 8) * 8 + 4*8
|
|
pop rax; add rsp, 216; ret $100 // (numParams - 9) * 8 + 4*8
|
|
pop rax; add rsp, 208; ret $108 // (numParams - 10) * 8 + 4*8
|
|
pop rax; add rsp, 200; ret $110 // (numParams - 11) * 8 + 4*8
|
|
pop rax; add rsp, 192; ret $118 // (numParams - 12) * 8 + 4*8
|
|
pop rax; add rsp, 184; ret $120 // (numParams - 13) * 8 + 4*8
|
|
pop rax; add rsp, 176; ret $128 // (numParams - 14) * 8 + 4*8
|
|
pop rax; add rsp, 168; ret $130 // (numParams - 15) * 8 + 4*8
|
|
pop rax; add rsp, 160; ret $138 // (numParams - 16) * 8 + 4*8
|
|
pop rax; add rsp, 152; ret $140 // (numParams - 17) * 8 + 4*8
|
|
pop rax; add rsp, 144; ret $148 // (numParams - 18) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $150 // (numParams - 19) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $158 // (numParams - 20) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $160 // (numParams - 21) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $168 // (numParams - 22) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $170 // (numParams - 23) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $178 // (numParams - 24) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $180 // (numParams - 25) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $188 // (numParams - 26) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $190 // (numParams - 27) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $198 // (numParams - 28) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1a0 // (numParams - 29) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1a8 // (numParams - 30) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1b0 // (numParams - 31) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1b8 // (numParams - 32) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1c0 // (numParams - 33) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1c8 // (numParams - 34) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1d0 // (numParams - 35) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1d8 // (numParams - 36) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1e0 // (numParams - 37) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1e8 // (numParams - 38) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1f0 // (numParams - 39) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1f8 // (numParams - 40) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $200 // (numParams - 41) * 8 + 4*8
|
|
|
|
{$else}
|
|
|
|
// keep 4 stack items untouched
|
|
sub esp, 4*4
|
|
// store current thread state to stack
|
|
pushad
|
|
pushfd
|
|
// add a signature and checksum
|
|
mov eax, 'mch4'
|
|
push eax
|
|
xor eax, esp
|
|
push eax
|
|
// let's reserve some space here for private use
|
|
sub esp, 10h
|
|
// copy the parameters and return address
|
|
lea esi, [esp+204] // (numParams + 15) * 4 + 4*4
|
|
mov ecx, 32 // (numParams + 0)
|
|
@copyLoop:
|
|
mov eax, [esi]
|
|
sub esi, 4
|
|
push eax
|
|
loop @copyLoop
|
|
// here comes (mostly) the usual InUseEnter code
|
|
mov edi, $22222222 // InUseTargetArr
|
|
mov esi, $33333333 // InUseThreadArr
|
|
mov ecx, 546 // CInUseCount
|
|
xor eax, eax
|
|
@inUseLoop:
|
|
// search for an empty (zero) InUseTargetArr array index
|
|
lock cmpxchg [edi], edi
|
|
jz @success
|
|
add edi, 4
|
|
add esi, 4
|
|
xor eax, eax
|
|
loop @inUseLoop
|
|
xor edi, edi
|
|
jmp @finish
|
|
@success:
|
|
// store GetCurrentThreadId in InUseThreadArr
|
|
mov eax, fs:[$18];
|
|
mov eax, [eax+$24]
|
|
mov [esi], eax
|
|
@finish:
|
|
// store the InUseTargetArr pointer in the private storage area
|
|
mov [esp+140], edi // (numParams + 3) * 4
|
|
// restore the registers by using the stored thread state
|
|
mov eax, [esp+184] // (numParams + 14) * 4
|
|
mov ecx, [esp+180] // (numParams + 13) * 4
|
|
mov edx, [esp+176] // (numParams + 12) * 4
|
|
mov ebx, [esp+172] // (numParams + 11) * 4
|
|
mov esi, [esp+160] // (numParams + 8) * 4
|
|
mov edi, [esp+156] // (numParams + 7) * 4
|
|
// correct rsp to account for the 4 untouched stack items
|
|
add [esp+168], 4*4 // (numParams + 10) * 4
|
|
|
|
// here we call the callback function
|
|
db $e8
|
|
dd $44444444
|
|
|
|
// the callback function returned
|
|
// we don't know how many stack items the callback function removed from the stack
|
|
// we "measure" that here by locating our magic stack signature
|
|
push eax
|
|
push ecx
|
|
lea eax, [esp+156] // (numParams + 7) * 4
|
|
@searchLoop:
|
|
mov ecx, [eax]
|
|
cmp ecx, 'mch4'
|
|
jz @searchMaybeComplete
|
|
sub eax, 4
|
|
jmp @searchLoop
|
|
@searchMaybeComplete:
|
|
// we found the stack signature, now let's verify the checksum
|
|
mov ecx, [eax-4]
|
|
xor ecx, eax
|
|
cmp ecx, 'mch4'
|
|
jz @searchDefinitelyComplete
|
|
sub eax, 4
|
|
jmp @searchLoop
|
|
@searchDefinitelyComplete:
|
|
// ok, we found it, clear the marker
|
|
and dword ptr [eax], 0
|
|
// now let's do some math
|
|
lea ecx, [esp+156] // (numParams + 7) * 4
|
|
sub eax, ecx
|
|
// eax now contains the negative number of stack items which the callback function removed from the stack
|
|
// for example, if the callback function has a "ret 16", then eax is now -16
|
|
// now we fetch InUseTargetArr pointer from the private storage area and clear it
|
|
mov ecx, [esp+eax+148] // (numParams + 5) * 4
|
|
cmp ecx, 0
|
|
jz @skipClear
|
|
lock and [ecx], 0
|
|
@skipClear:
|
|
// finally let's clean up
|
|
// unfortunately thanks to stupid CET this is rather complicated
|
|
// basically we have to perform a "ret x" jump which matches the one of the callback function
|
|
neg eax
|
|
mov ecx, eax
|
|
shl ecx, 1
|
|
shr eax, 1
|
|
add ecx, eax
|
|
add ecx, 6
|
|
call @myself
|
|
@myself:
|
|
pop eax
|
|
add eax, ecx
|
|
pop ecx
|
|
jmp eax
|
|
// return table, max supported number of parameters is 64
|
|
pop eax; add esp, 204; ret $00 // (numParams + 15) * 4 + 4*4
|
|
pop eax; add esp, 200; ret $04 // (numParams + 14) * 4 + 4*4
|
|
pop eax; add esp, 196; ret $08 // (numParams + 13) * 4 + 4*4
|
|
pop eax; add esp, 192; ret $0c // (numParams + 12) * 4 + 4*4
|
|
pop eax; add esp, 188; ret $10 // (numParams + 11) * 4 + 4*4
|
|
pop eax; add esp, 184; ret $14 // (numParams + 10) * 4 + 4*4
|
|
pop eax; add esp, 180; ret $18 // (numParams + 9) * 4 + 4*4
|
|
pop eax; add esp, 176; ret $1c // (numParams + 8) * 4 + 4*4
|
|
pop eax; add esp, 172; ret $20 // (numParams + 7) * 4 + 4*4
|
|
pop eax; add esp, 168; ret $24 // (numParams + 6) * 4 + 4*4
|
|
pop eax; add esp, 164; ret $28 // (numParams + 5) * 4 + 4*4
|
|
pop eax; add esp, 160; ret $2c // (numParams + 4) * 4 + 4*4
|
|
pop eax; add esp, 156; ret $30 // (numParams + 3) * 4 + 4*4
|
|
pop eax; add esp, 152; ret $34 // (numParams + 2) * 4 + 4*4
|
|
pop eax; add esp, 148; ret $38 // (numParams + 1) * 4 + 4*4
|
|
pop eax; add esp, 144; ret $3c // (numParams + 0) * 4 + 4*4
|
|
pop eax; add esp, 140; ret $40 // (numParams - 1) * 4 + 4*4
|
|
pop eax; add esp, 136; ret $44 // (numParams - 2) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $48 // (numParams - 3) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $4c // (numParams - 4) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $50 // (numParams - 5) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $54 // (numParams - 6) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $58 // (numParams - 7) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $5c // (numParams - 8) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $60 // (numParams - 9) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $64 // (numParams - 10) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $68 // (numParams - 11) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $6c // (numParams - 12) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $70 // (numParams - 13) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $74 // (numParams - 14) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $78 // (numParams - 15) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $7c // (numParams - 16) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $80 // (numParams - 17) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $84 // (numParams - 18) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $88 // (numParams - 19) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $8c // (numParams - 20) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $90 // (numParams - 21) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $94 // (numParams - 22) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $98 // (numParams - 23) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $9c // (numParams - 24) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $a0 // (numParams - 25) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $a4 // (numParams - 26) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $a8 // (numParams - 27) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $ac // (numParams - 28) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $b0 // (numParams - 29) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $b4 // (numParams - 30) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $b8 // (numParams - 31) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $bc // (numParams - 32) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $c0 // (numParams - 33) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $c4 // (numParams - 34) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $c8 // (numParams - 35) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $cc // (numParams - 36) * 4 + 4*4
|
|
pop eax; sub esp, 132; ret $d0 // (numParams - 37) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $d4 // (numParams - 38) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $d8 // (numParams - 39) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $dc // (numParams - 40) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $e0 // (numParams - 41) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $e4 // (numParams - 42) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $e8 // (numParams - 43) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $ec // (numParams - 44) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $f0 // (numParams - 45) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $f4 // (numParams - 46) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $f8 // (numParams - 47) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $fc // (numParams - 48) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $100 // (numParams - 49) * 4 + 4*4
|
|
|
|
{$endif}
|
|
end;
|
|
procedure InUse_ThreadStateEnd; begin end;
|
|
//*)
|
|
|
|
const
|
|
{$ifdef win64}
|
|
CInUse_ThreadState : array[0..1070] of Byte = (
|
|
$48, $83, $EC, $20, $54, $50, $51, $52, $53, $55, $56, $57, $41, $50, $41, $51,
|
|
$41, $52, $41, $53, $41, $54, $41, $55, $41, $56, $41, $57, $9C, $48, $B8, $74,
|
|
$73, $68, $74, $34, $68, $63, $6D, $50, $48, $31, $E0, $50, $48, $83, $EC, $20,
|
|
$48, $8D, $B4, $24, $D8, $01, $00, $00, $48, $B9, $20, $00, $00, $00, $00, $00,
|
|
$00, $00, $48, $8B, $06, $48, $83, $EE, $08, $50, $E2, $F6, $48, $BF, $22, $22,
|
|
$22, $22, $22, $22, $22, $22, $48, $BE, $33, $33, $33, $33, $33, $33, $33, $33,
|
|
$48, $B9, $22, $02, $00, $00, $00, $00, $00, $00, $48, $31, $C0, $F0, $48, $0F,
|
|
$B1, $3F, $74, $12, $48, $83, $C7, $08, $48, $83, $C6, $04, $48, $31, $C0, $E2,
|
|
$EC, $48, $31, $FF, $EB, $0F, $65, $48, $8B, $04, $25, $30, $00, $00, $00, $48,
|
|
$8B, $40, $48, $89, $06, $48, $89, $BC, $24, $18, $01, $00, $00, $48, $8B, $84,
|
|
$24, $A8, $01, $00, $00, $48, $8B, $8C, $24, $A0, $01, $00, $00, $48, $8B, $94,
|
|
$24, $98, $01, $00, $00, $48, $8B, $9C, $24, $90, $01, $00, $00, $48, $8B, $B4,
|
|
$24, $80, $01, $00, $00, $48, $8B, $BC, $24, $78, $01, $00, $00, $48, $83, $84,
|
|
$24, $B0, $01, $00, $00, $20, $FF, $15, $02, $00, $00, $00, $EB, $08, $00, $00,
|
|
$00, $00, $00, $00, $00, $00, $50, $51, $52, $48, $BA, $74, $73, $68, $74, $34,
|
|
$68, $63, $6D, $48, $8D, $84, $24, $40, $01, $00, $00, $48, $8B, $08, $48, $39,
|
|
$D1, $74, $06, $48, $83, $E8, $08, $EB, $F2, $48, $8B, $48, $F8, $48, $31, $C1,
|
|
$48, $39, $D1, $74, $06, $48, $83, $E8, $08, $EB, $E0, $48, $31, $10, $48, $8D,
|
|
$8C, $24, $40, $01, $00, $00, $48, $29, $C8, $48, $8B, $8C, $04, $30, $01, $00,
|
|
$00, $48, $83, $F9, $00, $74, $08, $F0, $48, $81, $21, $00, $00, $00, $00, $48,
|
|
$F7, $D8, $48, $89, $C1, $48, $C1, $E8, $02, $48, $01, $C1, $48, $D1, $E8, $48,
|
|
$01, $C1, $48, $83, $C1, $09, $E8, $00, $00, $00, $00, $58, $48, $01, $C8, $5A,
|
|
$59, $48, $FF, $E0, $58, $48, $81, $C4, $D8, $01, $00, $00, $C2, $00, $00, $58,
|
|
$48, $81, $C4, $D0, $01, $00, $00, $C2, $08, $00, $58, $48, $81, $C4, $C8, $01,
|
|
$00, $00, $C2, $10, $00, $58, $48, $81, $C4, $C0, $01, $00, $00, $C2, $18, $00,
|
|
$58, $48, $81, $C4, $B8, $01, $00, $00, $C2, $20, $00, $58, $48, $81, $C4, $B0,
|
|
$01, $00, $00, $C2, $28, $00, $58, $48, $81, $C4, $A8, $01, $00, $00, $C2, $30,
|
|
$00, $58, $48, $81, $C4, $A0, $01, $00, $00, $C2, $38, $00, $58, $48, $81, $C4,
|
|
$98, $01, $00, $00, $C2, $40, $00, $58, $48, $81, $C4, $90, $01, $00, $00, $C2,
|
|
$48, $00, $58, $48, $81, $C4, $88, $01, $00, $00, $C2, $50, $00, $58, $48, $81,
|
|
$C4, $80, $01, $00, $00, $C2, $58, $00, $58, $48, $81, $C4, $78, $01, $00, $00,
|
|
$C2, $60, $00, $58, $48, $81, $C4, $70, $01, $00, $00, $C2, $68, $00, $58, $48,
|
|
$81, $C4, $68, $01, $00, $00, $C2, $70, $00, $58, $48, $81, $C4, $60, $01, $00,
|
|
$00, $C2, $78, $00, $58, $48, $81, $C4, $58, $01, $00, $00, $C2, $80, $00, $58,
|
|
$48, $81, $C4, $50, $01, $00, $00, $C2, $88, $00, $58, $48, $81, $C4, $48, $01,
|
|
$00, $00, $C2, $90, $00, $58, $48, $81, $C4, $40, $01, $00, $00, $C2, $98, $00,
|
|
$58, $48, $81, $C4, $38, $01, $00, $00, $C2, $A0, $00, $58, $48, $81, $C4, $30,
|
|
$01, $00, $00, $C2, $A8, $00, $58, $48, $81, $C4, $28, $01, $00, $00, $C2, $B0,
|
|
$00, $58, $48, $81, $C4, $20, $01, $00, $00, $C2, $B8, $00, $58, $48, $81, $C4,
|
|
$18, $01, $00, $00, $C2, $C0, $00, $58, $48, $81, $C4, $10, $01, $00, $00, $C2,
|
|
$C8, $00, $58, $48, $81, $C4, $08, $01, $00, $00, $C2, $D0, $00, $58, $48, $81,
|
|
$C4, $00, $01, $00, $00, $C2, $D8, $00, $58, $48, $81, $C4, $F8, $00, $00, $00,
|
|
$C2, $E0, $00, $58, $48, $81, $C4, $F0, $00, $00, $00, $C2, $E8, $00, $58, $48,
|
|
$81, $C4, $E8, $00, $00, $00, $C2, $F0, $00, $58, $48, $81, $C4, $E0, $00, $00,
|
|
$00, $C2, $F8, $00, $58, $48, $81, $C4, $D8, $00, $00, $00, $C2, $00, $01, $58,
|
|
$48, $81, $C4, $D0, $00, $00, $00, $C2, $08, $01, $58, $48, $81, $C4, $C8, $00,
|
|
$00, $00, $C2, $10, $01, $58, $48, $81, $C4, $C0, $00, $00, $00, $C2, $18, $01,
|
|
$58, $48, $81, $C4, $B8, $00, $00, $00, $C2, $20, $01, $58, $48, $81, $C4, $B0,
|
|
$00, $00, $00, $C2, $28, $01, $58, $48, $81, $C4, $A8, $00, $00, $00, $C2, $30,
|
|
$01, $58, $48, $81, $C4, $A0, $00, $00, $00, $C2, $38, $01, $58, $48, $81, $C4,
|
|
$98, $00, $00, $00, $C2, $40, $01, $58, $48, $81, $C4, $90, $00, $00, $00, $C2,
|
|
$48, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $50, $01, $58, $48, $81,
|
|
$C4, $88, $00, $00, $00, $C2, $58, $01, $58, $48, $81, $C4, $88, $00, $00, $00,
|
|
$C2, $60, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $68, $01, $58, $48,
|
|
$81, $C4, $88, $00, $00, $00, $C2, $70, $01, $58, $48, $81, $C4, $88, $00, $00,
|
|
$00, $C2, $78, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $80, $01, $58,
|
|
$48, $81, $C4, $88, $00, $00, $00, $C2, $88, $01, $58, $48, $81, $C4, $88, $00,
|
|
$00, $00, $C2, $90, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $98, $01,
|
|
$58, $48, $81, $C4, $88, $00, $00, $00, $C2, $A0, $01, $58, $48, $81, $C4, $88,
|
|
$00, $00, $00, $C2, $A8, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $B0,
|
|
$01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $B8, $01, $58, $48, $81, $C4,
|
|
$88, $00, $00, $00, $C2, $C0, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2,
|
|
$C8, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $D0, $01, $58, $48, $81,
|
|
$C4, $88, $00, $00, $00, $C2, $D8, $01, $58, $48, $81, $C4, $88, $00, $00, $00,
|
|
$C2, $E0, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $E8, $01, $58, $48,
|
|
$81, $C4, $88, $00, $00, $00, $C2, $F0, $01, $58, $48, $81, $C4, $88, $00, $00,
|
|
$00, $C2, $F8, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $00, $02
|
|
);
|
|
{$else}
|
|
CInUse_ThreadState : array [0..891] of byte = (
|
|
$83, $EC, $10, $60, $9C, $B8, $34, $68, $63, $6D, $50, $31, $E0, $50, $83, $EC,
|
|
$10, $8D, $B4, $24, $CC, $00, $00, $00, $B9, $20, $00, $00, $00, $8B, $06, $83,
|
|
$EE, $04, $50, $E2, $F8, $BF, $22, $22, $22, $22, $BE, $33, $33, $33, $33, $B9,
|
|
$22, $02, $00, $00, $31, $C0, $F0, $0F, $B1, $3F, $74, $0E, $83, $C7, $04, $83,
|
|
$C6, $04, $31, $C0, $E2, $F0, $31, $FF, $EB, $0C, $64, $8B, $05, $18, $00, $00,
|
|
$00, $8B, $40, $24, $89, $06, $89, $BC, $24, $8C, $00, $00, $00, $8B, $84, $24,
|
|
$B8, $00, $00, $00, $8B, $8C, $24, $B4, $00, $00, $00, $8B, $94, $24, $B0, $00,
|
|
$00, $00, $8B, $9C, $24, $AC, $00, $00, $00, $8B, $B4, $24, $A0, $00, $00, $00,
|
|
$8B, $BC, $24, $9C, $00, $00, $00, $83, $84, $24, $A8, $00, $00, $00, $10, $E8,
|
|
$44, $44, $44, $44, $50, $51, $8D, $84, $24, $9C, $00, $00, $00, $8B, $08, $81,
|
|
$F9, $34, $68, $63, $6D, $74, $05, $83, $E8, $04, $EB, $F1, $8B, $48, $FC, $31,
|
|
$C1, $81, $F9, $34, $68, $63, $6D, $74, $05, $83, $E8, $04, $EB, $DF, $83, $20,
|
|
$00, $8D, $8C, $24, $9C, $00, $00, $00, $29, $C8, $8B, $8C, $04, $94, $00, $00,
|
|
$00, $83, $F9, $00, $74, $04, $F0, $83, $21, $00, $F7, $D8, $89, $C1, $D1, $E1,
|
|
$D1, $E8, $01, $C1, $83, $C1, $06, $E8, $00, $00, $00, $00, $58, $01, $C8, $59,
|
|
$FF, $E0, $58, $81, $C4, $CC, $00, $00, $00, $C2, $00, $00, $58, $81, $C4, $C8,
|
|
$00, $00, $00, $C2, $04, $00, $58, $81, $C4, $C4, $00, $00, $00, $C2, $08, $00,
|
|
$58, $81, $C4, $C0, $00, $00, $00, $C2, $0C, $00, $58, $81, $C4, $BC, $00, $00,
|
|
$00, $C2, $10, $00, $58, $81, $C4, $B8, $00, $00, $00, $C2, $14, $00, $58, $81,
|
|
$C4, $B4, $00, $00, $00, $C2, $18, $00, $58, $81, $C4, $B0, $00, $00, $00, $C2,
|
|
$1C, $00, $58, $81, $C4, $AC, $00, $00, $00, $C2, $20, $00, $58, $81, $C4, $A8,
|
|
$00, $00, $00, $C2, $24, $00, $58, $81, $C4, $A4, $00, $00, $00, $C2, $28, $00,
|
|
$58, $81, $C4, $A0, $00, $00, $00, $C2, $2C, $00, $58, $81, $C4, $9C, $00, $00,
|
|
$00, $C2, $30, $00, $58, $81, $C4, $98, $00, $00, $00, $C2, $34, $00, $58, $81,
|
|
$C4, $94, $00, $00, $00, $C2, $38, $00, $58, $81, $C4, $90, $00, $00, $00, $C2,
|
|
$3C, $00, $58, $81, $C4, $8C, $00, $00, $00, $C2, $40, $00, $58, $81, $C4, $88,
|
|
$00, $00, $00, $C2, $44, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $48, $00,
|
|
$58, $81, $C4, $84, $00, $00, $00, $C2, $4C, $00, $58, $81, $C4, $84, $00, $00,
|
|
$00, $C2, $50, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $54, $00, $58, $81,
|
|
$C4, $84, $00, $00, $00, $C2, $58, $00, $58, $81, $C4, $84, $00, $00, $00, $C2,
|
|
$5C, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $60, $00, $58, $81, $C4, $84,
|
|
$00, $00, $00, $C2, $64, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $68, $00,
|
|
$58, $81, $C4, $84, $00, $00, $00, $C2, $6C, $00, $58, $81, $C4, $84, $00, $00,
|
|
$00, $C2, $70, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $74, $00, $58, $81,
|
|
$C4, $84, $00, $00, $00, $C2, $78, $00, $58, $81, $C4, $84, $00, $00, $00, $C2,
|
|
$7C, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $80, $00, $58, $81, $C4, $84,
|
|
$00, $00, $00, $C2, $84, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $88, $00,
|
|
$58, $81, $C4, $84, $00, $00, $00, $C2, $8C, $00, $58, $81, $C4, $84, $00, $00,
|
|
$00, $C2, $90, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $94, $00, $58, $81,
|
|
$C4, $84, $00, $00, $00, $C2, $98, $00, $58, $81, $C4, $84, $00, $00, $00, $C2,
|
|
$9C, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $A0, $00, $58, $81, $C4, $84,
|
|
$00, $00, $00, $C2, $A4, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $A8, $00,
|
|
$58, $81, $C4, $84, $00, $00, $00, $C2, $AC, $00, $58, $81, $C4, $84, $00, $00,
|
|
$00, $C2, $B0, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $B4, $00, $58, $81,
|
|
$C4, $84, $00, $00, $00, $C2, $B8, $00, $58, $81, $C4, $84, $00, $00, $00, $C2,
|
|
$BC, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $C0, $00, $58, $81, $C4, $84,
|
|
$00, $00, $00, $C2, $C4, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $C8, $00,
|
|
$58, $81, $C4, $84, $00, $00, $00, $C2, $CC, $00, $58, $81, $EC, $84, $00, $00,
|
|
$00, $C2, $D0, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $D4, $00, $58, $81,
|
|
$C4, $84, $00, $00, $00, $C2, $D8, $00, $58, $81, $C4, $84, $00, $00, $00, $C2,
|
|
$DC, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $E0, $00, $58, $81, $C4, $84,
|
|
$00, $00, $00, $C2, $E4, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $E8, $00,
|
|
$58, $81, $C4, $84, $00, $00, $00, $C2, $EC, $00, $58, $81, $C4, $84, $00, $00,
|
|
$00, $C2, $F0, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $F4, $00, $58, $81,
|
|
$C4, $84, $00, $00, $00, $C2, $F8, $00, $58, $81, $C4, $84, $00, $00, $00, $C2,
|
|
$FC, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $00, $01
|
|
);
|
|
{$endif}
|
|
|
|
(*
|
|
procedure NoUse_ThreadState;
|
|
asm
|
|
{$ifdef win64}
|
|
|
|
.noframe
|
|
// keep 4 stack items untouched
|
|
sub rsp, 4*8
|
|
// store current thread state to stack
|
|
push rsp
|
|
push rax
|
|
push rcx
|
|
push rdx
|
|
push rbx
|
|
push rbp
|
|
push rsi
|
|
push rdi
|
|
push r8
|
|
push r9
|
|
push r10
|
|
push r11
|
|
push r12
|
|
push r13
|
|
push r14
|
|
push r15
|
|
pushfq
|
|
// add a signature and checksum
|
|
mov rax, 6D63683474687374h // 'mch4thst'
|
|
push rax
|
|
xor rax, rsp
|
|
push rax
|
|
// let's reserve some space here for private use
|
|
sub rsp, 20h
|
|
// copy the parameters and return address
|
|
lea rsi, [rsp+472] // (numParams + 23) * 8 + 4*8
|
|
mov rcx, 32 // (numParams + 0)
|
|
@copyLoop:
|
|
mov rax, [rsi]
|
|
sub rsi, 8
|
|
push rax
|
|
loop @copyLoop
|
|
// restore the registers by using the stored thread state
|
|
mov rax, [rsp+424] // (numParams + 21) * 8
|
|
mov rcx, [rsp+416] // (numParams + 20) * 8
|
|
mov rsi, [rsp+384] // (numParams + 16) * 8
|
|
// correct rsp to account for the 4 untouched stack items
|
|
add qword ptr [rsp+432], 4*8 // (numParams + 22) * 8
|
|
|
|
// here we call the callback function
|
|
db 0ffh
|
|
db 015h
|
|
dd 002h
|
|
jmp @skipTarget
|
|
dq 0
|
|
@skipTarget:
|
|
|
|
// the callback function returned
|
|
// we don't know how many stack items the callback function removed from the stack
|
|
// we "measure" that here by locating our magic stack signature
|
|
push rax
|
|
push rcx
|
|
push rdx
|
|
mov rdx, 6D63683474687374h // 'mch4thst'
|
|
lea rax, [rsp+320] // (numParams + 8) * 8
|
|
@searchLoop:
|
|
mov rcx, [rax]
|
|
cmp rcx, rdx
|
|
jz @searchMaybeComplete
|
|
sub rax, 8
|
|
jmp @searchLoop
|
|
@searchMaybeComplete:
|
|
// we found the stack signature, now let's verify the checksum
|
|
mov rcx, [rax-8]
|
|
xor rcx, rax
|
|
cmp rcx, rdx
|
|
jz @searchDefinitelyComplete
|
|
sub rax, 8
|
|
jmp @searchLoop
|
|
@searchDefinitelyComplete:
|
|
// ok, we found it, clear the marker
|
|
xor qword ptr [rax], rdx
|
|
// now let's do some math
|
|
lea rcx, [rsp+320] // (numParams + 8) * 8
|
|
sub rax, rcx
|
|
// eax now contains the negative number of stack items which the callback function removed from the stack
|
|
// for example, if the callback function has a "ret 16", then eax is now -16
|
|
// now let's clean up
|
|
// unfortunately thanks to stupid CET this is rather complicated
|
|
// basically we have to perform a "ret x" jump which matches the one of the callback function
|
|
neg rax
|
|
mov rcx, rax
|
|
shr rax, 2
|
|
add rcx, rax
|
|
shr rax, 1
|
|
add rcx, rax
|
|
add rcx, 9
|
|
call @myself
|
|
@myself:
|
|
pop rax
|
|
add rax, rcx
|
|
pop rdx
|
|
pop rcx
|
|
jmp rax
|
|
// return table, max supported number of parameters is 64
|
|
pop rax; add rsp, 472; ret $000 // (numParams + 23) * 8 + 4*8
|
|
pop rax; add rsp, 464; ret $008 // (numParams + 22) * 8 + 4*8
|
|
pop rax; add rsp, 456; ret $010 // (numParams + 21) * 8 + 4*8
|
|
pop rax; add rsp, 448; ret $018 // (numParams + 20) * 8 + 4*8
|
|
pop rax; add rsp, 440; ret $020 // (numParams + 19) * 8 + 4*8
|
|
pop rax; add rsp, 432; ret $028 // (numParams + 18) * 8 + 4*8
|
|
pop rax; add rsp, 424; ret $030 // (numParams + 17) * 8 + 4*8
|
|
pop rax; add rsp, 416; ret $038 // (numParams + 16) * 8 + 4*8
|
|
pop rax; add rsp, 408; ret $040 // (numParams + 15) * 8 + 4*8
|
|
pop rax; add rsp, 400; ret $048 // (numParams + 14) * 8 + 4*8
|
|
pop rax; add rsp, 392; ret $050 // (numParams + 13) * 8 + 4*8
|
|
pop rax; add rsp, 384; ret $058 // (numParams + 12) * 8 + 4*8
|
|
pop rax; add rsp, 376; ret $060 // (numParams + 11) * 8 + 4*8
|
|
pop rax; add rsp, 368; ret $068 // (numParams + 10) * 8 + 4*8
|
|
pop rax; add rsp, 360; ret $070 // (numParams + 9) * 8 + 4*8
|
|
pop rax; add rsp, 352; ret $078 // (numParams + 8) * 8 + 4*8
|
|
pop rax; add rsp, 344; ret $080 // (numParams + 7) * 8 + 4*8
|
|
pop rax; add rsp, 336; ret $088 // (numParams + 6) * 8 + 4*8
|
|
pop rax; add rsp, 328; ret $090 // (numParams + 5) * 8 + 4*8
|
|
pop rax; add rsp, 320; ret $098 // (numParams + 4) * 8 + 4*8
|
|
pop rax; add rsp, 312; ret $0a0 // (numParams + 3) * 8 + 4*8
|
|
pop rax; add rsp, 304; ret $0a8 // (numParams + 2) * 8 + 4*8
|
|
pop rax; add rsp, 296; ret $0b0 // (numParams + 1) * 8 + 4*8
|
|
pop rax; add rsp, 288; ret $0b8 // (numParams + 0) * 8 + 4*8
|
|
pop rax; add rsp, 280; ret $0c0 // (numParams - 1) * 8 + 4*8
|
|
pop rax; add rsp, 272; ret $0c8 // (numParams - 2) * 8 + 4*8
|
|
pop rax; add rsp, 264; ret $0d0 // (numParams - 3) * 8 + 4*8
|
|
pop rax; add rsp, 256; ret $0d8 // (numParams - 4) * 8 + 4*8
|
|
pop rax; add rsp, 248; ret $0e0 // (numParams - 5) * 8 + 4*8
|
|
pop rax; add rsp, 240; ret $0e8 // (numParams - 6) * 8 + 4*8
|
|
pop rax; add rsp, 232; ret $0f0 // (numParams - 7) * 8 + 4*8
|
|
pop rax; add rsp, 224; ret $0f8 // (numParams - 8) * 8 + 4*8
|
|
pop rax; add rsp, 216; ret $100 // (numParams - 9) * 8 + 4*8
|
|
pop rax; add rsp, 208; ret $108 // (numParams - 10) * 8 + 4*8
|
|
pop rax; add rsp, 200; ret $110 // (numParams - 11) * 8 + 4*8
|
|
pop rax; add rsp, 192; ret $118 // (numParams - 12) * 8 + 4*8
|
|
pop rax; add rsp, 184; ret $120 // (numParams - 13) * 8 + 4*8
|
|
pop rax; add rsp, 176; ret $128 // (numParams - 14) * 8 + 4*8
|
|
pop rax; add rsp, 168; ret $130 // (numParams - 15) * 8 + 4*8
|
|
pop rax; add rsp, 160; ret $138 // (numParams - 16) * 8 + 4*8
|
|
pop rax; add rsp, 152; ret $140 // (numParams - 17) * 8 + 4*8
|
|
pop rax; add rsp, 144; ret $148 // (numParams - 18) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $150 // (numParams - 19) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $158 // (numParams - 20) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $160 // (numParams - 21) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $168 // (numParams - 22) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $170 // (numParams - 23) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $178 // (numParams - 24) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $180 // (numParams - 25) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $188 // (numParams - 26) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $190 // (numParams - 27) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $198 // (numParams - 28) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1a0 // (numParams - 29) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1a8 // (numParams - 30) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1b0 // (numParams - 31) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1b8 // (numParams - 32) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1c0 // (numParams - 33) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1c8 // (numParams - 34) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1d0 // (numParams - 35) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1d8 // (numParams - 36) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1e0 // (numParams - 37) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1e8 // (numParams - 38) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1f0 // (numParams - 39) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $1f8 // (numParams - 40) * 8 + 4*8
|
|
pop rax; add rsp, 136; ret $200 // (numParams - 41) * 8 + 4*8
|
|
|
|
{$else}
|
|
|
|
// keep 4 stack items untouched
|
|
sub esp, 4*4
|
|
// store current thread state to stack
|
|
pushad
|
|
pushfd
|
|
// add a signature and checksum
|
|
mov eax, 'mch4'
|
|
push eax
|
|
xor eax, esp
|
|
push eax
|
|
// let's reserve some space here for private use
|
|
sub esp, 10h
|
|
// copy the parameters and return address
|
|
lea esi, [esp+204] // (numParams + 15) * 4 + 4*4
|
|
mov ecx, 32 // (numParams + 0)
|
|
@copyLoop:
|
|
mov eax, [esi]
|
|
sub esi, 4
|
|
push eax
|
|
loop @copyLoop
|
|
// restore the registers by using the stored thread state
|
|
mov eax, [esp+188] // (numParams + 14) * 4
|
|
mov ecx, [esp+184] // (numParams + 13) * 4
|
|
mov esi, [esp+164] // (numParams + 8) * 4
|
|
// correct rsp to account for the 4 untouched stack items
|
|
add [esp+172], 4*4 // (numParams + 10) * 4
|
|
|
|
// here we call the callback function
|
|
db $e8
|
|
dd $44444444
|
|
|
|
// the callback function returned
|
|
// we don't know how many stack items the callback function removed from the stack
|
|
// we "measure" that here by locating our magic stack signature
|
|
push eax
|
|
push ecx
|
|
lea eax, [esp+156] // (numParams + 7) * 4
|
|
@searchLoop:
|
|
mov ecx, [eax]
|
|
cmp ecx, 'mch4'
|
|
jz @searchMaybeComplete
|
|
sub eax, 4
|
|
jmp @searchLoop
|
|
@searchMaybeComplete:
|
|
// we found the stack signature, now let's verify the checksum
|
|
mov ecx, [eax-4]
|
|
xor ecx, eax
|
|
cmp ecx, 'mch4'
|
|
jz @searchDefinitelyComplete
|
|
sub eax, 4
|
|
jmp @searchLoop
|
|
@searchDefinitelyComplete:
|
|
// ok, we found it, clear the marker
|
|
and dword ptr [eax], 0
|
|
// now let's do some math
|
|
lea ecx, [esp+156] // (numParams + 7) * 4
|
|
sub eax, ecx
|
|
// eax now contains the negative number of stack items which the callback function removed from the stack
|
|
// for example, if the callback function has a "ret 16", then eax is now -16
|
|
// now let's clean up
|
|
// unfortunately thanks to stupid CET this is rather complicated
|
|
// basically we have to perform a "ret x" jump which matches the one of the callback function
|
|
neg eax
|
|
mov ecx, eax
|
|
shl ecx, 1
|
|
shr eax, 1
|
|
add ecx, eax
|
|
add ecx, 6
|
|
call @myself
|
|
@myself:
|
|
pop eax
|
|
add eax, ecx
|
|
pop ecx
|
|
jmp eax
|
|
// return table, max supported number of parameters is 64
|
|
pop eax; add esp, 204; ret $00 // (numParams + 15) * 4 + 4*4
|
|
pop eax; add esp, 200; ret $04 // (numParams + 14) * 4 + 4*4
|
|
pop eax; add esp, 196; ret $08 // (numParams + 13) * 4 + 4*4
|
|
pop eax; add esp, 192; ret $0c // (numParams + 12) * 4 + 4*4
|
|
pop eax; add esp, 188; ret $10 // (numParams + 11) * 4 + 4*4
|
|
pop eax; add esp, 184; ret $14 // (numParams + 10) * 4 + 4*4
|
|
pop eax; add esp, 180; ret $18 // (numParams + 9) * 4 + 4*4
|
|
pop eax; add esp, 176; ret $1c // (numParams + 8) * 4 + 4*4
|
|
pop eax; add esp, 172; ret $20 // (numParams + 7) * 4 + 4*4
|
|
pop eax; add esp, 168; ret $24 // (numParams + 6) * 4 + 4*4
|
|
pop eax; add esp, 164; ret $28 // (numParams + 5) * 4 + 4*4
|
|
pop eax; add esp, 160; ret $2c // (numParams + 4) * 4 + 4*4
|
|
pop eax; add esp, 156; ret $30 // (numParams + 3) * 4 + 4*4
|
|
pop eax; add esp, 152; ret $34 // (numParams + 2) * 4 + 4*4
|
|
pop eax; add esp, 148; ret $38 // (numParams + 1) * 4 + 4*4
|
|
pop eax; add esp, 144; ret $3c // (numParams + 0) * 4 + 4*4
|
|
pop eax; add esp, 140; ret $40 // (numParams - 1) * 4 + 4*4
|
|
pop eax; add esp, 136; ret $44 // (numParams - 2) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $48 // (numParams - 3) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $4c // (numParams - 4) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $50 // (numParams - 5) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $54 // (numParams - 6) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $58 // (numParams - 7) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $5c // (numParams - 8) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $60 // (numParams - 9) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $64 // (numParams - 10) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $68 // (numParams - 11) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $6c // (numParams - 12) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $70 // (numParams - 13) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $74 // (numParams - 14) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $78 // (numParams - 15) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $7c // (numParams - 16) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $80 // (numParams - 17) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $84 // (numParams - 18) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $88 // (numParams - 19) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $8c // (numParams - 20) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $90 // (numParams - 21) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $94 // (numParams - 22) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $98 // (numParams - 23) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $9c // (numParams - 24) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $a0 // (numParams - 25) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $a4 // (numParams - 26) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $a8 // (numParams - 27) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $ac // (numParams - 28) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $b0 // (numParams - 29) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $b4 // (numParams - 30) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $b8 // (numParams - 31) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $bc // (numParams - 32) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $c0 // (numParams - 33) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $c4 // (numParams - 34) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $c8 // (numParams - 35) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $cc // (numParams - 36) * 4 + 4*4
|
|
pop eax; sub esp, 132; ret $d0 // (numParams - 37) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $d4 // (numParams - 38) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $d8 // (numParams - 39) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $dc // (numParams - 40) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $e0 // (numParams - 41) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $e4 // (numParams - 42) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $e8 // (numParams - 43) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $ec // (numParams - 44) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $f0 // (numParams - 45) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $f4 // (numParams - 46) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $f8 // (numParams - 47) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $fc // (numParams - 48) * 4 + 4*4
|
|
pop eax; add esp, 132; ret $100 // (numParams - 49) * 4 + 4*4
|
|
|
|
{$endif}
|
|
end;
|
|
//*)
|
|
|
|
const
|
|
{$ifdef win64}
|
|
CNoUse_ThreadState : array [0..943] of byte = (
|
|
$48, $83, $EC, $20, $54, $50, $51, $52, $53, $55, $56, $57, $41, $50, $41, $51,
|
|
$41, $52, $41, $53, $41, $54, $41, $55, $41, $56, $41, $57, $9C, $48, $B8, $74,
|
|
$73, $68, $74, $34, $68, $63, $6D, $50, $48, $31, $E0, $50, $48, $83, $EC, $20,
|
|
$48, $8D, $B4, $24, $D8, $01, $00, $00, $48, $B9, $20, $00, $00, $00, $00, $00,
|
|
$00, $00, $48, $8B, $06, $48, $83, $EE, $08, $50, $E2, $F6, $48, $8B, $84, $24,
|
|
$A8, $01, $00, $00, $48, $8B, $8C, $24, $A0, $01, $00, $00, $48, $8B, $B4, $24,
|
|
$80, $01, $00, $00, $48, $83, $84, $24, $B0, $01, $00, $00, $20, $FF, $15, $02,
|
|
$00, $00, $00, $EB, $08, $00, $00, $00, $00, $00, $00, $00, $00, $50, $51, $52,
|
|
$48, $BA, $74, $73, $68, $74, $34, $68, $63, $6D, $48, $8D, $84, $24, $40, $01,
|
|
$00, $00, $48, $8B, $08, $48, $39, $D1, $74, $06, $48, $83, $E8, $08, $EB, $F2,
|
|
$48, $8B, $48, $F8, $48, $31, $C1, $48, $39, $D1, $74, $06, $48, $83, $E8, $08,
|
|
$EB, $E0, $48, $31, $10, $48, $8D, $8C, $24, $40, $01, $00, $00, $48, $29, $C8,
|
|
$48, $F7, $D8, $48, $89, $C1, $48, $C1, $E8, $02, $48, $01, $C1, $48, $D1, $E8,
|
|
$48, $01, $C1, $48, $83, $C1, $09, $E8, $00, $00, $00, $00, $58, $48, $01, $C8,
|
|
$5A, $59, $48, $FF, $E0, $58, $48, $81, $C4, $D8, $01, $00, $00, $C2, $00, $00,
|
|
$58, $48, $81, $C4, $D0, $01, $00, $00, $C2, $08, $00, $58, $48, $81, $C4, $C8,
|
|
$01, $00, $00, $C2, $10, $00, $58, $48, $81, $C4, $C0, $01, $00, $00, $C2, $18,
|
|
$00, $58, $48, $81, $C4, $B8, $01, $00, $00, $C2, $20, $00, $58, $48, $81, $C4,
|
|
$B0, $01, $00, $00, $C2, $28, $00, $58, $48, $81, $C4, $A8, $01, $00, $00, $C2,
|
|
$30, $00, $58, $48, $81, $C4, $A0, $01, $00, $00, $C2, $38, $00, $58, $48, $81,
|
|
$C4, $98, $01, $00, $00, $C2, $40, $00, $58, $48, $81, $C4, $90, $01, $00, $00,
|
|
$C2, $48, $00, $58, $48, $81, $C4, $88, $01, $00, $00, $C2, $50, $00, $58, $48,
|
|
$81, $C4, $80, $01, $00, $00, $C2, $58, $00, $58, $48, $81, $C4, $78, $01, $00,
|
|
$00, $C2, $60, $00, $58, $48, $81, $C4, $70, $01, $00, $00, $C2, $68, $00, $58,
|
|
$48, $81, $C4, $68, $01, $00, $00, $C2, $70, $00, $58, $48, $81, $C4, $60, $01,
|
|
$00, $00, $C2, $78, $00, $58, $48, $81, $C4, $58, $01, $00, $00, $C2, $80, $00,
|
|
$58, $48, $81, $C4, $50, $01, $00, $00, $C2, $88, $00, $58, $48, $81, $C4, $48,
|
|
$01, $00, $00, $C2, $90, $00, $58, $48, $81, $C4, $40, $01, $00, $00, $C2, $98,
|
|
$00, $58, $48, $81, $C4, $38, $01, $00, $00, $C2, $A0, $00, $58, $48, $81, $C4,
|
|
$30, $01, $00, $00, $C2, $A8, $00, $58, $48, $81, $C4, $28, $01, $00, $00, $C2,
|
|
$B0, $00, $58, $48, $81, $C4, $20, $01, $00, $00, $C2, $B8, $00, $58, $48, $81,
|
|
$C4, $18, $01, $00, $00, $C2, $C0, $00, $58, $48, $81, $C4, $10, $01, $00, $00,
|
|
$C2, $C8, $00, $58, $48, $81, $C4, $08, $01, $00, $00, $C2, $D0, $00, $58, $48,
|
|
$81, $C4, $00, $01, $00, $00, $C2, $D8, $00, $58, $48, $81, $C4, $F8, $00, $00,
|
|
$00, $C2, $E0, $00, $58, $48, $81, $C4, $F0, $00, $00, $00, $C2, $E8, $00, $58,
|
|
$48, $81, $C4, $E8, $00, $00, $00, $C2, $F0, $00, $58, $48, $81, $C4, $E0, $00,
|
|
$00, $00, $C2, $F8, $00, $58, $48, $81, $C4, $D8, $00, $00, $00, $C2, $00, $01,
|
|
$58, $48, $81, $C4, $D0, $00, $00, $00, $C2, $08, $01, $58, $48, $81, $C4, $C8,
|
|
$00, $00, $00, $C2, $10, $01, $58, $48, $81, $C4, $C0, $00, $00, $00, $C2, $18,
|
|
$01, $58, $48, $81, $C4, $B8, $00, $00, $00, $C2, $20, $01, $58, $48, $81, $C4,
|
|
$B0, $00, $00, $00, $C2, $28, $01, $58, $48, $81, $C4, $A8, $00, $00, $00, $C2,
|
|
$30, $01, $58, $48, $81, $C4, $A0, $00, $00, $00, $C2, $38, $01, $58, $48, $81,
|
|
$C4, $98, $00, $00, $00, $C2, $40, $01, $58, $48, $81, $C4, $90, $00, $00, $00,
|
|
$C2, $48, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $50, $01, $58, $48,
|
|
$81, $C4, $88, $00, $00, $00, $C2, $58, $01, $58, $48, $81, $C4, $88, $00, $00,
|
|
$00, $C2, $60, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $68, $01, $58,
|
|
$48, $81, $C4, $88, $00, $00, $00, $C2, $70, $01, $58, $48, $81, $C4, $88, $00,
|
|
$00, $00, $C2, $78, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $80, $01,
|
|
$58, $48, $81, $C4, $88, $00, $00, $00, $C2, $88, $01, $58, $48, $81, $C4, $88,
|
|
$00, $00, $00, $C2, $90, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $98,
|
|
$01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $A0, $01, $58, $48, $81, $C4,
|
|
$88, $00, $00, $00, $C2, $A8, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2,
|
|
$B0, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $B8, $01, $58, $48, $81,
|
|
$C4, $88, $00, $00, $00, $C2, $C0, $01, $58, $48, $81, $C4, $88, $00, $00, $00,
|
|
$C2, $C8, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $D0, $01, $58, $48,
|
|
$81, $C4, $88, $00, $00, $00, $C2, $D8, $01, $58, $48, $81, $C4, $88, $00, $00,
|
|
$00, $C2, $E0, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $E8, $01, $58,
|
|
$48, $81, $C4, $88, $00, $00, $00, $C2, $F0, $01, $58, $48, $81, $C4, $88, $00,
|
|
$00, $00, $C2, $F8, $01, $58, $48, $81, $C4, $88, $00, $00, $00, $C2, $00, $02
|
|
);
|
|
{$else}
|
|
CNoUse_ThreadState : array [0..798] of byte = (
|
|
$83, $EC, $10, $60, $9C, $B8, $34, $68, $63, $6D, $50, $31, $E0, $50, $83, $EC,
|
|
$10, $8D, $B4, $24, $CC, $00, $00, $00, $B9, $20, $00, $00, $00, $8B, $06, $83,
|
|
$EE, $04, $50, $E2, $F8, $8B, $84, $24, $BC, $00, $00, $00, $8B, $8C, $24, $B8,
|
|
$00, $00, $00, $8B, $B4, $24, $A4, $00, $00, $00, $83, $84, $24, $AC, $00, $00,
|
|
$00, $10, $E8, $44, $44, $44, $44, $50, $51, $8D, $84, $24, $9C, $00, $00, $00,
|
|
$8B, $08, $81, $F9, $34, $68, $63, $6D, $74, $05, $83, $E8, $04, $EB, $F1, $8B,
|
|
$48, $FC, $31, $C1, $81, $F9, $34, $68, $63, $6D, $74, $05, $83, $E8, $04, $EB,
|
|
$DF, $83, $20, $00, $8D, $8C, $24, $9C, $00, $00, $00, $29, $C8, $F7, $D8, $89,
|
|
$C1, $D1, $E1, $D1, $E8, $01, $C1, $83, $C1, $06, $E8, $00, $00, $00, $00, $58,
|
|
$01, $C8, $59, $FF, $E0, $58, $81, $C4, $CC, $00, $00, $00, $C2, $00, $00, $58,
|
|
$81, $C4, $C8, $00, $00, $00, $C2, $04, $00, $58, $81, $C4, $C4, $00, $00, $00,
|
|
$C2, $08, $00, $58, $81, $C4, $C0, $00, $00, $00, $C2, $0C, $00, $58, $81, $C4,
|
|
$BC, $00, $00, $00, $C2, $10, $00, $58, $81, $C4, $B8, $00, $00, $00, $C2, $14,
|
|
$00, $58, $81, $C4, $B4, $00, $00, $00, $C2, $18, $00, $58, $81, $C4, $B0, $00,
|
|
$00, $00, $C2, $1C, $00, $58, $81, $C4, $AC, $00, $00, $00, $C2, $20, $00, $58,
|
|
$81, $C4, $A8, $00, $00, $00, $C2, $24, $00, $58, $81, $C4, $A4, $00, $00, $00,
|
|
$C2, $28, $00, $58, $81, $C4, $A0, $00, $00, $00, $C2, $2C, $00, $58, $81, $C4,
|
|
$9C, $00, $00, $00, $C2, $30, $00, $58, $81, $C4, $98, $00, $00, $00, $C2, $34,
|
|
$00, $58, $81, $C4, $94, $00, $00, $00, $C2, $38, $00, $58, $81, $C4, $90, $00,
|
|
$00, $00, $C2, $3C, $00, $58, $81, $C4, $8C, $00, $00, $00, $C2, $40, $00, $58,
|
|
$81, $C4, $88, $00, $00, $00, $C2, $44, $00, $58, $81, $C4, $84, $00, $00, $00,
|
|
$C2, $48, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $4C, $00, $58, $81, $C4,
|
|
$84, $00, $00, $00, $C2, $50, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $54,
|
|
$00, $58, $81, $C4, $84, $00, $00, $00, $C2, $58, $00, $58, $81, $C4, $84, $00,
|
|
$00, $00, $C2, $5C, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $60, $00, $58,
|
|
$81, $C4, $84, $00, $00, $00, $C2, $64, $00, $58, $81, $C4, $84, $00, $00, $00,
|
|
$C2, $68, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $6C, $00, $58, $81, $C4,
|
|
$84, $00, $00, $00, $C2, $70, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $74,
|
|
$00, $58, $81, $C4, $84, $00, $00, $00, $C2, $78, $00, $58, $81, $C4, $84, $00,
|
|
$00, $00, $C2, $7C, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $80, $00, $58,
|
|
$81, $C4, $84, $00, $00, $00, $C2, $84, $00, $58, $81, $C4, $84, $00, $00, $00,
|
|
$C2, $88, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $8C, $00, $58, $81, $C4,
|
|
$84, $00, $00, $00, $C2, $90, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $94,
|
|
$00, $58, $81, $C4, $84, $00, $00, $00, $C2, $98, $00, $58, $81, $C4, $84, $00,
|
|
$00, $00, $C2, $9C, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $A0, $00, $58,
|
|
$81, $C4, $84, $00, $00, $00, $C2, $A4, $00, $58, $81, $C4, $84, $00, $00, $00,
|
|
$C2, $A8, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $AC, $00, $58, $81, $C4,
|
|
$84, $00, $00, $00, $C2, $B0, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $B4,
|
|
$00, $58, $81, $C4, $84, $00, $00, $00, $C2, $B8, $00, $58, $81, $C4, $84, $00,
|
|
$00, $00, $C2, $BC, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $C0, $00, $58,
|
|
$81, $C4, $84, $00, $00, $00, $C2, $C4, $00, $58, $81, $C4, $84, $00, $00, $00,
|
|
$C2, $C8, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $CC, $00, $58, $81, $EC,
|
|
$84, $00, $00, $00, $C2, $D0, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $D4,
|
|
$00, $58, $81, $C4, $84, $00, $00, $00, $C2, $D8, $00, $58, $81, $C4, $84, $00,
|
|
$00, $00, $C2, $DC, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $E0, $00, $58,
|
|
$81, $C4, $84, $00, $00, $00, $C2, $E4, $00, $58, $81, $C4, $84, $00, $00, $00,
|
|
$C2, $E8, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $EC, $00, $58, $81, $C4,
|
|
$84, $00, $00, $00, $C2, $F0, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $F4,
|
|
$00, $58, $81, $C4, $84, $00, $00, $00, $C2, $F8, $00, $58, $81, $C4, $84, $00,
|
|
$00, $00, $C2, $FC, $00, $58, $81, $C4, $84, $00, $00, $00, $C2, $00, $01
|
|
);
|
|
{$endif}
|
|
|
|
function WasCodeChanged(module: HMODULE; code: pointer; out orgCode: int64) : boolean;
|
|
|
|
function ApplyRelocation(nh: PImageNtHeaders; module: HMODULE; code, arr: pointer; size: dword) : boolean;
|
|
type TImageBaseRelocation = record
|
|
VirtualAddress : dword;
|
|
SizeOfBlock : dword;
|
|
end;
|
|
var
|
|
rel1 : ^TImageBaseRelocation;
|
|
rel2 : NativeUInt;
|
|
i1 : integer;
|
|
item : TPWord;
|
|
data : pointer;
|
|
begin
|
|
result := false;
|
|
with nh^.OptionalHeader do begin
|
|
rel1 := pointer(module + DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
|
|
rel2 := NativeUInt(rel1) + DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
|
|
while (NativeUInt(rel1) < rel2) and (rel1^.VirtualAddress <> 0) do begin
|
|
item := pointer(NativeUInt(rel1) + sizeOf(rel1^));
|
|
for i1 := 1 to (rel1.SizeOfBlock - sizeOf(rel1^)) div 2 do begin
|
|
if item^ and $f000 = $3000 then begin // IMAGE_REL_BASED_HIGHLOW - x86
|
|
data := pointer(module + rel1.VirtualAddress + item^ and $0fff);
|
|
if (NativeUInt(data) >= NativeUInt(code)) and (NativeUInt(data) < NativeUInt(code) + size) then begin
|
|
data := pointer(NativeUInt(arr) + (NativeUInt(data) - NativeUInt(code)));
|
|
dword(data^) := module + dword(data^) - ImageBase;
|
|
end;
|
|
end else
|
|
if item^ and $f000 = $a000 then begin // IMAGE_REL_BASED_DIR64 - x64
|
|
data := pointer(module + rel1.VirtualAddress + item^ and $0fff);
|
|
if (NativeUInt(data) >= NativeUInt(code)) and (NativeUInt(data) < NativeUInt(code) + size) then begin
|
|
data := pointer(NativeUInt(arr) + (NativeUInt(data) - NativeUInt(code)));
|
|
int64(data^) := int64(module) + int64(data^) - int64(ImageBase);
|
|
end;
|
|
end;
|
|
inc(item);
|
|
end;
|
|
NativeUInt(rel1) := NativeUInt(rel1) + rel1^.SizeOfBlock;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function IsCompressedModule(nh1, nh2: PImageNtHeaders) : boolean;
|
|
var sh1 : PImageSectionHeader;
|
|
begin
|
|
sh1 := pointer(NativeUInt(@nh1^.OptionalHeader) + sizeOf(TImageOptionalHeader));
|
|
result := (nh1^.OptionalHeader.BaseOfCode <> nh2^.OptionalHeader.BaseOfCode ) or
|
|
{$ifndef win64}
|
|
(nh1^.OptionalHeader.BaseOfData <> nh2^.OptionalHeader.BaseOfData ) or
|
|
{$endif}
|
|
(nh1^.FileHeader.NumberOfSections <> nh2^.FileHeader.NumberOfSections) or
|
|
((sh1^.Name[0] = ord('U')) and (sh1^.Name[1] = ord('P')) and (sh1^.Name[2] = ord('X')));
|
|
end;
|
|
|
|
var buf : pointer;
|
|
nh1, nh2 : PImageNtHeaders;
|
|
c1 : dword;
|
|
arr : array [0..19] of byte;
|
|
begin
|
|
result := false;
|
|
orgCode := 0;
|
|
if module <> 0 then begin
|
|
nh1 := pointer(GetImageNtHeaders(module));
|
|
if (nh1 <> nil) and (NativeUInt(code) > module) and (NativeUInt(code) < module + GetSizeOfImage(pointer(nh1))) then begin
|
|
buf := CollectCache_NeedModuleFileMap(module);
|
|
if buf <> nil then begin
|
|
c1 := VirtualToRaw(pointer(nh1), NativeUInt(code) - module);
|
|
nh2 := pointer(GetImageNtHeaders(NativeUInt(buf)));
|
|
if (nh2 <> nil) and (c1 < GetSizeOfImage(pointer(nh2))) then
|
|
try
|
|
result := (not IsCompressedModule(nh1, nh2)) and
|
|
( (dword(pointer(NativeUInt(code) + 0)^) <> dword(pointer(NativeUInt(buf) + c1 + 0)^)) or
|
|
( word(pointer(NativeUInt(code) + 4)^) <> word(pointer(NativeUInt(buf) + c1 + 4)^)) );
|
|
if result then begin
|
|
Move(pointer(NativeUInt(buf) + c1 - 4)^, arr[0], sizeOf(arr));
|
|
ApplyRelocation(nh2, module, pointer(NativeUInt(code) - 4), @arr, sizeOf(arr) - 4);
|
|
result := (dword(pointer(NativeUInt(@arr[4]) + 0)^) <> dword(pointer(NativeUInt(code) + 0)^)) or
|
|
( word(pointer(NativeUInt(@arr[4]) + 4)^) <> word(pointer(NativeUInt(code) + 4)^));
|
|
if result and (dword(pointer(NativeUInt(@arr[4]) + 8)^) = dword(pointer(NativeUInt(code) + 8)^)) then
|
|
Move(arr[4], orgCode, sizeOf(orgCode));
|
|
end;
|
|
except end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
constructor TCodeHook.Create(moduleH: HMODULE; hookThisFunc, callbackFunc: pointer; out nextHook: pointer; flags, numParams: dword);
|
|
|
|
function CheckMap(var map: THandle) : boolean;
|
|
var buf : ^pointer;
|
|
begin
|
|
if map <> 0 then begin
|
|
// there is a map, but is it filled?
|
|
// in win2k the Process Explorer duplicates one of our map handles :-(
|
|
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
if buf <> nil then begin
|
|
if buf^ = nil then begin
|
|
CloseHandle(map);
|
|
map := 0;
|
|
end;
|
|
UnmapViewOfFile(buf);
|
|
end else begin
|
|
CloseHandle(map);
|
|
map := 0;
|
|
end;
|
|
end;
|
|
result := map <> 0;
|
|
end;
|
|
|
|
var cc1, cc2 : pointer;
|
|
c1 : dword;
|
|
ci : TCodeInfo;
|
|
mutex : THandle;
|
|
map : THandle;
|
|
buf : pointer;
|
|
s1, s3 : AnsiString;
|
|
p1, p2 : pointer;
|
|
fi : TFunctionInfo;
|
|
i1 : integer;
|
|
tableStub : ^THookStub;
|
|
pa : pointer;
|
|
error : dword;
|
|
keepMapFile : boolean;
|
|
orgCode : int64;
|
|
op : dword;
|
|
ab : TPAByte;
|
|
{$ifdef win64}
|
|
sparePage : pointer;
|
|
{$else}
|
|
pp : TPPointer;
|
|
{$endif}
|
|
jmpSize : dword;
|
|
displace : NativeInt;
|
|
begin
|
|
error := 0;
|
|
FPHookStub := nil;
|
|
FPHookStubTarget := nil;
|
|
{$ifdef win64}
|
|
if numParams < 4 then
|
|
numParams := 4
|
|
else
|
|
if odd(numParams) then
|
|
inc(numParams);
|
|
{$else}
|
|
if numParams < 2 then
|
|
numParams := 2;
|
|
{$endif}
|
|
CollectCache_AddRef;
|
|
try
|
|
nextHook := hookThisFunc;
|
|
FModuleH := moduleH;
|
|
FStoreThreadState := flags and STORE_THREAD_STATE <> 0;
|
|
FSafeHooking := flags and SAFE_HOOKING <> 0;
|
|
FNoAutoUnhook := flags and NO_AUTO_UNHOOKING <> 0;
|
|
FInUseCodeArr := nil;
|
|
FInUseThreadArr := nil;
|
|
FInUseTargetArr := nil;
|
|
{$ifdef win64}
|
|
// (in case we're later using our MIXTURE_MODE)
|
|
// we need a page which is located *before* mpHookStub
|
|
// this is due to RIP relative addressing
|
|
sparePage := VirtualAlloc2(PAGE_SIZE, hookThisFunc);
|
|
if ((NativeUInt(sparePage) > NativeUInt(hookThisFunc)) and (NativeUInt(sparePage) - NativeUInt(hookThisFunc) >= $7fff0000)) or
|
|
((NativeUInt(sparePage) < NativeUInt(hookThisFunc)) and (NativeUInt(hookThisFunc) - NativeUInt(sparePage) >= $7fff0000)) then begin
|
|
// This is probably a 64bit process which is not large address aware.
|
|
// Furthermore the to-be-hooked API is probably an ntdll.dll API.
|
|
// This situation is currently not supported by madCodeHook because the
|
|
// OS doesn't allow madCodeHook to allocate memory near ntdll.dll in this situation.
|
|
VirtualFree(sparePage, 0, MEM_RELEASE);
|
|
error := CErrorNo_CodeNotInterceptable;
|
|
exit;
|
|
end;
|
|
try
|
|
{$endif}
|
|
// Allocate contiguous buffer for hookStub and inUse stuff
|
|
// page 1: PAGE_EXECUTE_READ, ["NextHook" calls this, it's a 6-byte JMP, JMP address stored in page 4][InUseEnter][InUseLeave]
|
|
// page 2+3: PAGE_EXECUTE_READ, [InUseCodeArr[CInUseCount]]
|
|
// pages 4+5: PAGE_READWRITE, [JMP address for JMP from page 1][HookCallback address][InUseThreadArr[CInUseCount]][InUseTargetArr[CInUseCount]]
|
|
FPHookStub := VirtualAlloc2(PAGE_SIZE * 5, {$ifdef win64} sparePage {$else} nil {$endif});
|
|
FPHookStubTarget := pointer(NativeUInt(FPHookStub) + PAGE_SIZE * 3);
|
|
FPHookStub^.jmpNextHook.opcode := $ff;
|
|
FPHookStub^.jmpNextHook.modRm := $25;
|
|
{$ifdef win64}
|
|
// caution: we need to use RIP relative addressing here!
|
|
FPHookStub^.jmpNextHook.target := PAGE_SIZE * 3 - 6;
|
|
{$else}
|
|
FPHookStub^.jmpNextHook.target := dword(FPHookStubTarget);
|
|
{$endif}
|
|
FHookedFunc := hookThisFunc;
|
|
pa := FHookedFunc;
|
|
FCallbackFunc := callbackFunc;
|
|
FPNextHook := @nextHook;
|
|
keepMapFile := false;
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)));
|
|
{$endif}
|
|
s1 := ApiSpecialName(CMAHPrefix, FHookedFunc);
|
|
mutex := CreateLocalMutex(PAnsiChar(DecryptStr(CMutex) + ', ' + s1));
|
|
try
|
|
if mutex <> 0 then
|
|
WaitForSingleObject(mutex, INFINITE);
|
|
try
|
|
map := OpenGlobalFileMapping(PAnsiChar(DecryptStr(CNamedBuffer) + ', ' + s1), true);
|
|
FNewHook := not CheckMap(map);
|
|
try
|
|
if flags and USE_ABSOLUTE_JMP <> 0 then
|
|
jmpSize := 6
|
|
else
|
|
jmpSize := 5;
|
|
if FNewHook then begin
|
|
if flags and MIXTURE_MODE = 0 then
|
|
if flags and $80000000 = 0 then begin
|
|
if DisasmHookTarget then
|
|
fi := ParseFunction(FHookedFunc)
|
|
else begin
|
|
cc1 := FHookedFunc;
|
|
repeat
|
|
ci := ParseCode(cc1);
|
|
cc1 := ci.Next;
|
|
until (not ci.IsValid) or (NativeUInt(cc1) - NativeUInt(pa) >= jmpSize) or (ci.Opcode in [$c2..$c3, $ca..$cb, $cf]);
|
|
if NativeUInt(cc1) - NativeUInt(pa) >= jmpSize then begin
|
|
fi.IsValid := true;
|
|
fi.Interceptable := true;
|
|
end;
|
|
end;
|
|
if (not fi.IsValid) or (not fi.Interceptable) or ((not DisableChangedCodeCheck) and WasCodeChanged(moduleH, FHookedFunc, orgCode)) then begin
|
|
flags := flags or MIXTURE_MODE;
|
|
if (not fi.IsValid) or (not fi.Interceptable) then begin
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
|
|
' -> not interceptable -> MIXTURE');
|
|
ParseFunction(FHookedFunc, s3);
|
|
log(s3);
|
|
{$endif}
|
|
end else begin
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
|
|
' -> already hooked by another hooking library? -> MIXTURE');
|
|
ParseFunction(FHookedFunc, s3);
|
|
log(s3);
|
|
{$endif}
|
|
error := CErrorNo_DoubleHook;
|
|
end;
|
|
if flags and FOLLOW_JMP <> 0 then begin
|
|
{$ifdef win64}
|
|
// check for weird hooking methods
|
|
ab := FHookedFunc;
|
|
if (ab[0] = $48) and (ab[1] = $c7) and (ab[2] = $c0) and (((ab[7] = $ff) and (ab[8] = $e0)) or ((ab[7] = $50) and (ab[8] = $c3))) then begin
|
|
// FRAPS
|
|
// 48c7c0???????? mov rax, $00000000????????
|
|
// ffe0 jmp rax
|
|
ci.Jmp := true;
|
|
ci.Target := pointer(dword(pointer(NativeUInt(FHookedFunc) + 3)^));
|
|
end else
|
|
if (ab[0] = $48) and (ab[1] = $b8) and (((ab[10] = $ff) and (ab[11] = $e0)) or ((ab[10] = $50) and (ab[11] = $c3))) then begin
|
|
// Bitdefender
|
|
// 48b8???????????????? mov rax, $????????????????
|
|
// 50 push rax
|
|
// c3 ret
|
|
ci.Jmp := true;
|
|
ci.Target := pointer(pointer(NativeUInt(FHookedFunc) + 2)^);
|
|
end else
|
|
{$endif}
|
|
ci := ParseCode(FHookedFunc);
|
|
if ci.Jmp and (ci.Target <> nil) then begin
|
|
fi := ParseFunction(ci.Target);
|
|
for i1 := 1 to 10 do
|
|
if fi.IsValid and (not fi.Interceptable) then begin
|
|
ci := ParseCode(ci.Target);
|
|
if ci.Jmp and (ci.Target <> nil) then
|
|
fi := ParseFunction(ci.Target)
|
|
else
|
|
break;
|
|
end else
|
|
break;
|
|
if fi.IsValid and fi.Interceptable then begin
|
|
pa := ci.Target;
|
|
flags := flags and (not MIXTURE_MODE);
|
|
{$ifdef win64}
|
|
VirtualFree(sparePage, 0, MEM_RELEASE);
|
|
VirtualFree(FPHookStub, 0, MEM_RELEASE);
|
|
sparePage := VirtualAlloc2(PAGE_SIZE, pa);
|
|
FPHookStub := VirtualAlloc2(PAGE_SIZE * 5, sparePage);
|
|
FPHookStubTarget := pointer(NativeUInt(FPHookStub) + PAGE_SIZE * 3);
|
|
FPHookStub^.jmpNextHook.opcode := $ff;
|
|
FPHookStub^.jmpNextHook.modRm := $25;
|
|
FPHookStub^.jmpNextHook.target := PAGE_SIZE * 3 - 6;
|
|
{$endif}
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
if flags and MIXTURE_MODE <> 0 then
|
|
if (flags and NO_MIXTURE_MODE = 0) and (moduleH <> 0) then begin
|
|
{$ifndef win64}
|
|
FIsWinsock2 := GetModuleHandleW(pointer(AnsiToWideEx(DecryptStr(CWs2_32)))) = moduleH;
|
|
if FIsWinsock2 and (flags and ALLOW_WINSOCK2_MIXTURE_MODE <> 0) then
|
|
p1 := FindWs2InternalProcList(moduleH)
|
|
else p1 := nil;
|
|
if (not FIsWinsock2) or (p1 <> nil) then begin
|
|
{$endif}
|
|
{$ifdef win64}
|
|
p1 := nil;
|
|
tableStub := sparePage;
|
|
sparePage := nil;
|
|
{$else}
|
|
tableStub := VirtualAlloc2(PAGE_SIZE, nil);
|
|
{$endif}
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
|
|
' -> MIXTURE -> ' + IntToHexExA(NativeUInt(tableStub)));
|
|
{$endif}
|
|
tableStub^.jmpNextHook.opcode := $FF;
|
|
tableStub^.jmpNextHook.modRm := $25;
|
|
{$ifdef win64}
|
|
tableStub^.jmpNextHook.target := 0;
|
|
{$else}
|
|
tableStub^.jmpNextHook.target := dword(@tableStub^.absTarget);
|
|
{$endif}
|
|
tableStub^.absTarget := FHookedFunc;
|
|
VirtualProtect(tableStub, PAGE_SIZE, PAGE_EXECUTE_READ, @op);
|
|
CheckProcAddress := ResolveMixtureMode;
|
|
s3 := ApiSpecialName(CMixPrefix, tableStub);
|
|
map := CreateLocalFileMapping(PAnsiChar(DecryptStr(CNamedBuffer) + ', ' + s3), sizeOf(pointer));
|
|
if map <> 0 then begin
|
|
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
if buf <> nil then begin
|
|
pointer(buf^) := FHookedFunc;
|
|
UnmapViewOfFile(buf);
|
|
end else begin
|
|
CloseHandle(map);
|
|
map := 0;
|
|
end;
|
|
end;
|
|
if (map <> 0) and PatchExportTable(moduleH, FHookedFunc, tableStub, p1) then begin
|
|
FPatchExport := true;
|
|
if FIsWinsock2 then
|
|
// we've hooked Winsock2 by using the mixture mode
|
|
// in order to improve the efficieny of our manipulations
|
|
// we're increasing the load count of the Winsock2 dll
|
|
// this way we won't have to redo the hook all the time
|
|
// if the application unloads and reloads the dll
|
|
LoadLibraryW(pointer(AnsiToWideEx(DecryptStr(CWs2_32))));
|
|
try
|
|
PatchMyImportTables(FHookedFunc, tableStub);
|
|
except end;
|
|
pa := tableStub;
|
|
keepMapFile := true;
|
|
error := 0;
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
|
|
' -> MIXTURE -> ' + IntToHexExA(NativeUInt(tableStub)) + ' -> done');
|
|
{$endif}
|
|
end else begin
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
|
|
' -> MIXTURE -> ' + IntToHexExA(NativeUInt(tableStub)) + ' -> error!');
|
|
{$endif}
|
|
if map <> 0 then
|
|
CloseHandle(map);
|
|
VirtualFree(tableStub, 0, MEM_RELEASE);
|
|
if error = 0 then
|
|
error := CErrorNo_CodeNotInterceptable;
|
|
exit;
|
|
end;
|
|
{$ifndef win64}
|
|
end else begin
|
|
(*ci := ParseCode(FHookedFunc);
|
|
if ci.Jmp and
|
|
( (ci.Opcode in [$e9..$eb]) or
|
|
((ci.Opcode = $ff) and (ci.ModRm and $30 = $20)) ) and
|
|
(ci.Target <> nil) then begin
|
|
fi := ParseFunction(ci.Target);
|
|
if fi.IsValid and fi.Interceptable then begin
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
|
|
' -> ws2_32.dll was already hooked -> we hooked the other hook''s callback function!');
|
|
{$endif}
|
|
pa := ci.Target;
|
|
end else begin
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
|
|
' -> MIXTURE -> ws2_32.dll can''t be hooked in mixture mode!');
|
|
{$endif}
|
|
error := CErrorNo_CodeNotInterceptable;
|
|
exit;
|
|
end;
|
|
end else begin *)
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
|
|
' -> MIXTURE -> ws2_32.dll can''t be hooked in mixture mode!');
|
|
{$endif}
|
|
error := CErrorNo_CodeNotInterceptable;
|
|
exit;
|
|
// end;
|
|
end;
|
|
{$endif}
|
|
end else begin
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) +
|
|
' -> MIXTURE -> NO_MIXTURE_MODE flag specified!');
|
|
{$endif}
|
|
error := CErrorNo_CodeNotInterceptable;
|
|
exit;
|
|
end;
|
|
end;
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)));
|
|
{$endif}
|
|
buf := nil;
|
|
try
|
|
if FNewHook then begin
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) + ' -> new hook');
|
|
{$endif}
|
|
// Allocate contiguous buffer for trampoline code and target address
|
|
// page 1: PAGE_EXECUTE_READ, [trampoline][unsed space][absolute JMP]
|
|
// page 2: PAGE_READWRITE, [JMP address for JMP from page 1]
|
|
FTramp := VirtualAlloc2(PAGE_SIZE * 2, pa);
|
|
cc1 := pa;
|
|
cc2 := FTramp;
|
|
repeat
|
|
ci := ParseCode(cc1);
|
|
if ci.RelTarget then begin
|
|
{$ifdef win64}
|
|
if (ci.Target <> nil) and (ci.TargetSize = 4) and ((ci.Opcode = $E8) or (ci.Opcode = $E9)) then begin
|
|
// Relative jumps can only jump 32bit far. When relocating code, the final
|
|
// address could be further away. So we replace relative jmp/call calls with
|
|
// a small assembler stub which supports any 64bit target address without
|
|
// changing any registers.
|
|
word(cc2^) := $ff48; // jmp/call [rip + 2]
|
|
inc(NativeUInt(cc2), 2);
|
|
if ci.Call then
|
|
byte(cc2^) := $15
|
|
else
|
|
byte(cc2^) := $25;
|
|
inc(NativeUInt(cc2));
|
|
dword(cc2^) := 2;
|
|
inc(NativeUInt(cc2), 4);
|
|
word(cc2^) := $08eb; // jmp +8 (skip "dq target")
|
|
inc(NativeUInt(cc2), 2);
|
|
pointer(cc2^) := ci.Target; // dq target
|
|
inc(NativeUInt(cc2), 8);
|
|
end else begin
|
|
{$endif}
|
|
if ci.TargetSize = 1 then begin
|
|
if ci.Opcode <> $EB then begin
|
|
// ($70 - $7F) 1 byte Jcc calls -> 4 byte Jcc calls
|
|
TPAByte(cc2)^[0] := $0F;
|
|
TPAByte(cc2)^[1] := $80 + ci.Opcode and $F;
|
|
NativeUInt(cc2) := NativeUInt(cc2) + 6;
|
|
end else begin
|
|
TPByte(cc2)^ := $E9;
|
|
NativeUInt(cc2) := NativeUInt(cc2) + 5;
|
|
end;
|
|
end else if ci.TargetSize = 2 then begin
|
|
// kill 16 bit prefix
|
|
c1 := NativeUInt(ci.Next) - NativeUInt(cc1);
|
|
Move(pointer(NativeUInt(cc1) + 1)^, cc2^, c1 - 3);
|
|
NativeUInt(cc2) := NativeUInt(cc2) + c1 + 1;
|
|
end else begin
|
|
c1 := NativeUInt(ci.Next) - NativeUInt(cc1);
|
|
Move(cc1^, cc2^, c1);
|
|
NativeUInt(cc2) := NativeUInt(cc2) + c1;
|
|
end;
|
|
TPInteger(NativeUInt(cc2) - 4)^ := int64(NativeUInt(ci.Target)) - int64(NativeUInt(cc2));
|
|
{$ifdef win64}
|
|
end;
|
|
{$endif}
|
|
end else begin
|
|
{$ifdef win64}
|
|
if (ci.Call or ci.Jmp) and ci.RipRelative then begin
|
|
// "jmp/call [RIP + someConstant]"
|
|
displace := NativeInt(ci.DisplDword) - NativeInt((NativeUInt(cc2) + (NativeUInt(ci.Next) - NativeUInt(cc1))));
|
|
if (displace >= low(integer)) and (displace <= high(integer)) then begin
|
|
// We can just modify this instruction, it's still within 2GB distance.
|
|
c1 := NativeUInt(ci.Next) - NativeUInt(cc1);
|
|
Move(cc1^, cc2^, c1);
|
|
integer(pointer(NativeUInt(cc2) + (NativeUInt(ci.PDispl) - NativeUInt(cc1)))^) := displace;
|
|
NativeUInt(cc2) := NativeUInt(cc2) + c1;
|
|
end else begin
|
|
// Our new code is more than 2GB away from the original displacement data.
|
|
// Ideally we should read out the displacement in real time and jmp/call it.
|
|
// Unfortunately we can't, because due to CET there's no way (I know of) to
|
|
// jmp/call an address without using registers, and sadly, we can't modify any registers.
|
|
// So instead we hard code the jmp/call address to the target it had when installing the hook.
|
|
// Hopefully in 99.99% of all situations this will be fine.
|
|
word(cc2^) := $ff48; // jmp/call [rip + 2]
|
|
inc(NativeUInt(cc2), 2);
|
|
if ci.Call then
|
|
byte(cc2^) := $15
|
|
else
|
|
byte(cc2^) := $25;
|
|
inc(NativeUInt(cc2));
|
|
dword(cc2^) := 2;
|
|
inc(NativeUInt(cc2), 4);
|
|
word(cc2^) := $08eb; // jmp +8 (skip "dq target")
|
|
inc(NativeUInt(cc2), 2);
|
|
pointer(cc2^) := ci.Target; // dq target
|
|
inc(NativeUInt(cc2), 8);
|
|
end;
|
|
end else begin
|
|
{$endif}
|
|
c1 := NativeUInt(ci.Next) - NativeUInt(cc1);
|
|
Move(cc1^, cc2^, c1);
|
|
{$ifdef win64}
|
|
if ci.RipRelative then
|
|
integer(pointer(NativeUInt(cc2) + (NativeUInt(ci.PDispl) - NativeUInt(cc1)))^) := NativeInt(ci.DisplDword) - NativeInt((NativeUInt(cc2) + (NativeUInt(ci.Next) - NativeUInt(cc1))));
|
|
{$endif}
|
|
NativeUInt(cc2) := NativeUInt(cc2) + c1;
|
|
{$ifdef win64}
|
|
end;
|
|
{$endif}
|
|
end;
|
|
cc1 := ci.Next;
|
|
until NativeUInt(cc1) - NativeUInt(pa) >= jmpSize;
|
|
TPByte(cc2)^ := $e9;
|
|
NativeUInt(cc2) := NativeUInt(cc2) + 5;
|
|
TPInteger(NativeUInt(cc2) - 4)^ := int64(NativeUInt(cc1)) - int64(NativeUInt(cc2));
|
|
cc2 := pointer(NativeUInt(FTramp) + PAGE_SIZE - 6);
|
|
TPByte(cc2)^ := $ff;
|
|
cc2 := pointer(NativeUInt(cc2) + 1);
|
|
TPByte(cc2)^ := $25;
|
|
cc2 := pointer(NativeUInt(cc2) + 1);
|
|
{$ifdef win64}
|
|
// caution: we need to use RIP relative addressing!
|
|
TPCardinal(cc2)^ := 0;
|
|
{$else}
|
|
TPCardinal(cc2)^ := NativeUInt(FTramp) + PAGE_SIZE;
|
|
{$endif}
|
|
VirtualProtect(FTramp, PAGE_SIZE, PAGE_EXECUTE_READ, @op);
|
|
VirtualProtect(pointer(NativeUInt(FTramp) + PAGE_SIZE), PAGE_SIZE, PAGE_READWRITE, @op);
|
|
map := CreateLocalFileMapping(PAnsiChar(DecryptStr(CNamedBuffer) + ', ' + s1), sizeOf(pointer));
|
|
if map <> 0 then begin
|
|
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
if buf <> nil then begin
|
|
TPPointer(buf)^ := VirtualAlloc2(CHookQueueSize + 8 * CHookEntrySize, nil);
|
|
VirtualProtect(TPPointer(buf)^, CHookQueueSize + 8 * CHookEntrySize, PAGE_READWRITE, @c1);
|
|
with THookQueue(TPPointer(buf)^^) do begin
|
|
ItemCount := 2;
|
|
Capacity := 8;
|
|
PatchAddr := pa;
|
|
OldCode := PatchAddr^;
|
|
if flags and USE_ABSOLUTE_JMP <> 0 then begin
|
|
NewCode.opcode := $ff;
|
|
NewCode.modRm := $25;
|
|
{$ifdef win64}
|
|
// caution: we need to use RIP relative addressing!
|
|
NewCode.target := NativeUInt(FTramp) + PAGE_SIZE - (NativeUInt(PatchAddr) + 6);
|
|
{$else}
|
|
NewCode.target := NativeUInt(FTramp) + PAGE_SIZE;
|
|
{$endif}
|
|
end else begin
|
|
NewCode := OldCode;
|
|
// For compatability with older madCodeHook builds, we keep Old/NewCode at 6 bytes.
|
|
// Doing so makes sure that we can still share the hook chain queue with older builds.
|
|
// But our new patch really uses a 5 byte JMP now.
|
|
TPByte(@NewCode)^ := $e9;
|
|
TPCardinal(NativeUInt(@NewCode) + 1)^ := NativeUInt(FTramp) + PAGE_SIZE - 6 - (NativeUInt(PatchAddr) + 5);
|
|
end;
|
|
pointer(pointer(NativeUInt(FTramp) + PAGE_SIZE)^) := FTramp;
|
|
if not keepMapFile then
|
|
MapHandle := map
|
|
else MapHandle := 0;
|
|
FMapHandle := map;
|
|
Items[0].HookProc := nil;
|
|
Items[0].PNextHook := pointer(NativeUInt(FTramp) + PAGE_SIZE);
|
|
Items[1].HookProc := FTramp;
|
|
Items[1].PNextHook := nil;
|
|
end;
|
|
end;
|
|
end;
|
|
end else begin
|
|
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
with THookQueue(TPPointer(buf)^^) do begin
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) + ' -> hook no ' + IntToStrExA(ItemCount - 1));
|
|
{$endif}
|
|
FTramp := Items[ItemCount - 1].HookProc;
|
|
FMapHandle := MapHandle;
|
|
if ItemCount = Capacity then begin
|
|
p1 := VirtualAlloc2(CHookQueueSize + Capacity * 2 * CHookEntrySize, nil);
|
|
VirtualProtect(p1, CHookQueueSize + Capacity * 2 * CHookEntrySize, PAGE_READWRITE, @op);
|
|
Move(TPPointer(buf)^^, p1^, CHookQueueSize + Capacity * CHookEntrySize);
|
|
THookQueue(p1^).Capacity := Capacity * 2;
|
|
p2 := TPPointer(buf)^;
|
|
TPPointer(buf)^ := p1;
|
|
VirtualFree(p2, 0, MEM_RELEASE);
|
|
end;
|
|
end;
|
|
end;
|
|
TPPointer(NativeUInt(FPHookStub) + PAGE_SIZE * 3 + sizeOf(pointer))^ := callbackFunc;
|
|
if flags and NO_SAFE_UNHOOKING = 0 then begin
|
|
NativeUInt(FInUseCodeArr) := NativeUInt(FPHookStub) + PAGE_SIZE;
|
|
NativeUInt(FInUseThreadArr) := NativeUInt(FPHookStub) + PAGE_SIZE * 3 + sizeOf(pointer) * 2;
|
|
NativeUInt(FInUseTargetArr) := NativeUInt(FInUseThreadArr) + CInUseCount * sizeOf(dword);
|
|
p1 := pointer(NativeUInt(FPHookStub) + sizeOf(FPHookStub^));
|
|
if flags and STORE_THREAD_STATE = 0 then begin
|
|
// initialize InUseEnter
|
|
Move(CInUseEnter, p1^, sizeOf(CInUseEnter));
|
|
{$ifdef win64}
|
|
TPPointer (NativeUInt(p1) + 8)^ := FInUseCodeArr;
|
|
TPPointer (NativeUInt(p1) + 18)^ := FInUseTargetArr;
|
|
TPPointer (NativeUInt(p1) + 28)^ := FInUseThreadArr;
|
|
TPPointer (NativeUInt(p1) + 121)^ := FCallbackFunc;
|
|
{$else}
|
|
TPPointer (NativeUInt(p1) + 7)^ := FInUseCodeArr;
|
|
TPPointer (NativeUInt(p1) + 12)^ := FInUseTargetArr;
|
|
TPPointer (NativeUInt(p1) + 17)^ := FInUseThreadArr;
|
|
TPCardinal(NativeUInt(p1) + 85)^ := NativeUInt(FCallbackFunc) - NativeUInt(p1) - sizeOf(CInUseEnter);
|
|
{$endif}
|
|
FCallbackFunc := p1;
|
|
// initialize InUseLeave
|
|
NativeUInt(p1) := NativeUInt(p1) + sizeOf(CInUseEnter);
|
|
Move(CInUseLeave, p1^, sizeOf(CInUseLeave));
|
|
{$ifdef win64}
|
|
TPPointer (NativeUInt(p1) + 10)^ := FInUseCodeArr;
|
|
TPPointer (NativeUInt(p1) + 42)^ := FInUseTargetArr;
|
|
{$else}
|
|
TPPointer (NativeUInt(p1) + 8)^ := FInUseCodeArr;
|
|
TPPointer (NativeUInt(p1) + 27)^ := FInUseTargetArr;
|
|
{$endif}
|
|
c1 := sizeOf(FPHookStub^) + sizeOf(CInUseEnter);
|
|
// initialize FInUseCodeArr
|
|
for i1 := 0 to CInUseCount - 1 do
|
|
with FInUseCodeArr[i1] do begin
|
|
call := $15ff;
|
|
jmp := $e8;
|
|
jmpOffset := NativeUInt(FPHookStub) + c1 - NativeUInt(@FInUseCodeArr[i1].jmp) - 5;
|
|
{$ifdef win64}
|
|
popReturn := $08c48348;
|
|
callOffset := NativeUInt(FPHookStub) + PAGE_SIZE * 3 + sizeOf(pointer) - NativeUInt(@FInUseCodeArr[i1].call) - 6;
|
|
{$else}
|
|
popReturn[0] := $83;
|
|
popReturn[1] := $c4;
|
|
popReturn[2] := $04;
|
|
callTarget := pointer(NativeUInt(FPHookStub) + PAGE_SIZE * 3 + sizeOf(pointer));
|
|
{$endif}
|
|
end;
|
|
end else begin
|
|
// initialize InUse_ThreadState
|
|
Move(CInUse_ThreadState, p1^, sizeOf(CInUse_ThreadState));
|
|
{$ifdef win64}
|
|
TPCardinal(NativeUInt(p1) + 52)^ := (numParams + 23) * 8 + 4*8;
|
|
TPCardinal(NativeUInt(p1) + 58)^ := (numParams + 0);
|
|
TPPointer (NativeUInt(p1) + 78)^ := FInUseTargetArr;
|
|
TPPointer (NativeUInt(p1) + 88)^ := FInUseThreadArr;
|
|
TPCardinal(NativeUInt(p1) + 153)^ := (numParams + 3) * 8;
|
|
TPCardinal(NativeUInt(p1) + 161)^ := (numParams + 21) * 8;
|
|
TPCardinal(NativeUInt(p1) + 169)^ := (numParams + 20) * 8;
|
|
TPCardinal(NativeUInt(p1) + 177)^ := (numParams + 19) * 8;
|
|
TPCardinal(NativeUInt(p1) + 185)^ := (numParams + 18) * 8;
|
|
TPCardinal(NativeUInt(p1) + 193)^ := (numParams + 16) * 8;
|
|
TPCardinal(NativeUInt(p1) + 201)^ := (numParams + 15) * 8;
|
|
TPCardinal(NativeUInt(p1) + 209)^ := (numParams + 22) * 8;
|
|
TPPointer (NativeUInt(p1) + 222)^ := FCallbackFunc;
|
|
TPCardinal(NativeUInt(p1) + 247)^ := (numParams + 8) * 8;
|
|
TPCardinal(NativeUInt(p1) + 290)^ := (numParams + 8) * 8;
|
|
TPCardinal(NativeUInt(p1) + 301)^ := (numParams + 6) * 8;
|
|
for i1 := 0 to numParams do begin
|
|
TPCardinal(NativeUInt(p1) + 360 + dword(i1) * 11)^ := (numParams + 23 - dword(i1)) * 8 + 4*8;
|
|
TPWord (NativeUInt(p1) + 365 + dword(i1) * 11)^ := dword(i1) * 8;
|
|
end;
|
|
{$else}
|
|
TPCardinal(NativeUInt(p1) + 20)^ := (numParams + 15) * 4 + 4*4;
|
|
TPCardinal(NativeUInt(p1) + 25)^ := (numParams + 0);
|
|
TPPointer (NativeUInt(p1) + 38)^ := FInUseTargetArr;
|
|
TPPointer (NativeUInt(p1) + 43)^ := FInUseThreadArr;
|
|
TPCardinal(NativeUInt(p1) + 89)^ := (numParams + 3) * 4;
|
|
TPCardinal(NativeUInt(p1) + 96)^ := (numParams + 14) * 4;
|
|
TPCardinal(NativeUInt(p1) + 103)^ := (numParams + 13) * 4;
|
|
TPCardinal(NativeUInt(p1) + 110)^ := (numParams + 12) * 4;
|
|
TPCardinal(NativeUInt(p1) + 117)^ := (numParams + 11) * 4;
|
|
TPCardinal(NativeUInt(p1) + 124)^ := (numParams + 8) * 4;
|
|
TPCardinal(NativeUInt(p1) + 131)^ := (numParams + 7) * 4;
|
|
TPCardinal(NativeUInt(p1) + 138)^ := (numParams + 10) * 4;
|
|
TPCardinal(NativeUInt(p1) + 144)^ := NativeUInt(FCallbackFunc) - NativeUInt(p1) - 148;
|
|
TPCardinal(NativeUInt(p1) + 153)^ := (numParams + 7) * 4;
|
|
TPCardinal(NativeUInt(p1) + 196)^ := (numParams + 7) * 4;
|
|
TPCardinal(NativeUInt(p1) + 205)^ := (numParams + 5) * 4;
|
|
for i1 := 0 to numParams do begin
|
|
TPCardinal(NativeUInt(p1) + 245 + dword(i1) * 10)^ := (numParams + 15 - dword(i1)) * 4 + 4*4;
|
|
TPWord (NativeUInt(p1) + 250 + dword(i1) * 10)^ := dword(i1) * 4;
|
|
end;
|
|
{$endif}
|
|
FCallbackFunc := p1;
|
|
end;
|
|
// initialize FInUseThreadArr and FInUseTargetArr
|
|
ZeroMemory(FInUseThreadArr, CInUseCount * sizeOf(dword ));
|
|
ZeroMemory(FInUseTargetArr, CInUseCount * sizeOf(pointer));
|
|
end else
|
|
if flags and STORE_THREAD_STATE <> 0 then begin
|
|
// initialize NoUse_ThreadState
|
|
NativeUInt(p1) := NativeUInt(FPHookStub) + sizeOf(FPHookStub^);
|
|
Move(CNoUse_ThreadState, p1^, sizeOf(CNoUse_ThreadState));
|
|
{$ifdef win64}
|
|
TPCardinal(NativeUInt(p1) + 52)^ := (numParams + 23) * 8 + 4*8;
|
|
TPCardinal(NativeUInt(p1) + 58)^ := (numParams + 0);
|
|
TPCardinal(NativeUInt(p1) + 80)^ := (numParams + 21) * 8;
|
|
TPCardinal(NativeUInt(p1) + 88)^ := (numParams + 20) * 8;
|
|
TPCardinal(NativeUInt(p1) + 96)^ := (numParams + 16) * 8;
|
|
TPCardinal(NativeUInt(p1) + 104)^ := (numParams + 22) * 8;
|
|
TPPointer (NativeUInt(p1) + 117)^ := FCallbackFunc;
|
|
TPCardinal(NativeUInt(p1) + 142)^ := (numParams + 8) * 8;
|
|
TPCardinal(NativeUInt(p1) + 185)^ := (numParams + 8) * 8;
|
|
for i1 := 0 to numParams do begin
|
|
TPCardinal(NativeUInt(p1) + 233 + dword(i1) * 11)^ := (numParams + 23 - dword(i1)) * 8 + 4*8;
|
|
TPWord (NativeUInt(p1) + 238 + dword(i1) * 11)^ := dword(i1) * 8;
|
|
end;
|
|
{$else}
|
|
TPCardinal(NativeUInt(p1) + 20)^ := (numParams + 15) * 4 + 4*4;
|
|
TPCardinal(NativeUInt(p1) + 25)^ := (numParams + 0);
|
|
TPCardinal(NativeUInt(p1) + 40)^ := (numParams + 14) * 4;
|
|
TPCardinal(NativeUInt(p1) + 47)^ := (numParams + 13) * 4;
|
|
TPCardinal(NativeUInt(p1) + 54)^ := (numParams + 8) * 4;
|
|
TPCardinal(NativeUInt(p1) + 61)^ := (numParams + 10) * 4;
|
|
TPCardinal(NativeUInt(p1) + 67)^ := NativeUInt(FCallbackFunc) - NativeUInt(p1) - 71;
|
|
TPCardinal(NativeUInt(p1) + 76)^ := (numParams + 7) * 4;
|
|
TPCardinal(NativeUInt(p1) + 119)^ := (numParams + 7) * 4;
|
|
for i1 := 0 to numParams do begin
|
|
TPCardinal(NativeUInt(p1) + 152 + dword(i1) * 10)^ := (numParams + 15 - dword(i1)) * 4 + 4*4;
|
|
TPWord (NativeUInt(p1) + 157 + dword(i1) * 10)^ := dword(i1) * 4;
|
|
end;
|
|
{$endif}
|
|
FCallbackFunc := p1;
|
|
end;
|
|
FPNextHook^ := FPHookStub;
|
|
with THookQueue(TPPointer(buf)^^) do begin
|
|
FPatchAddr := pointer(PatchAddr);
|
|
FNewCode := NewCode;
|
|
FOldCode := OldCode;
|
|
Items[ItemCount] := Items[ItemCount - 1];
|
|
Items[ItemCount - 1].HookProc := FCallbackFunc;
|
|
Items[ItemCount - 1].PNextHook := pointer(FPHookStubTarget);
|
|
Items[ItemCount - 1].PNextHook^ := Items[ItemCount ].HookProc;
|
|
Items[ItemCount - 2].PNextHook^ := Items[ItemCount - 1].HookProc;
|
|
inc(ItemCount);
|
|
end;
|
|
FValid := true;
|
|
if TPByte(@FNewCode)^ = $e9 then
|
|
jmpSize := 5
|
|
else
|
|
jmpSize := 6;
|
|
if FNewHook or RenewOverwrittenHooks or
|
|
( (not IsBadReadPtrEx(FPatchAddr, jmpSize)) and
|
|
(TPCardinal(NativeUInt(FPatchAddr))^ = TPCardinal(NativeUInt(@FOldCode))^) and
|
|
( ((jmpSize = 5) and (TPByte(NativeUInt(FPatchAddr) + 4)^ = TPByte(NativeUInt(@FOldCode) + 4)^)) or
|
|
((jmpSize = 6) and (TPWord(NativeUInt(FPatchAddr) + 4)^ = TPWord(NativeUInt(@FOldCode) + 4)^)) ) ) then
|
|
WritePatch(mutex, buf);
|
|
{$ifdef log}
|
|
log(' FHookedFunc: ' + IntToHexExA(NativeUInt(FHookedFunc)) + ' -> done');
|
|
{$endif}
|
|
finally
|
|
if buf <> nil then
|
|
UnmapViewOfFile(buf);
|
|
end;
|
|
finally
|
|
if (map <> 0) and (not FNewHook) then
|
|
CloseHandle(map);
|
|
end;
|
|
finally
|
|
if mutex <> 0 then
|
|
ReleaseMutex(mutex);
|
|
end;
|
|
finally
|
|
if mutex <> 0 then
|
|
CloseHandle(mutex );
|
|
if FValid then begin
|
|
VirtualProtect(pointer(NativeUInt(FPHookStub) + PAGE_SIZE * 0), PAGE_SIZE * 3, PAGE_EXECUTE_READ, @op);
|
|
VirtualProtect(pointer(NativeUInt(FPHookStub) + PAGE_SIZE * 3), PAGE_SIZE * 2, PAGE_READWRITE, @op);
|
|
end else begin
|
|
VirtualFree(FPHookStub, 0, MEM_RELEASE);
|
|
FPHookStub := nil;
|
|
FPHookStubTarget := nil;
|
|
end;
|
|
end;
|
|
{$ifdef win64}
|
|
finally
|
|
if sparePage <> nil then
|
|
VirtualFree(sparePage, 0, MEM_RELEASE);
|
|
end;
|
|
{$endif}
|
|
finally
|
|
CollectCache_Release;
|
|
SetLastError(error);
|
|
end;
|
|
end;
|
|
|
|
function GetStoredThreadState(var threadState: TPThreadState) : bool; stdcall;
|
|
|
|
function GetEsp : NativeUInt;
|
|
asm
|
|
{$ifdef win64}
|
|
mov rax, rsp
|
|
{$else}
|
|
mov eax, esp
|
|
{$endif}
|
|
end;
|
|
|
|
var esp_ : NativeUInt;
|
|
mi : TMemoryBasicInformation;
|
|
begin
|
|
esp_ := GetEsp;
|
|
result := false;
|
|
threadState := nil;
|
|
if VirtualQuery(pointer(esp_), mi, sizeOf(mi)) = sizeOf(mi) then
|
|
repeat
|
|
{$ifdef win64}
|
|
if (TPNativeUInt(esp_)^ = $6D63683474687374) and (TPNativeUInt(esp_ - 8)^ = $6D63683474687374 xor esp_) then begin
|
|
threadState := pointer(esp_ + 8);
|
|
result := true;
|
|
break;
|
|
end;
|
|
{$else}
|
|
if (TPCardinal(esp_)^ = $6D636834) and (TPCardinal(esp_ - 4)^ = $6D636834 xor esp_) then begin
|
|
threadState := pointer(esp_ + 4);
|
|
result := true;
|
|
break;
|
|
end;
|
|
{$endif}
|
|
inc(esp_, sizeOf(NativeUInt));
|
|
until esp_ >= NativeUInt(mi.BaseAddress) + mi.RegionSize;
|
|
end;
|
|
|
|
var
|
|
NtQuerySystemInformation : function (infoClass: NativeUInt; buffer: pointer; bufSize: dword; returnSize: TPCardinal) : dword; stdcall = nil;
|
|
FlushInstructionCacheProc : function (process: THandle; baseAddress: pointer; size: NativeUInt) : bool; stdcall = nil;
|
|
NtOpenThreadProc : function (var hThread: THandle; access: dword; objAttr, clientId: pointer) : dword; stdcall = nil;
|
|
QueryFullProcessImageName : function (process: THandle; flags: dword; exeName: PWideChar; var size: dword) : bool; stdcall = nil;
|
|
|
|
function GetOtherThreads(needHandles: boolean; out ids: TDACardinal; out handles: TDANativeUInt) : boolean;
|
|
// get a list of thread ids and optionally also thread handles
|
|
type
|
|
TNtProcessInfo = packed record
|
|
offset : dword;
|
|
numThreads : integer;
|
|
d1 : array [0..11] of dword;
|
|
d2 : pointer;
|
|
name : PWideChar;
|
|
d3 : pointer;
|
|
pid : NativeUInt;
|
|
parentPid : NativeUInt;
|
|
handleCount : dword;
|
|
sessionId : dword;
|
|
d4 : dword;
|
|
d5 : array [0..11] of pointer;
|
|
d6 : array [0..5] of dword;
|
|
d7 : NativeUInt;
|
|
threads : array [0..maxInt shr 7 - 1] of packed record
|
|
startAddr_nt4 : pointer;
|
|
pid_nt4 : dword;
|
|
tid_nt4 : dword;
|
|
d8 : array [44..52] of dword;
|
|
startAddr_nt5 : pointer;
|
|
pid_nt5 : NativeUInt;
|
|
tid_nt5 : NativeUInt;
|
|
d9 : dword;
|
|
end;
|
|
end;
|
|
const
|
|
THREAD_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED or SYNCHRONIZE or $3FF;
|
|
CNtObjAttr : TObjectAttributes = (len: sizeOf(TObjectAttributes); rootDir: 0; objName: nil; attr: 0; sd: nil; qos: nil);
|
|
var
|
|
c1, c2 : dword;
|
|
th : THandle;
|
|
p1 : pointer;
|
|
npi : ^TNtProcessInfo;
|
|
i1 : integer;
|
|
cid : array [0..1] of NativeUInt;
|
|
begin
|
|
result := false;
|
|
ids := nil;
|
|
handles := nil;
|
|
if @NtQuerySystemInformation = nil then
|
|
NtQuerySystemInformation := NtProc(CNtQuerySystemInformation);
|
|
if @NtOpenThreadProc = nil then
|
|
NtOpenThreadProc := NtProc(CNtOpenThread);
|
|
if (@NtQuerySystemInformation <> nil) and (@NtOpenThreadProc <> nil) then begin
|
|
c1 := 0;
|
|
NtQuerySystemInformation(5, nil, 0, @c1);
|
|
p1 := nil;
|
|
try
|
|
if c1 = 0 then begin
|
|
c1 := $10000;
|
|
repeat
|
|
c1 := c1 * 2;
|
|
LocalFree(HLOCAL(p1));
|
|
p1 := pointer(LocalAlloc(LPTR, c1));
|
|
c2 := NtQuerySystemInformation(5, p1, c1, nil);
|
|
until (c2 = 0) or (c1 = $400000);
|
|
end else begin
|
|
c1 := c1 * 2;
|
|
p1 := pointer(LocalAlloc(LPTR, c1));
|
|
c2 := NtQuerySystemInformation(5, p1, c1, nil);
|
|
end;
|
|
if c2 = 0 then begin
|
|
npi := p1;
|
|
while true do begin
|
|
if npi^.pid = GetCurrentProcessId then
|
|
for i1 := 0 to npi^.numThreads - 1 do begin
|
|
if byte(GetVersion) > 4 then
|
|
c1 := npi^.threads[i1].tid_nt5
|
|
else
|
|
c1 := npi^.threads[i1].tid_nt4;
|
|
if c1 <> GetCurrentThreadId then begin
|
|
cid[0] := 0;
|
|
cid[1] := c1;
|
|
if (not needHandles) or (NtOpenThreadProc(th, THREAD_ALL_ACCESS, @CNtObjAttr, @cid) = 0) then begin
|
|
SetLength(ids, Length(ids) + 1);
|
|
ids[high(ids)] := c1;
|
|
if needHandles then begin
|
|
SetLength(handles, Length(handles) + 1);
|
|
handles[high(handles)] := th;
|
|
end;
|
|
result := true;
|
|
end;
|
|
end;
|
|
end;
|
|
if npi^.offset = 0 then
|
|
break;
|
|
npi := pointer(NativeUInt(npi) + npi^.offset);
|
|
end;
|
|
end;
|
|
finally LocalFree(HLOCAL(p1)) end;
|
|
end;
|
|
end;
|
|
|
|
function OpenFirstThread(ph: THandle; pid: dword) : THandle;
|
|
// get a handle to the first thread of the specified process
|
|
type
|
|
TNtProcessInfo = packed record
|
|
offset : dword;
|
|
numThreads : integer;
|
|
d1 : array [0..11] of dword;
|
|
d2 : pointer;
|
|
name : PWideChar;
|
|
d3 : pointer;
|
|
pid : NativeUInt;
|
|
parentPid : NativeUInt;
|
|
handleCount : dword;
|
|
sessionId : dword;
|
|
d4 : dword;
|
|
d5 : array [0..11] of pointer;
|
|
d6 : array [0..5] of dword;
|
|
d7 : NativeUInt;
|
|
threads : array [0..maxInt shr 7 - 1] of packed record
|
|
startAddr_nt4 : pointer;
|
|
pid_nt4 : dword;
|
|
tid_nt4 : dword;
|
|
d8 : array [44..52] of dword;
|
|
startAddr_nt5 : pointer;
|
|
pid_nt5 : NativeUInt;
|
|
tid_nt5 : NativeUInt;
|
|
d9 : dword;
|
|
end;
|
|
end;
|
|
const
|
|
THREAD_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED or SYNCHRONIZE or $3FF;
|
|
CNtObjAttr : TObjectAttributes = (len: sizeOf(TObjectAttributes); rootDir: 0; objName: nil; attr: 0; sd: nil; qos: nil);
|
|
var
|
|
c1, c2 : dword;
|
|
th : THandle;
|
|
p1 : pointer;
|
|
npi : ^TNtProcessInfo;
|
|
cid : array [0..1] of NativeUInt;
|
|
begin
|
|
result := 0;
|
|
if @NtQuerySystemInformation = nil then
|
|
NtQuerySystemInformation := NtProc(CNtQuerySystemInformation);
|
|
if @NtOpenThreadProc = nil then
|
|
NtOpenThreadProc := NtProc(CNtOpenThread);
|
|
if (@NtQuerySystemInformation <> nil) and (@NtOpenThreadProc <> nil) then begin
|
|
c1 := 0;
|
|
NtQuerySystemInformation(5, nil, 0, @c1);
|
|
p1 := nil;
|
|
try
|
|
if c1 = 0 then begin
|
|
c1 := $10000;
|
|
repeat
|
|
c1 := c1 * 2;
|
|
LocalFree(HLOCAL(p1));
|
|
p1 := pointer(LocalAlloc(LPTR, c1));
|
|
c2 := NtQuerySystemInformation(5, p1, c1, nil);
|
|
until (c2 = 0) or (c1 = $400000);
|
|
end else begin
|
|
c1 := c1 * 2;
|
|
p1 := pointer(LocalAlloc(LPTR, c1));
|
|
c2 := NtQuerySystemInformation(5, p1, c1, nil);
|
|
end;
|
|
if c2 = 0 then begin
|
|
npi := p1;
|
|
while true do begin
|
|
if npi^.pid = pid then begin
|
|
if npi^.numThreads > 0 then begin
|
|
if byte(GetVersion) > 4 then
|
|
c1 := npi^.threads[0].tid_nt5
|
|
else c1 := npi^.threads[0].tid_nt4;
|
|
cid[0] := 0;
|
|
cid[1] := c1;
|
|
if NtOpenThreadProc(th, THREAD_ALL_ACCESS, @CNtObjAttr, @cid) = 0 then
|
|
result := th;
|
|
end;
|
|
break;
|
|
end;
|
|
if npi^.offset = 0 then
|
|
break;
|
|
npi := pointer(NativeUInt(npi) + npi^.offset);
|
|
end;
|
|
end;
|
|
finally LocalFree(HLOCAL(p1)) end;
|
|
end;
|
|
end;
|
|
|
|
procedure FlushInstructionCache(code: pointer; size: dword);
|
|
begin
|
|
if @FlushInstructionCacheProc = nil then
|
|
FlushInstructionCacheProc := KernelProc(CFlushInstructionCache);
|
|
if @FlushInstructionCacheProc <> nil then
|
|
FlushInstructionCacheProc(GetCurrentProcess, code, size);
|
|
end;
|
|
|
|
var OldEip, NewEip : NativeUInt;
|
|
RtlDispatchExceptionNext : function (excRec: PExceptionRecord; excCtxt: PContext) : integer; stdcall;
|
|
AddVectoredExceptionHandler : function (firstHandler: bool; handler: pointer) : THandle; stdcall;
|
|
RemoveVectoredExceptionHandler : function (handle: THandle) : dword; stdcall;
|
|
|
|
procedure TCodeHook.WritePatch(mutex: THandle; buf: pointer);
|
|
var rdecMutex : THandle;
|
|
|
|
function VectoredExceptionHandler(var exceptInfo: TExceptionPointers) : integer; stdcall;
|
|
begin
|
|
if (exceptInfo.ContextRecord.{$ifdef win64}rip{$else}eip{$endif} = OldEip) and
|
|
( (exceptInfo.ExceptionRecord.ExceptionCode = STATUS_PRIVILEGED_INSTRUCTION ) or
|
|
( (exceptInfo.ExceptionRecord.ExceptionCode = STATUS_ACCESS_VIOLATION) and
|
|
(exceptInfo.ExceptionRecord.ExceptionInformation[0] = 0 ) and
|
|
(exceptInfo.ExceptionRecord.ExceptionInformation[1] = $ffffffff ) ) ) then begin
|
|
exceptInfo.ContextRecord.{$ifdef win64}rip{$else}eip{$endif} := NewEip;
|
|
result := -1;
|
|
end else
|
|
result := 0;
|
|
end;
|
|
|
|
function InitRtlDispatchException(oeip, neip: pointer; var handle: THandle) : boolean;
|
|
|
|
function RtlDispatchExceptionCallback(var excRec: TExceptionRecord; var excCtxt: TContext) : integer; stdcall;
|
|
begin
|
|
if (excCtxt.{$ifdef win64}rip{$else}eip{$endif} = OldEip) and
|
|
( (excRec.ExceptionCode = STATUS_PRIVILEGED_INSTRUCTION ) or
|
|
( (excRec.ExceptionCode = STATUS_ACCESS_VIOLATION) and
|
|
(excRec.ExceptionInformation[0] = 0 ) and
|
|
(excRec.ExceptionInformation[1] = $ffffffff ) ) ) then begin
|
|
excCtxt.{$ifdef win64}rip{$else}eip{$endif} := NewEip;
|
|
result := -1;
|
|
end else
|
|
result := RtlDispatchExceptionNext(@excRec, @excCtxt);
|
|
end;
|
|
|
|
var s1 : AnsiString;
|
|
{$ifndef win64}
|
|
c1 : NativeUInt;
|
|
c2 : dword;
|
|
{$endif}
|
|
begin
|
|
result := false;
|
|
handle := 0;
|
|
AddVectoredExceptionHandler := KernelProc(CAddVecExceptHandler);
|
|
RemoveVectoredExceptionHandler := KernelProc(CRemoveVecExceptHandler);
|
|
SetLength(s1, 5);
|
|
s1[1] := 'r'; // Rtl
|
|
s1[2] := 'd'; // Dispatch
|
|
s1[3] := 'e'; // Exception
|
|
s1[4] := 'c'; // Callback
|
|
s1[5] := ' ';
|
|
s1 := DecryptStr(CMutex) + ', ' + s1 + IntToHexExA(GetCurrentProcessId);
|
|
rdecMutex := CreateMutexA(nil, false, PAnsiChar(s1));
|
|
if rdecMutex <> 0 then begin
|
|
WaitForSingleObject(rdecMutex, INFINITE);
|
|
OldEip := NativeUInt(oeip);
|
|
NewEip := NativeUInt(neip);
|
|
if (@AddVectoredExceptionHandler = nil) or (@RemoveVectoredExceptionHandler = nil) then begin
|
|
{$ifndef win64}
|
|
c1 := NativeUInt(NtProc(CKiUserExceptionDispatcher, true));
|
|
if (dword(pointer(c1 )^) = $04244c8b) and // mov ecx, [esp+4] ; PContext
|
|
(dword(pointer(c1 + 4)^) and $00ffffff = $241c8b) and // mov ebx, [esp+0] ; PExceptionRecord
|
|
(byte (pointer(c1 + 7)^) = $51) and // push ecx
|
|
(byte (pointer(c1 + 8)^) = $53) and // push ebx
|
|
(byte (pointer(c1 + 9)^) = $e8) and // call RtlDispatchException
|
|
VirtualProtect(pointer(c1 + 10), 4, PAGE_EXECUTE_READWRITE, @c2) then begin
|
|
RtlDispatchExceptionNext := pointer(c1 + 14 + dword(pointer(c1 + 10)^));
|
|
dword(pointer(c1 + 10)^) := NativeUInt(@RtlDispatchExceptionCallback) - c1 - 14;
|
|
VirtualProtect(pointer(c1 + 10), 4, c2, @c2);
|
|
result := true;
|
|
end;
|
|
{$endif}
|
|
end else begin
|
|
handle := AddVectoredExceptionHandler(bool(1), @VectoredExceptionHandler);
|
|
result := handle <> 0;
|
|
end;
|
|
if not result then begin
|
|
ReleaseMutex(rdecMutex);
|
|
CloseHandle(rdecMutex);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure DoneRtlDispatchException(handle: THandle);
|
|
{$ifndef win64}
|
|
var c1 : NativeUInt;
|
|
c2 : dword;
|
|
{$endif}
|
|
begin
|
|
if (@AddVectoredExceptionHandler = nil) or (@RemoveVectoredExceptionHandler = nil) then begin
|
|
{$ifndef win64}
|
|
c1 := NativeUInt(NtProc(CKiUserExceptionDispatcher, true));
|
|
if VirtualProtect(pointer(c1 + 10), 4, PAGE_EXECUTE_READWRITE, @c2) then begin
|
|
dword(pointer(c1 + 10)^) := NativeUInt(@RtlDispatchExceptionNext) - c1 - 14;
|
|
VirtualProtect(pointer(c1 + 10), 4, c2, @c2);
|
|
end;
|
|
{$endif}
|
|
end else
|
|
RemoveVectoredExceptionHandler(handle);
|
|
ReleaseMutex(rdecMutex);
|
|
CloseHandle(rdecMutex);
|
|
end;
|
|
|
|
var wait : boolean;
|
|
s1 : AnsiString;
|
|
map : THandle;
|
|
pa : pointer;
|
|
op : dword;
|
|
b2 : boolean;
|
|
ok : boolean;
|
|
hlt : byte;
|
|
tids : TDACardinal;
|
|
ths : TDANativeUInt;
|
|
i1 : integer;
|
|
ctxt : TContext;
|
|
c1, c2 : NativeUInt;
|
|
ci : TCodeInfo;
|
|
p1 : pointer;
|
|
vectoredHandle : THandle;
|
|
tick : dword;
|
|
jmpSize : dword;
|
|
begin
|
|
tids := nil;
|
|
ths := nil;
|
|
if FValid and (not FDestroying) then begin
|
|
pa := nil;
|
|
wait := mutex = 0;
|
|
s1 := ApiSpecialName(CMAHPrefix, FHookedFunc);
|
|
if wait then
|
|
mutex := CreateLocalMutex(PAnsiChar(DecryptStr(CMutex) + ', ' + s1));
|
|
if mutex <> 0 then
|
|
try
|
|
if wait then
|
|
WaitForSingleObject(mutex, INFINITE);
|
|
try
|
|
if buf = nil then
|
|
map := OpenGlobalFileMapping(PAnsiChar(DecryptStr(CNamedBuffer) + ', ' + s1), true)
|
|
else
|
|
map := 0;
|
|
if (buf <> nil) or (map <> 0) then
|
|
try
|
|
if buf = nil then
|
|
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
if buf <> nil then
|
|
try
|
|
with THookQueue(TPPointer(buf)^^) do begin
|
|
if TPByte(@NewCode)^ = $e9 then
|
|
jmpSize := 5
|
|
else
|
|
jmpSize := 6;
|
|
if (TPCardinal(NativeUInt(PatchAddr))^ <> TPCardinal(NativeUInt(@NewCode))^) or
|
|
((jmpSize = 5) and (TPByte(NativeUInt(PatchAddr) + 4)^ <> TPByte(NativeUInt(@NewCode) + 4)^)) or
|
|
((jmpSize = 6) and (TPWord(NativeUInt(PatchAddr) + 4)^ <> TPWord(NativeUInt(@NewCode) + 4)^)) then begin
|
|
pa := PatchAddr;
|
|
ok := VirtualProtect(PatchAddr, 8, PAGE_EXECUTE_READWRITE, @op);
|
|
if ok then begin
|
|
ci := ParseCode(PatchAddr);
|
|
b2 := FSafeHooking and
|
|
(NativeUInt(ci.Next) - NativeUInt(ci.This) < jmpSize) and
|
|
InitRtlDispatchException(PatchAddr, FTramp, vectoredHandle);
|
|
if b2 then begin
|
|
hlt := $f4; // asm HLT instruction
|
|
if not AtomicMove(@hlt, PatchAddr, 1) then
|
|
byte(pointer(PatchAddr)^) := hlt;
|
|
FlushInstructionCache(PatchAddr, 1);
|
|
if GetOtherThreads(true, tids, ths) then
|
|
for i1 := 0 to high(ths) do begin
|
|
tick := GetTickCount;
|
|
while (SafeHookingTimeout = 0) or (GetTickCount - tick < SafeHookingTimeout) do begin
|
|
ctxt.ContextFlags := CONTEXT_CONTROL;
|
|
if (not GetThreadContext(ths[i1], ctxt)) or
|
|
(ctxt.{$ifdef win64}rip{$else}eip{$endif} <= NativeUInt(PatchAddr)) or
|
|
(ctxt.{$ifdef win64}rip{$else}eip{$endif} >= NativeUInt(PatchAddr) + 5) then
|
|
break;
|
|
Sleep(10);
|
|
end;
|
|
CloseHandle(ths[i1]);
|
|
end;
|
|
end;
|
|
if not AtomicMove(@NewCode, PatchAddr, jmpSize) then
|
|
Move(NewCode, PatchAddr^, jmpSize);
|
|
FlushInstructionCache(PatchAddr, jmpSize);
|
|
if b2 then begin
|
|
c1 := NativeUInt(NtProc(CKiUserExceptionDispatcher, true));
|
|
c2 := c1 + 14 + dword(pointer(c1 + 10)^);
|
|
if GetOtherThreads(true, tids, ths) then
|
|
for i1 := 0 to high(ths) do begin
|
|
tick := GetTickCount;
|
|
while (SafeHookingTimeout = 0) or (GetTickCount - tick < SafeHookingTimeout) do begin
|
|
ctxt.ContextFlags := CONTEXT_CONTROL;
|
|
if (not GetThreadContext(ths[i1], ctxt)) or
|
|
( (ctxt.{$ifdef win64}rip{$else}eip{$endif} <> NativeUInt(PatchAddr)) and
|
|
( (ctxt.{$ifdef win64}rip{$else}eip{$endif} < c1) or (ctxt.{$ifdef win64}rip{$else}eip{$endif} > c1 + 10) ) and
|
|
( {$ifdef win64} ctxt.rip and $ffffffffffff0000 <> c2 and $ffffffffffff0000 {$else} ctxt.eip and $ffff0000 <> c2 and $ffff0000 {$endif}) ) then
|
|
break;
|
|
Sleep(10);
|
|
end;
|
|
CloseHandle(ths[i1]);
|
|
end;
|
|
end;
|
|
VirtualProtect(PatchAddr, 8, op, @op);
|
|
if b2 then
|
|
DoneRtlDispatchException(vectoredHandle);
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
if map <> 0 then
|
|
UnmapViewOfFile(buf);
|
|
end;
|
|
finally
|
|
if map <> 0 then
|
|
CloseHandle(map);
|
|
end;
|
|
if (pa <> nil) and FPatchExport then begin
|
|
{$ifndef win64}
|
|
if FIsWinsock2 then
|
|
p1 := FindWs2InternalProcList(FModuleH)
|
|
else
|
|
{$endif}
|
|
p1 := nil;
|
|
if (not FIsWinsock2) or (p1 <> nil) then
|
|
PatchExportTable(FModuleH, FHookedFunc, pa, p1);
|
|
end;
|
|
finally
|
|
if wait then
|
|
ReleaseMutex(mutex);
|
|
end;
|
|
finally
|
|
if wait then
|
|
CloseHandle(mutex);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TCodeHook.IsHookInUse : integer;
|
|
var i1, i2 : integer;
|
|
tids : TDACardinal;
|
|
ths : TDANativeUInt;
|
|
begin
|
|
result := 0;
|
|
tids := nil;
|
|
ths := nil;
|
|
if FValid and (FInUseTargetArr <> nil) then begin
|
|
GetOtherThreads(false, tids, ths);
|
|
for i1 := 0 to CInUseCount - 1 do
|
|
if FInUseTargetArr[i1] <> nil then
|
|
if FInUseThreadArr[i1] = GetCurrentThreadId then
|
|
inc(result)
|
|
else
|
|
for i2 := 0 to high(tids) do
|
|
if FInUseThreadArr[i1] = tids[i2] then begin
|
|
inc(result);
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
destructor TCodeHook.Destroy;
|
|
var mutex : THandle;
|
|
map : THandle;
|
|
buf : pointer;
|
|
s1 : AnsiString;
|
|
i1, i2 : integer;
|
|
b1, b2 : boolean;
|
|
tramp : pointer;
|
|
queue : pointer;
|
|
tids : TDACardinal;
|
|
ths : TDANativeUInt;
|
|
ctxt : TContext;
|
|
op : dword;
|
|
mbi : TMemoryBasicInformation;
|
|
jmpSize : dword;
|
|
begin
|
|
FDestroying := true;
|
|
tids := nil;
|
|
ths := nil;
|
|
if FValid then begin
|
|
tramp := nil;
|
|
queue := nil;
|
|
s1 := ApiSpecialName(CMAHPrefix, FHookedFunc);
|
|
mutex := CreateLocalMutex(PAnsiChar(DecryptStr(CMutex) + ', ' + s1));
|
|
try
|
|
if mutex <> 0 then
|
|
WaitForSingleObject(mutex, INFINITE);
|
|
try
|
|
map := FMapHandle;
|
|
if FMapHandle = 0 then
|
|
map := OpenGlobalFileMapping(PAnsiChar(DecryptStr(CNamedBuffer) + ', ' + s1), true);
|
|
if map <> 0 then
|
|
try
|
|
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
if buf <> nil then
|
|
try
|
|
with THookQueue(TPPointer(buf)^^) do begin
|
|
if TPByte(@NewCode)^ = $e9 then
|
|
jmpSize := 5
|
|
else
|
|
jmpSize := 6;
|
|
for i1 := 1 to ItemCount - 2 do
|
|
if (Items[i1].HookProc = FCallbackFunc) and
|
|
(Items[i1].PNextHook = {$ifdef win64} pointer(FPHookStubTarget) {$else} pointer(FPHookStub^.jmpNextHook.target) {$endif}) then begin
|
|
for i2 := i1 to ItemCount - 2 do
|
|
Items[i2] := Items[i2 + 1];
|
|
Items[i1 - 1].PNextHook^ := Items[i1].HookProc;
|
|
dec(ItemCount);
|
|
if (ItemCount = 2) and (MapHandle <> 0) then begin
|
|
if (not IsBadReadPtrEx(PatchAddr, jmpSize)) and
|
|
(TPCardinal(NativeUInt(PatchAddr))^ = TPCardinal(NativeUInt(@NewCode))^) and
|
|
( ((jmpSize = 5) and (TPByte(NativeUInt(PatchAddr) + 4)^ = TPByte(NativeUInt(@NewCode) + 4)^)) or
|
|
((jmpSize = 6) and (TPWord(NativeUInt(PatchAddr) + 4)^ = TPWord(NativeUInt(@NewCode) + 4)^)) ) then begin
|
|
try
|
|
b1 := VirtualProtect(PatchAddr, 8, PAGE_EXECUTE_READWRITE, @op);
|
|
except
|
|
// Edge might raise "The operation was blocked as the process prohibits dynamic code generation."
|
|
b1 := false;
|
|
end;
|
|
if b1 then begin
|
|
if not AtomicMove(@OldCode, PatchAddr, jmpSize) then
|
|
Move(OldCode, PatchAddr^, jmpSize);
|
|
FlushInstructionCache(PatchAddr, jmpSize);
|
|
VirtualProtect(PatchAddr, 8, op, @op);
|
|
tramp := Items[1].HookProc;
|
|
end;
|
|
end;
|
|
queue := TPPointer(buf)^;
|
|
TPPointer(buf)^ := nil;
|
|
CloseHandle(MapHandle);
|
|
end;
|
|
break;
|
|
end;
|
|
end;
|
|
finally UnmapViewOfFile(buf) end;
|
|
finally
|
|
if FMapHandle = 0 then
|
|
CloseHandle(map);
|
|
end;
|
|
finally if mutex <> 0 then ReleaseMutex(mutex) end;
|
|
finally if mutex <> 0 then CloseHandle(mutex) end;
|
|
if (not FDoubleHook) and (FPNextHook <> nil) then
|
|
FPNextHook^ := FHookedFunc;
|
|
if FInUseCodeArr <> nil then begin
|
|
GetOtherThreads(true, tids, ths);
|
|
try
|
|
b1 := false;
|
|
while true do begin
|
|
b2 := true;
|
|
for i2 := 0 to high(ths) do begin
|
|
ctxt.ContextFlags := CONTEXT_CONTROL;
|
|
if GetThreadContext(ths[i2], ctxt) and
|
|
(ctxt.{$ifdef win64}rip{$else}eip{$endif} >= NativeUInt(FPHookStub)) and
|
|
(ctxt.{$ifdef win64}rip{$else}eip{$endif} <= NativeUInt(FPHookStub) + PAGE_SIZE * 3) then begin
|
|
b2 := false;
|
|
break;
|
|
end;
|
|
end;
|
|
if FInUseTargetArr <> nil then
|
|
for i1 := 0 to CInUseCount - 1 do
|
|
if FInUseTargetArr[i1] <> nil then
|
|
if FInUseThreadArr[i1] = GetCurrentThreadId then begin
|
|
b2 := false;
|
|
break;
|
|
end else begin
|
|
for i2 := 0 to high(tids) do
|
|
if FInUseThreadArr[i1] = tids[i2] then begin
|
|
b2 := false;
|
|
break;
|
|
end;
|
|
if not b2 then
|
|
break;
|
|
end;
|
|
if b2 then
|
|
break;
|
|
b1 := true;
|
|
if FLeakUnhook then
|
|
exit;
|
|
Sleep(500);
|
|
end;
|
|
finally
|
|
for i2 := 0 to high(ths) do
|
|
CloseHandle(ths[i2]);
|
|
end;
|
|
if b1 then
|
|
Sleep(500);
|
|
end;
|
|
if tramp <> nil then
|
|
if (VirtualQuery(tramp, mbi, sizeOf(mbi)) <> sizeOf(mbi)) or
|
|
((mbi.Protect <> PAGE_EXECUTE_READWRITE) and (mbi.Protect <> PAGE_EXECUTE_READ)) then
|
|
LocalFree(HLOCAL(tramp))
|
|
else
|
|
VirtualFree(tramp, 0, MEM_RELEASE);
|
|
if queue <> nil then
|
|
VirtualFree(queue, 0, MEM_RELEASE);
|
|
end;
|
|
VirtualFree(FPHookStub, 0, MEM_RELEASE);
|
|
FInUseCodeArr := nil;
|
|
FInUseTargetArr := nil;
|
|
FPHookStub := nil;
|
|
FPHookStubTarget := nil;
|
|
end;
|
|
|
|
type
|
|
THookItem = record
|
|
owner : HMODULE;
|
|
moduleH : HMODULE;
|
|
module : AnsiString;
|
|
moduleW : AnsiString;
|
|
api : AnsiString;
|
|
ordinal : dword;
|
|
code : pointer;
|
|
ch : TCodeHook;
|
|
nextHook : TPPointer;
|
|
callback : pointer;
|
|
flags : dword;
|
|
numParams : integer;
|
|
end;
|
|
|
|
var
|
|
HookSection : TRTLCriticalSection;
|
|
HookReady : boolean = false;
|
|
HookList : array of THookItem;
|
|
|
|
type
|
|
IMAGE_DELAYLOAD_DESCRIPTOR = record
|
|
AllAttributes : dword;
|
|
DllNameRVA : dword;
|
|
ModuleHandleRVA : dword;
|
|
ImportAddressTableRVA : dword;
|
|
ImportNameTableRVA : dword;
|
|
BoundImportAddressTableRVA : dword;
|
|
UnloadInformationTableRVA : dword;
|
|
TimeDateStamp : dword;
|
|
end;
|
|
|
|
var LoadLibraryExWNextHook : function (lib: PWideChar; file_: THandle; flags: dword) : HMODULE stdcall = nil;
|
|
LdrLoadDllNextHook : function (path: pointer; flags: pointer; var name: TUnicodeStr; var handle: HMODULE) : dword stdcall = nil;
|
|
LdrResolveDelayLoadedAPINextHook : function (base: pointer; var desc: IMAGE_DELAYLOAD_DESCRIPTOR; dllhook, syshook, addr: pointer; flags: ULONG) : pointer stdcall = nil;
|
|
LoadLibraryHooked : boolean = false;
|
|
LoadLibraryExWDone : boolean = false;
|
|
LoadLibraryCallsNtDll : pointer = nil;
|
|
LdrGetDllHandle : function (dllPath: PWideChar; dllChars: pointer; var dllName: TUnicodeStr; out handle: HMODULE) : dword stdcall = nil;
|
|
LdrRegisterDllNotification : function (flags: ULONG; callback, context: pointer; out cookie: pointer) : dword; stdcall = nil;
|
|
LdrUnregisterDllNotification : function (cookie: pointer) : dword; stdcall = nil;
|
|
LdrDllNotificationCookie : pointer = nil;
|
|
|
|
procedure CheckHooks(dll: HMODULE); forward;
|
|
|
|
type
|
|
TLdrDllNotificationData = record
|
|
Flags : ULONG; // Reserved.
|
|
FullDllName : pointer; // The full path name of the DLL module.
|
|
BaseDllName : pointer; // The base file name of the DLL module.
|
|
DllBase : pointer; // A pointer to the base address for the DLL in memory.
|
|
SizeOfImage : ULONG; // The size of the DLL image, in bytes.
|
|
end;
|
|
|
|
procedure LdrDllNotificationFunction(reason: ULONG; var data: TLdrDllNotificationData; context: pointer); stdcall;
|
|
begin
|
|
CheckHooks(HMODULE(data.DllBase));
|
|
end;
|
|
|
|
function GetModuleHandleEx(var us: TUnicodeStr) : HMODULE;
|
|
begin
|
|
if @LdrGetDllHandle <> nil then begin
|
|
// Calling GetModuleHandleW in a LoadLibrary hook callback can sometimes
|
|
// screw up strings (don't ask me why). We work around this by calling
|
|
// LdrGetDllHandle instead, which doesn't have this problem.
|
|
if LdrGetDllHandle(nil, nil, us, result) <> 0 then
|
|
result := 0;
|
|
end else
|
|
result := GetModuleHandleW(us.str);
|
|
end;
|
|
|
|
function LoadLibraryExWCallbackProc(lib: PWideChar; file_: THandle; flags: dword) : HMODULE; stdcall;
|
|
var module : HMODULE;
|
|
le : dword;
|
|
us : TUnicodeStr;
|
|
begin
|
|
module := 0;
|
|
if not AlwaysCheckHooks then begin
|
|
le := GetLastError;
|
|
try
|
|
us.str := lib;
|
|
us.len := lstrlenW(lib) * 2;
|
|
us.maxLen := us.len + 2;
|
|
module := GetModuleHandleEx(us);
|
|
except module := 0 end;
|
|
SetLastError(le);
|
|
end;
|
|
result := LoadLibraryExWNextHook(lib, file_, flags);
|
|
if (result <> 0) and (result <> module) and (flags and LOAD_LIBRARY_AS_DATAFILE = 0) then
|
|
CheckHooks(result);
|
|
end;
|
|
|
|
function LdrLoadDllCallbackProc(path, flags: pointer; var name: TUnicodeStr; var handle: HMODULE) : dword stdcall;
|
|
var module : HMODULE;
|
|
le : dword;
|
|
begin
|
|
module := 0;
|
|
if not AlwaysCheckHooks then begin
|
|
le := GetLastError;
|
|
try
|
|
module := GetModuleHandleEx(name);
|
|
except module := 0 end;
|
|
SetLastError(le);
|
|
end;
|
|
result := LdrLoadDllNextHook(path, flags, name, handle);
|
|
if (handle <> 0) and (handle <> module) then
|
|
CheckHooks(handle);
|
|
end;
|
|
|
|
function LdrResolveDelayLoadedAPICallbackProc(base: pointer; var desc: IMAGE_DELAYLOAD_DESCRIPTOR; dllhook, syshook, addr: pointer; flags: ULONG) : pointer stdcall;
|
|
var module : HMODULE;
|
|
le : dword;
|
|
name : AnsiString;
|
|
us : TUnicodeStr;
|
|
mbi : TMemoryBasicInformation;
|
|
begin
|
|
module := 0;
|
|
if not AlwaysCheckHooks then begin
|
|
le := GetLastError;
|
|
try
|
|
if (desc.DllNameRVA <> 0) then begin
|
|
name := AnsiToWideEx(PAnsiChar(base) + desc.DllNameRVA, true);
|
|
us.str := pointer(name);
|
|
us.len := lstrlenW(us.str) * 2;
|
|
us.maxLen := us.len + 2;
|
|
module := GetModuleHandleEx(us);
|
|
end;
|
|
except module := 0 end;
|
|
SetLastError(le);
|
|
end;
|
|
result := LdrResolveDelayLoadedAPINextHook(base, desc, dllhook, syshook, addr, flags);
|
|
if (result <> nil) and (module = 0) then begin
|
|
le := GetLastError;
|
|
if (VirtualQuery(result, mbi, sizeOf(MEMORY_BASIC_INFORMATION)) = sizeof(MEMORY_BASIC_INFORMATION)) and (mbi.State = MEM_COMMIT) and (mbi.AllocationBase <> nil) then
|
|
CheckHooks(HMODULE(mbi.AllocationBase));
|
|
SetLastError(le);
|
|
end;
|
|
end;
|
|
|
|
procedure HookLoadLibrary;
|
|
type TMchLLEW = packed record
|
|
name : array [0..7] of AnsiChar;
|
|
jmp : ^THookStub;
|
|
end;
|
|
type THookStubEx = packed record
|
|
jmpNextHook : TAbsoluteJmp; // $FF / $25 / @absTarget
|
|
absTarget : pointer; // absolute target
|
|
self : pointer;
|
|
end;
|
|
var map : THandle;
|
|
newMap : boolean;
|
|
buf1 : ^TMchLLEW;
|
|
buf2 : ^THookStubEx;
|
|
s1 : AnsiString;
|
|
i1 : integer;
|
|
c1 : dword;
|
|
fi : TFunctionInfo;
|
|
p1 : pointer;
|
|
api : pointer;
|
|
orgCode : int64;
|
|
op : dword;
|
|
begin
|
|
if not LoadLibraryHooked then begin
|
|
// I had high hopes for the "LdrRegisterDllNotification" API to replace my old self-made LoadLibrary API hook.
|
|
// However, plays out "LdrRegisterDllNotification" is rather sensitive/unstable. Several things don't work,
|
|
// inside of the callback function. Furthermore, in Windows 7 x64 (and only there) CorelDraw X6 crashes inside
|
|
// of the callback, when hooking a WinSock API. The crash seems to be caused by trying to disassemble the
|
|
// WinSock API code. Even a "__try __except(1)" block isn't able to solve the crash. So right now I see no
|
|
// other option than to continue using my self-mode hook instead of the Ldr* API.
|
|
LdrRegisterDllNotification := nil; // NtProc(CLdrRegisterDllNotification);
|
|
LdrUnregisterDllNotification := nil; // NtProc(CLdrUnregisterDllNotification);
|
|
if (@LdrRegisterDllNotification = nil) or (@LdrUnregisterDllNotification = nil) or (LdrRegisterDllNotification(0, @LdrDllNotificationFunction, nil, LdrDllNotificationCookie) <> 0) then begin
|
|
LdrDllNotificationCookie := nil;
|
|
if @LdrGetDllHandle = nil then
|
|
LdrGetDllHandle := NtProc(CLdrGetDllHandle);
|
|
if not LoadLibraryExWDone then begin
|
|
LoadLibraryExWDone := true;
|
|
if not DisableLdrLoadDllSpecialHook then begin
|
|
s1 := DecryptStr(CMchLLEW2);
|
|
map := CreateFileMappingA(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, sizeOf(TMchLLEW), PAnsiChar(s1 + IntToHexExA(GetCurrentProcessId)));
|
|
if map <> 0 then begin
|
|
newMap := GetLastError <> ERROR_ALREADY_EXISTS;
|
|
buf1 := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
if buf1 <> nil then begin
|
|
for i1 := 1 to 50 do
|
|
if newMap or (TPInt64(@buf1^.name)^ = TPInt64(s1)^) then
|
|
break
|
|
else Sleep(50);
|
|
if newMap or (TPInt64(@buf1^.name)^ <> TPInt64(s1)^) then begin
|
|
p1 := NtProc(CLdrLoadDll, true);
|
|
api := KernelProc(CLoadLibraryExW, true);
|
|
orgCode := int64(api^);
|
|
if not RestoreCode(api) then
|
|
orgCode := 0;
|
|
fi := ParseFunction(api);
|
|
buf2 := VirtualAlloc2(sizeOf(THookStubEx), fi.EntryPoint);
|
|
for i1 := 0 to high(fi.FarCalls) do
|
|
if fi.FarCalls[i1].Target = p1 then begin
|
|
if (fi.FarCalls[i1].PTarget <> nil) or (fi.FarCalls[i1].PPTarget <> nil) then begin
|
|
buf2^.jmpNextHook.opcode := $ff;
|
|
buf2^.jmpNextHook.modRm := $25;
|
|
{$ifdef win64}
|
|
buf2^.jmpNextHook.target := 0;
|
|
{$else}
|
|
buf2^.jmpNextHook.target := dword(@buf2^.absTarget);
|
|
{$endif}
|
|
buf2^.absTarget := p1;
|
|
buf2^.self := buf2;
|
|
VirtualProtect(buf2, sizeOf(THookStubEx), PAGE_EXECUTE_READ, @op);
|
|
buf1^.jmp := pointer(buf2);
|
|
if fi.FarCalls[i1].RelTarget then begin
|
|
if VirtualProtect(fi.FarCalls[i1].PTarget, 8, PAGE_EXECUTE_READWRITE, @op) then begin
|
|
c1 := NativeUInt(buf2) - NativeUInt(fi.FarCalls[i1].CodeAddr1) - 5;
|
|
if not AtomicMove(@c1, fi.FarCalls[i1].PTarget, 4) then
|
|
dword(fi.FarCalls[i1].PTarget^) := c1;
|
|
VirtualProtect(fi.FarCalls[i1].PTarget, 8, op, @op);
|
|
LoadLibraryCallsNtDll := buf2;
|
|
map := 0;
|
|
end;
|
|
end else
|
|
if fi.FarCalls[i1].PTarget <> nil then begin
|
|
if VirtualProtect(fi.FarCalls[i1].PTarget, 8, PAGE_EXECUTE_READWRITE, @op) then begin
|
|
if not AtomicMove(@buf2, fi.FarCalls[i1].PTarget, sizeOf(pointer)) then
|
|
pointer(fi.FarCalls[i1].PTarget^) := buf2;
|
|
VirtualProtect(fi.FarCalls[i1].PTarget, 8, op, @op);
|
|
LoadLibraryCallsNtDll := buf2;
|
|
map := 0;
|
|
end;
|
|
end else begin
|
|
if VirtualProtect(fi.FarCalls[i1].PPTarget, 8, PAGE_EXECUTE_READWRITE, @op) then begin
|
|
{$ifdef win64}
|
|
// RIP-relative addressing
|
|
c1 := NativeUInt(@buf2.Self) - NativeUInt(fi.FarCalls[i1].CodeAddr2);
|
|
{$else}
|
|
c1 := NativeUInt(@buf2.Self);
|
|
{$endif}
|
|
if not AtomicMove(@c1, fi.FarCalls[i1].PPTarget, 4) then
|
|
TPCardinal(fi.FarCalls[i1].PPTarget)^ := c1;
|
|
VirtualProtect(fi.FarCalls[i1].PPTarget, 8, op, @op);
|
|
LoadLibraryCallsNtDll := buf2;
|
|
map := 0;
|
|
end;
|
|
end;
|
|
end;
|
|
break;
|
|
end;
|
|
if orgCode <> 0 then begin
|
|
if VirtualProtect(api, 8, PAGE_EXECUTE_READWRITE, @op) then begin
|
|
if not AtomicMove(@orgCode, api, 8) then
|
|
int64(api^) := orgCode;
|
|
FlushInstructionCache(api, 8);
|
|
VirtualProtect(api, 8, op, @op);
|
|
end;
|
|
end;
|
|
if LoadLibraryCallsNtDll = nil then begin
|
|
UnmapViewOfFile(buf1);
|
|
VirtualFree(buf2, 0, MEM_RELEASE);
|
|
end else
|
|
TPInt64(@buf1^.name)^ := TPInt64(s1)^;
|
|
end else begin
|
|
LoadLibraryCallsNtDll := buf1^.jmp;
|
|
UnmapViewOfFile(buf1);
|
|
end;
|
|
end;
|
|
if map <> 0 then
|
|
CloseHandle(map);
|
|
end;
|
|
end;
|
|
end;
|
|
if LoadLibraryCallsNtDll <> nil then
|
|
HookCodeX(0, 0, '', '', 0, LoadLibraryCallsNtDll, @LdrLoadDllCallbackProc, @LdrLoadDllNextHook, 0, 0)
|
|
else
|
|
HookCodeX(0, 0, '', '', 0, FindRealCode(KernelProc(CLoadLibraryExW, true)), @LoadLibraryExWCallbackProc, @LoadLibraryExWNextHook, 0, 0);
|
|
HookCodeX(0, 0, '', '', 0, FindRealCode(NtProc(CLdrResolveDelayLoadedAPI, true)), @LdrResolveDelayLoadedAPICallbackProc, @LdrResolveDelayLoadedAPINextHook, 0, 0);
|
|
end;
|
|
LoadLibraryHooked := true;
|
|
end;
|
|
end;
|
|
|
|
function UnhookCodeEx (var nextHook: pointer; dontUnhookHelperHooks, wait: boolean) : boolean; forward;
|
|
|
|
procedure UnhookLoadLibrary(wait: boolean);
|
|
begin
|
|
if LoadLibraryHooked then begin
|
|
if LdrDllNotificationCookie <> nil then begin
|
|
LdrUnregisterDllNotification(LdrDllNotificationCookie);
|
|
LdrDllNotificationCookie := nil;
|
|
end;
|
|
if @LoadLibraryExWNextHook <> nil then UnhookCodeEx(@LoadLibraryExWNextHook, true, wait);
|
|
if @LdrLoadDllNextHook <> nil then UnhookCodeEx(@LdrLoadDllNextHook, true, wait);
|
|
if @LdrResolveDelayLoadedAPINextHook <> nil then UnhookCodeEx(@LdrResolveDelayLoadedAPINextHook, true, wait);
|
|
LoadLibraryHooked := false;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
AutoUnhookCounter : integer = -1;
|
|
AutoUnhookMarker : array [0..31] of byte = ($00, $00, $00, $00, $00, $00, $00, $00, $38, $34, $31, $16, $1D, $3A, $3A, $3E, $14, $20, $21, $3A, $00, $3B, $3D, $3A, $3A, $3E, $18, $34, $27, $3E, $30, $27);
|
|
|
|
procedure AutoUnhookUninject(moduleHandle: HMODULE);
|
|
// this gets called from the DLL uninjection thread
|
|
begin
|
|
if InterlockedIncrement(AutoUnhookCounter) <> 0 then begin
|
|
// only the first uninjection thread gets to actually perform unhooking
|
|
// the 2nd (and 3rd etc) thread is stopped right here to avoid conflicts
|
|
ExitThread(0);
|
|
end;
|
|
AutoUnhook(moduleHandle);
|
|
FireUninjectCallbacks;
|
|
end;
|
|
|
|
procedure InitAutoUnhook;
|
|
begin
|
|
TPPointer(@AutoUnhookMarker)^ := @AutoUnhookUninject;
|
|
end;
|
|
|
|
{$ifdef unlocked}
|
|
function HookCodeX(owner : HMODULE;
|
|
moduleH : HMODULE;
|
|
module : AnsiString;
|
|
api : AnsiString;
|
|
ordinal : dword;
|
|
code : pointer;
|
|
callbackFunc : pointer;
|
|
out nextHook : pointer;
|
|
flags : dword;
|
|
numStackParams : dword) : boolean;
|
|
|
|
{$else}
|
|
var forbiddenMutex : THandle = 0;
|
|
function HookCodeX_(owner : HMODULE;
|
|
moduleH : HMODULE;
|
|
module : AnsiString;
|
|
api : AnsiString;
|
|
ordinal : dword;
|
|
code : pointer;
|
|
callbackFunc : pointer;
|
|
out nextHook : pointer;
|
|
flags : dword;
|
|
numStackParams : dword) : boolean;
|
|
{$endif}
|
|
var ch1 : TCodeHook;
|
|
c1 : dword;
|
|
b1 : boolean;
|
|
s1 : AnsiString;
|
|
i1 : integer;
|
|
doubleInstalled : boolean;
|
|
begin
|
|
result := false;
|
|
{$ifdef log}
|
|
SetLength(s1, MAX_PATH + 1);
|
|
GetModuleFileNameA(owner, PAnsiChar(s1), MAX_PATH);
|
|
s1 := PAnsiChar(s1);
|
|
log('HookCodeX (owner: ' + IntToHexExA(owner) + ' (' + s1 + ')' +
|
|
'; moduleH: ' + IntToHexExA(moduleH) +
|
|
'; module: ' + module +
|
|
'; api: ' + api +
|
|
'; ordinal: ' + IntToStrExA(ordinal) +
|
|
'; code: ' + IntToHexExA(NativeUInt(code)) +
|
|
'; callback: ' + IntToHexExA(NativeUInt(callbackFunc)) +
|
|
'; nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
|
|
'; flags: ' + IntToHexExA(flags) +
|
|
'; numStackParams: ' + IntToStrExA(numStackParams) +
|
|
')');
|
|
{$endif}
|
|
InitProcs(true);
|
|
SetLastError(0);
|
|
{$ifndef win64}
|
|
InitUnprotectMemory;
|
|
{$endif}
|
|
if code <> nil then begin
|
|
try
|
|
if moduleH = 0 then begin
|
|
FindModuleA(code, moduleH, s1);
|
|
ch1 := TCodeHook.Create(moduleH, code, callbackFunc, nextHook, flags, numStackParams);
|
|
end else
|
|
ch1 := TCodeHook.Create(moduleH, code, callbackFunc, nextHook, flags, numStackParams);
|
|
if not ch1.FValid then begin
|
|
c1 := GetLastError;
|
|
ch1.Free;
|
|
SetLastError(c1);
|
|
ch1 := nil;
|
|
moduleH := 0;
|
|
end;
|
|
except ch1 := nil end;
|
|
end else
|
|
ch1 := nil;
|
|
if (ch1 <> nil) or ((code = nil) and (module <> '')) then begin
|
|
result := true;
|
|
if not HookReady then begin
|
|
InitializeCriticalSection(HookSection);
|
|
HookReady := true;
|
|
end;
|
|
doubleInstalled := false;
|
|
EnterCriticalSection(HookSection);
|
|
try
|
|
for i1 := 0 to high(HookList) do
|
|
if HookList[i1].nextHook = @nextHook then begin
|
|
// There's already a hook for this "nextHook" variable installed.
|
|
// This can happen if multiple threads are loading dlls at the same
|
|
// time, and if madCodeHook detects a new DLL it's supposed to hook.
|
|
// In that situation madCodeHook might try to install an API hook for
|
|
// the new DLL from multiple threads simultaneously.
|
|
// Our new hook is superfluous, so we will delete it right away.
|
|
// But it has already modified the "nextHook" shared variable of the
|
|
// already existing hook. So we need to restore the original value.
|
|
if HookList[i1].ch <> nil then
|
|
HookList[i1].ch.FPNextHook^ := HookList[i1].ch.FPHookStub;
|
|
doubleInstalled := true;
|
|
break;
|
|
end;
|
|
if not doubleInstalled then begin
|
|
c1 := Length(HookList);
|
|
SetLength(HookList, c1 + 1);
|
|
HookList[c1].owner := owner;
|
|
HookList[c1].moduleH := moduleH;
|
|
HookList[c1].module := module;
|
|
HookList[c1].moduleW := AnsiToWideEx(module);
|
|
HookList[c1].api := api;
|
|
HookList[c1].ordinal := ordinal;
|
|
HookList[c1].code := code;
|
|
HookList[c1].ch := ch1;
|
|
HookList[c1].nextHook := @nextHook;
|
|
HookList[c1].callback := callbackFunc;
|
|
HookList[c1].flags := flags;
|
|
HookList[c1].numParams := numStackParams;
|
|
end;
|
|
b1 := (owner <> 0) and (not LoadLibraryHooked);
|
|
finally LeaveCriticalSection(HookSection) end;
|
|
if doubleInstalled and (ch1 <> nil) then begin
|
|
ch1.FDoubleHook := true;
|
|
ch1.Free;
|
|
end;
|
|
if b1 then begin
|
|
if HookLoadLibraryOption then
|
|
HookLoadLibrary;
|
|
InitAutoUnhook;
|
|
end;
|
|
end;
|
|
if result then
|
|
SetLastError(0);
|
|
{$ifdef log}
|
|
SetLength(s1, MAX_PATH + 1);
|
|
GetModuleFileNameA(owner, PAnsiChar(s1), MAX_PATH);
|
|
s1 := PAnsiChar(s1);
|
|
log('HookCodeX (owner: ' + IntToHexExA(owner) + ' (' + s1 + ')' +
|
|
'; moduleH: ' + IntToHexExA(moduleH) +
|
|
'; module: ' + module +
|
|
'; api: ' + api +
|
|
'; ordinal: ' + ordinal +
|
|
'; code: ' + IntToHexExA(NativeUInt(code)) +
|
|
'; callback: ' + IntToHexExA(NativeUInt(callbackFunc)) +
|
|
'; nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
|
|
'; flags: ' + IntToHexExA(flags) +
|
|
'; numStackParams: ' + IntToStrExA(numStackParams) +
|
|
') -> ' + booleanToCharA(result));
|
|
{$endif}
|
|
end;
|
|
|
|
{$ifndef unlocked}
|
|
function HookCodeX(owner : HMODULE;
|
|
moduleH : HMODULE;
|
|
module : AnsiString;
|
|
api : AnsiString;
|
|
ordinal : dword;
|
|
code : pointer;
|
|
callbackFunc : pointer;
|
|
out nextHook : pointer;
|
|
flags : dword;
|
|
numStackParams : dword) : boolean;
|
|
var FindHookCodeX3Addr : NativeUInt;
|
|
|
|
procedure JmpRel;
|
|
asm
|
|
push dword ptr [esp]
|
|
add [esp+4], eax
|
|
end;
|
|
|
|
function HookCodeX3(owner : HMODULE;
|
|
moduleH : HMODULE;
|
|
module : AnsiString;
|
|
api : AnsiString;
|
|
ordinal : dword;
|
|
code : pointer;
|
|
callbackFunc : pointer;
|
|
out nextHook : pointer;
|
|
flags : dword;
|
|
numStackParams : dword) : boolean;
|
|
begin
|
|
result := false;
|
|
end;
|
|
|
|
procedure HookCodeX2;
|
|
asm
|
|
db $82
|
|
db $0d
|
|
jmp HookCodeX_
|
|
end;
|
|
|
|
function FindHookCodeX5(api: AnsiString; ofs: integer) : pointer;
|
|
begin
|
|
result := pointer(NativeUInt(@HookCodeX3) - 2);
|
|
end;
|
|
|
|
function FindHookCodeX4(api: AnsiString; ofs: integer) : pointer;
|
|
|
|
function CmpHookThis(api: AnsiString) : bool;
|
|
begin
|
|
result := (api <> DecryptStr(CProcess32Next )) and
|
|
(api <> DecryptStr(CEnumServicesStatusA )) and
|
|
(api <> DecryptStr(CEnumServicesStatusW )) and
|
|
(api <> DecryptStr(CNtQuerySystemInformation));
|
|
end;
|
|
|
|
asm
|
|
cmp ofs, 0
|
|
jz @Data
|
|
push ofs
|
|
call CmpHookThis
|
|
cmp eax, 0
|
|
jnz @Ok
|
|
pop eax
|
|
mov eax, offset HookCodeX3
|
|
sub eax, 2
|
|
ret
|
|
@Ok:
|
|
pop ecx
|
|
mov eax, offset @Data
|
|
add eax, ecx
|
|
mov eax, [eax]
|
|
ret
|
|
@Data:
|
|
db $82
|
|
db $1d
|
|
db $00
|
|
dd offset HookCodeX2
|
|
end;
|
|
|
|
procedure FindHookCodeX3;
|
|
asm
|
|
cmp eax, 0
|
|
jnz @zeroParam
|
|
mov eax, 8
|
|
call JmpRel
|
|
@zeroParam:
|
|
mov eax, offset FindHookCodeX5
|
|
ret
|
|
db $82
|
|
db $05
|
|
mov eax, offset FindHookCodeX4
|
|
end;
|
|
|
|
procedure FindHookCodeX2;
|
|
asm
|
|
mov [esp], eax
|
|
mov eax, [eax]
|
|
xor [esp], eax
|
|
xor eax, eax
|
|
end;
|
|
|
|
function FindHookCodeX1(param: pointer) : NativeUInt;
|
|
asm
|
|
call FindHookCodeX2
|
|
end;
|
|
|
|
var FindHookCode1 : function (api: AnsiString; offset: integer) : NativeUInt;
|
|
FindHookCode2 : function (owner : HMODULE;
|
|
moduleH : HMODULE;
|
|
module : AnsiString;
|
|
api : AnsiString;
|
|
ordinal : dword;
|
|
code : pointer;
|
|
callbackFunc : pointer;
|
|
out nextHook : pointer;
|
|
flags : dword;
|
|
numStackParams : dword) : boolean;
|
|
begin
|
|
FindHookCodeX3Addr := NativeUInt(@FindHookCodeX3Addr) xor NativeUInt(@FindHookCodeX3);
|
|
FindHookCode1 := pointer(FindHookCodeX1(@FindHookCodeX3Addr));
|
|
FindHookCode2 := pointer(FindHookCode1(api, 3) + 2);
|
|
result := FindHookCode2(owner, moduleH, module, api, ordinal, code, callbackFunc, nextHook, flags, numStackParams);
|
|
end;
|
|
|
|
procedure CheckForbiddenAPIs(api: AnsiString);
|
|
begin
|
|
if (api = DecryptStr(CProcess32Next2 )) or
|
|
(api = DecryptStr(CEnumServicesStatusA2)) or
|
|
(api = DecryptStr(CEnumServicesStatusW2)) or
|
|
(api = DecryptStr(CNtQuerySystemInfo2 )) then
|
|
if (forbiddenMutex = 0) and (not AmSystemProcess) and AmUsingInputDesktop then begin
|
|
forbiddenMutex := CreateMutex(nil, false, 'forbiddenAPIsMutex');
|
|
if GetLastError <> ERROR_ALREADY_EXISTS then
|
|
MessageBoxA(0, PAnsiChar('You''ve tried to hook one of the following APIs: ' + #$D#$A + #$D#$A +
|
|
'- ' + DecryptStr(CProcess32Next2) + #$D#$A +
|
|
'- ' + DecryptStr(CEnumServicesStatusA2) + #$D#$A +
|
|
'- ' + DecryptStr(CEnumServicesStatusW2) + #$D#$A +
|
|
'- ' + DecryptStr(CNtQuerySystemInfo2) + #$D#$A + #$D#$A +
|
|
'These APIs are usually hooked in order to hide a ' + #$D#$A +
|
|
'process. Of course madCodeHook can do that just ' + #$D#$A +
|
|
'fine. But I don''t want virus/trojan writers to misuse ' + #$D#$A +
|
|
'madCodeHook for illegal purposes. So I''ve decided ' + #$D#$A +
|
|
'to not allow these APIs to be hooked.' + #$D#$A + #$D#$A +
|
|
'If you absolutely have to hook these APIs, and if ' + #$D#$A +
|
|
'you have a commercial madCodeHook license, you ' + #$D#$A +
|
|
'may contact me.'),
|
|
'madCodeHook warning...', 0);
|
|
end;
|
|
end;
|
|
{$endif}
|
|
|
|
function HookCode(code : pointer;
|
|
callbackFunc : pointer;
|
|
out nextHook : pointer;
|
|
flags : dword = 0;
|
|
numStackParams : dword = 32) : bool; stdcall;
|
|
var s1, s2 : AnsiString;
|
|
module : HMODULE;
|
|
begin
|
|
result := false;
|
|
{$ifdef log}
|
|
log('HookCode (code: ' + IntToHexExA(NativeUInt(code)) +
|
|
'; callback: ' + IntToHexExA(NativeUInt(callbackFunc)) +
|
|
'; nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
|
|
'; flags: ' + IntToHexExA(flags) +
|
|
'; numStackParams: ' + IntToStrExA(numStackParams) +
|
|
')');
|
|
{$endif}
|
|
if code <> nil then begin
|
|
s1 := '';
|
|
s2 := '';
|
|
module := 0;
|
|
if VerifyHookAddress then begin
|
|
code := FindRealCode(code);
|
|
if FindModule(code, module, s1) then begin
|
|
s2 := GetImageProcName(module, code, false);
|
|
if s2 = '' then begin
|
|
module := 0;
|
|
s1 := '';
|
|
end;
|
|
end;
|
|
end;
|
|
result := HookCodeX(GetCallingModule, module, s1, s2, 0, code, callbackFunc, nextHook, flags, numStackParams);
|
|
{$ifndef unlocked}
|
|
CheckForbiddenAPIs(s2);
|
|
{$endif}
|
|
end else
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
{$ifdef log}
|
|
log('HookCode (code: ' + IntToHexExA(NativeUInt(code)) +
|
|
'; callback: ' + IntToHexExA(NativeUInt(callbackFunc)) +
|
|
'; nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
|
|
'; flags: ' + IntToHexExA(flags) +
|
|
'; numStackParams: ' + IntToStrExA(numStackParams) +
|
|
') -> ' + booleanToCharA(result));
|
|
{$endif}
|
|
end;
|
|
|
|
function HookAPI(module, api : PAnsiChar;
|
|
callbackFunc : pointer;
|
|
out nextHook : pointer;
|
|
flags : dword = 0;
|
|
numStackParams : dword = 32) : bool; stdcall;
|
|
var dll : HMODULE;
|
|
code : pointer;
|
|
ordinal : dword;
|
|
s2 : AnsiString;
|
|
begin
|
|
result := false;
|
|
if NativeUInt(api) < $10000 then begin
|
|
ordinal := dword(api);
|
|
api := nil;
|
|
end else
|
|
ordinal := 0;
|
|
{$ifdef log}
|
|
log('HookAPI (module: ' + module +
|
|
'; api: ' + AnsiString(api) +
|
|
'; ordinal: ' + IntToStrExA(ordinal) +
|
|
'; callback: ' + IntToHexExA(NativeUInt(callbackFunc)) +
|
|
'; nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
|
|
'; flags: ' + IntToHexExA(flags) +
|
|
'; numStackParams: ' + IntToStrExA(numStackParams) +
|
|
')');
|
|
{$endif}
|
|
if (module <> '') and ((ordinal > 0) or (api <> '')) then begin
|
|
dll := GetModuleHandleW(pointer(AnsiToWideEx(module)));
|
|
if dll <> 0 then begin
|
|
if VerifyHookAddress then begin
|
|
if ordinal = 0 then begin
|
|
code := GetImageProcAddress(dll, api, true);
|
|
if code <> nil then begin
|
|
code := FindRealCode(code);
|
|
s2 := GetImageProcName(dll, code, false);
|
|
if s2 <> '' then
|
|
api := PAnsiChar(s2);
|
|
end;
|
|
end else
|
|
code := GetImageProcAddress(dll, ordinal);
|
|
end else begin
|
|
if ordinal > 0 then
|
|
code := GetProcAddress(dll, PAnsiChar(ordinal))
|
|
else
|
|
code := GetProcAddress(dll, api);
|
|
ResolveMixtureMode(code);
|
|
end;
|
|
end else
|
|
code := nil;
|
|
result := HookCodeX(GetCallingModule, dll, module, api, ordinal, code, callbackFunc, nextHook, flags, numStackParams);
|
|
{$ifndef unlocked}
|
|
CheckForbiddenAPIs(s2);
|
|
{$endif}
|
|
end else
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
{$ifdef log}
|
|
log('HookAPI (module: ' + module +
|
|
'; api: ' + AnsiString(api) +
|
|
'; ordinal: ' + IntToStrExA(ordinal) +
|
|
'; callback: ' + IntToHexExA(NativeUInt(callbackFunc)) +
|
|
'; nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
|
|
'; flags: ' + IntToHexExA(flags) +
|
|
'; numStackParams: ' + IntToStrExA(numStackParams) +
|
|
') -> ' + booleanToCharA(result));
|
|
{$endif}
|
|
end;
|
|
|
|
function RenewHook(var nextHook: pointer) : bool; stdcall;
|
|
var {$ifdef CheckRecursion}
|
|
recTestBuf : array [0..3] of pointer;
|
|
{$endif}
|
|
ch : TCodeHook;
|
|
i1 : integer;
|
|
le : dword;
|
|
begin
|
|
result := false;
|
|
le := GetLastError;
|
|
{$ifdef CheckRecursion}
|
|
if NotRecursedYet(@RenewHook, @recTestBuf) then begin
|
|
{$endif}
|
|
{$ifdef log}
|
|
log('RenewHook (nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) + ')');
|
|
{$endif}
|
|
if HookReady then begin
|
|
ch := nil;
|
|
EnterCriticalSection(HookSection);
|
|
try
|
|
for i1 := high(HookList) downto 0 do
|
|
if HookList[i1].nextHook = @nextHook then begin
|
|
result := true;
|
|
ch := HookList[i1].ch;
|
|
break;
|
|
end;
|
|
finally LeaveCriticalSection(HookSection) end;
|
|
if ch <> nil then
|
|
ch.WritePatch(0, nil);
|
|
end;
|
|
{$ifdef log}
|
|
log('RenewHook (nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) + ') -> ' + booleanToCharA(result));
|
|
{$endif}
|
|
{$ifdef CheckRecursion}
|
|
recTestBuf[3] := nil;
|
|
end;
|
|
{$endif}
|
|
SetLastError(le);
|
|
end;
|
|
|
|
function IsHookInUse(var nextHook: pointer) : integer; stdcall;
|
|
var {$ifdef CheckRecursion}
|
|
recTestBuf : array [0..3] of pointer;
|
|
{$endif}
|
|
ch : TCodeHook;
|
|
i1 : integer;
|
|
le : dword;
|
|
begin
|
|
result := 0;
|
|
le := GetLastError;
|
|
{$ifdef CheckRecursion}
|
|
if NotRecursedYet(@IsHookInUse, @recTestBuf) then begin
|
|
{$endif}
|
|
{$ifdef log}
|
|
log('IsHookInUse (nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) + ')');
|
|
{$endif}
|
|
if HookReady then begin
|
|
ch := nil;
|
|
EnterCriticalSection(HookSection);
|
|
try
|
|
for i1 := high(HookList) downto 0 do
|
|
if HookList[i1].nextHook = @nextHook then begin
|
|
ch := HookList[i1].ch;
|
|
break;
|
|
end;
|
|
finally LeaveCriticalSection(HookSection) end;
|
|
if ch <> nil then
|
|
result := ch.IsHookInUse;
|
|
end;
|
|
{$ifdef log}
|
|
log('IsHookInUse (nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) + ') -> ' + IntToStrExA(result));
|
|
{$endif}
|
|
{$ifdef CheckRecursion}
|
|
recTestBuf[3] := nil;
|
|
end;
|
|
{$endif}
|
|
SetLastError(le);
|
|
end;
|
|
|
|
function UnhookCodeEx(var nextHook: pointer; dontUnhookHelperHooks, wait: boolean) : boolean;
|
|
var ch : TCodeHook;
|
|
i1, i2 : integer;
|
|
b1 : boolean;
|
|
begin
|
|
result := false;
|
|
{$ifdef log}
|
|
log('UnhookCode (nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
|
|
', dontUnhookHelperHooks: ' + booleanToCharA(dontUnhookHelperHooks) +
|
|
', wait: ' + booleanToCharA(wait) + ')');
|
|
{$endif}
|
|
if HookReady then begin
|
|
EnterCriticalSection(HookSection);
|
|
try
|
|
ch := nil;
|
|
for i1 := high(HookList) downto 0 do
|
|
if HookList[i1].nextHook = @nextHook then begin
|
|
result := true;
|
|
ch := HookList[i1].ch;
|
|
i2 := high(HookList);
|
|
HookList[i1] := HookList[i2];
|
|
SetLength(HookList, i2);
|
|
break;
|
|
end;
|
|
b1 := result and (not dontUnhookHelperHooks);
|
|
if b1 then
|
|
for i1 := 0 to high(HookList) do
|
|
if HookList[i1].owner <> 0 then begin
|
|
b1 := false;
|
|
break;
|
|
end;
|
|
finally LeaveCriticalSection(HookSection) end;
|
|
if result then begin
|
|
if ch <> nil then begin
|
|
ch.FLeakUnhook := not wait;
|
|
ch.Free;
|
|
end;
|
|
if b1 then
|
|
UnhookLoadLibrary(wait);
|
|
end;
|
|
end;
|
|
{$ifdef log}
|
|
log('UnhookCode (nextHook: ' + IntToHexExA(NativeUInt(@nextHook)) +
|
|
', dontUnhookHelperHooks: ' + booleanToCharA(dontUnhookHelperHooks) +
|
|
', wait: ' + booleanToCharA(wait) + ') -> ' + booleanToCharA(result));
|
|
{$endif}
|
|
end;
|
|
|
|
function UnhookCode(var nextHook: pointer) : bool; stdcall;
|
|
begin
|
|
result := UnhookCodeEx(nextHook, false, true);
|
|
end;
|
|
|
|
function UnhookAPI(var nextHook: pointer) : bool; stdcall;
|
|
begin
|
|
result := UnhookCodeEx(nextHook, false, true);
|
|
end;
|
|
|
|
function IsProcessBlocked : boolean; forward;
|
|
|
|
var LastFullCheckHooksTime : dword = 0;
|
|
procedure CheckHooks(dll: HMODULE);
|
|
var {$ifdef CheckRecursion}
|
|
recTestBuf : array [0..3] of pointer;
|
|
{$endif}
|
|
i1, i2 : integer;
|
|
ach : array of THookItem;
|
|
m1 : HMODULE;
|
|
p1 : pointer;
|
|
error : dword;
|
|
b1 : boolean;
|
|
fullCheck : boolean;
|
|
dllName : PWideChar;
|
|
jmpSize : dword;
|
|
begin
|
|
error := GetLastError;
|
|
{$ifdef CheckRecursion}
|
|
if NotRecursedYet(@CheckHooks, @recTestBuf) then begin
|
|
{$endif}
|
|
{$ifdef log}
|
|
log('CheckHooks (dll: ' + IntToHexExA(dll) + ')');
|
|
{$endif}
|
|
if not IsProcessBlocked then begin
|
|
try
|
|
if (dll <> 0) and HookReady then begin
|
|
// we don't check *all* API hooks more often than once every 50ms
|
|
fullCheck := (LastFullCheckHooksTime = 0) or (TickDif(LastFullCheckHooksTime) > 50);
|
|
dllName := nil;
|
|
if not fullCheck then begin
|
|
dllName := pointer(LocalAlloc(LPTR, MAX_PATH * 2 + 2));
|
|
if GetModuleFileNameW(dll, dllName, MAX_PATH) = 0 then
|
|
fullCheck := true;
|
|
end;
|
|
if fullCheck then
|
|
LastFullCheckHooksTime := GetTickCount;
|
|
i2 := 0;
|
|
EnterCriticalSection(HookSection);
|
|
try
|
|
SetLength(ach, Length(HookList));
|
|
for i1 := 0 to high(HookList) do
|
|
if (HookList[i1].module <> '') and (fullCheck or (PosPCharW(PWideChar(pointer(HookList[i1].moduleW)), dllName) > 0)) then begin
|
|
ach[i2] := HookList[i1];
|
|
inc(i2);
|
|
end;
|
|
finally LeaveCriticalSection(HookSection) end;
|
|
if dllName <> nil then
|
|
LocalFree(HLOCAL(dllName));
|
|
b1 := false;
|
|
try
|
|
for i1 := 0 to i2 - 1 do
|
|
with ach[i1] do begin
|
|
if (ch = nil) or (TPByte(@ch.FNewCode)^ = $e9) then
|
|
jmpSize := 5
|
|
else
|
|
jmpSize := 6;
|
|
if (module <> '') and
|
|
( (ch = nil) or
|
|
IsBadReadPtrEx(ch.FPatchAddr, sizeOf(ch.FNewCode)) or
|
|
(TPCardinal(NativeUInt(ch.FPatchAddr))^ <> TPCardinal(NativeUInt(@ch.FNewCode))^) or
|
|
((jmpSize = 5) and (TPByte(NativeUInt(ch.FPatchAddr) + 4)^ <> TPByte(NativeUInt(@ch.FNewCode) + 4)^)) or
|
|
((jmpSize = 6) and (TPWord(NativeUInt(ch.FPatchAddr) + 4)^ <> TPWord(NativeUInt(@ch.FNewCode) + 4)^)) ) then begin
|
|
if not b1 then begin
|
|
b1 := true;
|
|
CollectCache_AddRef;
|
|
end;
|
|
m1 := GetModuleHandleW(pointer(moduleW));
|
|
if ordinal > 0 then
|
|
p1 := FindRealCode(GetImageProcAddress(m1, ordinal))
|
|
else
|
|
p1 := FindRealCode(GetImageProcAddress(m1, PAnsiChar(api), true));
|
|
if ((ch = nil) and (m1 <> 0)) or ((ch <> nil) and (code <> p1)) then begin
|
|
UnhookCodeEx(nextHook^, true, true);
|
|
HookCodeX(owner, m1, module, api, ordinal, p1, callback, nextHook^, flags, numParams);
|
|
end else
|
|
if ch <> nil then begin
|
|
if TPByte(@ch.FNewCode)^ = $e9 then
|
|
jmpSize := 5
|
|
else
|
|
jmpSize := 6;
|
|
if RenewOverwrittenHooks or
|
|
( (not IsBadReadPtrEx(ch.FPatchAddr, jmpSize)) and
|
|
(TPCardinal(NativeUInt(ch.FPatchAddr))^ = TPCardinal(NativeUInt(@ch.FOldCode))^) and
|
|
( ((jmpSize = 5) and (TPByte(NativeUInt(ch.FPatchAddr) + 4)^ = TPByte(NativeUInt(@ch.FOldCode) + 4)^)) or
|
|
((jmpSize = 6) and (TPWord(NativeUInt(ch.FPatchAddr) + 4)^ = TPWord(NativeUInt(@ch.FOldCode) + 4)^)) ) ) then
|
|
RenewHook(nextHook^);
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
if b1 then
|
|
CollectCache_Release;
|
|
end;
|
|
end;
|
|
except end;
|
|
end;
|
|
{$ifdef log}
|
|
log('CheckHooks (dll: ' + IntToHexExA(dll) + ') -> +');
|
|
{$endif}
|
|
{$ifdef CheckRecursion}
|
|
recTestBuf[3] := nil;
|
|
end;
|
|
{$endif}
|
|
SetLastError(error);
|
|
end;
|
|
|
|
procedure CollectHooks;
|
|
begin
|
|
{$ifdef log}
|
|
log('CollectHooks');
|
|
{$endif}
|
|
CollectCache_AddRef;
|
|
end;
|
|
|
|
procedure FlushHooks;
|
|
begin
|
|
{$ifdef log}
|
|
log('FlushHooks');
|
|
{$endif}
|
|
CollectCache_Release;
|
|
end;
|
|
|
|
function RestoreCode(code: pointer) : bool; stdcall;
|
|
var module : HMODULE;
|
|
orgCode : int64;
|
|
s1 : AnsiString;
|
|
op : dword;
|
|
begin
|
|
{$ifdef log}
|
|
log('RestoreCode (code: ' + IntToHexExA(NativeUInt(code)) + ')');
|
|
{$endif}
|
|
result := false;
|
|
CollectCache_AddRef;
|
|
try
|
|
if FindModule(code, module, s1) and WasCodeChanged(module, code, orgCode) and (orgCode <> 0) then begin
|
|
{$ifndef win64}
|
|
InitUnprotectMemory;
|
|
{$endif}
|
|
if VirtualProtect(code, 8, PAGE_EXECUTE_READWRITE, @op) then begin
|
|
result := true;
|
|
if not AtomicMove(@orgCode, code, sizeOf(orgCode)) then
|
|
int64(code^) := orgCode;
|
|
FlushInstructionCache(code, sizeOf(orgCode));
|
|
VirtualProtect(code, 8, op, @op);
|
|
end;
|
|
end;
|
|
finally CollectCache_Release end;
|
|
{$ifdef log}
|
|
log('RestoreCode (code: ' + IntToHexExA(NativeUInt(code)) + ') -> ' + booleanToCharA(result));
|
|
{$endif}
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
function AmUsingInputDesktop : bool; stdcall;
|
|
var {$ifdef CheckRecursion}
|
|
recTestBuf : array [0..3] of pointer;
|
|
{$endif}
|
|
s1, s2 : AnsiString;
|
|
h1 : THandle;
|
|
c1 : dword;
|
|
error : dword;
|
|
begin
|
|
error := GetLastError;
|
|
{$ifdef CheckRecursion}
|
|
result := false;
|
|
if NotRecursedYet(@AmUsingInputDesktop, @recTestBuf) then begin
|
|
{$endif}
|
|
{$ifdef log}
|
|
log('AmUsingInputDesktop');
|
|
{$endif}
|
|
SetLength(s1, MAX_PATH + 1);
|
|
s1[1] := #0;
|
|
h1 := OpenInputDesktop(0, false, READ_CONTROL or GENERIC_READ);
|
|
GetUserObjectInformationA(h1, UOI_NAME, PAnsiChar(s1), MAX_PATH, c1);
|
|
CloseDesktop(h1);
|
|
s1 := PAnsiChar(s1);
|
|
SetLength(s2, MAX_PATH + 1);
|
|
s2[1] := #0;
|
|
h1 := GetThreadDesktop(GetCurrentThreadId);
|
|
GetUserObjectInformationA(h1, UOI_NAME, PAnsiChar(s2), MAX_PATH, c1);
|
|
CloseDesktop(h1);
|
|
s2 := PAnsiChar(s2);
|
|
result := (s1 <> '') and (s1 = s2);
|
|
{$ifdef log}
|
|
log('AmUsingInputDesktop -> ' + booleanToCharA(result));
|
|
{$endif}
|
|
{$ifdef CheckRecursion}
|
|
recTestBuf[3] := nil;
|
|
end;
|
|
{$endif}
|
|
SetLastError(error);
|
|
end;
|
|
|
|
function InternalGetCallingModule(returnAddress: pointer) : HMODULE;
|
|
var {$ifdef CheckRecursion}
|
|
recTestBuf : array [0..3] of pointer;
|
|
{$endif}
|
|
mbi : TMemoryBasicInformation;
|
|
w1 : word;
|
|
i1 : integer;
|
|
index : integer;
|
|
error : dword;
|
|
begin
|
|
result := 0;
|
|
error := GetLastError;
|
|
{$ifdef CheckRecursion}
|
|
if NotRecursedYet(@InternalGetCallingModule, @recTestBuf) then begin
|
|
{$endif}
|
|
{$ifdef log}
|
|
log('GetCallingModule');
|
|
{$endif}
|
|
if (VirtualQuery(returnAddress, mbi, sizeOf(mbi)) <> sizeOf(mbi)) or (not TryRead(mbi.AllocationBase, @w1, 2)) or (w1 <> CEMAGIC) then begin
|
|
if HookReady then begin
|
|
EnterCriticalSection(HookSection);
|
|
try
|
|
for i1 := high(HookList) downto 0 do begin
|
|
if (HookList[i1].ch <> nil) and
|
|
(HookList[i1].ch.FInUseCodeArr <> nil) and
|
|
(NativeUInt(returnAddress) >= NativeUInt(HookList[i1].ch.FInUseCodeArr)) and
|
|
(NativeUInt(returnAddress) < NativeUInt(HookList[i1].ch.FInUseCodeArr) + sizeOf(TInUseCodeArr)) then begin
|
|
index := (NativeUInt(returnAddress) - NativeUInt(HookList[i1].ch.FInUseCodeArr)) div sizeOf(TInUseItem);
|
|
returnAddress := HookList[i1].ch.FInUseTargetArr[index];
|
|
if HookList[i1].ch.FStoreThreadState then
|
|
{$ifdef win64}
|
|
returnAddress := pointer(pointer(NativeUInt(returnAddress) + $20)^);
|
|
{$else}
|
|
returnAddress := pointer(pointer(NativeUInt(returnAddress) + $10)^);
|
|
{$endif}
|
|
if (VirtualQuery(returnAddress, mbi, sizeOf(mbi)) = sizeOf(mbi)) and
|
|
TryRead(mbi.AllocationBase, @w1, 2) and (w1 = CEMAGIC) then
|
|
result := HMODULE(mbi.AllocationBase);
|
|
break;
|
|
end;
|
|
end;
|
|
finally LeaveCriticalSection(HookSection) end;
|
|
end;
|
|
end else
|
|
result := HMODULE(mbi.AllocationBase);
|
|
{$ifdef log}
|
|
log('GetCallingModule -> ' + IntToHexExA(result));
|
|
{$endif}
|
|
{$ifdef CheckRecursion}
|
|
recTestBuf[3] := nil;
|
|
end;
|
|
{$endif}
|
|
SetLastError(error);
|
|
end;
|
|
|
|
{$ifdef win64}
|
|
|
|
function RtlGetCallersAddress(var caller, callersCaller: pointer) : HRESULT; stdcall; external 'ntdll.dll';
|
|
function GetCallingModule(returnAddress: pointer = nil) : HMODULE; stdcall;
|
|
var dummy : pointer;
|
|
error : dword;
|
|
begin
|
|
if returnAddress = nil then begin
|
|
error := GetLastError;
|
|
RtlGetCallersAddress(dummy, returnAddress);
|
|
SetLastError(error);
|
|
end;
|
|
result := InternalGetCallingModule(returnAddress);
|
|
end;
|
|
|
|
{$else}
|
|
|
|
{$W+}
|
|
|
|
function GetCallingModule(returnAddress: pointer = nil) : HMODULE; stdcall;
|
|
var ebp_ : NativeUInt;
|
|
error : dword;
|
|
begin
|
|
asm
|
|
mov ebp_, ebp
|
|
end;
|
|
if returnAddress = nil then begin
|
|
error := GetLastError;
|
|
if TryRead(pointer(ebp_), @ebp_, sizeOf(pointer)) then
|
|
TryRead(pointer(ebp_ + sizeOf(pointer)), @returnAddress, sizeOf(pointer));
|
|
SetLastError(error);
|
|
end;
|
|
result := InternalGetCallingModule(returnAddress);
|
|
end;
|
|
|
|
{$W-}
|
|
|
|
{$endif}
|
|
|
|
var NtQueryInformationProcess : function (ph: THandle; level: dword; buf: pointer; len: dword; retLen: pointer) : dword; stdcall = nil;
|
|
function GetProcessSessionId(pid: dword) : dword;
|
|
var {$ifdef CheckRecursion}
|
|
recTestBuf : array [0..3] of pointer;
|
|
{$endif}
|
|
pid2sid : function (processId: dword; var sessionId: dword) : bool; stdcall;
|
|
error : dword;
|
|
begin
|
|
error := GetLastError;
|
|
{$ifdef CheckRecursion}
|
|
result := 0;
|
|
if NotRecursedYet(@GetProcessSessionId, @recTestBuf) then begin
|
|
{$endif}
|
|
{$ifdef log}
|
|
log('GetProcessSessionId (pid: ' + IntToHexExA(pid) + ')');
|
|
{$endif}
|
|
pid2sid := KernelProc(CProcessIdToSessionId);
|
|
if ((@pid2sid = nil) or (not pid2sid(pid, result))) and (pid = GetCurrentProcessId) then begin
|
|
if @NtQueryInformationProcess = nil then
|
|
NtQueryInformationProcess := NtProc(CNtQueryInformationProcess);
|
|
if (@NtQueryInformationProcess = nil) or (NtQueryInformationProcess(GetCurrentProcess, 24 (*ProcessSessionInformation*), @result, sizeOf(result), nil) <> 0) then
|
|
result := 0;
|
|
end;
|
|
{$ifdef log}
|
|
log('GetProcessSessionId (pid: ' + IntToHexExA(pid) + ') -> ' + IntToStrExA(result));
|
|
{$endif}
|
|
{$ifdef CheckRecursion}
|
|
recTestBuf[3] := nil;
|
|
end;
|
|
{$endif}
|
|
SetLastError(error);
|
|
end;
|
|
|
|
function IsSystemProcess(processHandle: THandle) : boolean;
|
|
var {$ifdef CheckRecursion}
|
|
recTestBuf : array [0..3] of pointer;
|
|
{$endif}
|
|
saa : PSidAndAttributes;
|
|
error : dword;
|
|
session : dword;
|
|
begin
|
|
error := GetLastError;
|
|
{$ifdef CheckRecursion}
|
|
result := false;
|
|
if NotRecursedYet(@IsSystemProcess, @recTestBuf) then begin
|
|
{$endif}
|
|
result := true;
|
|
saa := nil;
|
|
if GetProcessSid(processHandle, saa) and (saa <> nil) then begin
|
|
result := (saa^.Sid = nil) or (TPAByte(saa^.Sid)^[1] <= 1);
|
|
LocalFree(HLOCAL(saa));
|
|
end else begin
|
|
// asking the Sid of a process in another session sometimes doesn't work
|
|
// processes in other sessions are usually not system processes
|
|
session := GetProcessSessionId(ProcessHandleToId(processHandle));
|
|
if (session <> 0) and (session <> GetProcessSessionId(GetCurrentProcessId)) then
|
|
result := false;
|
|
end;
|
|
{$ifdef CheckRecursion}
|
|
recTestBuf[3] := nil;
|
|
end;
|
|
{$endif}
|
|
SetLastError(error);
|
|
end;
|
|
|
|
type
|
|
PROCESS_BASIC_INFORMATION = record
|
|
exitStatus : NativeUInt;
|
|
pebBaseAddress : NativeUInt;
|
|
affinityMask : NativeUInt;
|
|
basePriority : NativeUInt;
|
|
pid : NativeUInt;
|
|
parentPid : NativeUInt;
|
|
end;
|
|
PROCESS_EXTENDED_BASIC_INFORMATION = record
|
|
Size : NativeUInt;
|
|
BasicInfo : PROCESS_BASIC_INFORMATION;
|
|
Flags : dword;
|
|
end;
|
|
|
|
function GetPeb(process: THandle) : NativeUInt;
|
|
// get the process environment block ("peb")
|
|
var pbi : PROCESS_BASIC_INFORMATION;
|
|
begin
|
|
if @NtQueryInformationProcess = nil then
|
|
NtQueryInformationProcess := NtProc(CNtQueryInformationProcess);
|
|
if (@NtQueryInformationProcess <> nil) and (NtQueryInformationProcess(process, 0, @pbi, sizeOf(pbi), nil) = 0) then
|
|
result := pbi.pebBaseAddress
|
|
else result := 0;
|
|
end;
|
|
|
|
{$ifndef win64}
|
|
|
|
var NtWow64QueryInformationProcess64 : function (ph: THandle; level: dword; buf: pointer; len, retLen: dword) : dword; stdcall = nil;
|
|
function GetPeb64(process: THandle) : int64;
|
|
// get the 64bit process environment block ("peb")
|
|
var pbi : record
|
|
ExitStatus : dword;
|
|
PebBaseAddress : int64;
|
|
AffinityMask : int64;
|
|
BasePriority : dword;
|
|
ProcessId : int64;
|
|
ParentProcessId : int64;
|
|
end;
|
|
begin
|
|
if @NtWow64QueryInformationProcess64 = nil then
|
|
NtWow64QueryInformationProcess64 := NtProc(CNtWow64QueryInfoProcess64);
|
|
if (@NtWow64QueryInformationProcess64 <> nil) and (NtWow64QueryInformationProcess64(process, 0, @pbi, sizeOf(pbi), 0) = 0) then
|
|
result := pbi.PebBaseAddress
|
|
else result := 0;
|
|
end;
|
|
|
|
var NtWow64ReadVirtualMemory64 : function (ph: THandle; baseAddress: int64; buf: pointer; bufSize: int64; var read: int64) : dword; stdcall = nil;
|
|
function ReadProcessMemory64(process: THandle; baseAddress: int64; buf: pointer; bufSize: dword) : boolean;
|
|
var read64 : int64;
|
|
begin
|
|
if @NtWow64ReadVirtualMemory64 = nil then
|
|
NtWow64ReadVirtualMemory64 := NtProc(CNtWow64ReadVirtualMemory64);
|
|
result := (@NtWow64ReadVirtualMemory64 <> nil) and
|
|
(NtWow64ReadVirtualMemory64(process, baseAddress, buf, bufSize, read64) = 0) and
|
|
(read64 = bufSize);
|
|
end;
|
|
|
|
{$endif}
|
|
|
|
var CsVersion : integer = -1;
|
|
function IsProcessBlocked : boolean;
|
|
|
|
// beginning with 2k3sp1 TRtlCriticalVersion.LockCount behaves differently
|
|
function IsNewCs : boolean;
|
|
var cs : TRtlCriticalSection;
|
|
begin
|
|
if CsVersion = -1 then begin
|
|
InitializeCriticalSection(cs);
|
|
EnterCriticalSection(cs);
|
|
if cs.LockCount <> 0 then
|
|
CsVersion := 1
|
|
else
|
|
CsVersion := 0;
|
|
LeaveCriticalSection(cs);
|
|
DeleteCriticalSection(cs);
|
|
end;
|
|
result := CsVersion = 1;
|
|
end;
|
|
|
|
function GetTebIndex(index: NativeUInt) : pointer;
|
|
asm
|
|
{$ifdef win64}
|
|
.noframe
|
|
mov rax, gs:[abs $30]
|
|
mov rax, [rax + $60]
|
|
add rax, index
|
|
mov rax, [rax]
|
|
{$else}
|
|
mov ecx, fs:[$30]
|
|
add ecx, index
|
|
mov eax, [ecx]
|
|
{$endif}
|
|
end;
|
|
|
|
function IsSectionLocked(section: pointer) : boolean;
|
|
begin
|
|
result := section <> nil;
|
|
if result then
|
|
if IsNewCS then
|
|
result := PRtlCriticalSection(section).LockCount and 1 = 0
|
|
else
|
|
result := PRtlCriticalSection(section).LockCount > -1;
|
|
end;
|
|
|
|
begin
|
|
result := IsSectionLocked(GetTebIndex({$ifdef win64}$110{$else}$a0{$endif})); // loader lock -> initialization
|
|
end;
|
|
|
|
(*function IsNativeProcess(processHandle: THandle) : boolean;
|
|
var peb, subSystem, c1 : NativeUInt;
|
|
begin
|
|
peb := GetPeb(processHandle);
|
|
result := (peb <> 0) and
|
|
ReadProcessMemory(processHandle, pointer(peb + $b4), @subSystem, 4, c1) and
|
|
(subSystem = IMAGE_SUBSYSTEM_NATIVE);
|
|
end;*)
|
|
|
|
function IsProtectedProcess(process: THandle) : boolean;
|
|
var pbi : PROCESS_EXTENDED_BASIC_INFORMATION;
|
|
begin
|
|
if @NtQueryInformationProcess = nil then
|
|
NtQueryInformationProcess := NtProc(CNtQueryInformationProcess);
|
|
ZeroMemory(@pbi, sizeOf(pbi));
|
|
pbi.Size := sizeof(pbi);
|
|
if (@NtQueryInformationProcess <> nil) and (NtQueryInformationProcess(process, 0, @pbi, sizeOf(pbi), nil) = 0) then
|
|
result := pbi.Flags and $1 <> 0
|
|
else result := false;
|
|
end;
|
|
|
|
function IsMetroApp(process: THandle) : boolean;
|
|
var pbi : PROCESS_EXTENDED_BASIC_INFORMATION;
|
|
begin
|
|
if @NtQueryInformationProcess = nil then
|
|
NtQueryInformationProcess := NtProc(CNtQueryInformationProcess);
|
|
ZeroMemory(@pbi, sizeOf(pbi));
|
|
pbi.Size := sizeof(pbi);
|
|
if (@NtQueryInformationProcess <> nil) and (NtQueryInformationProcess(process, 0, @pbi, sizeOf(pbi), nil) = 0) then
|
|
result := pbi.Flags and $f0 <> 0
|
|
else result := false;
|
|
end;
|
|
|
|
function AmSystemProcess : bool; stdcall;
|
|
begin
|
|
{$ifdef log}
|
|
log('AmSystemProcess');
|
|
{$endif}
|
|
result := IsSystemProcess(GetCurrentProcess);
|
|
{$ifdef log}
|
|
log('AmSystemProcess -> ' + booleanToCharA(result));
|
|
{$endif}
|
|
end;
|
|
|
|
function GetCurrentSessionId : dword; stdcall;
|
|
begin
|
|
{$ifdef log}
|
|
log('GetCurrentSessionId');
|
|
{$endif}
|
|
result := GetProcessSessionId(GetCurrentProcessId);
|
|
{$ifdef log}
|
|
log('GetCurrentSessionId -> ' + IntToHexExA(result));
|
|
{$endif}
|
|
end;
|
|
|
|
function GetInputSessionId : dword; stdcall;
|
|
var {$ifdef CheckRecursion}
|
|
recTestBuf : array [0..3] of pointer;
|
|
{$endif}
|
|
gacsid : function : dword; stdcall;
|
|
error : dword;
|
|
begin
|
|
error := GetLastError;
|
|
{$ifdef CheckRecursion}
|
|
result := 0;
|
|
if NotRecursedYet(@GetInputSessionId, @recTestBuf) then begin
|
|
{$endif}
|
|
{$ifdef log}
|
|
log('GetInputSessionId');
|
|
{$endif}
|
|
gacsid := KernelProc(CGetInputSessionId);
|
|
if @gacsid <> nil then
|
|
result := gacsid
|
|
else result := 0;
|
|
{$ifdef log}
|
|
log('GetInputSessionId -> ' + IntToHexExA(result));
|
|
{$endif}
|
|
{$ifdef CheckRecursion}
|
|
recTestBuf[3] := nil;
|
|
end;
|
|
{$endif}
|
|
SetLastError(error);
|
|
end;
|
|
|
|
function ProcessIdToFileNameEx(processId: dword; fileNameA: PAnsiChar; fileNameW: PWideChar; bufLenInChars: dword; enumIfNecessary: boolean) : boolean;
|
|
// find out what file the specified process was executed from
|
|
const PROCESS_QUERY_LIMITED_INFORMATION = $1000;
|
|
var {$ifdef CheckRecursion}
|
|
recTestBuf : array [0..3] of pointer;
|
|
{$endif}
|
|
prcs : TDAProcess;
|
|
i1 : integer;
|
|
error : dword;
|
|
ph : THandle;
|
|
len1 : NativeUInt;
|
|
len2 : dword;
|
|
peb : NativeUInt;
|
|
peb64 : int64;
|
|
pp : NativeUInt;
|
|
ifn : TUnicodeStr;
|
|
{$ifndef win64}
|
|
pp64 : int64;
|
|
ifn64 : TUnicodeStr64;
|
|
{$endif}
|
|
ifnLen : dword;
|
|
flags : dword;
|
|
readOk : boolean;
|
|
arrChW : array [0..MAX_PATH - 1] of wideChar;
|
|
begin
|
|
result := false;
|
|
error := GetLastError;
|
|
prcs := nil;
|
|
{$ifdef CheckRecursion}
|
|
if NotRecursedYet(@ProcessIdToFileNameEx, @recTestBuf) then begin
|
|
{$endif}
|
|
if fileNameA <> nil then
|
|
fileNameW := pointer(LocalAlloc(LPTR, bufLenInChars * 2));
|
|
fileNameW[0] := '?';
|
|
fileNameW[1] := #0;
|
|
if processId <> 0 then begin
|
|
if @QueryFullProcessImageName = nil then begin
|
|
QueryFullProcessImageName := KernelProc(CQueryFullProcessImageName);
|
|
if @QueryFullProcessImageName = nil then
|
|
QueryFullProcessImageName := pointer(1);
|
|
end;
|
|
if NativeUInt(@QueryFullProcessImageName) > 1 then begin
|
|
ph := OpenProcess(PROCESS_QUERY_INFORMATION, false, processId);
|
|
if ph = 0 then
|
|
ph := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, processId);
|
|
if ph <> 0 then begin
|
|
len2 := bufLenInChars;
|
|
result := QueryFullProcessImageName(ph, 0, fileNameW, len2);
|
|
CloseHandle(ph);
|
|
end;
|
|
end else begin
|
|
ph := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, processId);
|
|
if ph <> 0 then begin
|
|
peb := GetPeb(ph);
|
|
{$ifndef win64}
|
|
if Is64bitOS then
|
|
peb64 := GetPeb64(ph)
|
|
else
|
|
{$endif}
|
|
peb64 := 0;
|
|
if (peb <> 0) or (peb64 <> 0) then begin
|
|
readOk := false;
|
|
ifnLen := 0;
|
|
if peb64 <> 0 then begin
|
|
{$ifndef win64}
|
|
if ReadProcessMemory64(ph, peb64 + $20, @ pp64, sizeOf( pp64)) and (pp64 <> 0) and
|
|
ReadProcessMemory64(ph, pp64 + $08, @flags, sizeOf(flags)) and
|
|
ReadProcessMemory64(ph, pp64 + $60, @ifn64, sizeOf(ifn64)) and (ifn64.len > 0) and (ifn64.str <> 0) then begin
|
|
if ifn64.len >= bufLenInChars * 2 then begin
|
|
ifn64.str := ifn64.str + ifn64.len - (bufLenInChars - 1) * 2;
|
|
ifn64.len := (bufLenInChars - 1) * 2;
|
|
end;
|
|
if flags and $1 = 0 then // PROCESS_PARAMETERS_NORMALIZED
|
|
ifn64.str := ifn64.str + pp64;
|
|
readOk := ReadProcessMemory64(ph, ifn64.str, fileNameW, ifn64.len);
|
|
ifnLen := ifn64.len;
|
|
end;
|
|
{$endif}
|
|
end else
|
|
{$ifdef win64}
|
|
if ReadProcessMemory(ph, pointer(peb + $20), @ pp, sizeOf( pp), len1) and (len1 = sizeOf( pp)) and (pp <> 0) and
|
|
ReadProcessMemory(ph, pointer( pp + $08), @flags, sizeOf(flags), len1) and (len1 = sizeOf(flags)) and
|
|
ReadProcessMemory(ph, pointer( pp + $60), @ ifn, sizeOf( ifn), len1) and (len1 = sizeOf( ifn)) and (ifn.len > 0) and (ifn.str <> nil) then begin
|
|
{$else}
|
|
if ReadProcessMemory(ph, pointer(peb + $10), @ pp, sizeOf( pp), len1) and (len1 = sizeOf( pp)) and (pp <> 0) and
|
|
ReadProcessMemory(ph, pointer( pp + $08), @flags, sizeOf(flags), len1) and (len1 = sizeOf(flags)) and
|
|
ReadProcessMemory(ph, pointer( pp + $38), @ ifn, sizeOf( ifn), len1) and (len1 = sizeOf( ifn)) and (ifn.len > 0) and (ifn.str <> nil) then begin
|
|
{$endif}
|
|
if ifn.len >= bufLenInChars * 2 then begin
|
|
ifn.str := pointer(NativeUInt(ifn.str) + dword(ifn.len) - (bufLenInChars - 1) * 2);
|
|
ifn.len := (bufLenInChars - 1) * 2;
|
|
end;
|
|
if flags and $1 = 0 then // PROCESS_PARAMETERS_NORMALIZED
|
|
NativeUInt(ifn.str) := NativeUInt(ifn.str) + pp;
|
|
readOk := ReadProcessMemory(ph, ifn.str, fileNameW, ifn.len, len1) and (len1 = ifn.len);
|
|
ifnLen := ifn.len;
|
|
end;
|
|
if readOk then begin
|
|
fileNameW[ifnLen div 2] := #0;
|
|
if (ifnLen > 8) and (fileNameW[0] = '\') and (fileNameW[1] = '?') and (fileNameW[2] = '?') and (fileNameW[3] = '\') then
|
|
Move(fileNameW[4], fileNameW[0], ifnLen + 2 - 4 * 2)
|
|
else
|
|
if (ifnLen > 24) and (fileNameW[0] = '\') and (fileNameW[1] = 'S') and (fileNameW[ 2] = 'y') and (fileNameW[ 3] = 's') and
|
|
(fileNameW[4] = 't') and (fileNameW[5] = 'e') and (fileNameW[ 6] = 'm') and (fileNameW[ 7] = 'R') and
|
|
(fileNameW[8] = 'o') and (fileNameW[9] = 'o') and (fileNameW[10] = 't') and (fileNameW[11] = '\') then begin
|
|
len2 := GetWindowsDirectoryW(arrChW, MAX_PATH);
|
|
if dword(ifnLen) div 2 + 1 - 11 + len2 >= bufLenInChars then begin
|
|
ifnLen := (bufLenInChars - 1 + 11 - len2) * 2;
|
|
fileNameW[ifnLen div 2] := #0;
|
|
end;
|
|
Move(fileNameW[11], fileNameW[len2], ifnLen + 2 - 11 * 2);
|
|
Move(arrChW[0], fileNameW[0], len2 * 2);
|
|
end;
|
|
result := true;
|
|
end;
|
|
end;
|
|
CloseHandle(ph);
|
|
end;
|
|
end;
|
|
end;
|
|
if (not result) and enumIfNecessary then begin
|
|
prcs := EnumProcesses;
|
|
for i1 := 0 to high(prcs) do
|
|
if prcs[i1].id = processId then begin
|
|
result := true;
|
|
if dword(Length(prcs[i1].exeFile)) >= bufLenInChars then
|
|
SetLength(prcs[i1].exeFile, bufLenInChars - 1);
|
|
lstrcpyW(fileNameW, PWideChar(prcs[i1].exeFile));
|
|
break;
|
|
end;
|
|
end;
|
|
if fileNameA <> nil then begin
|
|
if result then
|
|
WideToAnsi(fileNameW, fileNameA);
|
|
LocalFree(HLOCAL(fileNameW));
|
|
end;
|
|
|
|
{$ifdef CheckRecursion}
|
|
recTestBuf[3] := nil;
|
|
end;
|
|
{$endif}
|
|
SetLastError(error);
|
|
end;
|
|
|
|
function ProcessIdToFileNameA(processId: dword; fileName: PAnsiChar; bufLenInChars: word) : bool; stdcall;
|
|
begin
|
|
result := ProcessIdToFileNameEx(processId, fileName, nil, bufLenInChars, true);
|
|
end;
|
|
|
|
function ProcessIdToFileNameW(processId: dword; fileName: PWideChar; bufLenInChars: word) : bool; stdcall;
|
|
begin
|
|
result := ProcessIdToFileNameEx(processId, nil, fileName, bufLenInChars, true);
|
|
end;
|
|
|
|
function ProcessIdToFileName(processId: dword; var fileName: AnsiString) : boolean;
|
|
var buf : PWideChar;
|
|
begin
|
|
buf := pointer(LocalAlloc(LPTR, 64 * 1024));
|
|
result := ProcessIdToFileNameEx(processId, nil, buf, 32 * 1024, true);
|
|
if result and (buf[0] <> #0) then begin
|
|
SetLength(fileName, lstrlenW(buf));
|
|
WideToAnsi(buf, PAnsiChar(fileName));
|
|
end else
|
|
fileName := '';
|
|
LocalFree(HLOCAL(buf));
|
|
end;
|
|
|
|
function GetProcessCommandLine(processHandle: THandle; is64bitProcess: boolean) : UnicodeString;
|
|
// retrieve the command line of the specified process
|
|
var {$ifdef CheckRecursion}
|
|
recTestBuf : array [0..3] of pointer;
|
|
{$endif}
|
|
error : dword;
|
|
len1 : NativeUInt;
|
|
len2 : dword;
|
|
peb : NativeUInt;
|
|
peb64 : int64;
|
|
pp : NativeUInt;
|
|
ifn : TUnicodeStr;
|
|
{$ifndef win64}
|
|
pp64 : int64;
|
|
ifn64 : TUnicodeStr64;
|
|
{$endif}
|
|
flags : dword;
|
|
arrChW : array [0..MAX_PATH - 1] of wideChar;
|
|
begin
|
|
result := '';
|
|
error := GetLastError;
|
|
{$ifdef CheckRecursion}
|
|
if NotRecursedYet(@GetProcessCommandLine, @recTestBuf) then begin
|
|
{$endif}
|
|
if processHandle <> 0 then begin
|
|
peb := GetPeb(processHandle);
|
|
{$ifndef win64}
|
|
if Is64bitOS then
|
|
peb64 := GetPeb64(processHandle)
|
|
else
|
|
{$endif}
|
|
peb64 := 0;
|
|
if (peb <> 0) or (peb64 <> 0) then begin
|
|
if peb64 <> 0 then begin
|
|
{$ifndef win64}
|
|
if ReadProcessMemory64(processHandle, peb64 + $20, @ pp64, sizeOf( pp64)) and (pp64 <> 0) and
|
|
ReadProcessMemory64(processHandle, pp64 + $08, @flags, sizeOf(flags)) and
|
|
ReadProcessMemory64(processHandle, pp64 + $70, @ifn64, sizeOf(ifn64)) and (ifn64.len > 0) and (ifn64.str <> 0) then begin
|
|
if flags and $1 = 0 then // PROCESS_PARAMETERS_NORMALIZED
|
|
ifn64.str := ifn64.str + pp64;
|
|
SetLength(result, ifn64.len div 2);
|
|
if not ReadProcessMemory64(processHandle, ifn64.str, PWideChar(result), ifn64.len) then
|
|
result := '';
|
|
end;
|
|
{$endif}
|
|
end else
|
|
{$ifdef win64}
|
|
if ReadProcessMemory(processHandle, pointer(peb + $20), @ pp, sizeOf( pp), len1) and (len1 = sizeOf( pp)) and (pp <> 0) and
|
|
ReadProcessMemory(processHandle, pointer( pp + $08), @flags, sizeOf(flags), len1) and (len1 = sizeOf(flags)) and
|
|
ReadProcessMemory(processHandle, pointer( pp + $70), @ ifn, sizeOf( ifn), len1) and (len1 = sizeOf( ifn)) and (ifn.len > 0) and (ifn.str <> nil) then begin
|
|
{$else}
|
|
if ReadProcessMemory(processHandle, pointer(peb + $10), @ pp, sizeOf( pp), len1) and (len1 = sizeOf( pp)) and (pp <> 0) and
|
|
ReadProcessMemory(processHandle, pointer( pp + $08), @flags, sizeOf(flags), len1) and (len1 = sizeOf(flags)) and
|
|
ReadProcessMemory(processHandle, pointer( pp + $40), @ ifn, sizeOf( ifn), len1) and (len1 = sizeOf( ifn)) and (ifn.len > 0) and (ifn.str <> nil) then begin
|
|
{$endif}
|
|
if flags and $1 = 0 then // PROCESS_PARAMETERS_NORMALIZED
|
|
NativeUInt(ifn.str) := NativeUInt(ifn.str) + pp;
|
|
SetLength(result, ifn.len div 2);
|
|
if not ReadProcessMemory(processHandle, ifn.str, PWideChar(result), ifn.len, len1) or (len1 <> ifn.len) then
|
|
result := '';
|
|
end;
|
|
result := PWideChar(result);
|
|
if (Length(result) > 4) and (result[1] = '\') and (result[2] = '?') and (result[3] = '?') and (result[4] = '\') then
|
|
Delete(result, 1, 4)
|
|
else
|
|
if (Length(result) > 12) and (result[1] = '\') and (result[ 2] = 'S') and (result[ 3] = 'y') and (result[ 4] = 's') and
|
|
(result[5] = 't') and (result[ 6] = 'e') and (result[ 7] = 'm') and (result[ 8] = 'R') and
|
|
(result[9] = 'o') and (result[10] = 'o') and (result[11] = 't') and (result[12] = '\') then begin
|
|
len2 := GetWindowsDirectoryW(arrChW, MAX_PATH);
|
|
arrChW[len2] := #0;
|
|
result := arrChW + Copy(result, 12, maxInt);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{$ifdef CheckRecursion}
|
|
recTestBuf[3] := nil;
|
|
end;
|
|
{$endif}
|
|
SetLastError(error);
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
const
|
|
SERVICE_KERNEL_DRIVER = $00000001;
|
|
SERVICE_SYSTEM_START = $00000001;
|
|
SERVICE_ERROR_NORMAL = $00000001;
|
|
|
|
SERVICE_CONTROL_STOP = 1;
|
|
|
|
SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED or $3f;
|
|
SERVICE_ALL_ACCESS = $01ff;
|
|
SERVICE_QUERY_STATUS = $0004;
|
|
|
|
SERVICE_STOPPED = 1;
|
|
SERVICE_RUNNING = 4;
|
|
|
|
type
|
|
TServiceStatus = record
|
|
dwServiceType : dword;
|
|
dwCurrentState : dword;
|
|
dwControlsAccepted : dword;
|
|
dwWin32ExitCode : dword;
|
|
dwServiceSpecificExitCode : dword;
|
|
dwCheckpoint : dword;
|
|
dwWaitHint : dword;
|
|
end;
|
|
|
|
TQueryServiceConfig = record
|
|
serviceType : dword;
|
|
startType : dword;
|
|
errorControl : dword;
|
|
pathName : PWideChar;
|
|
loadOrderGroup : PWideChar;
|
|
tagId : dword;
|
|
dependencies : PWideChar;
|
|
startName : PWideChar;
|
|
displayName : PWideChar;
|
|
end;
|
|
|
|
var
|
|
CloseServiceHandle : function (handle: THandle) : bool; stdcall;
|
|
ControlService : function (service: THandle; control: dword; var ss: TServiceStatus) : bool; stdcall;
|
|
QueryServiceStatus : function (service: THandle; var ss: TServiceStatus) : bool; stdcall;
|
|
DeleteService : function (service: THandle) : bool; stdcall;
|
|
OpenSCManagerW : function (machine, database: PWideChar; access: dword) : THandle; stdcall;
|
|
OpenServiceW : function (scMan: THandle; service: PWideChar; access: dword) : THandle; stdcall;
|
|
StartServiceW : function (service: THandle; argCnt: dword; args: pointer) : bool; stdcall;
|
|
CreateServiceW : function (scMan: THandle; service, display: PWideChar;
|
|
access, serviceType, startType, errorControl: dword;
|
|
pathName, loadOrderGroup: PWideChar; tagId: pointer;
|
|
dependencies, startName, password: PWideChar) : THandle; stdcall;
|
|
ChangeServiceConfigW : function (service: THandle; serviceType, startType, errorControl: dword;
|
|
pathName, loadOrderGroup: PWideChar; tagId: pointer;
|
|
dependencies, startName, password, displayName: PWideChar) : bool; stdcall;
|
|
QueryServiceConfigW : function (service: THandle; var buf: TQueryServiceConfig;
|
|
size: dword; var needed: dword) : bool; stdcall;
|
|
|
|
function InitServiceProcs : boolean;
|
|
var dll : HMODULE;
|
|
begin
|
|
result := @OpenSCManagerW <> nil;
|
|
if not result then begin
|
|
dll := LoadLibraryA(PAnsiChar(DecryptStr(CAdvApi32)));
|
|
CloseServiceHandle := GetProcAddress(dll, PAnsiChar(DecryptStr(CCloseServiceHandle)));
|
|
ControlService := GetProcAddress(dll, PAnsiChar(DecryptStr(CControlService)));
|
|
QueryServiceStatus := GetProcAddress(dll, PAnsiChar(DecryptStr(CQueryServiceStatus)));
|
|
DeleteService := GetProcAddress(dll, PAnsiChar(DecryptStr(CDeleteService)));
|
|
OpenSCManagerW := GetProcAddress(dll, PAnsiChar(DecryptStr(COpenSCManagerW)));
|
|
OpenServiceW := GetProcAddress(dll, PAnsiChar(DecryptStr(COpenServiceW)));
|
|
StartServiceW := GetProcAddress(dll, PAnsiChar(DecryptStr(CStartServiceW)));
|
|
CreateServiceW := GetProcAddress(dll, PAnsiChar(DecryptStr(CCreateServiceW)));
|
|
ChangeServiceConfigW := GetProcAddress(dll, PAnsiChar(DecryptStr(CChangeServiceConfigW)));
|
|
QueryServiceConfigW := GetProcAddress(dll, PAnsiChar(DecryptStr(CQueryServiceConfigW)));
|
|
result := @OpenSCManagerW <> nil;
|
|
end;
|
|
end;
|
|
|
|
function CheckService(driverName, fileName, description: PWideChar; start: boolean) : boolean;
|
|
var h1, h2 : THandle;
|
|
c1 : dword;
|
|
qsc : ^TQueryServiceConfig;
|
|
ss : TServiceStatus;
|
|
le : dword;
|
|
begin
|
|
result := false;
|
|
if InitServiceProcs then begin
|
|
// first we contact the service control manager
|
|
h1 := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
|
|
if h1 <> 0 then begin
|
|
// okay, that worked, now we try to open our service
|
|
h2 := OpenServiceW(h1, driverName, SERVICE_ALL_ACCESS);
|
|
if h2 <> 0 then begin
|
|
// our service is installed, let's check the parameters
|
|
c1 := 0;
|
|
QueryServiceConfigW(h2, TQueryServiceConfig(nil^), 0, c1);
|
|
if c1 <> 0 then begin
|
|
qsc := pointer(LocalAlloc(LPTR, c1 * 2));
|
|
if QueryServiceConfigW(h2, qsc^, c1 * 2, c1) then begin
|
|
if not QueryServiceStatus(h2, ss) then
|
|
ss.dwCurrentState := SERVICE_STOPPED;
|
|
// all is fine if either the parameters are already correct or:
|
|
// (1) if the service isn't running yet and
|
|
// (2) we're able to successfully reset the parameters
|
|
result := ( (qsc^.serviceType = SERVICE_KERNEL_DRIVER) and
|
|
(qsc^.startType = SERVICE_SYSTEM_START ) and
|
|
(qsc^.errorControl = SERVICE_ERROR_NORMAL ) and
|
|
(PosTextW(fileName, qsc^.pathName) > 0) ) or
|
|
( ChangeServiceConfigW(h2, SERVICE_KERNEL_DRIVER, SERVICE_SYSTEM_START, SERVICE_ERROR_NORMAL,
|
|
fileName, nil, nil, nil, nil, nil, description) and
|
|
(ss.dwCurrentState = SERVICE_STOPPED) );
|
|
if start then
|
|
result := result and ((ss.dwCurrentState = SERVICE_RUNNING) or StartServiceW(h2, 0, nil));
|
|
end;
|
|
le := GetLastError;
|
|
LocalFree(HLOCAL(qsc));
|
|
SetLastError(le);
|
|
end;
|
|
le := GetLastError;
|
|
CloseServiceHandle(h2);
|
|
SetLastError(le);
|
|
end;
|
|
le := GetLastError;
|
|
CloseServiceHandle(h1);
|
|
SetLastError(le);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function InstallInjectionDriver(driverName, fileName32bit, fileName64bit, description: PWideChar) : bool; stdcall;
|
|
var fileName : PWideChar;
|
|
c1, c2 : THandle;
|
|
sla : AnsiString;
|
|
slw : AnsiString;
|
|
libA : PAnsiChar;
|
|
le : dword;
|
|
begin
|
|
result := false;
|
|
libA := nil;
|
|
EnableAllPrivileges;
|
|
if Is64bitOS then
|
|
fileName := fileName64bit
|
|
else
|
|
fileName := fileName32bit;
|
|
if InitServiceProcs and CheckLibFilePath(libA, fileName, sla, slw) then begin
|
|
// first we contact the service control manager
|
|
c1 := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
|
|
if c1 <> 0 then begin
|
|
// okay, that worked, now we try to open our service
|
|
c2 := OpenServiceW(c1, driverName, SERVICE_ALL_ACCESS);
|
|
if c2 <> 0 then begin
|
|
// our service is already installed, so let's check the parameters
|
|
result := CheckService(driverName, fileName, description, false);
|
|
le := GetLastError;
|
|
CloseServiceHandle(c2);
|
|
SetLastError(le);
|
|
end else begin
|
|
// probably our service is not installed yet, so we do that now
|
|
c2 := CreateServiceW(c1, driverName, description,
|
|
SERVICE_ALL_ACCESS or STANDARD_RIGHTS_ALL,
|
|
SERVICE_KERNEL_DRIVER, SERVICE_SYSTEM_START,
|
|
SERVICE_ERROR_NORMAL, fileName, nil, nil, nil, nil, nil);
|
|
if c2 <> 0 then begin
|
|
// installation went smooth
|
|
result := true;
|
|
CloseServiceHandle(c2);
|
|
end;
|
|
end;
|
|
le := GetLastError;
|
|
CloseServiceHandle(c1);
|
|
SetLastError(le);
|
|
if result then begin
|
|
result := StartInjectionDriver(driverName);
|
|
if not result then begin
|
|
le := GetLastError;
|
|
UninstallInjectionDriver(driverName);
|
|
SetLastError(le);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function UninstallInjectionDriver(driverName: PWideChar) : bool; stdcall;
|
|
var c1, c2 : THandle;
|
|
le : dword;
|
|
begin
|
|
result := false;
|
|
if (driverName = nil) or (driverName[0] = #0) then
|
|
exit;
|
|
EnableAllPrivileges;
|
|
if InitServiceProcs then begin
|
|
// first we contact the service control manager
|
|
c1 := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
|
|
if c1 <> 0 then begin
|
|
// okay, that worked, now we try to open our service
|
|
c2 := OpenServiceW(c1, driverName, SERVICE_ALL_ACCESS or _DELETE);
|
|
if c2 <> 0 then begin
|
|
// our service is installed, let's try to remove it
|
|
result := DeleteService(c2);
|
|
le := GetLastError;
|
|
CloseServiceHandle(c2);
|
|
SetLastError(le);
|
|
end;
|
|
le := GetLastError;
|
|
CloseServiceHandle(c1);
|
|
SetLastError(le);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
const
|
|
FILE_DEVICE_UNKNOWN = $22;
|
|
METHOD_BUFFERED = 0;
|
|
FILE_READ_ACCESS = 1;
|
|
FILE_WRITE_ACCESS = 2;
|
|
|
|
function CTL_CODE(devtype, func, method, acc: dword) : dword;
|
|
begin
|
|
result := devtype shl 16 + acc shl 14 + func shl 2 + method;
|
|
end;
|
|
|
|
function Ioctl_InjectDll : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $800, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
|
|
function Ioctl_UninjectDll : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $801, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
|
|
function Ioctl_AllowUnload : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $802, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
|
|
function Ioctl_InjectMethod : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $803, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
|
|
function Ioctl_EnumInject : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $804, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
|
|
function Ioctl_InjectApproval : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $805, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
|
|
function Ioctl_AddPids : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $806, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
|
|
function Ioctl_Deactivate : dword; begin result := CTL_CODE(FILE_DEVICE_UNKNOWN, $807, METHOD_BUFFERED, FILE_READ_ACCESS or FILE_WRITE_ACCESS) end;
|
|
|
|
type
|
|
TDrvCmdHeader = packed record
|
|
Size : dword;
|
|
Hash : TDigest;
|
|
end;
|
|
TDrvCmdAllowUnload = packed record
|
|
Header : TDrvCmdHeader;
|
|
Allow : boolean;
|
|
end;
|
|
TDrvCmdInjectMethod = packed record
|
|
Header : TDrvCmdHeader;
|
|
Method : boolean;
|
|
end;
|
|
TDrvCmdEnumInject = packed record
|
|
Header : TDrvCmdHeader;
|
|
Index : integer;
|
|
end;
|
|
TDrvCmdDll = packed record
|
|
Header : TDrvCmdHeader;
|
|
OwnerHash : TDigest;
|
|
Session : dword;
|
|
Flags : dword;
|
|
X86AllocAddr : dword;
|
|
Dummy : array [0..9] of int64; // used by driver for temporary storage
|
|
Name : array [0..259] of WideChar;
|
|
IncludeLen : dword;
|
|
ExcludeLen : dword;
|
|
Data : array [0..0] of WideChar;
|
|
end;
|
|
TDrvCmdInjectApproval = packed record
|
|
Header : TDrvCmdHeader;
|
|
ApprovalId : dword;
|
|
Approved : boolean;
|
|
Dll : TDrvCmdDll;
|
|
end;
|
|
TDrvCmdAddPids = packed record
|
|
Header : TDrvCmdHeader;
|
|
PidCount : dword;
|
|
Pids : array [0..0] of dword;
|
|
end;
|
|
|
|
function SendDriverCommand(const driverName: UnicodeString; command: dword; var inbuf: TDrvCmdHeader; outbuf: pointer; outbufSize: dword) : boolean;
|
|
var fh : THandle;
|
|
c1 : dword;
|
|
pb1 : ^byte;
|
|
i1 : integer;
|
|
byte1 : byte;
|
|
le : dword;
|
|
buf : ^TDrvCmdHeader;
|
|
begin
|
|
fh := CreateFileW(PWideChar('\\.\' + driverName), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
|
|
if fh <> INVALID_HANDLE_VALUE then begin
|
|
buf := pointer(LocalAlloc(LPTR, inbuf.Size));
|
|
Move(inbuf, buf^, inbuf.Size);
|
|
c1 := GetCurrentThreadId;
|
|
pb1 := pointer(NativeUInt(buf) + sizeOf(TDrvCmdHeader));
|
|
for i1 := 0 to integer(buf.Size) - sizeOf(TDrvCmdHeader) - 1 do begin
|
|
pb1^ := pb1^ xor byte(c1) xor (i1 and $ff);
|
|
inc(pb1);
|
|
end;
|
|
buf.Hash := Hash(pointer(NativeUInt(buf) + sizeOf(TDrvCmdHeader)), buf.Size - sizeOf(TDrvCmdHeader));
|
|
pb1 := pointer(NativeUInt(buf) + sizeOf(TDrvCmdHeader));
|
|
for i1 := 0 to integer(buf.Size) - sizeOf(TDrvCmdHeader) - 1 do begin
|
|
byte1 := buf.Hash[i1 mod sizeOf(buf.Hash)];
|
|
pb1^ := pb1^ xor (byte1 shr 4) xor ((byte1 and $f) shl 4);
|
|
inc(pb1);
|
|
end;
|
|
result := DeviceIoControl(fh, command, buf, buf.Size, outbuf, outbufSize, c1, nil);
|
|
le := GetLastError;
|
|
CloseHandle(fh);
|
|
LocalFree(HLOCAL(buf));
|
|
SetLastError(le);
|
|
end else
|
|
result := false;
|
|
end;
|
|
|
|
procedure RegDelTree(key: HKEY; name: PWideChar);
|
|
var arrChW : PWideChar;
|
|
hk : HKEY;
|
|
c1 : dword;
|
|
begin
|
|
if RegOpenKeyExW(key, name, 0, KEY_ALL_ACCESS, hk) = 0 then begin
|
|
c1 := 0;
|
|
arrChW := pointer(LocalAlloc(LPTR, MAX_PATH * 2));
|
|
while RegEnumKeyW(hk, c1, arrChW, MAX_PATH) = 0 do begin
|
|
RegDelTree(hk, arrChW);
|
|
inc(c1);
|
|
end;
|
|
LocalFree(HLOCAL(arrChW));
|
|
RegCloseKey(hk);
|
|
end;
|
|
RegDeleteKeyW(key, name);
|
|
end;
|
|
|
|
function ConvertNtErrorCode(error: dword) : dword;
|
|
const CRtlNtStatusToDosError : AnsiString = (* RtlNtStatusToDosError *) #$07#$21#$39#$1B#$21#$06#$21#$34#$21#$20#$26#$01#$3A#$11#$3A#$26#$10#$27#$27#$3A#$27;
|
|
var ns2de : function (ntstatus: dword) : dword; stdcall;
|
|
begin
|
|
result := error;
|
|
if result and $c0000000 <> 0 then begin
|
|
ns2de := NtProc(CRtlNtStatusToDosError);
|
|
if @ns2de <> nil then
|
|
result := ns2de(result);
|
|
end;
|
|
end;
|
|
|
|
function StopInjectionDriver(driverName: PWideChar) : bool; stdcall;
|
|
var allow_ : TDrvCmdAllowUnload;
|
|
c1, c2 : THandle;
|
|
ss : TServiceStatus;
|
|
stopAllowed : boolean;
|
|
hk1, hk2 : HKEY;
|
|
arrChW2 : array [0..MAX_PATH] of WideChar;
|
|
nud : function (name: pointer) : dword; stdcall;
|
|
name : TUnicodeStr;
|
|
le : dword;
|
|
begin
|
|
result := false;
|
|
if (driverName = nil) or (driverName[0] = #0) then
|
|
exit;
|
|
EnableAllPrivileges;
|
|
allow_.Header.Size := sizeOf(allow_);
|
|
allow_.Allow := true;
|
|
stopAllowed := SendDriverCommand(driverName, Ioctl_AllowUnload, allow_.Header, nil, 0);
|
|
if InitServiceProcs then begin
|
|
c1 := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
|
|
if c1 = 0 then
|
|
c1 := OpenSCManagerW(nil, nil, 0);
|
|
if c1 <> 0 then begin
|
|
c2 := OpenServiceW(c1, PWideChar(driverName), SERVICE_ALL_ACCESS);
|
|
if c2 <> 0 then begin
|
|
ControlService(c2, SERVICE_CONTROL_STOP, ss);
|
|
result := QueryServiceStatus(c2, ss) and (ss.dwCurrentState = SERVICE_STOPPED);
|
|
CloseServiceHandle(c2);
|
|
end;
|
|
CloseServiceHandle(c1);
|
|
if (not result) and
|
|
(RegOpenKeyExA(HKEY_LOCAL_MACHINE, PAnsiChar(DecryptStr(CSystemCcsServices)), 0, KEY_ALL_ACCESS, hk1) = 0) then begin
|
|
if RegOpenKeyExW(HKEY_LOCAL_MACHINE, driverName, 0, KEY_READ, hk2) <> 0 then begin
|
|
// the service registry key for the driver is not there
|
|
// but we can still communicate with the driver
|
|
// that means that "LoadInjectionDriver" was used
|
|
// in order to unload that driver, we need to restore the registry
|
|
if RegCreateKeyExW(hk1, driverName, 0, nil, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nil, hk2, nil) = 0 then begin
|
|
c1 := 1;
|
|
if RegSetValueExA(hk2, PAnsiChar(DecryptStr(CType)), 0, REG_DWORD, @c1, 4) = 0 then begin
|
|
AnsiToWide(PAnsiChar('\' + DecryptStr(CRegistryMachine) + '\' + DecryptStr(CSystemCcsServices) + '\'), arrChW2);
|
|
lstrcatW(arrChW2, driverName);
|
|
name.str := arrChW2;
|
|
name.len := lstrlenW(name.str) * 2;
|
|
name.maxlen := name.len + 2;
|
|
nud := NtProc(CNtUnloadDriver);
|
|
if @nud <> nil then begin
|
|
SetLastError(ConvertNtErrorCode(nud(@name)));
|
|
result := GetLastError = 0;
|
|
end else
|
|
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
|
end;
|
|
le := GetLastError;
|
|
RegCloseKey(hk2);
|
|
RegDelTree(hk1, driverName);
|
|
SetLastError(le);
|
|
end;
|
|
end else begin
|
|
le := GetLastError;
|
|
RegCloseKey(hk2);
|
|
SetLastError(le);
|
|
end;
|
|
le := GetLastError;
|
|
RegCloseKey(hk1);
|
|
SetLastError(le);
|
|
end;
|
|
end;
|
|
end;
|
|
if (not result) and stopAllowed then begin
|
|
// stopping the driver failed, so let's secure stopping again
|
|
allow_.Allow := false;
|
|
le := GetLastError;
|
|
SendDriverCommand(driverName, Ioctl_AllowUnload, allow_.Header, nil, 0);
|
|
SetLastError(le);
|
|
end;
|
|
end;
|
|
|
|
function StartInjectionDriver(driverName: PWideChar) : bool; stdcall;
|
|
var c1, c2 : THandle;
|
|
ss : TServiceStatus;
|
|
le : dword;
|
|
begin
|
|
result := false;
|
|
if (driverName = nil) or (driverName[0] = #0) then
|
|
exit;
|
|
EnableAllPrivileges;
|
|
if InitServiceProcs then begin
|
|
c1 := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
|
|
if c1 = 0 then
|
|
c1 := OpenSCManagerW(nil, nil, 0);
|
|
if c1 <> 0 then begin
|
|
c2 := OpenServiceW(c1, PWideChar(driverName), SERVICE_ALL_ACCESS);
|
|
if c2 <> 0 then begin
|
|
result := (QueryServiceStatus(c2, ss) and (ss.dwCurrentState = SERVICE_RUNNING)) or
|
|
StartServiceW(c2, 0, nil);
|
|
le := GetLastError;
|
|
CloseServiceHandle(c2);
|
|
SetLastError(le);
|
|
end;
|
|
le := GetLastError;
|
|
CloseServiceHandle(c1);
|
|
SetLastError(le);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function IsInjectionDriverInstalled(driverName: PWideChar) : bool; stdcall;
|
|
var c1, c2 : THandle;
|
|
le : dword;
|
|
begin
|
|
result := false;
|
|
if (driverName = nil) or (driverName[0] = #0) then
|
|
exit;
|
|
EnableAllPrivileges;
|
|
if InitServiceProcs then begin
|
|
c1 := OpenSCManagerW(nil, nil, SC_MANAGER_ALL_ACCESS);
|
|
if c1 = 0 then
|
|
c1 := OpenSCManagerW(nil, nil, 0);
|
|
if c1 <> 0 then begin
|
|
c2 := OpenServiceW(c1, PWideChar(driverName), SERVICE_QUERY_STATUS);
|
|
if c2 <> 0 then begin
|
|
result := true;
|
|
CloseServiceHandle(c2);
|
|
end;
|
|
le := GetLastError;
|
|
CloseServiceHandle(c1);
|
|
SetLastError(le);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function IsInjectionDriverRunning(driverName: PWideChar) : bool; stdcall;
|
|
var fh : THandle;
|
|
begin
|
|
if (driverName = nil) or (driverName[0] = #0) then begin
|
|
result := false;
|
|
exit;
|
|
end;
|
|
EnableAllPrivileges;
|
|
fh := CreateFileW(PWideChar('\\.\' + UnicodeString(driverName)), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
|
|
result := fh <> INVALID_HANDLE_VALUE;
|
|
if result then
|
|
CloseHandle(fh);
|
|
end;
|
|
|
|
type
|
|
// types for the inject approval callback logic
|
|
TInjectApprovalCallbackItem = record
|
|
DriverName : UnicodeString;
|
|
Callback : TInjectApprovalCallbackRoutine;
|
|
CallbackContext : pointer;
|
|
Overl : OVERLAPPED;
|
|
AbortEvent : THandle;
|
|
Dll : TDrvCmdDll; // this must be last in the structure, because the exact size varies
|
|
end;
|
|
PInjectApprovalCallbackItem = ^TInjectApprovalCallbackItem;
|
|
TInjectApprovalCallbackList = array of PInjectApprovalCallbackItem;
|
|
|
|
var
|
|
// global vars for the inject approval callback logic
|
|
InjectApprovalCallbackReady : boolean = false;
|
|
InjectApprovalCallbackSection : TRTLCriticalSection;
|
|
InjectApprovalCallbackEmptyEvent : THandle = 0;
|
|
InjectApprovalCallbackList : TInjectApprovalCallbackList = nil;
|
|
InjectApprovalCallbackCount : integer = 0;
|
|
|
|
function InjectApprovalCallbackThread(info: PInjectApprovalCallbackItem) : dword; stdcall;
|
|
// every injection approval callback runs in its own thread
|
|
type
|
|
TInjectApprovalDriverMsg = packed record
|
|
TargetProcessId : dword;
|
|
ParentProcessId : dword;
|
|
SessionId : dword;
|
|
Flags : dword;
|
|
ApprovalId : dword;
|
|
end;
|
|
var injAppr : ^TDrvCmdInjectApproval;
|
|
fh : THandle;
|
|
c1 : dword;
|
|
pb1 : ^byte;
|
|
i1 : integer;
|
|
byte1 : byte;
|
|
outBuf : TInjectApprovalDriverMsg;
|
|
appr : boolean;
|
|
ph : THandle;
|
|
ws1, ws2 : UnicodeString;
|
|
wait : array [0..1] of THandle;
|
|
emptyEvent : THandle;
|
|
begin
|
|
injAppr := pointer(LocalAlloc(LPTR, info.Dll.Header.Size - sizeOf(TDrvCmdDll) + sizeOf(TDrvCmdInjectApproval)));
|
|
if injAppr <> nil then begin
|
|
// We send a DeviceIoControl message down to the driver.
|
|
// The driver will keep it "pending" until it wants to notify us about something.
|
|
// In order to allow us to cancel the whole thing, we use asynchronous communication.
|
|
fh := CreateFileW(PWideChar('\\.\' + info.DriverName), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
|
|
if fh <> INVALID_HANDLE_VALUE then begin
|
|
ZeroMemory(@outBuf, sizeOf(outBuf));
|
|
ZeroMemory(@info.Overl, sizeOf(info.Overl));
|
|
info.Overl.hEvent := CreateEvent(nil, true, false, nil);
|
|
// we will wait until either the driver replies, or until we want to cancel
|
|
wait[0] := info.Overl.hEvent;
|
|
wait[1] := info.AbortEvent;
|
|
appr := true;
|
|
// We do the whole thing in a loop, until we cancel, or until the driver reports an error.
|
|
// The driver will report an error if it's unloaded, or if the injection request was stopped.
|
|
// So we don't need to manually cleanup when unloading the driver or stopping an injection request!
|
|
while true do begin
|
|
injAppr.Header.Size := info.Dll.Header.Size - sizeOf(TDrvCmdDll) + sizeOf(TDrvCmdInjectApproval);
|
|
injAppr.ApprovalId := outBuf.ApprovalId;
|
|
injAppr.Approved := appr;
|
|
Move(info.Dll, injAppr.Dll, info.Dll.Header.Size);
|
|
c1 := GetCurrentThreadId;
|
|
pb1 := pointer(NativeUInt(injAppr) + sizeOf(TDrvCmdHeader));
|
|
for i1 := 0 to integer(injAppr.Header.Size) - sizeOf(TDrvCmdHeader) - 1 do begin
|
|
pb1^ := pb1^ xor byte(c1) xor (i1 and $ff);
|
|
inc(pb1);
|
|
end;
|
|
injAppr.Header.Hash := Hash(pointer(NativeUInt(injAppr) + sizeOf(TDrvCmdHeader)), injAppr.Header.Size - sizeOf(TDrvCmdHeader));
|
|
pb1 := pointer(NativeUInt(injAppr) + sizeOf(TDrvCmdHeader));
|
|
for i1 := 0 to integer(injAppr.Header.Size) - sizeOf(TDrvCmdHeader) - 1 do begin
|
|
byte1 := injAppr.Header.Hash[i1 mod sizeOf(injAppr.Header.Hash)];
|
|
pb1^ := pb1^ xor (byte1 shr 4) xor ((byte1 and $f) shl 4);
|
|
inc(pb1);
|
|
end;
|
|
// DeviceIoControl is supposed to fail with ERROR_IO_PENDING, because we're using asynchronous communication mode
|
|
if (not DeviceIoControl(fh, Ioctl_InjectApproval, injAppr, injAppr.Header.Size, @outBuf, sizeof(outBuf), c1, @info.Overl)) and (GetLastError <> ERROR_IO_PENDING) then
|
|
break;
|
|
// WAIT_OBJECT_0 means the driver sent us a message, everything else means we cancel
|
|
if WaitForMultipleObjects(2, @wait, false, INFINITE) <> WAIT_OBJECT_0 then
|
|
break;
|
|
// Now we fetch the driver information and call the registered callback.
|
|
// The next DeviceIoControl call in the loop will inform the driver about the callback's result value.
|
|
if GetOverlappedResult(fh, info.Overl, c1, false) then begin
|
|
SetLength(ws1, 32 * 1024);
|
|
ProcessIdToFileNameW(outBuf.TargetProcessId, PWideChar(ws1), 32 * 1024);
|
|
ws1 := PWideChar(ws1);
|
|
ph := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, outBuf.TargetProcessId);
|
|
if ph <> 0 then begin
|
|
if IsElevatedProcess(ph) then
|
|
outBuf.Flags := outBuf.Flags or TARGET_PROCESS_IS_ELEVATED;
|
|
ws2 := GetProcessCommandLine(ph, outBuf.Flags and TARGET_PROCESS_IS_64BIT <> 0);
|
|
CloseHandle(ph);
|
|
end;
|
|
appr := info.Callback(info.CallbackContext, outBuf.TargetProcessId, outBuf.ParentProcessId, outBuf.SessionId, outBuf.Flags, PWideChar(ws1), PWideChar(ws2));
|
|
end;
|
|
ResetEvent(info.Overl.hEvent);
|
|
end;
|
|
// we want to stop, for one reason or another, so we cancel all open async communication and cleanup
|
|
CancelIo(fh);
|
|
CloseHandle(info.Overl.hEvent);
|
|
CloseHandle(fh);
|
|
end;
|
|
LocalFree(HLOCAL(injAppr));
|
|
end;
|
|
CloseHandle(info.AbortEvent);
|
|
emptyEvent := 0;
|
|
// now let's remove ourselves from the global list
|
|
EnterCriticalSection(InjectApprovalCallbackSection);
|
|
try
|
|
for i1 := 0 to InjectApprovalCallbackCount - 1 do
|
|
if InjectApprovalCallbackList[i1] = info then begin
|
|
InjectApprovalCallbackList[i1] := InjectApprovalCallbackList[InjectApprovalCallbackCount - 1];
|
|
InjectApprovalCallbackList[InjectApprovalCallbackCount - 1] := nil;
|
|
dec(InjectApprovalCallbackCount);
|
|
end;
|
|
if InjectApprovalCallbackCount = 0 then
|
|
// we were the last active callback, so let's set the "empty" event
|
|
emptyEvent := InjectApprovalCallbackEmptyEvent;
|
|
finally
|
|
LeaveCriticalSection(InjectApprovalCallbackSection);
|
|
end;
|
|
// finally cleanup the rest and exit
|
|
info.DriverName := '';
|
|
LocalFree(HLOCAL(info));
|
|
if emptyEvent <> 0 then
|
|
SetEvent(emptyEvent);
|
|
result := 0;
|
|
end;
|
|
|
|
function CancelAllInjectApprovalCallbacks : boolean;
|
|
// This cleans up all inject approval callback.
|
|
// This gets called when madCodeHook is finalized.
|
|
var i1 : integer;
|
|
begin
|
|
result := false;
|
|
// First of all we tell all inject approval callback threads to shut down.
|
|
EnterCriticalSection(InjectApprovalCallbackSection);
|
|
try
|
|
for i1 := 0 to InjectApprovalCallbackCount - 1 do begin
|
|
result := true;
|
|
SetEvent(InjectApprovalCallbackList[i1].AbortEvent);
|
|
end;
|
|
finally
|
|
LeaveCriticalSection(InjectApprovalCallbackSection);
|
|
end;
|
|
// Then we wait until the list of active inject approval callbacks it empty.
|
|
WaitForSingleObject(InjectApprovalCallbackEmptyEvent, 5000);
|
|
end;
|
|
|
|
function ReportInjectedPidsToDriver(driverName: PWideChar; pids: array of dword; dllFileName: PWideChar; session: dword; options: dword; includeMask, excludeMask: PWideChar) : boolean;
|
|
var addPids : ^TDrvCmdAddPids;
|
|
dll : ^TDrvCmdDll;
|
|
size : dword;
|
|
le : dword;
|
|
begin
|
|
result := false;
|
|
size := sizeOf(addPids^) - sizeOf(dword) + Length(pids) * sizeOf(dword) + sizeof(dll^) + lstrlenW(includeMask) * 2 + 2 + lstrlenW(excludeMask) * 2 + 2;
|
|
addPids := pointer(LocalAlloc(LPTR, size));
|
|
if addPids <> nil then begin
|
|
addPids.Header.Size := size;
|
|
addPids.PidCount := Length(pids);
|
|
Move(pids[0], addPids.Pids[0], Length(pids) * sizeOf(dword));
|
|
dll := pointer(NativeUInt(addPids) + sizeOf(addPids^) - sizeOf(dword) + dword(Length(pids)) * sizeOf(dword));
|
|
dll.Header.Size := sizeOf(dll^) + lstrlenW(includeMask) * 2 + 2 + lstrlenW(excludeMask) * 2 + 2;
|
|
dll.Session := session;
|
|
dll.Flags := options;
|
|
if not DisableParallelDllLoading then
|
|
dll.Flags := dll.Flags or INJECT_GLOBAL_ENABLE_PARALLEL_DLL_LOADING;
|
|
if UseIatDllInjection then
|
|
dll.Flags := dll.Flags or INJECT_GLOBAL_USE_IAT_DLL_INJECTION;
|
|
dll.X86AllocAddr := dword(X86AllocationAddress) + $10000;
|
|
lstrcpyW(dll.Name, dllFileName);
|
|
dll.IncludeLen := lstrlenW(includeMask);
|
|
dll.ExcludeLen := lstrlenW(excludeMask);
|
|
if includeMask <> nil then
|
|
lstrcpyW(dll.Data, includeMask);
|
|
if excludeMask <> nil then
|
|
lstrcpyW(pointer(NativeUInt(@dll.Data) + dll.IncludeLen * 2 + 2), excludeMask);
|
|
result := SendDriverCommand(driverName, Ioctl_AddPids, addPids.Header, nil, 0);
|
|
le := GetLastError;
|
|
LocalFree(HLOCAL(addPids));
|
|
SetLastError(le);
|
|
end;
|
|
end;
|
|
|
|
function JectDll(driverName: PWideChar; inject: boolean; dllFileName: PWideChar; session: dword; options: dword; includeMask, excludeMask: PWideChar; callback: TInjectApprovalCallbackRoutine; callbackContext: pointer; pidsValid: TPBoolean; pids: TPDACardinal) : boolean;
|
|
|
|
function CompareMem(buf1, buf2: pointer; len: integer) : boolean;
|
|
begin
|
|
result := true;
|
|
while len >= 4 do begin
|
|
if dword(buf1^) <> dword(buf2^) then begin
|
|
result := false;
|
|
exit;
|
|
end;
|
|
inc(NativeUInt(buf1), 4);
|
|
inc(NativeUInt(buf2), 4);
|
|
dec(len, 4);
|
|
end;
|
|
while len > 0 do begin
|
|
if byte(buf1^) <> byte(buf2^) then begin
|
|
result := false;
|
|
exit;
|
|
end;
|
|
inc(NativeUInt(buf1), 1);
|
|
inc(NativeUInt(buf2), 1);
|
|
dec(len, 1);
|
|
end;
|
|
end;
|
|
|
|
function GetFileHash(fileName: PWideChar; var fileHash: TDigest; maxFileSize: dword) : boolean;
|
|
// open a file and calculate a hash of the file contents
|
|
// MaxFileSize: can be used to limit the hash to a part of the file, only
|
|
var fh, map : THandle;
|
|
buf : pointer;
|
|
size : dword;
|
|
begin
|
|
result := false;
|
|
fh := CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
|
|
if fh <> INVALID_HANDLE_VALUE then begin
|
|
map := CreateFileMappingA(fh, nil, PAGE_READONLY, 0, 0, nil);
|
|
if map <> 0 then begin
|
|
buf := MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
|
|
if buf <> nil then begin
|
|
size := GetFileSize(fh, nil);
|
|
if (maxFileSize <> 0) and (maxFileSize < size) then
|
|
size := maxFileSize;
|
|
fileHash := Hash(buf, size);
|
|
result := true;
|
|
UnmapViewOfFile(buf);
|
|
end;
|
|
CloseHandle(map);
|
|
end;
|
|
CloseHandle(fh);
|
|
end;
|
|
end;
|
|
|
|
var dll : ^TDrvCmdDll;
|
|
size : dword;
|
|
sla : AnsiString;
|
|
slw : AnsiString;
|
|
libA : PAnsiChar;
|
|
le : dword;
|
|
buf : ^TDrvCmdDll;
|
|
found : boolean;
|
|
hk1, hk2 : HKEY;
|
|
c1, c2 : dword;
|
|
ok : boolean;
|
|
name1, name2 : AnsiString;
|
|
arrChW : array [0..MAX_PATH] of WideChar;
|
|
info : PInjectApprovalCallbackItem;
|
|
th : THandle;
|
|
tid : dword;
|
|
pidCount : integer;
|
|
begin
|
|
result := false;
|
|
libA := nil;
|
|
if not CheckLibFilePath(libA, dllFileName, sla, slw) then
|
|
exit;
|
|
EnableAllPrivileges;
|
|
size := sizeOf(dll^) + lstrlenW(includeMask) * 2 + 2 + lstrlenW(excludeMask) * 2 + 2;
|
|
dll := pointer(LocalAlloc(LPTR, size));
|
|
if dll <> nil then begin
|
|
if session = CURRENT_SESSION then
|
|
session := GetCurrentSessionId;
|
|
dll.Header.Size := size;
|
|
dll.Session := session;
|
|
dll.Flags := options;
|
|
if not DisableParallelDllLoading then
|
|
dll.Flags := dll.Flags or INJECT_GLOBAL_ENABLE_PARALLEL_DLL_LOADING;
|
|
if UseIatDllInjection then
|
|
dll.Flags := dll.Flags or INJECT_GLOBAL_USE_IAT_DLL_INJECTION;
|
|
dll.X86AllocAddr := dword(X86AllocationAddress) + $10000;
|
|
lstrcpyW(dll.Name, dllFileName);
|
|
dll.IncludeLen := lstrlenW(includeMask);
|
|
dll.ExcludeLen := lstrlenW(excludeMask);
|
|
if includeMask <> nil then
|
|
lstrcpyW(dll.Data, includeMask);
|
|
if excludeMask <> nil then
|
|
lstrcpyW(pointer(NativeUInt(@dll.Data) + dll.IncludeLen * 2 + 2), excludeMask);
|
|
pidCount := 0;
|
|
if not inject then begin
|
|
pids^ := nil;
|
|
pidsValid^ := false;
|
|
if SendDriverCommand(driverName, Ioctl_Deactivate, dll.Header, @pidCount, sizeOf(pidCount)) then begin
|
|
pidsValid^ := true;
|
|
SetLength(pids^, pidCount);
|
|
end else
|
|
pidCount := 0;
|
|
end;
|
|
if options and INJECT_PERMANENTLY <> 0 then begin
|
|
// permanent injection requests are stored in the registry, so they require the driver to be installed (not loaded)
|
|
if RegOpenKeyExA(HKEY_LOCAL_MACHINE, PAnsiChar(DecryptStr(CSystemCcsServices)), 0, KEY_ALL_ACCESS, hk1) = 0 then begin
|
|
if RegOpenKeyExW(hk1, driverName, 0, KEY_ALL_ACCESS, hk2) = 0 then begin
|
|
// we were able to open the registry key, which means 1) we have enough privileges and 2) the driver is installed
|
|
if inject then
|
|
result := SendDriverCommand(driverName, Ioctl_InjectDll, dll.Header, nil, 0)
|
|
else
|
|
result := SendDriverCommand(driverName, Ioctl_UninjectDll, dll.Header, pointer(pids^), pidCount * sizeOf(dword));
|
|
if result or (not inject) then
|
|
for c1 := 1 to 999 do begin
|
|
// let's loop through all 999 possible permanent dll injection requests
|
|
name1 := 'DLL ' + IntToStrExA(c1, 3);
|
|
if RegQueryValueExA(hk2, PAnsiChar(name1), nil, nil, nil, @size) = 0 then begin
|
|
// a permanent injection request with the number "c1" exists
|
|
if size = dll.Header.Size then begin
|
|
// and the size matches, so we check if the content also matches
|
|
buf := pointer(LocalAlloc(LPTR, size));
|
|
found := RegQueryValueExA(hk2, PAnsiChar(name1), nil, nil, pointer(buf), @size) = 0;
|
|
if found then begin
|
|
buf.OwnerHash := dll.OwnerHash;
|
|
found := CompareMem(buf, dll, dll.Header.Size);
|
|
end;
|
|
LocalFree(HLOCAL(buf));
|
|
if found then begin
|
|
// The content also matches, so we found an exact match.
|
|
// uninjection: This is what we were looking for, let's delete it.
|
|
if (not inject) and (RegDeleteValueA(hk2, PAnsiChar(name1)) = 0) then
|
|
// now let's decrement the numbers of the following permanent injection requests
|
|
for c2 := c1 + 1 to 999 do begin
|
|
name2 := 'DLL ' + IntToStrExA(c2, 3);
|
|
if RegQueryValueExA(hk2, PAnsiChar(name2), nil, nil, nil, @size) <> 0 then
|
|
break;
|
|
buf := pointer(LocalAlloc(LPTR, size));
|
|
ok := (RegQueryValueExA(hk2, PAnsiChar(name2), nil, nil, pointer(buf), @size) = 0) and
|
|
(RegSetValueExA(hk2, PAnsiChar(name1), 0, REG_BINARY, buf, size) = 0) and
|
|
(RegDeleteValueA(hk2, PAnsiChar(name2)) = 0);
|
|
LocalFree(HLOCAL(buf));
|
|
if not ok then
|
|
break;
|
|
name1 := name2;
|
|
end;
|
|
break;
|
|
end;
|
|
end;
|
|
end else begin
|
|
// there's no injection request with the number "c1"
|
|
if inject then begin
|
|
GetModuleFileNameW(0, arrChW, MAX_PATH);
|
|
if GetFileHash(arrChW, dll.OwnerHash, 1 * 1024 * 1024) then
|
|
// injection: so let's store the new request under this number
|
|
RegSetValueExA(hk2, PAnsiChar(name1), 0, REG_BINARY, dll, dll.Header.Size);
|
|
end;
|
|
break;
|
|
end;
|
|
end;
|
|
RegCloseKey(hk2);
|
|
end;
|
|
RegCloseKey(hk1);
|
|
end;
|
|
end else
|
|
if inject then
|
|
result := SendDriverCommand(driverName, Ioctl_InjectDll, dll.Header, nil, 0)
|
|
else
|
|
result := SendDriverCommand(driverName, Ioctl_UninjectDll, dll.Header, pointer(pids^), pidCount * sizeOf(dword));
|
|
le := GetLastError;
|
|
if @callback <> nil then begin
|
|
// The caller wants to register a callback for injection approval.
|
|
// We create a private thread for that purpose.
|
|
info := pointer(LocalAlloc(LPTR, sizeof(TInjectApprovalCallbackItem) + dll.Header.Size - sizeof(TDrvCmdDll)));
|
|
if info <> nil then begin
|
|
Move(dll^, info.Dll, dll.Header.Size);
|
|
th := CreateThread(nil, 0, @InjectApprovalCallbackThread, info, CREATE_SUSPENDED, tid);
|
|
if th <> 0 then begin
|
|
// Creating the thread succeeded.
|
|
// Now we store information about this callback into the global list.
|
|
info.DriverName := driverName;
|
|
info.Callback := callback;
|
|
info.CallbackContext := callbackContext;
|
|
info.AbortEvent := CreateEvent(nil, true, false, nil);
|
|
if not InjectApprovalCallbackReady then begin
|
|
InjectApprovalCallbackReady := true;
|
|
InitializeCriticalSection(InjectApprovalCallbackSection);
|
|
InjectApprovalCallbackEmptyEvent := CreateEvent(nil, true, false, nil);
|
|
end;
|
|
EnterCriticalSection(InjectApprovalCallbackSection);
|
|
try
|
|
if InjectApprovalCallbackCount = Length(InjectApprovalCallbackList) then
|
|
SetLength(InjectApprovalCallbackList, InjectApprovalCallbackCount + 16);
|
|
InjectApprovalCallbackList[InjectApprovalCallbackCount] := info;
|
|
inc(InjectApprovalCallbackCount);
|
|
ResetEvent(InjectApprovalCallbackEmptyEvent);
|
|
finally
|
|
LeaveCriticalSection(InjectApprovalCallbackSection);
|
|
end;
|
|
// finally resume the injection approval callback thread
|
|
ResumeThread(th);
|
|
CloseHandle(th);
|
|
end else begin
|
|
// creating the thread failed, for unknown reasons
|
|
LocalFree(HLOCAL(info));
|
|
end;
|
|
end;
|
|
end;
|
|
LocalFree(HLOCAL(dll));
|
|
SetLastError(le);
|
|
end;
|
|
end;
|
|
|
|
function StartDllInjection(driverName, dllFileName: PWideChar; session: dword; options: dword; includeMask, excludeMask: PWideChar; callback: TInjectApprovalCallbackRoutine; callbackContext: pointer) : bool; stdcall;
|
|
begin
|
|
if (driverName = nil) or (driverName[0] = #0) then begin
|
|
result := false;
|
|
exit;
|
|
end;
|
|
result := JectDll(driverName, true, dllFileName, session, options, includeMask, excludeMask, callback, callbackContext, nil, nil);
|
|
end;
|
|
|
|
function StopDllInjection(driverName, dllFileName: PWideChar; session: dword; options: dword; includeMask, excludeMask: PWideChar; out pidsValid: boolean; out pids: TDACardinal) : bool; stdcall;
|
|
begin
|
|
if (driverName = nil) or (driverName[0] = #0) then begin
|
|
result := false;
|
|
exit;
|
|
end;
|
|
result := JectDll(driverName, false, dllFileName, session, options, includeMask, excludeMask, nil, nil, @pidsValid, @pids);
|
|
end;
|
|
|
|
function LoadInjectionDriver(driverName, fileName32bit, fileName64bit: PWideChar) : bool; stdcall;
|
|
var fileName : PWideChar;
|
|
c1, c2, c3 : dword;
|
|
arrChW2 : array [0..MAX_PATH] of WideChar;
|
|
hk1, hk2 : HKEY;
|
|
nld : function (name: pointer) : dword; stdcall;
|
|
wImagePath : array [0..9] of WideChar;
|
|
name : TUnicodeStr;
|
|
sla : AnsiString;
|
|
slw : AnsiString;
|
|
libA : PAnsiChar;
|
|
le : dword;
|
|
begin
|
|
result := false;
|
|
if (driverName = nil) or (driverName[0] = #0) then
|
|
exit;
|
|
libA := nil;
|
|
if Is64bitOS then
|
|
fileName := fileName64bit
|
|
else
|
|
fileName := fileName32bit;
|
|
if not CheckLibFilePath(libA, fileName, sla, slw) then
|
|
exit;
|
|
EnableAllPrivileges;
|
|
arrChW2[0] := '\';
|
|
arrChW2[1] := '?';
|
|
arrChW2[2] := '?';
|
|
arrChW2[3] := '\';
|
|
arrChW2[4] := #0;
|
|
lstrcatW(arrChW2, fileName);
|
|
if RegOpenKeyExA(HKEY_LOCAL_MACHINE, PAnsiChar(DecryptStr(CSystemCcsServices)), 0, KEY_ALL_ACCESS, hk1) = 0 then begin
|
|
if RegCreateKeyExW(hk1, driverName, 0, nil, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nil, hk2, nil) = 0 then begin
|
|
c1 := 1;
|
|
c2 := 0;
|
|
c3 := 4;
|
|
AnsiToWide(PAnsiChar(DecryptStr(CImagePath)), wImagePath);
|
|
if (RegSetValueExA(hk2, PAnsiChar(DecryptStr(CType )), 0, REG_DWORD, @c1, 4) = 0) and
|
|
(RegSetValueExA(hk2, PAnsiChar(DecryptStr(CErrorControl)), 0, REG_DWORD, @c2, 4) = 0) and
|
|
(RegSetValueExA(hk2, PAnsiChar(DecryptStr(CStart )), 0, REG_DWORD, @c3, 4) = 0) and
|
|
(RegSetValueExW(hk2, wImagePath, 0, REG_SZ, @arrChW2, lstrlenW(arrChW2) * 2 + 2) = 0) then begin
|
|
AnsiToWide(PAnsiChar('\' + DecryptStr(CRegistryMachine) + '\' + DecryptStr(CSystemCcsServices) + '\'), arrChW2);
|
|
lstrcatW(arrChW2, driverName);
|
|
name.str := arrChW2;
|
|
name.len := lstrlenW(name.str) * 2;
|
|
name.maxlen := name.len + 2;
|
|
nld := NtProc(CNtLoadDriver);
|
|
if @nld <> nil then begin
|
|
SetLastError(ConvertNtErrorCode(nld(@name)));
|
|
result := GetLastError = 0;
|
|
end else
|
|
SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
|
|
end;
|
|
le := GetLastError;
|
|
RegCloseKey(hk2);
|
|
SetLastError(le);
|
|
end;
|
|
le := GetLastError;
|
|
RegDelTree(hk1, driverName);
|
|
RegCloseKey(hk1);
|
|
SetLastError(le);
|
|
end;
|
|
end;
|
|
|
|
type TDAInjectionRequest = array of ^TDrvCmdDll;
|
|
|
|
function EnumInjectionRequests(driverName: PWideChar) : TDAInjectionRequest;
|
|
var enum : TDrvCmdEnumInject;
|
|
dll : ^TDrvCmdDll;
|
|
index : integer;
|
|
begin
|
|
result := nil;
|
|
enum.Header.Size := sizeOf(TDrvCmdEnumInject);
|
|
dll := VirtualAlloc(nil, 2 * 1024 * 1024, MEM_COMMIT, PAGE_READWRITE);
|
|
if dll <> nil then begin
|
|
index := 0;
|
|
enum.Index := index;
|
|
while SendDriverCommand(driverName, Ioctl_EnumInject, enum.Header, dll, 2 * 1024 * 1024) do begin
|
|
SetLength(result, Length(result) + 1);
|
|
result[high(result)] := pointer(LocalAlloc(LPTR, dll.Header.Size));
|
|
Move(dll^, result[high(result)]^, dll.Header.Size);
|
|
inc(index);
|
|
enum.Index := index;
|
|
end;
|
|
VirtualFree(dll, 0, MEM_RELEASE);
|
|
end;
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
function SetMadCHookOption(option: dword; param: PWideChar) : bool; stdcall;
|
|
begin
|
|
result := true;
|
|
case option of
|
|
DISABLE_CHANGED_CODE_CHECK : DisableChangedCodeCheck := true;
|
|
USE_OLD_IPC_LOGIC : {$ifndef win64}
|
|
if not IsVista then
|
|
UseNewIpcLogic := false
|
|
{$endif};
|
|
SET_INTERNAL_IPC_TIMEOUT : InternalIpcTimeout := dword(param);
|
|
VMWARE_INJECTION_MODE : VmWareInjectionMode := true;
|
|
DONT_TOUCH_RUNNING_PROCESSES : InjectIntoRunningProcesses := false;
|
|
RENEW_OVERWRITTEN_HOOKS : RenewOverwrittenHooks := true;
|
|
INJECT_INTO_RUNNING_PROCESSES : InjectIntoRunningProcesses := param <> nil;
|
|
UNINJECT_FROM_RUNNING_PROCESSES : UninjectFromRunningProcesses := param <> nil;
|
|
X86_ALLOCATION_ADDRESS : if NativeUInt(param) < $80000000 then begin
|
|
X86AllocationAddress := pointer(NativeUInt(param) and $ffff0000);
|
|
madRemote.X86AllocationAddress := X86AllocationAddress;
|
|
end else
|
|
result := false;
|
|
VERIFY_HOOK_ADDRESS : VerifyHookAddress := param <> nil;
|
|
DISASM_HOOK_TARGET : DisAsmHookTarget := param <> nil;
|
|
LIMITED_IPC_PORT : LimitedIpcPort := param <> nil;
|
|
HOOK_LOAD_LIBRARY : HookLoadLibraryOption := param <> nil;
|
|
DISABLE_LDR_LOAD_DLL_SPECIAL_HOOK : DisableLdrLoadDllSpecialHook := param <> nil;
|
|
DISABLE_PARALLEL_DLL_LOADING : DisableParallelDllLoading := param <> nil;
|
|
USE_IAT_DLL_INJECTION : UseIatDllInjection := param <> nil;
|
|
DISABLE_IPC_FIX_1 : DisableIpcFix1 := param <> nil;
|
|
DISABLE_IPC_FIX_2 : DisableIpcFix2 := param <> nil;
|
|
SET_SAFE_HOOKING_TIMEOUT : SafeHookingTimeout := dword(param);
|
|
ALWAYS_CHECK_HOOKS : AlwaysCheckHooks := param <> nil;
|
|
else result := false;
|
|
end;
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
(* static injection works fine, but unfortunately uninjecting is not possible
|
|
function GetFirstExportIndex(libA: PAnsiChar; libW: PWideChar) : integer;
|
|
|
|
function VirtualToRaw(nh: PImageNtHeaders; addr: dword) : dword;
|
|
type TAImageSectionHeader = packed array [0..maxInt shr 6] of TImageSectionHeader;
|
|
var i1 : integer;
|
|
sh : ^TAImageSectionHeader;
|
|
begin
|
|
result := addr;
|
|
NativeUInt(sh) := NativeUInt(nh) + sizeOf(nh^);
|
|
for i1 := 0 to nh^.FileHeader.NumberOfSections - 1 do
|
|
if (addr >= sh[i1].VirtualAddress) and
|
|
((i1 = nh^.FileHeader.NumberOfSections - 1) or (addr < sh[i1 + 1].VirtualAddress)) then begin
|
|
result := addr - sh[i1].VirtualAddress + sh[i1].PointerToRawData;
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
var fh : THandle;
|
|
map : THandle;
|
|
module : HMODULE;
|
|
nh : PImageNtHeaders;
|
|
ed : PImageExportDirectory;
|
|
i1 : integer;
|
|
begin
|
|
result := -1;
|
|
if libW <> nil then
|
|
fh := CreateFileW(libW, GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0)
|
|
else fh := CreateFileA(libA, GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
|
|
if fh <> INVALID_HANDLE_VALUE then begin
|
|
map := CreateFileMapping(fh, @UnlimitedSa, PAGE_READONLY, 0, 0, nil);
|
|
if map <> 0 then begin
|
|
module := HMODULE(MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0));
|
|
if module <> 0 then begin
|
|
nh := GetImageNtHeaders(module);
|
|
if nh <> nil then
|
|
try
|
|
NativeUInt(ed) := module + VirtualToRaw(nh, nh^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
|
|
with ed^ do
|
|
for i1 := 0 to NumberOfFunctions - 1 do
|
|
if TPACardinal(module + VirtualToRaw(nh, AddressOfFunctions))^[i1] <> 0 then begin
|
|
result := integer(Base) + i1;
|
|
break;
|
|
end;
|
|
except end;
|
|
UnmapViewOfFile(pointer(module));
|
|
end;
|
|
CloseHandle(map);
|
|
end;
|
|
CloseHandle(fh);
|
|
end;
|
|
end;
|
|
|
|
function InjectLibrariesStatic(const libs: array of AnsiString; const exp: array of dword) : integer;
|
|
const
|
|
InjectMagic = $77712345abcde777; // magic number for our buffer
|
|
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = $b; // data directory of new bind logic
|
|
type
|
|
TAImageDataDirectory = packed array [0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES - 1] of TImageDataDirectory;
|
|
TImageImportDirectory = packed record
|
|
HintNameArray : dword;
|
|
TimeDateStamp : dword;
|
|
ForwarderChain : dword;
|
|
Name_ : dword;
|
|
ThunkArray : dword;
|
|
end;
|
|
PImageImportDirectory = ^TImageImportDirectory;
|
|
TAImageImportDirectory = packed array [0..maxInt shr 5] of TImageImportDirectory;
|
|
TBufHeader = packed record
|
|
magic : int64;
|
|
dataCount : integer;
|
|
dataStart : dword;
|
|
dataSize : dword;
|
|
mainCount : integer;
|
|
mainStart : dword;
|
|
mainSize : dword;
|
|
end;
|
|
|
|
function CountImportDirectories(id: PImageImportDirectory) : integer;
|
|
var buf : ^TAImageImportDirectory;
|
|
i1, i2 : integer;
|
|
begin
|
|
result := 0;
|
|
GetMem(buf, sizeOf(TImageImportDirectory) * 64);
|
|
i1 := 64;
|
|
while true do begin
|
|
while (not Read(id, buf^, i1 * sizeOf(TImageImportDirectory))) and (i1 > 0) do
|
|
i1 := i1 shr 2;
|
|
for i2 := 0 to i1 - 1 do begin
|
|
with buf^[i2] do
|
|
if (HintNameArray = 0) and (TimeDateStamp = 0) and
|
|
(ForwarderChain = 0) and (Name_ = 0) and (ThunkArray = 0) then
|
|
exit;
|
|
inc(result);
|
|
end;
|
|
inc(id, i1);
|
|
end;
|
|
FreeMem(buf);
|
|
end;
|
|
|
|
var module : HMODULE; // remote exe's module handle
|
|
dd : ^TAImageDataDirectory; // remote exe's ^data directories
|
|
nh : TImageNtHeaders; // remote exe's nt image headers
|
|
import : TImageDataDirectory; // remote exe's import directory
|
|
oldhead : TBufHeader; // head of the old remote buffer
|
|
newhead : TBufHeader; // head of our new remote buffer
|
|
canFree : boolean; // can we free the remote buffer without danger?
|
|
prepBuf : NativeUInt; // our local preparation buffer
|
|
c1 : NativeUInt; // multi purpose NativeUInt
|
|
i1 : integer; // multi purpose integer
|
|
i64 : int64; // multi purpose int64
|
|
begin
|
|
// this kind of patch works only on not yet initialized processes
|
|
if NotInitializedYet then begin
|
|
// can we locate the main module?
|
|
if GetExeModuleInfos(module, nh, pointer(dd)) then begin
|
|
// we found the main module
|
|
import := nh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
|
// fill IAT directory, if necessary
|
|
with nh.OptionalHeader do
|
|
if int64(DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT]) = 0 then
|
|
Write(@dd^[IMAGE_DIRECTORY_ENTRY_IAT], DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT], 8);
|
|
// did we already patch this process?
|
|
if (not Read(pointer(module + import.VirtualAddress - sizeOf(oldHead)), oldHead, sizeOf(oldHead))) or
|
|
(oldHead.magic <> InjectMagic) then begin
|
|
// nope, so fill the old buffer infos manually
|
|
ZeroMemory(@oldHead, sizeOf(oldHead));
|
|
oldHead.dataStart := module + import.VirtualAddress;
|
|
oldHead.mainStart := module + import.VirtualAddress;
|
|
oldHead.mainCount := CountImportDirectories(pointer(oldHead.mainStart));
|
|
oldHead.mainSize := (oldHead.mainCount + 1) * sizeOf(TImageImportDirectory);
|
|
end;
|
|
canFree := true;
|
|
// fill new header
|
|
newHead.magic := InjectMagic;
|
|
newHead.dataCount := oldHead.dataCount + Length(libs);
|
|
newHead.dataSize := newHead.dataCount * (8 + MAX_PATH);
|
|
newHead.mainCount := oldHead.mainCount + Length(libs);
|
|
newHead.mainSize := (newHead.mainCount + 1) * sizeOf(TImageImportDirectory);
|
|
newHead.dataStart := dword(AllocMemEx(newHead.dataSize + sizeOf(newHead) + newHead.mainSize, process));
|
|
newHead.mainStart := newHead.dataStart + newHead.dataSize + sizeOf(newHead);
|
|
// use local preparation buffer to fill the remote buffer
|
|
prepBuf := LocalAlloc(LPTR, newHead.dataSize + sizeOf(newHead) + newHead.mainSize);
|
|
// fill the preparation buffer with the old remote buffer or
|
|
// with the original import data
|
|
if Read(pointer(oldHead.dataStart), pointer(prepBuf + newHead.dataSize - oldHead.dataSize)^, oldHead.dataSize) and
|
|
Read(pointer(oldHead.mainStart), pointer(prepBuf + newHead.dataSize + sizeOf(newHead) + newHead.mainSize - oldHead.mainSize)^, oldHead.mainSize) then begin
|
|
// fill the new head into the preparation buffer
|
|
TBufHeader(pointer(prepBuf + newHead.dataSize)^) := newHead;
|
|
// now we do the rest of the preparation
|
|
for i1 := 0 to newHead.dataCount - 1 do begin
|
|
with TAImageImportDirectory(pointer(prepBuf + newHead.dataSize + sizeOf(newHead))^)[i1] do begin
|
|
// we adjust the auxiliary data for the old dlls and fill in the new
|
|
HintNameArray := 0;
|
|
TimeDateStamp := 0;
|
|
ForwarderChain := maxCard;
|
|
ThunkArray := newHead.dataStart + dword(i1) * (8 + MAX_PATH) - module;
|
|
Name_ := ThunkArray + 8;
|
|
end;
|
|
if i1 < Length(libs) then begin
|
|
// the new dlls get a dll path and a 1 element dummy import thunk
|
|
c1 := prepBuf + dword(i1) * (8 + MAX_PATH);
|
|
TPInt64(c1)^ := $80000000 + exp[i1];
|
|
lstrcpyA(PAnsiChar(libs[i1]), PAnsiChar(c1 + 8));
|
|
end;
|
|
end;
|
|
// the preparation buffer is done, now we copy it to the remote buffer
|
|
if Write(pointer(newHead.dataStart), pointer(prepBuf)^, newHead.dataSize + sizeOf(newHead) + newHead.mainSize, false) then begin
|
|
// hopefully the process is still not initialized yet?
|
|
if NotInitializedYet then begin
|
|
// let's do the real modification, namely patching the data directories
|
|
import.VirtualAddress := newHead.mainStart - module;
|
|
import.Size := newHead.mainSize;
|
|
if Write(@dd^[IMAGE_DIRECTORY_ENTRY_IMPORT], import, 8) then begin
|
|
// we want to convert the new bind method into the old one
|
|
// for this purpose we now kill IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT
|
|
i64 := 0;
|
|
if (int64(nh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]) = 0) or
|
|
Write(@dd^[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT], i64, 8) then begin
|
|
if NotInitializedYet then begin
|
|
// injection fully succeeded
|
|
result := 2;
|
|
if oldHead.dataCount > 0 then
|
|
// free the old remote buffer (if it exists)
|
|
FreeMemEx(pointer(oldHead.dataStart), process);
|
|
end else result := 1;
|
|
end else result := 0;
|
|
canFree := false;
|
|
end else result := 0;
|
|
end else result := 1;
|
|
end else result := 0;
|
|
end else result := 0;
|
|
// we can kill our preparation buffer
|
|
LocalFree(prepBuf);
|
|
if (result < 2) and canFree then
|
|
// injection failed, so we free the remote buffer (if allowed)
|
|
FreeMemEx(pointer(newHead.dataStart), process);
|
|
end else result := 0;
|
|
end else result := 1;
|
|
end;
|
|
*)
|
|
|
|
// ***************************************************************
|
|
|
|
procedure AutoUnhook2(dll: HMODULE; wait: boolean);
|
|
var anh : array of TPPointer;
|
|
i1, i2 : integer;
|
|
begin
|
|
{$ifdef log}
|
|
log('AutoUnhook (module: ' + IntToHexExA(dll) + '; wait: ' + booleanToCharA(wait) + ')');
|
|
{$endif}
|
|
if (dll <> 0) and HookReady then begin
|
|
EnterCriticalSection(HookSection);
|
|
try
|
|
SetLength(anh, Length(HookList));
|
|
i2 := 0;
|
|
for i1 := 0 to high(HookList) do
|
|
if (((dll = HMODULE(-1)) and (not wait)) or (HookList[i1].owner = dll)) and
|
|
((HookList[i1].ch = nil) or (not HookList[i1].ch.FNoAutoUnhook)) then begin
|
|
anh[i2] := HookList[i1].nextHook;
|
|
inc(i2);
|
|
end;
|
|
finally LeaveCriticalSection(HookSection) end;
|
|
for i1 := 0 to i2 - 1 do
|
|
UnhookCodeEx(anh[i1]^, false, wait);
|
|
end;
|
|
{$ifdef log}
|
|
log('AutoUnhook (module: ' + IntToHexExA(dll) + '; wait: ' + booleanToCharA(wait) + ') -> +');
|
|
{$endif}
|
|
end;
|
|
|
|
procedure AutoUnhook(moduleHandle: HMODULE);
|
|
begin
|
|
{$ifdef log}
|
|
log('AutoUnhook (module: ' + IntToHexExA(moduleHandle) + ')');
|
|
{$endif}
|
|
AutoUnhook2(moduleHandle, true);
|
|
{$ifdef log}
|
|
log('AutoUnhook (module: ' + IntToHexExA(moduleHandle) + ') -> +');
|
|
{$endif}
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
function CheckLibFilePath(var libA: PAnsiChar; var libW: PWideChar;
|
|
var strA: AnsiString; var strW: AnsiString) : boolean;
|
|
|
|
function CheckIt(pathW: PWideChar = nil; killName: boolean = false) : boolean;
|
|
var len : integer;
|
|
i1 : integer;
|
|
begin
|
|
len := lstrlenW(pathW);
|
|
if killName then
|
|
for i1 := len - 2 downto 1 do
|
|
if pathW[i1] = '\' then begin
|
|
pathW[i1] := #0;
|
|
len := i1;
|
|
break;
|
|
end;
|
|
if (len > 0) and (pathW[len - 1] <> '\') then begin
|
|
pathW[len ] := '\';
|
|
pathW[len + 1] := #0;
|
|
end;
|
|
if libA = nil then
|
|
strW := WideToWideEx(pathW, false) + WideToWideEx (libW)
|
|
else strW := WideToWideEx(pathW, false) + AnsiToWideEx2(libA);
|
|
result := (Length(strW) > 6) and ( (strW[3] = ':') or
|
|
((strW[1] = '\') and (strW[3] = '\')) ) and
|
|
(GetFileAttributesW(pointer(strW)) <> INVALID_FILE_ATTRIBUTES);
|
|
if result then
|
|
libW := pointer(strW);
|
|
end;
|
|
|
|
var arrChW : array [0..MAX_PATH] of WideChar;
|
|
begin
|
|
{$ifdef log}
|
|
log('CheckLibFilePath (lib: %1)', libA, libW);
|
|
{$endif}
|
|
result := true;
|
|
if not CheckIt then begin
|
|
GetModuleFileNameW(0, arrChW, MAX_PATH);
|
|
if not CheckIt(arrChW, true) then begin
|
|
GetModuleFileNameW(HInstance, arrChW, MAX_PATH);
|
|
if not CheckIt(arrChW, true) then begin
|
|
GetCurrentDirectoryW(MAX_PATH, arrChW);
|
|
if not CheckIt(arrChW) then begin
|
|
GetSystemDirectoryW(arrChW, MAX_PATH);
|
|
result := CheckIt(arrChW);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
{$ifdef log}
|
|
log('CheckLibFilePath (lib -> %1) -> ' + booleanToCharA(result), libA, libW);
|
|
{$endif}
|
|
end;
|
|
|
|
type
|
|
PListEntry = ^TListEntry;
|
|
TListEntry = packed record
|
|
Flink : PListEntry; // 0x00
|
|
Blink : PListEntry; // 0x04
|
|
end;
|
|
|
|
PLdrDependencies = ^TLdrDependencies;
|
|
PLdrDdagNode = ^TLdrDdagNode;
|
|
PLdrDataTableEntry = ^TLdrDataTableEntry;
|
|
|
|
TLdrDependencies = record
|
|
nextDependency : PLdrDependencies;
|
|
whoIsBeingDependedOn : PLdrDdagNode;
|
|
nextIncomingDependency : pointer;
|
|
whoDependsOnSomething : PLdrDdagNode;
|
|
end;
|
|
|
|
TLdrDdagNode = packed record
|
|
Modules : TListEntry; // 0x00 | 0x00
|
|
ServiceTagList : pointer; // 0x08 | 0x10
|
|
LoadCount : dword; // 0x0c | 0x18
|
|
ObsoleteReferenceCount : dword; // 0x10 | 0x1c
|
|
LowestLink : dword;
|
|
Dependencies : PLdrDependencies;
|
|
IncomingDependencies : TPPointer;
|
|
State : dword;
|
|
CondenseLink : TPPointer;
|
|
PreorderNumber : dword;
|
|
end;
|
|
|
|
TLdrDataTableEntry = record
|
|
InLoadOrderLinks : TListEntry; // 0x00 | 0x00
|
|
InMemoryOrderLinks : TListEntry; // 0x08 | 0x10
|
|
InInitializationOrderLinks : TListEntry; // 0x10 | 0x20
|
|
DllBase : pointer; // 0x18 | 0x30
|
|
EntryPoint : pointer; // 0x1c | 0x38
|
|
SizeOfImage : dword; // 0x20 | 0x40
|
|
FullDllName : TUnicodeStr; // 0x24 | 0x48
|
|
BaseDllName : TUnicodeStr; // 0x2c | 0x58
|
|
Flags : dword; // 0x34 | 0x68
|
|
ObsoleteLoadCount : word; // 0x38 | 0x6c
|
|
TlsIndex : word; // 0x3a | 0x6a
|
|
HashLinks : TListEntry; // 0x3c | 0x70
|
|
TimeDateStamp : dword; // 0x44 | 0x80
|
|
EntryPointActivationContext : pointer; // 0x48 | 0x88
|
|
Lock : pointer; // 0x4c | 0x90
|
|
DdagNode : PLdrDdagNode; // 0x50 | 0x98
|
|
NodeModuleLink : TListEntry; // 0x54 | 0xa0
|
|
LoadContext : pointer; // 0x5c | 0xb0
|
|
ParentDllBase : pointer; // 0x60 | 0xb8
|
|
SwitchBackContext : pointer; // 0x64 | 0xc0
|
|
BaseAddressIndexNode : array [0..2] of pointer; // 0x68 | 0xc8
|
|
MappingInfoIndexNode : array [0..2] of pointer; // 0x74 | 0xe0
|
|
OriginalBase : HMODULE; // 0x80 | 0xf8
|
|
LoadTime : int64; // 0x88 | 0x100
|
|
BaseNameHashValue : dword; // 0x90 | 0x108
|
|
LoadReason : dword; // 0x94 | 0x10C
|
|
ImplicitPathOptions : dword; // 0x98 | 0x110
|
|
ReferenceCount : dword; // 0x9c | 0x114
|
|
end;
|
|
|
|
(*
|
|
function RtlSizeHeap (heap: THandle; flags: dword; buf: pointer) : integer; stdcall; external 'ntdll.dll';
|
|
function RtlGetProcessHeaps (count: dword; var heaps: dword) : integer; stdcall; external 'ntdll.dll';
|
|
|
|
procedure FreeStaticallyLinkedLibrary(module: HMODULE);
|
|
|
|
function ParseLdr(firstDll: PLdrDataTableEntry) : AnsiString;
|
|
|
|
function ParseDependencies(deps: TPPointer; showContents: integer; showSize: boolean; heap: THandle) : AnsiString;
|
|
var pp1 : TPPointer;
|
|
i1 : integer;
|
|
begin
|
|
result := '';
|
|
try
|
|
pp1 := deps;
|
|
if pp1 <> nil then
|
|
repeat
|
|
result := result + ', ' + IntToHexEx(integer(pp1));
|
|
if showContents > 0 then begin
|
|
result := result + ' (';
|
|
for i1 := 0 to showContents - 1 do
|
|
result := result + IntToHexEx(TPAInteger(pp1)[i1]) + ', ';
|
|
Delete(result, Length(result) - 1, 2);
|
|
result := result + ')';
|
|
end;
|
|
pp1 := pp1^;
|
|
until (pp1 = nil) or (pp1 = deps);
|
|
Delete(result, 1, 2);
|
|
if showSize and (result <> '') then
|
|
result := result + ' (size: ' + IntToStrEx(RtlSizeHeap(heap, 0, deps)) + ')';
|
|
except
|
|
result := '*crash*';
|
|
end;
|
|
end;
|
|
|
|
var dll : PLdrDataTableEntry;
|
|
heap : dword;
|
|
begin
|
|
dll := firstDll;
|
|
result := 'InLoadOrderLinks:' + #$D#$A;
|
|
repeat
|
|
if dll.DllBase <> nil then
|
|
result := result + AnsiString(dll.FullDllName.str) + #$D#$A;
|
|
dll := pointer(dll.InLoadOrderLinks.Flink);
|
|
until (dll = firstDll) or (dll = nil);
|
|
dll := @firstDll.InMemoryOrderLinks.Flink;
|
|
result := result + #$D#$A + 'InMemoryOrderLinks:' + #$D#$A;
|
|
repeat
|
|
if dll.DllBase <> nil then
|
|
result := result + AnsiString(PLdrDataTableEntry(dword(dll) - $8).FullDllName.str) + #$D#$A;
|
|
dll := pointer(dll.InLoadOrderLinks.Flink);
|
|
until (dll = @firstDll.InMemoryOrderLinks.Flink) or (dll = nil);
|
|
dll := @firstDll.InInitializationOrderLinks.Flink;
|
|
if dll.InLoadOrderLinks.Flink <> nil then begin
|
|
result := result + #$D#$A + 'InInitializationOrderLinks:' + #$D#$A;
|
|
repeat
|
|
if dll.DllBase <> nil then
|
|
result := result + AnsiString(PLdrDataTableEntry(dword(dll) - $10).FullDllName.str) + #$D#$A;
|
|
dll := pointer(dll.InLoadOrderLinks.Flink);
|
|
until (dll = @firstDll.InInitializationOrderLinks.Flink) or (dll = nil);
|
|
end;
|
|
dll := @firstDll.HashLinks.Flink;
|
|
result := result + #$D#$A + 'HashLinks:' + #$D#$A;
|
|
repeat
|
|
if dll.DllBase <> nil then
|
|
result := result + AnsiString(PLdrDataTableEntry(dword(dll) - $3c).FullDllName.str) + #$D#$A;
|
|
dll := pointer(dll.InLoadOrderLinks.Flink);
|
|
until (dll = @firstDll.HashLinks.Flink) or (dll = nil);
|
|
RtlGetProcessHeaps(1, heap);
|
|
dll := firstDll;
|
|
repeat
|
|
if dll.DllBase <> nil then
|
|
result := result + #$D#$A +
|
|
AnsiString(dll.FullDllName.str) + ':' + #$D#$A +
|
|
'LdrDataTableEntry: ' + IntToHexEx(integer(dll)) + ' (size: ' + IntToStrEx(RtlSizeHeap(heap, 0, dll)) + ')' + #$D#$A +
|
|
'Flags: ' + IntToHexEx(integer(dll.Flags)) + #$D#$A +
|
|
'ObsoleteLoadCount: ' + IntToStrEx(integer(dll.ObsoleteLoadCount)) + #$D#$A +
|
|
'NodeModuleLink: ' + IntToHexEx(integer(dll.NodeModuleLink.Flink)) + ', ' + IntToHexEx(integer(dll.NodeModuleLink.Blink)) + #$D#$A +
|
|
'LoadReason: ' + IntToStrEx(integer(dll.LoadReason)) + #$D#$A +
|
|
'ReferenceCount: ' + IntToStrEx(integer(dll.ReferenceCount)) + #$D#$A +
|
|
'DDAG: ' + IntToHexEx(integer(dll.DdagNode)) + ' (size: ' + IntToStrEx(RtlSizeHeap(heap, 0, dll.DdagNode)) + ')' + #$D#$A +
|
|
'DDAG.Modules: ' + IntToHexEx(integer(dll.DdagNode.Modules.Flink)) + ', ' + IntToHexEx(integer(dll.DdagNode.Modules.Blink)) + #$D#$A +
|
|
'DDAG.LoadCount: ' + IntToStrEx(integer(dll.DdagNode.LoadCount)) + #$D#$A +
|
|
'DDAG.ObsoleteReferenceCount: ' + IntToStrEx(integer(dll.DdagNode.ObsoleteReferenceCount)) + #$D#$A +
|
|
'DDAG.LowestLink: ' + IntToStrEx(dll.DdagNode.LowestLink) + #$D#$A +
|
|
'DDAG.Dependencies: ' + ParseDependencies(pointer(dll.DdagNode.Dependencies), 4, true, heap) + #$D#$A +
|
|
'DDAG.IncomingDependencies: ' + ParseDependencies(dll.DdagNode.IncomingDependencies, 2, false, heap) + #$D#$A;
|
|
dll := pointer(dll.InLoadOrderLinks.Flink);
|
|
until (dll = firstDll) or (dll = nil);
|
|
end;
|
|
|
|
procedure CleanupLdrData(dll: PLdrDataTableEntry);
|
|
var deps : PLdrDependencies;
|
|
pdeps : ^PLdrDependencies;
|
|
pp1 : TPPointer;
|
|
begin
|
|
dll.LoadReason := 4;
|
|
dll.ReferenceCount := 0;
|
|
dll.Flags := dll.Flags and (not $20);
|
|
dll.DdagNode.LoadCount := 1;
|
|
dll.HashLinks.Blink.Flink := dll.HashLinks.Flink;
|
|
dll.HashLinks.Flink.Blink := dll.HashLinks.Blink;
|
|
dll.InInitializationOrderLinks.Blink.Flink := dll.InInitializationOrderLinks.Flink;
|
|
dll.InInitializationOrderLinks.Flink.Blink := dll.InInitializationOrderLinks.Blink;
|
|
dll.InMemoryOrderLinks.Blink.Flink := dll.InMemoryOrderLinks.Flink;
|
|
dll.InMemoryOrderLinks.Flink.Blink := dll.InMemoryOrderLinks.Blink;
|
|
dll.InLoadOrderLinks.Blink.Flink := dll.InLoadOrderLinks.Flink;
|
|
dll.InLoadOrderLinks.Flink.Blink := dll.InLoadOrderLinks.Blink;
|
|
dll.NodeModuleLink.Blink.Flink := dll.NodeModuleLink.Flink;
|
|
dll.NodeModuleLink.Flink.Blink := dll.NodeModuleLink.Blink;
|
|
// cleanup dependencies
|
|
deps := dll.DdagNode.Dependencies;
|
|
if deps <> nil then
|
|
repeat
|
|
pp1 := @deps.whoIsBeingDependedOn.IncomingDependencies;
|
|
repeat
|
|
if pp1^ = @deps.nextIncomingDependency then
|
|
pp1^ := deps.nextIncomingDependency
|
|
else
|
|
pp1 := pp1^;
|
|
until (pp1 = nil) or (pp1^ = deps.whoIsBeingDependedOn.IncomingDependencies);
|
|
deps := deps.nextDependency;
|
|
until (deps = nil) or (deps = dll.DdagNode.Dependencies);
|
|
// cleanup incoming dependency (there should only be one)
|
|
pdeps := @(PLdrDdagNode(TPAPointer(dll.DdagNode.IncomingDependencies)[1]).Dependencies);
|
|
repeat
|
|
if pdeps^.whoIsBeingDependedOn = dll.DdagNode then
|
|
pdeps^ := pdeps^.nextDependency
|
|
else
|
|
pdeps := @pdeps^.nextDependency;
|
|
until (pdeps = nil) or (pdeps^ = PLdrDdagNode(TPAPointer(dll.DdagNode.IncomingDependencies)[1]).Dependencies);
|
|
dll.DdagNode.Dependencies := nil;
|
|
dll.DdagNode.IncomingDependencies := nil;
|
|
end;
|
|
|
|
var peb : NativeUInt;
|
|
firstDll, dll : PLdrDataTableEntry;
|
|
begin
|
|
peb := GetPeb(GetCurrentProcess);
|
|
firstDll := TPPointer(TPCardinal(peb + $c)^ + $c)^; // peb.Ldr.InLoadOrderModuleList.FLink
|
|
if firstDll <> nil then begin
|
|
dll := firstDll;
|
|
repeat
|
|
if dword(dll.DllBase) = module then begin
|
|
CleanupLdrData(dll);
|
|
FreeLibrary(module);
|
|
break;
|
|
end;
|
|
dll := pointer(dll.InLoadOrderLinks.Flink);
|
|
until dll = firstDll;
|
|
end;
|
|
end;
|
|
*)
|
|
|
|
// ***************************************************************
|
|
|
|
type
|
|
TInjectRec = packed record
|
|
load : boolean;
|
|
libA : array [0..MAX_PATH + 10] of AnsiChar;
|
|
libW : array [0..MAX_PATH + 10] of WideChar;
|
|
GetVersionFunc : function : dword; stdcall;
|
|
SharedMem9x_Free : function (ptr: pointer) : bool; stdcall;
|
|
VirtualFreeFunc : function (address: pointer; size: NativeUInt; flags: dword) : bool; stdcall;
|
|
SetErrorModeFunc : function (mode: dword) : dword; stdcall;
|
|
LoadLibraryAFunc : function (lib: PAnsiChar) : HMODULE; stdcall;
|
|
LoadLibraryWFunc : function (lib: PWideChar) : HMODULE; stdcall;
|
|
GetLastErrorFunc : function : integer; stdcall;
|
|
GetModuleHandleAFunc : function (lib: PAnsiChar) : HMODULE; stdcall;
|
|
GetModuleHandleWFunc : function (lib: PWideChar) : HMODULE; stdcall;
|
|
GetCurrentProcessIdFunc : function : dword; stdcall;
|
|
OpenFileMappingAFunc : function (access: dword; inheritHandle: bool; name: PAnsiChar) : THandle; stdcall;
|
|
MapViewOfFileFunc : function (map: THandle; access, ofsHi, ofsLo: dword; size: NativeUInt) : pointer; stdcall;
|
|
UnmapViewOfFileFunc : function (addr: pointer) : bool; stdcall;
|
|
CloseHandleFunc : function (obj: THandle) : bool; stdcall;
|
|
FreeLibraryFunc : function (module: HMODULE) : bool; stdcall;
|
|
EnterCriticalSectionFunc : procedure (var criticalSection: TRTLCriticalSection); stdcall;
|
|
LeaveCriticalSectionFunc : procedure (var criticalSection: TRTLCriticalSection); stdcall;
|
|
end;
|
|
|
|
{$ifdef win64}
|
|
TInjectRec32 = packed record
|
|
load : boolean;
|
|
libA : array [0..MAX_PATH + 10] of AnsiChar;
|
|
libW : array [0..MAX_PATH + 10] of WideChar;
|
|
GetVersionFunc : dword;
|
|
SharedMem9x_Free : dword;
|
|
VirtualFreeFunc : dword;
|
|
SetErrorModeFunc : dword;
|
|
LoadLibraryAFunc : dword;
|
|
LoadLibraryWFunc : dword;
|
|
GetLastErrorFunc : dword;
|
|
GetModuleHandleAFunc : dword;
|
|
GetModuleHandleWFunc : dword;
|
|
GetCurrentProcessIdFunc : dword;
|
|
OpenFileMappingAFunc : dword;
|
|
MapViewOfFileFunc : dword;
|
|
UnmapViewOfFileFunc : dword;
|
|
CloseHandleFunc : dword;
|
|
FreeLibraryFunc : dword;
|
|
EnterCriticalSectionFunc : dword;
|
|
LeaveCriticalSectionFunc : dword;
|
|
end;
|
|
{$endif}
|
|
|
|
function InjectThread(var injRec: TInjectRec) : integer; stdcall;
|
|
var module : HMODULE;
|
|
i1 : integer;
|
|
oldErr : dword;
|
|
aup : procedure (moduleHandle: HMODULE);
|
|
ir : TInjectRec;
|
|
c1, c2 : NativeUInt;
|
|
nh : PImageNtHeaders;
|
|
{$ifndef win64}
|
|
peb : NativeUInt;
|
|
loaderLock : PRtlCriticalSection;
|
|
firstDll : PLdrDataTableEntry;
|
|
dll : PLdrDataTableEntry;
|
|
{$endif}
|
|
begin
|
|
{$ifndef win64}
|
|
asm
|
|
(*
|
|
// set up an exception handler
|
|
db $e8 // call +0
|
|
dd 0
|
|
pop eax
|
|
add eax, $16
|
|
push eax
|
|
mov eax, fs:[0]
|
|
push eax
|
|
mov fs:[0], esp
|
|
jmp @skipExcept
|
|
|
|
// here comes the exception handler
|
|
// let's simply ignore the exception and continue at @failure
|
|
@except:
|
|
mov eax, [esp+$c]
|
|
db $e8 // call +0
|
|
dd 0
|
|
pop ecx
|
|
add ecx, $1a
|
|
mov [eax+$b8], ecx
|
|
mov ecx, [esp+$8]
|
|
mov [eax+$c4], ecx
|
|
mov eax, 0
|
|
ret
|
|
@failure:
|
|
mov ecx, [esp]
|
|
mov fs:[0], ecx
|
|
add esp, 8
|
|
mov eax, ERROR_ACCESS_DENIED
|
|
pop edi
|
|
pop esi
|
|
pop ebx
|
|
mov esp, ebp
|
|
pop ebp
|
|
ret 4
|
|
|
|
@skipExcept:
|
|
*)
|
|
|
|
mov eax, fs:[$30] // eax := threadEnvironmentBlock^.processDataBase
|
|
mov peb, eax
|
|
end;
|
|
{$endif}
|
|
result := 0;
|
|
ir := injRec;
|
|
ir.VirtualFreeFunc(@injRec, 0, MEM_RELEASE);
|
|
oldErr := ir.SetErrorModeFunc(SEM_FAILCRITICALERRORS or SEM_NOGPFAULTERRORBOX or SEM_NOOPENFILEERRORBOX);
|
|
if ir.load then begin
|
|
module := ir.LoadLibraryWFunc(ir.libW);
|
|
if module = 0 then
|
|
result := ir.GetLastErrorFunc;
|
|
end else begin
|
|
module := ir.GetModuleHandleWFunc(ir.libW);
|
|
if module = 0 then
|
|
result := ir.GetLastErrorFunc;
|
|
end;
|
|
ir.SetErrorModeFunc(oldErr);
|
|
if module <> 0 then begin
|
|
if ir.load then begin
|
|
{$ifndef win64}
|
|
loaderLock := TPPointer(peb + $a0)^;
|
|
ir.EnterCriticalSectionFunc(loaderLock^);
|
|
firstDll := TPPointer(TPCardinal(peb + $c)^ + $c)^; // peb.Ldr.InLoadOrderModuleList.FLink
|
|
if firstDll <> nil then begin
|
|
dll := firstDll;
|
|
repeat
|
|
if dll.DllBase = pointer(module) then begin
|
|
if firstDll.ObsoleteLoadCount >= $ff then
|
|
dll.ObsoleteLoadCount := firstDll.ObsoleteLoadCount
|
|
else
|
|
dll.ObsoleteLoadCount := $ffff;
|
|
break;
|
|
end;
|
|
dll := pointer(dll.InLoadOrderLinks.Flink);
|
|
until dll = firstDll;
|
|
end;
|
|
ir.LeaveCriticalSectionFunc(loaderLock^);
|
|
{$endif}
|
|
end else begin
|
|
aup := nil;
|
|
if TPWord(module)^ = CEMAGIC then begin
|
|
nh := pointer(module + dword(pointer(module + CENEWHDR)^));
|
|
if nh.signature = CPEMAGIC then begin
|
|
if nh.OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR64_MAGIC then
|
|
c1 := PImageOptionalHeader64(@nh.OptionalHeader).SizeOfImage
|
|
else
|
|
c1 := nh.OptionalHeader.SizeOfImage;
|
|
if c1 > 0 then
|
|
for c2 := 8 to c1 - 1 - 24 do
|
|
if (TPInt64(module + c2 + 0)^ = $3E3A3A1D16313438) and
|
|
(TPInt64(module + c2 + 8)^ = $3A3D3B003A212014) and
|
|
(TPInt64(module + c2 + 16)^ = $27303E2734183E3A) then begin
|
|
aup := TPPointer(module + c2 - 8)^;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
if @aup <> nil then
|
|
aup(module);
|
|
{$ifndef win64}
|
|
loaderLock := TPPointer(peb + $a0)^;
|
|
ir.EnterCriticalSectionFunc(loaderLock^);
|
|
firstDll := TPPointer(TPCardinal(peb + $c)^ + $c)^; // peb.Ldr.InLoadOrderModuleList.FLink
|
|
if firstDll <> nil then begin
|
|
dll := firstDll;
|
|
repeat
|
|
if dll.DllBase = pointer(module) then begin
|
|
dll.ObsoleteLoadCount := 1;
|
|
break;
|
|
end;
|
|
dll := pointer(dll.InLoadOrderLinks.Flink);
|
|
until dll = firstDll;
|
|
end;
|
|
{$endif}
|
|
if ir.FreeLibraryFunc(module) then begin
|
|
i1 := 0;
|
|
while ir.FreeLibraryFunc(module) and (i1 < $ffff) do
|
|
inc(i1);
|
|
result := 0;
|
|
end else
|
|
result := ir.GetLastErrorFunc;
|
|
{$ifndef win64}
|
|
loaderLock := TPPointer(peb + $a0)^;
|
|
ir.LeaveCriticalSectionFunc(loaderLock^);
|
|
{$endif}
|
|
end;
|
|
end;
|
|
(*
|
|
asm
|
|
// final cleanup: remove the exception handler again
|
|
mov ecx, [esp]
|
|
mov fs:[0], ecx
|
|
add esp, 8
|
|
end;
|
|
*)
|
|
end;
|
|
|
|
const CInjectThread32 : array [0..520] of byte = (
|
|
$55, $8B, $EC, $81, $C4, $80, $FC, $FF, $FF, $53, $56, $57, $8B, $55, $08, $64,
|
|
$8B, $05, $30, $00, $00, $00, $89, $45, $FC, $33, $C0, $89, $45, $F8, $8B, $F2,
|
|
$8D, $BD, $82, $FC, $FF, $FF, $B9, $DC, $00, $00, $00, $F3, $A5, $66, $A5, $68,
|
|
$00, $80, $00, $00, $6A, $00, $52, $FF, $55, $B8, $68, $03, $80, $00, $00, $FF,
|
|
$55, $BC, $8B, $F0, $80, $BD, $82, $FC, $FF, $FF, $00, $74, $18, $8D, $85, $92,
|
|
$FD, $FF, $FF, $50, $FF, $55, $C4, $8B, $D8, $85, $DB, $75, $1E, $FF, $55, $C8,
|
|
$89, $45, $F8, $EB, $16, $8D, $85, $92, $FD, $FF, $FF, $50, $FF, $55, $D0, $8B,
|
|
$D8, $85, $DB, $75, $06, $FF, $55, $C8, $89, $45, $F8, $56, $FF, $55, $BC, $85,
|
|
$DB, $0F, $84, $76, $01, $00, $00, $80, $BD, $82, $FC, $FF, $FF, $00, $74, $50,
|
|
$8B, $45, $FC, $05, $A0, $00, $00, $00, $8B, $30, $56, $FF, $55, $EC, $8B, $45,
|
|
$FC, $83, $C0, $0C, $8B, $00, $83, $C0, $0C, $8B, $10, $85, $D2, $74, $28, $8B,
|
|
$C2, $8B, $48, $18, $3B, $CB, $75, $19, $66, $8B, $4A, $38, $66, $81, $F9, $FF,
|
|
$00, $72, $06, $66, $89, $48, $38, $EB, $0E, $66, $C7, $40, $38, $FF, $00, $EB,
|
|
$06, $8B, $00, $3B, $D0, $75, $DA, $56, $FF, $55, $F0, $E9, $1D, $01, $00, $00,
|
|
$33, $C9, $66, $81, $3B, $4D, $5A, $0F, $85, $87, $00, $00, $00, $8D, $43, $3C,
|
|
$8B, $00, $03, $C3, $81, $38, $50, $45, $00, $00, $75, $78, $66, $81, $78, $18,
|
|
$0B, $02, $75, $08, $83, $C0, $18, $8B, $40, $38, $EB, $03, $8B, $40, $50, $85,
|
|
$C0, $76, $61, $48, $83, $E8, $18, $83, $E8, $08, $72, $58, $40, $89, $45, $F4,
|
|
$BE, $08, $00, $00, $00, $8D, $3C, $1E, $8B, $C7, $81, $78, $04, $1D, $3A, $3A,
|
|
$3E, $75, $3B, $81, $38, $38, $34, $31, $16, $75, $33, $8B, $C7, $83, $C0, $08,
|
|
$81, $78, $04, $00, $3B, $3D, $3A, $75, $25, $81, $38, $14, $20, $21, $3A, $75,
|
|
$1D, $8B, $C7, $83, $C0, $10, $81, $78, $04, $27, $3E, $30, $27, $75, $0F, $81,
|
|
$38, $3A, $3E, $18, $34, $75, $07, $83, $EF, $08, $8B, $0F, $EB, $06, $46, $FF,
|
|
$4D, $F4, $75, $B1, $85, $C9, $74, $06, $8B, $F1, $8B, $C3, $FF, $D6, $8B, $45,
|
|
$FC, $05, $A0, $00, $00, $00, $8B, $30, $56, $FF, $55, $EC, $8B, $45, $FC, $83,
|
|
$C0, $0C, $8B, $00, $83, $C0, $0C, $8B, $10, $85, $D2, $74, $28, $8B, $C2, $8B,
|
|
$48, $18, $3B, $CB, $75, $19, $80, $BD, $82, $FC, $FF, $FF, $00, $74, $08, $66,
|
|
$C7, $40, $38, $FF, $00, $EB, $0E, $66, $C7, $40, $38, $01, $00, $EB, $06, $8B,
|
|
$00, $3B, $D0, $75, $DA, $53, $FF, $55, $E8, $85, $C0, $74, $1C, $33, $F6, $EB,
|
|
$01, $46, $53, $FF, $55, $E8, $85, $C0, $74, $08, $81, $FE, $FF, $FF, $00, $00,
|
|
$7C, $EF, $33, $C0, $89, $45, $F8, $EB, $06, $FF, $55, $C8, $89, $45, $F8, $8B,
|
|
$45, $FC, $05, $A0, $00, $00, $00, $8B, $30, $56, $FF, $55, $F0, $8B, $45, $F8,
|
|
$5F, $5E, $5B, $8B, $E5, $5D, $C2, $04, $00);
|
|
|
|
{$ifdef win64}
|
|
|
|
const CInjectThread64 : array [0..716] of byte = (
|
|
$48, $89, $4C, $24, $08, $56, $57, $48, $81, $EC, $D8, $07, $00, $00, $C7,
|
|
$84, $24, $F0, $03, $00, $00, $00, $00, $00, $00, $48, $8D, $84, $24, $18,
|
|
$04, $00, $00, $48, $8B, $F8, $48, $8B, $B4, $24, $F0, $07, $00, $00, $B9,
|
|
$B6, $03, $00, $00, $F3, $A4, $48, $8D, $44, $24, $20, $48, $8D, $8C, $24,
|
|
$18, $04, $00, $00, $48, $8B, $F8, $48, $8B, $F1, $B9, $B6, $03, $00, $00,
|
|
$F3, $A4, $41, $B8, $00, $80, $00, $00, $33, $D2, $48, $8B, $8C, $24, $F0,
|
|
$07, $00, $00, $FF, $94, $24, $5E, $03, $00, $00, $B9, $01, $00, $00, $00,
|
|
$FF, $94, $24, $66, $03, $00, $00, $89, $84, $24, $E0, $03, $00, $00, $0F,
|
|
$BE, $44, $24, $20, $85, $C0, $74, $32, $48, $8D, $8C, $24, $30, $01, $00,
|
|
$00, $FF, $94, $24, $76, $03, $00, $00, $48, $89, $84, $24, $E8, $03, $00,
|
|
$00, $48, $83, $BC, $24, $E8, $03, $00, $00, $00, $75, $0E, $FF, $94, $24,
|
|
$7E, $03, $00, $00, $89, $84, $24, $F0, $03, $00, $00, $EB, $30, $48, $8D,
|
|
$8C, $24, $30, $01, $00, $00, $FF, $94, $24, $8E, $03, $00, $00, $48, $89,
|
|
$84, $24, $E8, $03, $00, $00, $48, $83, $BC, $24, $E8, $03, $00, $00, $00,
|
|
$75, $0E, $FF, $94, $24, $7E, $03, $00, $00, $89, $84, $24, $F0, $03, $00,
|
|
$00, $8B, $8C, $24, $E0, $03, $00, $00, $FF, $94, $24, $66, $03, $00, $00,
|
|
$48, $83, $BC, $24, $E8, $03, $00, $00, $00, $0F, $84, $BD, $01, $00, $00,
|
|
$0F, $BE, $44, $24, $20, $85, $C0, $74, $05, $E9, $AF, $01, $00, $00, $48,
|
|
$C7, $84, $24, $F8, $03, $00, $00, $00, $00, $00, $00, $48, $8B, $84, $24,
|
|
$E8, $03, $00, $00, $0F, $B7, $00, $3D, $4D, $5A, $00, $00, $0F, $85, $10,
|
|
$01, $00, $00, $48, $8B, $84, $24, $E8, $03, $00, $00, $8B, $40, $3C, $89,
|
|
$84, $24, $00, $04, $00, $00, $8B, $84, $24, $00, $04, $00, $00, $48, $8B,
|
|
$8C, $24, $E8, $03, $00, $00, $48, $03, $C8, $48, $8B, $C1, $48, $89, $84,
|
|
$24, $08, $04, $00, $00, $48, $8B, $84, $24, $08, $04, $00, $00, $81, $38,
|
|
$50, $45, $00, $00, $0F, $85, $CD, $00, $00, $00, $48, $8B, $84, $24, $08,
|
|
$04, $00, $00, $83, $78, $50, $00, $0F, $84, $BB, $00, $00, $00, $C7, $84,
|
|
$24, $10, $04, $00, $00, $08, $00, $00, $00, $EB, $11, $8B, $84, $24, $10,
|
|
$04, $00, $00, $83, $C0, $01, $89, $84, $24, $10, $04, $00, $00, $48, $8B,
|
|
$84, $24, $08, $04, $00, $00, $8B, $40, $50, $83, $E8, $18, $39, $84, $24,
|
|
$10, $04, $00, $00, $0F, $83, $82, $00, $00, $00, $8B, $84, $24, $10, $04,
|
|
$00, $00, $48, $8B, $8C, $24, $E8, $03, $00, $00, $48, $BA, $38, $34, $31,
|
|
$16, $1D, $3A, $3A, $3E, $48, $39, $14, $01, $75, $5E, $8B, $84, $24, $10,
|
|
$04, $00, $00, $48, $8B, $8C, $24, $E8, $03, $00, $00, $48, $BA, $14, $20,
|
|
$21, $3A, $00, $3B, $3D, $3A, $48, $39, $54, $01, $08, $75, $3E, $8B, $84,
|
|
$24, $10, $04, $00, $00, $48, $8B, $8C, $24, $E8, $03, $00, $00, $48, $BA,
|
|
$3A, $3E, $18, $34, $27, $3E, $30, $27, $48, $39, $54, $01, $10, $75, $1E,
|
|
$8B, $84, $24, $10, $04, $00, $00, $48, $8B, $8C, $24, $E8, $03, $00, $00,
|
|
$48, $8B, $44, $01, $F8, $48, $89, $84, $24, $F8, $03, $00, $00, $EB, $05,
|
|
$E9, $52, $FF, $FF, $FF, $48, $83, $BC, $24, $F8, $03, $00, $00, $00, $74,
|
|
$0F, $48, $8B, $8C, $24, $E8, $03, $00, $00, $FF, $94, $24, $F8, $03, $00,
|
|
$00, $48, $8B, $8C, $24, $E8, $03, $00, $00, $FF, $94, $24, $BE, $03, $00,
|
|
$00, $85, $C0, $74, $42, $C7, $84, $24, $14, $04, $00, $00, $00, $00, $00,
|
|
$00, $48, $8B, $8C, $24, $E8, $03, $00, $00, $FF, $94, $24, $BE, $03, $00,
|
|
$00, $85, $C0, $74, $22, $8B, $84, $24, $14, $04, $00, $00, $83, $C0, $01,
|
|
$89, $84, $24, $14, $04, $00, $00, $81, $BC, $24, $14, $04, $00, $00, $FF,
|
|
$FF, $00, $00, $75, $02, $EB, $02, $EB, $CB, $EB, $0E, $FF, $94, $24, $7E,
|
|
$03, $00, $00, $89, $84, $24, $F0, $03, $00, $00, $8B, $84, $24, $F0, $03,
|
|
$00, $00, $48, $81, $C4, $D8, $07, $00, $00, $5F, $5E, $C3);
|
|
|
|
{$endif}
|
|
|
|
function InstallInjectThreadProc(process: THandle; pid: dword) : pointer;
|
|
type TMchIInjT = packed record
|
|
name : array [0..7] of AnsiChar;
|
|
pid : dword;
|
|
injThread : pointer;
|
|
end;
|
|
var map : THandle;
|
|
newMap : boolean;
|
|
buf : ^TMchIInjT;
|
|
s1 : AnsiString;
|
|
i1 : integer;
|
|
c1 : THandle;
|
|
thProc : pointer;
|
|
op : dword;
|
|
{$ifdef win64}
|
|
is32proc : boolean;
|
|
{$endif}
|
|
begin
|
|
result := nil;
|
|
{$ifdef win64}
|
|
is32proc := true;
|
|
{$endif}
|
|
s1 := DecryptStr(CMchIInjT);
|
|
if pid <> 0 then begin
|
|
{$ifdef win64}
|
|
is32proc := not Is64bitProcess(process);
|
|
{$endif}
|
|
map := CreateGlobalFileMapping(PAnsiChar(s1 + IntToHexExA(pid)), sizeOf(buf^));
|
|
end else
|
|
map := 0;
|
|
if map <> 0 then begin
|
|
newMap := GetLastError <> ERROR_ALREADY_EXISTS;
|
|
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
if buf <> nil then begin
|
|
for i1 := 1 to 50 do
|
|
if newMap or ((TPInt64(@buf^.name)^ = TPInt64(s1)^) and (buf^.pid = pid)) then
|
|
break
|
|
else
|
|
Sleep(50);
|
|
if newMap or ((TPInt64(@buf^.name)^ <> TPInt64(s1)^) and (buf^.pid = pid)) then begin
|
|
{$ifdef win64}
|
|
if is32proc then begin
|
|
thProc := AllocMemEx(sizeOf(CInjectThread32), process, X86AllocationAddress);
|
|
WriteProcessMemory(process, thProc, @CInjectThread32, sizeOf(CInjectThread32), c1);
|
|
VirtualProtectEx(process, thProc, sizeof(CInjectThread32), PAGE_EXECUTE_READ, @op);
|
|
TPCardinal(@buf^.injThread)^ := dword(thProc);
|
|
end else begin
|
|
thProc := AllocMemEx(sizeOf(CInjectThread64), process);
|
|
WriteProcessMemory(process, thProc, @CInjectThread64, sizeOf(CInjectThread64), c1);
|
|
VirtualProtectEx(process, thProc, sizeof(CInjectThread64), PAGE_EXECUTE_READ, @op);
|
|
buf^.injThread := thProc;
|
|
end;
|
|
{$else}
|
|
thProc := AllocMemEx(sizeOf(CInjectThread32), process);
|
|
WriteProcessMemory(process, thProc, @CInjectThread32, sizeOf(CInjectThread32), c1);
|
|
VirtualProtectEx(process, thProc, sizeof(CInjectThread32), PAGE_EXECUTE_READ, @op);
|
|
buf^.injThread := thProc;
|
|
{$endif}
|
|
buf^.pid := pid;
|
|
AtomicMove(pointer(s1), @buf^.name, 8);
|
|
DuplicateHandle(GetCurrentProcess, map, process, @c1, 0, false, DUPLICATE_SAME_ACCESS);
|
|
{$ifdef d2006}
|
|
if pid = GetCurrentProcessId then
|
|
RegisterExpectedMemoryLeak(pointer(c1));
|
|
{$endif}
|
|
end;
|
|
{$ifdef win64}
|
|
if is32proc then
|
|
result := pointer(TPCardinal(@buf^.injThread)^)
|
|
else
|
|
{$endif}
|
|
result := buf^.injThread;
|
|
UnmapViewOfFile(buf);
|
|
end;
|
|
CloseHandle(map);
|
|
end;
|
|
end;
|
|
|
|
{$ifdef win64}
|
|
|
|
function IsProcessLargeAddressAware(process: THandle) : boolean;
|
|
// find out if the specified 64bit process is aware of large addresses
|
|
// if it is not, system dlls are loaded in the lower 4GB of the address range
|
|
var exeImage : NativeUInt;
|
|
nh : TImageNtHeaders64;
|
|
nhOffset : dword;
|
|
read : NativeUInt;
|
|
begin
|
|
result := (not ReadProcessMemory(process, pointer(GetPeb(process) + $10), @exeImage, sizeOf(exeImage), read)) or
|
|
(not ReadProcessMemory(process, pointer(exeImage + CENEWHDR), @nhOffset, sizeOf(nhOffset), read)) or
|
|
(not ReadProcessMemory(process, pointer(exeImage + nhOffset), @nh, sizeOf(nh ), read)) or
|
|
(nh.FileHeader.Characteristics and IMAGE_FILE_LARGE_ADDRESS_AWARE <> 0);
|
|
end;
|
|
|
|
function LargeAddressAwareApiTo2GB(process: THandle; largeApi: pointer) : pointer;
|
|
var mbi : TMemoryBasicInformation;
|
|
arrCh : array [0..MAX_PATH] of wideChar;
|
|
remoteModule : HMODULE;
|
|
dllName : PWideChar;
|
|
i1 : integer;
|
|
begin
|
|
result := largeApi;
|
|
if (VirtualQuery(largeApi, mbi, sizeOf(mbi)) = sizeOf(mbi)) and
|
|
(mbi.State = MEM_COMMIT) and
|
|
(mbi.AllocationBase <> nil) and
|
|
(GetModuleFileNameW(HMODULE(mbi.AllocationBase), arrCh, MAX_PATH) > 0) then begin
|
|
dllName := arrCh;
|
|
for i1 := lstrlenW(arrCh) - 1 downto 1 do
|
|
if arrCh[i1] = '\' then begin
|
|
dllName := @arrCh[i1 + 1];
|
|
break;
|
|
end;
|
|
remoteModule := GetRemoteModuleHandle(process, false, dllName);
|
|
if remoteModule <> 0 then
|
|
result := pointer(NativeUInt(largeApi) - NativeUInt(mbi.AllocationBase) + remoteModule);
|
|
end;
|
|
end;
|
|
|
|
{$endif}
|
|
|
|
function StartInjectLibraryX(process: THandle; pid: dword; inject: boolean; libA: PAnsiChar; libW: PWideChar) : THandle;
|
|
var ir : TInjectRec;
|
|
irSize : integer;
|
|
{$ifdef win64}
|
|
pir32 : ^TInjectRec32;
|
|
{$endif}
|
|
proc : pointer;
|
|
par : pointer;
|
|
c1, c2 : dword;
|
|
c64 : NativeUInt;
|
|
size : NativeUInt;
|
|
op : dword;
|
|
pos : integer;
|
|
nh : {$ifdef win64} PImageNtHeaders64 {$else} PImageNtHeaders32 {$endif};
|
|
procs : array [0..13] of pointer;
|
|
apis : array [0..13] of AnsiString;
|
|
b1 : boolean;
|
|
begin
|
|
{$ifdef log}
|
|
log('StartInjectLibraryX (process: ' + '%p' +
|
|
'; pid: ' + IntToHexExA(pid) +
|
|
'; inject: ' + booleanToCharA(inject) +
|
|
'; lib: %1)', libA, libW, process);
|
|
{$endif}
|
|
result := 0;
|
|
if libW <> nil then begin
|
|
if not inject then begin
|
|
// for uninjection we only use the dll file name instead of the full path
|
|
// this works around a chrome uninjection issue
|
|
pos := PosPCharW('\', libW, 0, 0, false, maxInt, 0);
|
|
if pos >= 0 then
|
|
libW := libW + pos + 1;
|
|
end;
|
|
lstrcpyW(ir.libW, libW);
|
|
end;
|
|
if libA <> nil then
|
|
lstrcpyA(ir.libA, libA);
|
|
ir.load := inject;
|
|
proc := InstallInjectThreadProc(process, pid);
|
|
if proc <> nil then begin
|
|
par := nil;
|
|
{$ifdef win64}
|
|
if not Is64bitProcess(process) then begin
|
|
irSize := sizeOf(TInjectRec32);
|
|
if Init32bitKernelAPIs(process) then begin
|
|
pir32 := @ir;
|
|
pir32.GetVersionFunc := GetKernelAPI(0);
|
|
pir32.SharedMem9x_Free := 0;
|
|
pir32.VirtualFreeFunc := GetKernelAPI(1);
|
|
pir32.SetErrorModeFunc := GetKernelAPI(2);
|
|
pir32.LoadLibraryAFunc := 0;
|
|
pir32.LoadLibraryWFunc := GetKernelAPI(3);
|
|
pir32.GetLastErrorFunc := GetKernelAPI(4);
|
|
pir32.GetModuleHandleAFunc := 0;
|
|
pir32.GetModuleHandleWFunc := GetKernelAPI(5);
|
|
pir32.GetCurrentProcessIdFunc := GetKernelAPI(6);
|
|
pir32.OpenFileMappingAFunc := GetKernelAPI(7);
|
|
pir32.MapViewOfFileFunc := GetKernelAPI(8);
|
|
pir32.UnmapViewOfFileFunc := GetKernelAPI(9);
|
|
pir32.CloseHandleFunc := GetKernelAPI(10);
|
|
pir32.FreeLibraryFunc := GetKernelAPI(11);
|
|
pir32.EnterCriticalSectionFunc := GetKernelAPI(12);
|
|
pir32.LeaveCriticalSectionFunc := GetKernelAPI(13);
|
|
par := AllocMemEx(irSize, process, pointer(NativeUInt(X86AllocationAddress) - $10000));
|
|
end;
|
|
end else
|
|
if not IsProcessLargeAddressAware(process) then begin
|
|
if GetVersion2GB = nil then begin
|
|
GetVersion2GB := LargeAddressAwareApiTo2GB(process, @GetVersionFunc );
|
|
GetLastError2GB := LargeAddressAwareApiTo2GB(process, @GetLastErrorFunc );
|
|
GetCurrentProcessId2GB := LargeAddressAwareApiTo2GB(process, @GetCurrentProcessIdFunc );
|
|
VirtualFree2GB := LargeAddressAwareApiTo2GB(process, @VirtualFreeFunc );
|
|
SetErrorMode2GB := LargeAddressAwareApiTo2GB(process, @SetErrorModeFunc );
|
|
LoadLibraryW2GB := LargeAddressAwareApiTo2GB(process, @LoadLibraryWFunc );
|
|
GetModuleHandleW2GB := LargeAddressAwareApiTo2GB(process, @GetModuleHandleWFunc );
|
|
OpenFileMappingA2GB := LargeAddressAwareApiTo2GB(process, @OpenFileMappingAFunc );
|
|
MapViewOfFile2GB := LargeAddressAwareApiTo2GB(process, @MapViewOfFileFunc );
|
|
UnmapViewOfFile2GB := LargeAddressAwareApiTo2GB(process, @UnmapViewOfFileFunc );
|
|
CloseHandle2GB := LargeAddressAwareApiTo2GB(process, @CloseHandleFunc );
|
|
FreeLibrary2GB := LargeAddressAwareApiTo2GB(process, @FreeLibraryFunc );
|
|
EnterCriticalSection2GB := LargeAddressAwareApiTo2GB(process, @EnterCriticalSectionFunc);
|
|
LeaveCriticalSection2GB := LargeAddressAwareApiTo2GB(process, @LeaveCriticalSectionFunc);
|
|
end;
|
|
irSize := sizeOf(TInjectRec);
|
|
ir.GetVersionFunc := GetVersion2GB;
|
|
ir.SharedMem9x_Free := nil;
|
|
ir.VirtualFreeFunc := VirtualFree2GB;
|
|
ir.SetErrorModeFunc := SetErrorMode2GB;
|
|
ir.LoadLibraryAFunc := nil;
|
|
ir.LoadLibraryWFunc := LoadLibraryW2GB;
|
|
ir.GetLastErrorFunc := GetLastError2GB;
|
|
ir.GetModuleHandleAFunc := nil;
|
|
ir.GetModuleHandleWFunc := GetModuleHandleW2GB;
|
|
ir.GetCurrentProcessIdFunc := GetCurrentProcessId2GB;
|
|
ir.OpenFileMappingAFunc := OpenFileMappingA2GB;
|
|
ir.MapViewOfFileFunc := MapViewOfFile2GB;
|
|
ir.UnmapViewOfFileFunc := UnmapViewOfFile2GB;
|
|
ir.CloseHandleFunc := CloseHandle2GB;
|
|
ir.FreeLibraryFunc := FreeLibrary2GB;
|
|
ir.EnterCriticalSectionFunc := EnterCriticalSection2GB;
|
|
ir.LeaveCriticalSectionFunc := LeaveCriticalSection2GB;
|
|
if @ir.VirtualFreeFunc <> nil then
|
|
par := AllocMemEx(irSize, process);
|
|
end else begin
|
|
{$endif}
|
|
irSize := sizeOf(TInjectRec);
|
|
nh := pointer(GetImageNtHeaders(GetModuleHandle(kernel32)));
|
|
if (nh <> nil) and ReadProcessMemory(process, @nh.OptionalHeader.CheckSum, @c2, 4, c64) and (c64 = 4) and (c2 = nh.OptionalHeader.CheckSum) then begin
|
|
b1 := true;
|
|
ir.GetVersionFunc := GetVersionFunc;
|
|
ir.SharedMem9x_Free := nil;
|
|
ir.VirtualFreeFunc := VirtualFreeFunc;
|
|
ir.SetErrorModeFunc := SetErrorModeFunc;
|
|
ir.LoadLibraryAFunc := LoadLibraryAFunc;
|
|
ir.LoadLibraryWFunc := LoadLibraryWFunc;
|
|
ir.GetLastErrorFunc := GetLastErrorFunc;
|
|
ir.GetModuleHandleAFunc := GetModuleHandleAFunc;
|
|
ir.GetModuleHandleWFunc := GetModuleHandleWFunc;
|
|
ir.GetCurrentProcessIdFunc := GetCurrentProcessIdFunc;
|
|
ir.OpenFileMappingAFunc := OpenFileMappingAFunc;
|
|
ir.MapViewOfFileFunc := MapViewOfFileFunc;
|
|
ir.UnmapViewOfFileFunc := UnmapViewOfFileFunc;
|
|
ir.CloseHandleFunc := CloseHandleFunc;
|
|
ir.FreeLibraryFunc := FreeLibraryFunc;
|
|
end else begin
|
|
// for some reason the target process seems to have a different kernel32.dll version loaded !?
|
|
// so we can't use the API addresses from our own process, but have to grab them from the target process
|
|
apis[ 0] := DecryptStr(CGetVersion);
|
|
apis[ 1] := DecryptStr(CVirtualFree);
|
|
apis[ 2] := DecryptStr(CSetErrorMode);
|
|
apis[ 3] := DecryptStr(CLoadLibraryA);
|
|
apis[ 4] := DecryptStr(CLoadLibraryW);
|
|
apis[ 5] := DecryptStr(CGetLastError);
|
|
apis[ 6] := DecryptStr(CGetModuleHandleA);
|
|
apis[ 7] := DecryptStr(CGetModuleHandleW);
|
|
apis[ 8] := DecryptStr(CGetCurrentProcessId);
|
|
apis[ 9] := DecryptStr(COpenFileMappingA);
|
|
apis[10] := DecryptStr(CMapViewOfFile);
|
|
apis[11] := DecryptStr(CUnmapViewOfFile);
|
|
apis[12] := DecryptStr(CCloseHandle);
|
|
apis[13] := DecryptStr(CFreeLibrary);
|
|
b1 := GetRemoteProcAddresses(process, {$ifdef win64} false {$else} true {$endif}, kernel32, apis, @procs, 14);
|
|
if b1 then begin
|
|
ir.GetVersionFunc := procs[ 0];
|
|
ir.SharedMem9x_Free := nil;
|
|
ir.VirtualFreeFunc := procs[ 1];
|
|
ir.SetErrorModeFunc := procs[ 2];
|
|
ir.LoadLibraryAFunc := procs[ 3];
|
|
ir.LoadLibraryWFunc := procs[ 4];
|
|
ir.GetLastErrorFunc := procs[ 5];
|
|
ir.GetModuleHandleAFunc := procs[ 6];
|
|
ir.GetModuleHandleWFunc := procs[ 7];
|
|
ir.GetCurrentProcessIdFunc := procs[ 8];
|
|
ir.OpenFileMappingAFunc := procs[ 9];
|
|
ir.MapViewOfFileFunc := procs[10];
|
|
ir.UnmapViewOfFileFunc := procs[11];
|
|
ir.CloseHandleFunc := procs[12];
|
|
ir.FreeLibraryFunc := procs[13];
|
|
end;
|
|
end;
|
|
if b1 then begin
|
|
nh := pointer(GetImageNtHeaders(GetModuleHandle('ntdll.dll')));
|
|
if (nh <> nil) and ReadProcessMemory(process, @nh.OptionalHeader.CheckSum, @c2, 4, c64) and (c64 = 4) and (c2 = nh.OptionalHeader.CheckSum) then begin
|
|
ir.EnterCriticalSectionFunc := EnterCriticalSectionFunc;
|
|
ir.LeaveCriticalSectionFunc := LeaveCriticalSectionFunc;
|
|
end else begin
|
|
// for some reason the target process seems to have a different ntdll.dll version loaded !?
|
|
// so we can't use the API addresses from our own process, but have to grab them from the target process
|
|
apis[0] := DecryptStr(CRtlEnterCriticalSection);
|
|
apis[1] := DecryptStr(CRtlLeaveCriticalSection);
|
|
b1 := GetRemoteProcAddresses(process, {$ifdef win64} false {$else} true {$endif}, 'ntdll.dll', apis, @procs, 2);
|
|
if b1 then begin
|
|
ir.EnterCriticalSectionFunc := procs[0];
|
|
ir.LeaveCriticalSectionFunc := procs[1];
|
|
end;
|
|
end;
|
|
end;
|
|
if b1 and (@ir.VirtualFreeFunc <> nil) then
|
|
par := AllocMemEx(irSize, process);
|
|
{$ifdef win64}
|
|
end;
|
|
{$endif}
|
|
if par <> nil then begin
|
|
VirtualProtectEx(process, par, irSize, PAGE_READWRITE, @op);
|
|
if WriteProcessMemory(process, par, @ir, irSize, size) then
|
|
result := CreateRemoteThreadEx(process, nil, 1 * 1024 * 1024, proc, par, 0, c1);
|
|
if result = 0 then begin
|
|
c1 := GetLastError;
|
|
FreeMemEx(par, process);
|
|
SetLastError(c1);
|
|
end;
|
|
end;
|
|
end;
|
|
{$ifdef log}
|
|
c1 := GetLastError;
|
|
log('StartInjectLibraryX (process: ' + '%p' +
|
|
'; pid: ' + IntToHexExA(pid) +
|
|
'; inject: ' + booleanToCharA(inject) +
|
|
'; lib: %1) -> th' + IntToHexExA(result),
|
|
libA, libW, process);
|
|
SetLastError(c1);
|
|
{$endif}
|
|
end;
|
|
|
|
function WaitForInjectLibraryX(thread: THandle; timeOut, time: dword) : boolean;
|
|
var error : dword;
|
|
begin
|
|
{$ifdef log}
|
|
log('WaitForInjectLibraryX (thread: ' + IntToHexExA(thread) +
|
|
'; timeOut: ' + IntToHexExA(timeOut) +
|
|
'; time: ' + IntToHexExA(time) +
|
|
')');
|
|
{$endif}
|
|
if timeOut = INFINITE then
|
|
time := 0
|
|
else time := TickDif(time);
|
|
if timeOut > time then begin
|
|
if WaitForSingleObject(thread, timeOut - time) = WAIT_OBJECT_0 then
|
|
GetExitCodeThread(thread, error)
|
|
else error := GetLastError;
|
|
end else
|
|
if timeOut = 0 then
|
|
error := 0
|
|
else error := ERROR_TIMEOUT;
|
|
CloseHandle(thread);
|
|
result := error = 0;
|
|
{$ifdef log}
|
|
log('WaitForInjectLibraryX (thread: ' + IntToHexExA(thread) + ') -> ' + booleanToCharA(result) + ' (' + IntToStrExA(error) + ')');
|
|
{$endif}
|
|
if not result then
|
|
SetLastError(error);
|
|
end;
|
|
|
|
{$ifdef log}
|
|
procedure LogProcesses(const prcs: TDAProcess);
|
|
var i1 : integer;
|
|
begin
|
|
log('EnumProcesses -> ' + IntToStrExA(Length(prcs)));
|
|
for i1 := 0 to Length(prcs) - 1 do
|
|
log(' process[' + IntToStrExA(i1) + ']: ' + IntToHexExA(prcs[i1].id, 8) + ' ' + ExtractFileNameA(AnsiString(prcs[i1].exeFile)));
|
|
end;
|
|
{$endif}
|
|
|
|
var CharLowerBuffW : function (str: PWideChar; len: dword) : dword; stdcall = nil;
|
|
function SplitNamePath(str: UnicodeString; var path, name: UnicodeString) : boolean;
|
|
var i1 : integer;
|
|
begin
|
|
if @CharLowerBuffW = nil then
|
|
CharLowerBuffW := GetProcAddress(GetModuleHandle(user32), PAnsiChar(DecryptStr(CCharLowerBuffW)));
|
|
if @CharLowerBuffW <> nil then begin
|
|
{$ifndef ver120}{$ifndef ver130}
|
|
UniqueString(str);
|
|
{$endif}{$endif}
|
|
CharLowerBuffW(PWideChar(str), Length(str));
|
|
end else
|
|
str := LowStrW(str);
|
|
i1 := PosStrW('\', str, Length(str) - 1, 1);
|
|
result := i1 <> 0;
|
|
if result then begin
|
|
path := str;
|
|
name := Copy(str, i1 + 1, maxInt);
|
|
end else begin
|
|
path := '';
|
|
name := str;
|
|
end;
|
|
end;
|
|
|
|
function MatchStrArray(const path, name: UnicodeString; const paths, names: array of UnicodeString) : boolean;
|
|
var i1 : integer;
|
|
begin
|
|
result := false;
|
|
for i1 := 0 to high(paths) do begin
|
|
if (path <> '') and (paths[i1] <> '') then
|
|
result := InternalStrMatchW(path, paths[i1], true)
|
|
else
|
|
result := InternalStrMatchW(name, names[i1], true);
|
|
if result then
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
var LdrLoadDll32 : pointer = nil;
|
|
NtProtectVirtualMemory32 : pointer = nil;
|
|
NtAllocateVirtualMemory32 : pointer = nil;
|
|
NtFreeVirtualMemory32 : pointer = nil;
|
|
NtQuerySystemInformation32 : pointer = nil;
|
|
NtDelayExecution32 : pointer = nil;
|
|
NtQueueApcThread : function (threadHandle: THandle; apcProc, param, statusBock, reserved: pointer) : HRESULT; stdcall = nil;
|
|
|
|
function InjectLibraryX(owner : HMODULE;
|
|
libA : PAnsiChar;
|
|
libW : PWideChar;
|
|
multiInject : boolean;
|
|
process : THandle;
|
|
driverName : PWideChar;
|
|
session : dword;
|
|
options : dword;
|
|
includes : PWideChar;
|
|
excludes : PWideChar;
|
|
excludePIDs : TPACardinal;
|
|
callback : TInjectApprovalCallbackRoutine;
|
|
callbackContext : pointer;
|
|
timeOut : dword;
|
|
mayPatch : boolean;
|
|
mayThread : boolean;
|
|
forcePatch : boolean;
|
|
thread : THandle ) : boolean;
|
|
var peb32 : NativeUInt; // 32bit process environment block
|
|
peb64 : int64; // 64bit process environment block
|
|
|
|
function Read(process: THandle; addr: pointer; var buf; len: NativeInt) : boolean; overload;
|
|
var c1 : NativeUInt;
|
|
begin
|
|
result := ReadProcessMemory(process, addr, @buf, len, c1);
|
|
end;
|
|
|
|
function Read(process: THandle; addr: NativeUInt; var buf; len: NativeInt) : boolean; overload;
|
|
var c1 : NativeUInt;
|
|
begin
|
|
result := ReadProcessMemory(process, pointer(addr), @buf, len, c1);
|
|
end;
|
|
|
|
function Write(process: THandle; addr: pointer; const buf; len: NativeInt; unprotect: boolean = true) : boolean;
|
|
var c1 : NativeUInt;
|
|
begin
|
|
result := ( (not unprotect) or
|
|
VirtualProtectEx(process, addr, len, PAGE_EXECUTE_READWRITE, @c1) ) and
|
|
WriteProcessMemory(process, addr, @buf, len, c1);
|
|
end;
|
|
|
|
function NotInitializedYet(process: THandle) : boolean;
|
|
var c1 : dword;
|
|
i64 : int64;
|
|
size : NativeUInt;
|
|
begin
|
|
if peb64 <> 0 then begin
|
|
{$ifdef win64}
|
|
result := ReadProcessMemory(process, pointer(peb64 + $18), @i64, 8, size) and (i64 = 0);
|
|
{$else}
|
|
result := ReadProcessMemory64(process, peb64 + $18, @i64, 8) and (i64 = 0);
|
|
{$endif}
|
|
end else
|
|
result := (peb32 <> 0) and ReadProcessMemory(process, pointer(peb32 + $c), @c1, 4, size) and (c1 = 0);
|
|
end;
|
|
|
|
function GetExeModuleInfos(process: THandle; var module: HMODULE; var nh: TImageNtHeaders32; var dd: pointer) : boolean;
|
|
|
|
function CheckModule : boolean;
|
|
var w1 : word;
|
|
c1 : dword;
|
|
begin
|
|
result := (module <> 0) and
|
|
Read(process, module, w1, 2) and (w1 = CEMAGIC) and
|
|
Read(process, module + CENEWHDR, c1, 4) and
|
|
Read(process, module + c1, nh, sizeOf(nh)) and (nh.Signature = CPEMAGIC) and
|
|
(nh.FileHeader.Characteristics and IMAGE_FILE_DLL = 0);
|
|
if result then
|
|
dd := @PImageNtHeaders(module + w1)^.OptionalHeader.DataDirectory;
|
|
end;
|
|
|
|
var p1, p2 : pointer;
|
|
mbi : TMemoryBasicInformation;
|
|
c1 : dword;
|
|
i64 : int64;
|
|
begin
|
|
result := false;
|
|
if GetVersion and $80000000 <> 0 then begin
|
|
p1 := nil;
|
|
p2 := nil;
|
|
while VirtualQueryEx(process, p1, mbi, sizeOf(mbi)) = sizeOf(mbi) do begin
|
|
if mbi.AllocationBase <> p2 then begin
|
|
module := HMODULE(mbi.AllocationBase);
|
|
if (mbi.State = MEM_COMMIT) and CheckModule then begin
|
|
result := true;
|
|
break;
|
|
end;
|
|
p2 := mbi.AllocationBase;
|
|
end;
|
|
inc(NativeUInt(p1), mbi.RegionSize);
|
|
end;
|
|
end else begin
|
|
module := 0;
|
|
if (peb32 <> 0) and Read(process, peb32 + $8, c1, 4) then
|
|
module := c1
|
|
else
|
|
{$ifdef win64}
|
|
if (peb64 <> 0) and Read(process, peb64 + $10, i64, 8) then
|
|
{$else}
|
|
if (peb64 <> 0) and ReadProcessMemory64(process, peb64 + $10, @i64, 8) then
|
|
{$endif}
|
|
module := i64;
|
|
result := (module <> 0) and CheckModule;
|
|
end;
|
|
end;
|
|
|
|
type
|
|
TRelJump = packed record
|
|
jmp : byte; // $e9
|
|
target : dword;
|
|
end;
|
|
|
|
function InjectLibraryPatch(process: THandle; pid: dword; thread: THandle) : integer;
|
|
|
|
{$ifndef win64}
|
|
|
|
function InjectLibraryPatchXp32(process: THandle) : integer;
|
|
type
|
|
TInjectRec = packed record
|
|
pEntryPoint : TPCardinal;
|
|
oldEntryPoint : dword;
|
|
oldEntryPointFunc : function (hInstance: HMODULE; reason, reserved: dword) : bool; stdcall;
|
|
NtProtectVirtualMemory : function (process: THandle; var addr: pointer; var size: NativeUInt; newProt: dword; var oldProt: dword) : dword; stdcall;
|
|
LdrLoadDll : function (path, flags, name: pointer; var handle: HMODULE) : dword; stdcall;
|
|
dll : record
|
|
len : word; // length * 2
|
|
maxLen : word; // length * 2 + 2
|
|
str : PWideChar; // @DllStr
|
|
chars : array [0..259] of WideChar;
|
|
end;
|
|
stub : packed record
|
|
movEax : byte;
|
|
buf : pointer;
|
|
jmp : byte;
|
|
target : integer;
|
|
end;
|
|
end;
|
|
|
|
const
|
|
CNewEntryPoint32 : array [0..154] of byte =
|
|
($55, $8b, $ec, $83, $c4, $f4, $53, $56, $57, $8b, $d8, $8b, $75, $0c, $83, $fe,
|
|
$01, $75, $49, $8b, $03, $89, $45, $fc, $c7, $45, $f8, $04, $00, $00, $00, $8d,
|
|
$45, $f4, $50, $6a, $40, $8d, $45, $f8, $50, $8d, $45, $fc, $50, $6a, $ff, $ff,
|
|
$53, $0c, $85, $c0, $75, $26, $8b, $53, $04, $8b, $03, $89, $10, $89, $45, $fc,
|
|
$c7, $45, $f8, $04, $00, $00, $00, $8d, $45, $f4, $50, $8b, $45, $f4, $50, $8d,
|
|
$45, $f8, $50, $8d, $45, $fc, $50, $6a, $ff, $ff, $53, $0c, $83, $7b, $04, $00,
|
|
$74, $14, $8b, $45, $10, $50, $56, $8b, $45, $08, $50, $ff, $53, $08, $85, $c0,
|
|
$75, $04, $33, $c0, $eb, $02, $b0, $01, $f6, $d8, $1b, $ff, $83, $fe, $01, $75,
|
|
$0f, $8d, $45, $f8, $50, $8d, $43, $14, $50, $6a, $00, $6a, $00, $ff, $53, $10,
|
|
$8b, $c7, $5f, $5e, $5b, $8b, $e5, $5d, $c2, $0c, $00);
|
|
(*
|
|
function NewEntryPoint(var injectRec: TInjectRec; d1, d2, reserved, reason: dword; hInstance: NativeUInt) : bool;
|
|
var p1 : pointer;
|
|
c1, c2 : dword;
|
|
begin
|
|
with injectRec do begin
|
|
if reason = DLL_PROCESS_ATTACH then begin
|
|
p1 := pEntryPoint;
|
|
c1 := 4;
|
|
if NtProtectVirtualMemory($ffffffff, p1, c1, PAGE_EXECUTE_READWRITE, c2) = 0 then begin
|
|
pEntryPoint^ := oldEntryPoint;
|
|
p1 := pEntryPoint;
|
|
c1 := 4;
|
|
NtProtectVirtualMemory($ffffffff, p1, c1, c2, c2);
|
|
end;
|
|
end;
|
|
result := (oldEntryPoint = 0) or oldEntryPointFunc(hInstance, reason, reserved);
|
|
if reason = DLL_PROCESS_ATTACH then
|
|
LdrLoadDll(0, nil, @dll, c1);
|
|
end;
|
|
end;
|
|
*)
|
|
var c1 : NativeUInt;
|
|
c2, c3 : dword;
|
|
ir : TInjectRec;
|
|
buf : pointer;
|
|
op : dword;
|
|
begin
|
|
result := 0;
|
|
with ir do begin
|
|
pEntryPoint := NtProc(''); // get the address where ntdll.dll's entry point is stored
|
|
{$ifdef log}log('ntdll.dll''s entry point is stored at ' + IntToHexExA(NativeUInt(pEntryPoint)));{$endif}
|
|
if (pEntryPoint <> nil) and
|
|
ReadProcessMemory(process, pEntryPoint, @oldEntryPoint, 4, c1) then begin
|
|
buf := AllocMemEx(sizeOf(CNewEntryPoint32) + sizeOf(ir), process);
|
|
if buf <> nil then begin
|
|
if oldEntryPoint = 0 then
|
|
oldEntryPointFunc := nil
|
|
else oldEntryPointFunc := pointer(ntdllhandle + oldEntryPoint);
|
|
NtProtectVirtualMemory := NtProc(CNtProtectVirtualMemory, true);
|
|
LdrLoadDll := NtProc(CLdrLoadDll, true);
|
|
dll.len := lstrlenW(libW) * 2;
|
|
dll.maxLen := dll.len + 2;
|
|
dll.str := pointer(NativeUInt(buf) + sizeOf(CNewEntryPoint32) + (NativeUInt(@dll.chars) - NativeUInt(@ir)));
|
|
Move(libW^, dll.chars, dll.maxLen);
|
|
stub.movEax := $b8; // mov eax, dw
|
|
stub.buf := pointer(NativeUInt(buf) + sizeOf(CNewEntryPoint32));
|
|
stub.jmp := $e9; // jmp dw
|
|
stub.target := - sizeOf(CNewEntryPoint32) - sizeOf(ir);
|
|
c2 := NativeUInt(buf) + sizeOf(CNewEntryPoint32) + sizeOf(ir) - sizeOf(stub) - ntdllhandle;
|
|
if WriteProcessMemory(process, buf, @CNewEntryPoint32, sizeOf(CNewEntryPoint32), c1) and
|
|
WriteProcessMemory(process, pointer(NativeUInt(buf) + sizeOf(CNewEntryPoint32)), @ir, sizeOf(ir), c1) and
|
|
VirtualProtectEx(process, pEntryPoint, 4, PAGE_EXECUTE_READWRITE, @c3) then begin
|
|
if WriteProcessMemory(process, pEntryPoint, @c2, 4, c1) then
|
|
result := 1;
|
|
VirtualProtectEx(process, pEntryPoint, 4, c3, @c2);
|
|
end;
|
|
VirtualProtectEx(process, buf, sizeOf(CNewEntryPoint32) + sizeOf(ir), PAGE_EXECUTE_READ, @op);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{$endif}
|
|
|
|
type
|
|
PInjectLib32 = ^TInjectLib32;
|
|
TInjectLib32 = packed record
|
|
movEax : byte; // 0xb8
|
|
param : dword; // pointer // pointer to "self"
|
|
movEcx : byte; // 0xb9
|
|
proc : dword; // pointer // pointer to "self.injectFunc"
|
|
callEcx : word; // 0xd1ff
|
|
magic : dword; // "mciL" / 0x4c69636d
|
|
next : dword; // PInjectLib32Old // next dll (if any)
|
|
pOldApi : dword; // ^TRelJump // which code location in user land do we patch?
|
|
oldApi : TRelJump; // patch backup buffer, contains overwritten code
|
|
// align : byte;
|
|
dll : TUnicodeStr32; // dll path/name
|
|
dllBuf : array [0..259] of WideChar; // string buffer for the dll path/name
|
|
npvm : dword; // pointer // ntdll.NtProtectVirtualMemory
|
|
lld : dword; // pointer // ntdll.LdrLoadDll
|
|
navm : dword; // pointer // ntdll.NtAllocateVirtualMemory
|
|
nfvm : dword; // pointer // ntdll.NtFreeVirtualMemory
|
|
nqsi : dword; // pointer // ntdll.NtQuerySystemInformation
|
|
nde : dword; // pointer // ntdll.NtDelayExcecution
|
|
injectFunc : array [0..41] of byte; // will be filled with CInjectLibFunc32 (see below)
|
|
// injectFunc : array [0..3514] of byte; // will be filled with CInjectLibFunc32 (see below)
|
|
// skip : dword; // skip flag, only in case we can't uninstall our patch
|
|
end;
|
|
|
|
{$ifdef win64}
|
|
|
|
const CInjectLibFunc32 : array [0..1223] of byte =
|
|
($55, $8B, $EC, $83, $C4, $90, $53, $56, $57, $89, $45, $EC, $64, $8B, $05, $30,
|
|
$00, $00, $00, $89, $45, $FC, $64, $8B, $05, $18, $00, $00, $00, $8B, $40, $20,
|
|
$89, $45, $F8, $64, $8B, $05, $18, $00, $00, $00, $8B, $40, $24, $89, $45, $F4,
|
|
$89, $6D, $F0, $8B, $45, $EC, $8B, $40, $14, $8B, $55, $F0, $83, $C2, $04, $89,
|
|
$02, $33, $C0, $89, $45, $CC, $33, $C0, $89, $45, $C8, $33, $C0, $89, $45, $C4,
|
|
$33, $C0, $89, $45, $C0, $33, $C0, $89, $45, $BC, $33, $C0, $89, $45, $B8, $8B,
|
|
$45, $FC, $83, $C0, $0C, $8B, $00, $83, $C0, $14, $89, $45, $D8, $8B, $45, $D8,
|
|
$8B, $F0, $8D, $7D, $90, $B9, $09, $00, $00, $00, $F3, $A5, $E9, $32, $04, $00,
|
|
$00, $8B, $F0, $8D, $7D, $90, $B9, $09, $00, $00, $00, $F3, $A5, $83, $7D, $B0,
|
|
$00, $0F, $84, $1C, $04, $00, $00, $33, $C9, $EB, $01, $41, $8B, $45, $B0, $66,
|
|
$83, $3C, $48, $00, $75, $F5, $8B, $45, $B0, $8D, $44, $48, $EE, $81, $78, $04,
|
|
$64, $00, $6C, $00, $0F, $85, $F9, $03, $00, $00, $81, $38, $6E, $00, $74, $00,
|
|
$0F, $85, $ED, $03, $00, $00, $8B, $45, $B0, $8D, $44, $48, $F6, $81, $78, $04,
|
|
$64, $00, $6C, $00, $0F, $85, $D9, $03, $00, $00, $81, $38, $6C, $00, $2E, $00,
|
|
$0F, $85, $CD, $03, $00, $00, $8B, $5D, $A0, $8D, $43, $3C, $8B, $00, $03, $C3,
|
|
$8B, $70, $78, $03, $F3, $89, $F7, $8B, $47, $18, $48, $85, $C0, $0F, $8C, $F4,
|
|
$01, $00, $00, $40, $89, $45, $B4, $33, $F6, $8B, $47, $20, $03, $C3, $8B, $0C,
|
|
$B0, $03, $CB, $85, $C9, $0F, $84, $D2, $01, $00, $00, $81, $79, $04, $6F, $61,
|
|
$64, $44, $75, $1D, $81, $39, $4C, $64, $72, $4C, $75, $15, $8D, $41, $08, $8B,
|
|
$00, $25, $FF, $FF, $FF, $00, $3D, $6C, $6C, $00, $00, $0F, $84, $31, $01, $00,
|
|
$00, $81, $79, $04, $6F, $74, $65, $63, $75, $3D, $81, $39, $4E, $74, $50, $72,
|
|
$75, $35, $8D, $41, $08, $81, $78, $04, $74, $75, $61, $6C, $75, $29, $81, $38,
|
|
$74, $56, $69, $72, $75, $21, $8D, $41, $10, $8B, $50, $04, $8B, $00, $81, $E2,
|
|
$FF, $FF, $FF, $00, $81, $FA, $72, $79, $00, $00, $75, $05, $3D, $4D, $65, $6D,
|
|
$6F, $0F, $84, $EB, $00, $00, $00, $81, $79, $04, $6C, $6F, $63, $61, $75, $34,
|
|
$81, $39, $4E, $74, $41, $6C, $75, $2C, $8D, $41, $08, $81, $78, $04, $72, $74,
|
|
$75, $61, $75, $20, $81, $38, $74, $65, $56, $69, $75, $18, $8D, $41, $10, $81,
|
|
$78, $04, $6F, $72, $79, $00, $75, $06, $81, $38, $6C, $4D, $65, $6D, $0F, $84,
|
|
$AE, $00, $00, $00, $81, $79, $04, $65, $65, $56, $69, $75, $27, $81, $39, $4E,
|
|
$74, $46, $72, $75, $1F, $8D, $41, $08, $81, $78, $04, $6C, $4D, $65, $6D, $75,
|
|
$13, $81, $38, $72, $74, $75, $61, $75, $0B, $8D, $41, $10, $81, $38, $6F, $72,
|
|
$79, $00, $74, $7E, $81, $79, $04, $65, $72, $79, $53, $75, $38, $81, $39, $4E,
|
|
$74, $51, $75, $75, $30, $8D, $41, $08, $81, $78, $04, $6D, $49, $6E, $66, $75,
|
|
$24, $81, $38, $79, $73, $74, $65, $75, $1C, $8D, $41, $10, $81, $78, $04, $74,
|
|
$69, $6F, $6E, $75, $10, $81, $38, $6F, $72, $6D, $61, $75, $08, $8D, $41, $18,
|
|
$80, $38, $00, $74, $3D, $81, $79, $04, $6C, $61, $79, $45, $0F, $85, $AB, $00,
|
|
$00, $00, $81, $39, $4E, $74, $44, $65, $0F, $85, $9F, $00, $00, $00, $8D, $41,
|
|
$08, $81, $78, $04, $74, $69, $6F, $6E, $0F, $85, $8F, $00, $00, $00, $81, $38,
|
|
$78, $65, $63, $75, $0F, $85, $83, $00, $00, $00, $8D, $41, $10, $80, $38, $00,
|
|
$75, $7B, $8B, $47, $24, $03, $C3, $0F, $B7, $04, $70, $89, $45, $E4, $8B, $47,
|
|
$1C, $03, $C3, $8B, $55, $E4, $8B, $04, $90, $89, $45, $E4, $33, $C0, $8A, $41,
|
|
$02, $83, $F8, $50, $7F, $13, $74, $25, $83, $E8, $41, $74, $2A, $83, $E8, $03,
|
|
$74, $43, $83, $E8, $02, $74, $2A, $EB, $44, $83, $E8, $51, $74, $2D, $83, $E8,
|
|
$21, $75, $3A, $8B, $45, $E4, $03, $C3, $89, $45, $C8, $EB, $30, $8B, $45, $E4,
|
|
$03, $C3, $89, $45, $CC, $EB, $26, $8B, $45, $E4, $03, $C3, $89, $45, $C4, $EB,
|
|
$1C, $8B, $45, $E4, $03, $C3, $89, $45, $C0, $EB, $12, $8B, $45, $E4, $03, $C3,
|
|
$89, $45, $BC, $EB, $08, $8B, $45, $E4, $03, $C3, $89, $45, $B8, $46, $FF, $4D,
|
|
$B4, $0F, $85, $12, $FE, $FF, $FF, $83, $7D, $C8, $00, $0F, $84, $BE, $01, $00,
|
|
$00, $83, $7D, $CC, $00, $0F, $84, $B4, $01, $00, $00, $33, $DB, $83, $7D, $C4,
|
|
$00, $0F, $84, $A6, $00, $00, $00, $83, $7D, $C0, $00, $0F, $84, $9C, $00, $00,
|
|
$00, $83, $7D, $BC, $00, $0F, $84, $92, $00, $00, $00, $83, $7D, $B8, $00, $0F,
|
|
$84, $88, $00, $00, $00, $33, $C0, $89, $45, $E4, $8D, $45, $E4, $50, $6A, $00,
|
|
$6A, $00, $6A, $05, $FF, $55, $BC, $83, $7D, $E4, $00, $74, $70, $8B, $45, $E4,
|
|
$03, $C0, $89, $45, $E8, $33, $C0, $89, $45, $DC, $6A, $04, $68, $00, $10, $00,
|
|
$00, $8D, $45, $E8, $50, $6A, $00, $8D, $45, $DC, $50, $6A, $FF, $FF, $55, $C4,
|
|
$85, $C0, $75, $49, $6A, $00, $8B, $45, $E8, $50, $8B, $45, $DC, $50, $6A, $05,
|
|
$FF, $55, $BC, $85, $C0, $75, $1F, $8B, $45, $DC, $8B, $50, $44, $3B, $55, $F8,
|
|
$75, $08, $8B, $98, $DC, $00, $00, $00, $EB, $0C, $8B, $10, $85, $D2, $74, $06,
|
|
$03, $D0, $8B, $C2, $EB, $E4, $33, $C0, $89, $45, $E8, $68, $00, $80, $00, $00,
|
|
$8D, $45, $E8, $50, $8D, $45, $DC, $50, $6A, $FF, $FF, $55, $C0, $85, $DB, $74,
|
|
$47, $3B, $5D, $F4, $74, $42, $C7, $45, $E4, $01, $00, $00, $00, $8B, $45, $EC,
|
|
$8B, $40, $14, $8A, $10, $8B, $4D, $EC, $3A, $51, $18, $75, $0B, $8B, $40, $01,
|
|
$8B, $55, $EC, $3B, $42, $19, $74, $20, $C7, $45, $D0, $60, $79, $FE, $FF, $C7,
|
|
$45, $D4, $FF, $FF, $FF, $FF, $8D, $45, $D0, $50, $6A, $00, $FF, $55, $B8, $FF,
|
|
$45, $E4, $83, $7D, $E4, $65, $75, $C5, $8B, $45, $EC, $8B, $40, $14, $8A, $10,
|
|
$8B, $4D, $EC, $3A, $51, $18, $75, $0F, $8B, $50, $01, $8B, $4D, $EC, $3B, $51,
|
|
$19, $0F, $84, $98, $00, $00, $00, $89, $45, $DC, $C7, $45, $E4, $05, $00, $00,
|
|
$00, $8D, $45, $E0, $50, $6A, $40, $8D, $45, $E4, $50, $8D, $45, $DC, $50, $6A,
|
|
$FF, $FF, $55, $CC, $85, $C0, $75, $32, $8B, $45, $EC, $8B, $40, $14, $8B, $55,
|
|
$EC, $8B, $4A, $18, $89, $08, $8A, $4A, $1C, $88, $48, $04, $C7, $45, $E4, $05,
|
|
$00, $00, $00, $8D, $45, $E0, $50, $8B, $45, $E0, $50, $8D, $45, $E4, $50, $8D,
|
|
$45, $DC, $50, $6A, $FF, $FF, $55, $CC, $EB, $0D, $8B, $45, $EC, $C7, $80, $00,
|
|
$10, $00, $00, $FF, $FF, $FF, $FF, $85, $DB, $74, $05, $3B, $5D, $F4, $75, $2F,
|
|
$8D, $45, $E4, $50, $8B, $45, $EC, $83, $C0, $1D, $50, $6A, $00, $6A, $00, $FF,
|
|
$55, $C8, $8B, $45, $EC, $8B, $40, $10, $89, $45, $EC, $83, $7D, $EC, $00, $75,
|
|
$DF, $EB, $0C, $8B, $45, $90, $3B, $45, $D8, $0F, $85, $C2, $FB, $FF, $FF, $5F,
|
|
$5E, $5B, $8B, $E5, $5D,
|
|
// $C3}; // can't use this, anymore, due to CET
|
|
$58, $FF, $E0); // so we replace "RET" with "POP EAX + JMP EAX"
|
|
|
|
// the CInjectLib32FuncOld data is a compilation of the following Delphi code
|
|
// this user land code is copied to newly created wow64 processes to execute the dll injection
|
|
|
|
// this code is used by default for all 64bit OSs
|
|
|
|
// the 32bit injection code is rather complicated in the 64bit driver
|
|
// the reason for that is that the 64bit driver doesn''t know some 32bit ntdll APIs
|
|
// so the 32bit code has to find out the address of these APIs at runtime
|
|
|
|
// procedure InjectLibFunc32Old(buf: PInjectLib32);
|
|
// var ctid, mtid : dword; // current and main thread ids
|
|
// cpid : dword;
|
|
// ebp_ : dword;
|
|
// size : NativeInt;
|
|
// c1, c2 : dword;
|
|
// peb : dword;
|
|
// p1 : pointer;
|
|
// loopEnd : TPNtModuleInfo;
|
|
// mi : TNtModuleInfo;
|
|
// len : dword;
|
|
// ntdll : dword;
|
|
// nh : PImageNtHeaders;
|
|
// ed : PImageExportDirectory;
|
|
// i1 : integer;
|
|
// api : pchar;
|
|
// npi : ^TNtProcessInfo;
|
|
// sleep : int64;
|
|
// npvm : function (process: THandle; var addr: pointer; var size: NativeUInt; newProt: dword; var oldProt: dword) : dword; stdcall;
|
|
// lld : function (path, flags, name: pointer; var handle: HMODULE) : dword; stdcall;
|
|
// navm : function (process: THandle; var addr: pointer; zeroBits: NativeUInt; var regionSize: NativeInt; allocationType, protect: dword) : dword; stdcall;
|
|
// nfvm : function (process: THandle; var addr: pointer; var regionSize: NativeInt; freeType: dword) : dword; stdcall;
|
|
// nqsi : function (infoClass: dword; buffer: pointer; bufSize: dword; returnSize: TPCardinal) : dword; stdcall;
|
|
// nde : function (alertable: pointer; var delayInterval: int64) : dword; stdcall;
|
|
// begin
|
|
// asm
|
|
// mov eax, fs:[$30]
|
|
// mov peb, eax
|
|
// mov eax, fs:[$18]
|
|
// mov eax, [eax+$20]
|
|
// mov cpid, eax
|
|
// mov eax, fs:[$18]
|
|
// mov eax, [eax+$24]
|
|
// mov ctid, eax
|
|
// mov ebp_, ebp
|
|
// end;
|
|
// TPPointer(ebp_ + 4)^ := buf.pOldApi;
|
|
//
|
|
// npvm := nil;
|
|
// lld := nil;
|
|
// navm := nil;
|
|
// nfvm := nil;
|
|
// nqsi := nil;
|
|
// nde := nil;
|
|
//
|
|
// // step 1: locate ntdll.dll
|
|
// loopEnd := pointer(dword(pointer(peb + $C)^) + $14);
|
|
// mi := loopEnd^;
|
|
// while mi.next <> loopEnd do begin
|
|
// mi := mi.next^;
|
|
// if mi.name <> nil then begin
|
|
// len := 0;
|
|
// while mi.name[len] <> #0 do
|
|
// inc(len);
|
|
// if (int64(pointer(@mi.name[len - 9])^) = $006c00640074006e) and // ntdl
|
|
// (int64(pointer(@mi.name[len - 5])^) = $006c0064002e006c) then begin // l.dl
|
|
// // found it!
|
|
// ntdll := mi.handle;
|
|
//
|
|
// // step 2: locate LdrLoadDll and NtProtectVirtualMemory
|
|
// nh := pointer(ntdll + dword(pointer(ntdll + $3C)^));
|
|
// dword(ed) := ntdll + nh^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
|
// for i1 := 0 to ed^.NumberOfNames - 1 do begin
|
|
// api := pointer(ntdll + TPACardinal(ntdll + ed^.AddressOfNames)^[i1]);
|
|
// if (api <> nil) and
|
|
// ( ( (int64(pointer(@api[ 0])^) = $4464616f4c72644c) and // LdrLoadD
|
|
// (dword(pointer(@api[ 8])^) and $00ffffff = $00006c6c ) ) or // ll
|
|
// ( (int64(pointer(@api[ 0])^) = $6365746f7250744e) and // NtProtec
|
|
// (int64(pointer(@api[ 8])^) = $6c61757472695674) and // tVirtual
|
|
// (int64(pointer(@api[16])^) and $00ffffffffffffff = $000079726f6d654d) ) or // Memory
|
|
// ( (int64(pointer(@api[ 0])^) = $61636F6C6C41744E) and // NtAlloca
|
|
// (int64(pointer(@api[ 8])^) = $6175747269566574) and // teVirtua
|
|
// (int64(pointer(@api[16])^) = $0079726F6D654D6C) ) or // lMemory
|
|
// ( (int64(pointer(@api[ 0])^) = $695665657246744E) and // NtFreeVi
|
|
// (int64(pointer(@api[ 8])^) = $6D654D6C61757472) and // rtualMem
|
|
// (dword(pointer(@api[16])^) = $0079726F ) ) or // ory
|
|
// ( (int64(pointer(@api[ 0])^) = $537972657551744E) and // NtQueryS
|
|
// (int64(pointer(@api[ 8])^) = $666E496D65747379) and // ystemInf
|
|
// (int64(pointer(@api[16])^) = $6E6F6974616D726F) and // ormation
|
|
// (byte (pointer(@api[24])^) = $00 ) ) or // ormation
|
|
// ( (int64(pointer(@api[ 0])^) = $4579616c6544744e) and // NtDelayE
|
|
// (int64(pointer(@api[ 8])^) = $6e6f697475636578) and // xecution
|
|
// (byte (pointer(@api[16])^) = $00 ) ) ) then begin
|
|
// c1 := TPAWord(ntdll + ed^.AddressOfNameOrdinals)^[i1];
|
|
// c1 := TPACardinal(ntdll + ed^.AddressOfFunctions)^[c1];
|
|
// case api[2] of
|
|
// 'r': lld := pointer(ntdll + c1);
|
|
// 'P': npvm := pointer(ntdll + c1);
|
|
// 'A': navm := pointer(ntdll + c1);
|
|
// 'F': nfvm := pointer(ntdll + c1);
|
|
// 'Q': nqsi := pointer(ntdll + c1);
|
|
// 'D': nde := pointer(ntdll + c1);
|
|
// end;
|
|
// end;
|
|
// end;
|
|
// if (@lld <> nil) and (@npvm <> nil) then begin
|
|
// // found both APIs!
|
|
// mtid := 0;
|
|
// if (@navm <> nil) and (@nfvm <> nil) and (@nqsi <> nil) and (@nde <> nil) then begin
|
|
// c1 := 0;
|
|
// nqsi(5, nil, 0, @c1);
|
|
// if c1 <> 0 then begin
|
|
// size := c1 * 2;
|
|
// p1 := nil;
|
|
// if navm(THandle(-1), p1, 0, size, MEM_COMMIT, PAGE_READWRITE) = 0 then begin
|
|
// if nqsi(5, p1, size, nil) = 0 then begin
|
|
// npi := p1;
|
|
// while true do begin
|
|
// if npi^.pid = cpid then begin
|
|
// mtid := npi^.threads[0].tid_nt5;
|
|
// break;
|
|
// end;
|
|
// if npi^.offset = 0 then
|
|
// break;
|
|
// npi := pointer(NativeUInt(npi) + npi^.offset);
|
|
// end;
|
|
// end;
|
|
// size := 0;
|
|
// nfvm(THandle(-1), p1, size, MEM_RELEASE);
|
|
// end;
|
|
// end;
|
|
// end;
|
|
// if (mtid <> 0) and (mtid <> ctid) then begin
|
|
// // This is not the main thread! This usually doesn't happen, except sometimes in win10.
|
|
// // We "solve" this by waiting until the main thread has completed executing our loader stub.
|
|
// // Max wait time 1 second, just to be safe.
|
|
// for c1 := 1 to 100 do begin
|
|
// if (buf.pOldApi^.jmp = buf.oldApi.jmp) and (buf.pOldApi^.target = buf.oldApi.target) then
|
|
// // Our loader stub patch was removed, so we assume that the main thread has completed running it.
|
|
// break;
|
|
// sleep := -100000; // 10 milliseconds
|
|
// nde(nil, sleep);
|
|
// end;
|
|
// end;
|
|
// if (buf.pOldApi^.jmp <> buf.oldApi.jmp) or (buf.pOldApi^.target <> buf.oldApi.target) then begin
|
|
// // step 3: uninstall our patch
|
|
// p1 := buf.pOldApi;
|
|
// c1 := 5;
|
|
// if npvm(dword(-1), p1, c1, PAGE_EXECUTE_READWRITE, c2) = 0 then begin
|
|
// buf.pOldApi^ := buf.oldApi;
|
|
// c1 := 5;
|
|
// npvm(dword(-1), p1, c1, c2, c2);
|
|
// end else begin
|
|
// // For some reason we can't uninstall our patch correctly.
|
|
// // That happens if "dynamic code execution" is forbidden.
|
|
// // As a workaround we keep our callback installed, but we
|
|
// // rewire it to execute our copy of the original API code.
|
|
// buf.skip := true;
|
|
// end;
|
|
// // step 4: finally load the to-be-injected dll - but only if we're in the context of the main thread
|
|
// if (mtid = 0) or (mtid = ctid) then
|
|
// repeat
|
|
// lld(nil, nil, @buf.dll, c1);
|
|
// buf := buf.next;
|
|
// until buf = nil;
|
|
// end;
|
|
// end;
|
|
// break;
|
|
// end;
|
|
// end;
|
|
// end;
|
|
// end;
|
|
|
|
{$else}
|
|
|
|
const CInjectLibFunc32 : array [0..434] of byte =
|
|
($55, $8B, $EC, $83, $C4, $D8, $53, $56, $8B, $D8, $64, $8B, $05, $18, $00, $00,
|
|
$00, $8B, $40, $20, $89, $45, $FC, $64, $8B, $05, $18, $00, $00, $00, $8B, $40,
|
|
$24, $89, $45, $F8, $89, $6D, $F4, $8B, $45, $F4, $83, $C0, $04, $8B, $53, $14,
|
|
$89, $10, $33, $F6, $83, $BB, $3D, $02, $00, $00, $00, $0F, $84, $94, $00, $00,
|
|
$00, $33, $C0, $89, $45, $F0, $8D, $45, $F0, $50, $6A, $00, $6A, $00, $6A, $05,
|
|
$FF, $93, $3D, $02, $00, $00, $83, $7D, $F0, $00, $74, $79, $8B, $45, $F0, $03,
|
|
$C0, $89, $45, $E8, $33, $C0, $89, $45, $E4, $6A, $04, $68, $00, $10, $00, $00,
|
|
$8D, $45, $E8, $50, $6A, $00, $8D, $45, $E4, $50, $6A, $FF, $FF, $93, $35, $02,
|
|
$00, $00, $85, $C0, $75, $4F, $6A, $00, $8B, $45, $F0, $50, $8B, $45, $E4, $50,
|
|
$6A, $05, $FF, $93, $3D, $02, $00, $00, $85, $C0, $75, $1F, $8B, $45, $E4, $8B,
|
|
$50, $44, $3B, $55, $FC, $75, $08, $8B, $B0, $DC, $00, $00, $00, $EB, $0C, $8B,
|
|
$10, $85, $D2, $74, $06, $03, $D0, $8B, $C2, $EB, $E4, $33, $C0, $89, $45, $E8,
|
|
$68, $00, $80, $00, $00, $8D, $45, $E8, $50, $8D, $45, $E4, $50, $6A, $FF, $FF,
|
|
$93, $39, $02, $00, $00, $85, $F6, $74, $41, $3B, $75, $F8, $74, $3C, $C7, $45,
|
|
$F0, $01, $00, $00, $00, $8B, $43, $14, $8A, $10, $3A, $53, $18, $75, $08, $8B,
|
|
$40, $01, $3B, $43, $19, $74, $23, $C7, $45, $D8, $60, $79, $FE, $FF, $C7, $45,
|
|
$DC, $FF, $FF, $FF, $FF, $8D, $45, $D8, $50, $6A, $00, $FF, $93, $41, $02, $00,
|
|
$00, $FF, $45, $F0, $83, $7D, $F0, $65, $75, $CB, $8B, $43, $14, $8A, $10, $3A,
|
|
$53, $18, $75, $08, $8B, $50, $01, $3B, $53, $19, $74, $7F, $89, $45, $E4, $C7,
|
|
$45, $F0, $05, $00, $00, $00, $8D, $45, $EC, $50, $6A, $40, $8D, $45, $F0, $50,
|
|
$8D, $45, $E4, $50, $6A, $FF, $FF, $93, $2D, $02, $00, $00, $85, $C0, $75, $2F,
|
|
$8B, $43, $14, $8B, $53, $18, $89, $10, $8A, $53, $1C, $88, $50, $04, $C7, $45,
|
|
$F0, $05, $00, $00, $00, $8D, $45, $EC, $50, $8B, $45, $EC, $50, $8D, $45, $F0,
|
|
$50, $8D, $45, $E4, $50, $6A, $FF, $FF, $93, $2D, $02, $00, $00, $EB, $0A, $C7,
|
|
$83, $00, $10, $00, $00, $FF, $FF, $FF, $FF, $85, $F6, $74, $05, $3B, $75, $F8,
|
|
$75, $19, $8D, $45, $F0, $50, $8D, $43, $1D, $50, $6A, $00, $6A, $00, $FF, $93,
|
|
$31, $02, $00, $00, $8B, $5B, $10, $85, $DB, $75, $E7, $5E, $5B, $8B, $E5, $5D,
|
|
// $C3}; // can't use this, anymore, due to CET
|
|
$58, $FF, $E0); // so we replace "RET" with "POP EAX + JMP EAX"
|
|
|
|
// the CInjectLibFunc32Old data is a compilation of the following Delphi code
|
|
// this user land code is copied to newly created 32bit processes to execute the dll injection
|
|
// this solution is used by default in all 32bit OSs
|
|
|
|
// procedure InjectLibFunc32Old(buf: PInjectLib32);
|
|
// var ctid, mtid : dword; // current and main thread ids
|
|
// cpid : dword;
|
|
// ebp_ : dword;
|
|
// c1, c2 : dword;
|
|
// size : NativeInt;
|
|
// p1 : pointer;
|
|
// sleep : int64;
|
|
// npi : ^TNtProcessInfo;
|
|
// begin
|
|
// asm
|
|
// mov eax, fs:[$18]
|
|
// mov eax, [eax+$20]
|
|
// mov cpid, eax
|
|
// mov eax, fs:[$18]
|
|
// mov eax, [eax+$24]
|
|
// mov ctid, eax
|
|
// mov ebp_, ebp
|
|
// end;
|
|
// TPPointer(ebp_ + 4)^ := buf.pOldApi;
|
|
// mtid := 0;
|
|
// if @buf.nqsi <> nil then begin
|
|
// c1 := 0;
|
|
// buf.nqsi(5, nil, 0, @c1);
|
|
// if c1 <> 0 then begin
|
|
// size := c1 * 2;
|
|
// p1 := nil;
|
|
// if buf.navm(THandle(-1), p1, 0, size, MEM_COMMIT, PAGE_READWRITE) = 0 then begin
|
|
// if buf.nqsi(5, p1, c1, nil) = 0 then begin
|
|
// npi := p1;
|
|
// while true do begin
|
|
// if npi^.pid = cpid then begin
|
|
// mtid := npi^.threads[0].tid_nt5;
|
|
// break;
|
|
// end;
|
|
// if npi^.offset = 0 then
|
|
// break;
|
|
// npi := pointer(NativeUInt(npi) + npi^.offset);
|
|
// end;
|
|
// end;
|
|
// size := 0;
|
|
// buf.nfvm(THandle(-1), p1, size, MEM_RELEASE);
|
|
// end;
|
|
// end;
|
|
// end;
|
|
// if (mtid <> 0) and (mtid <> ctid) then begin
|
|
// // This is not the main thread! This usually doesn't happen, except sometimes in win10.
|
|
// // We "solve" this by waiting until the main thread has completed executing our loader stub.
|
|
// // Max wait time 1 second, just to be safe.
|
|
// for c1 := 1 to 100 do begin
|
|
// if (buf.pOldApi^.jmp = buf.oldApi.jmp) and (buf.pOldApi^.target = buf.oldApi.target) then
|
|
// // Our loader stub patch was removed, so we assume that the main thread has completed running it.
|
|
// break;
|
|
// sleep := -100000;// 10 milliseconds
|
|
// buf.nde(nil, sleep);
|
|
// end;
|
|
// end;
|
|
// if (buf.pOldApi^.jmp <> buf.oldApi.jmp) or (buf.pOldApi^.target <> buf.oldApi.target) then begin
|
|
// // step 3: uninstall our patch
|
|
// p1 := buf.pOldApi;
|
|
// c1 := 5;
|
|
// if buf.npvm(dword(-1), p1, c1, PAGE_EXECUTE_READWRITE, c2) = 0 then begin
|
|
// buf.pOldApi^ := buf.oldApi;
|
|
// c1 := 5;
|
|
// buf.npvm(dword(-1), p1, c1, c2, c2);
|
|
// end else begin
|
|
// // For some reason we can't uninstall our patch correctly.
|
|
// // That happens if "dynamic code execution" is forbidden.
|
|
// // As a workaround we keep our callback installed, but we
|
|
// // rewire it to execute our copy of the original API code.
|
|
// buf.skip := true;
|
|
// end;
|
|
// // step 4: finally load the to-be-injected dll - but only if we're in the context of the main thread
|
|
// if (mtid = 0) or (mtid = ctid) then
|
|
// repeat
|
|
// buf.lld(nil, nil, @buf.dll, c1);
|
|
// buf := buf.next;
|
|
// until buf = nil;
|
|
// end;
|
|
// end;
|
|
|
|
const CInjectLibApcFunc32 : array [0..29] of byte =
|
|
($55, $8B, $EC, $51, $8B, $45, $08, $8D, $55, $FC, $52, $8D, $50, $1D, $52, $6A,
|
|
$00, $6A, $00, $FF, $90, $31, $02, $00, $00, $59, $5D, $C2, $04, $00);
|
|
|
|
// the CInjectLibApcFunc32 data is a compilation of the following Delphi code
|
|
// this user land code is copied to newly created 32bit processes to execute the dll injection
|
|
// this solution is used by default in all 32bit OSs
|
|
|
|
// procedure InjectLibApcFunc32(buf: PInjectLib32); stdcall;
|
|
// var c1 : HMODULE;
|
|
// begin
|
|
// buf.lld(0, nil, @buf.dll, c1);
|
|
// end;
|
|
|
|
{$endif}
|
|
|
|
function IsProcessDotNet64(process: THandle) : boolean;
|
|
// find out if the specified 64bit process is a .NET process
|
|
var exeImage : int64;
|
|
nh : packed record
|
|
Signature: DWORD;
|
|
FileHeader: TImageFileHeader;
|
|
OptionalHeader: TImageOptionalHeader64;
|
|
end;
|
|
nhOffset : dword;
|
|
begin
|
|
{$ifdef win64}
|
|
result := Read(process, GetPeb(process) + $10, exeImage, sizeOf(exeImage)) and
|
|
Read(process, exeImage + CENEWHDR, nhOffset, sizeOf(nhOffset)) and
|
|
Read(process, exeImage + nhOffset, nh, sizeOf(nh )) and
|
|
(nh.OptionalHeader.DataDirectory[14].VirtualAddress <> 0) and
|
|
(nh.OptionalHeader.DataDirectory[14].Size <> 0);
|
|
{$else}
|
|
result := ReadProcessMemory64(process, GetPeb64(process) + $10, @exeImage, sizeOf(exeImage)) and
|
|
ReadProcessMemory64(process, exeImage + CENEWHDR, @nhOffset, sizeOf(nhOffset)) and
|
|
ReadProcessMemory64(process, exeImage + nhOffset, @nh, sizeOf(nh )) and
|
|
(nh.OptionalHeader.DataDirectory[14].VirtualAddress <> 0) and
|
|
(nh.OptionalHeader.DataDirectory[14].Size <> 0);
|
|
{$endif}
|
|
end;
|
|
|
|
function IsProcessDotNet32(process: THandle) : boolean;
|
|
// find out if the specified 32bit process is a .NET process
|
|
var exeImage : dword;
|
|
nh : TImageNtHeaders32;
|
|
nhOffset : dword;
|
|
c1 : NativeUInt;
|
|
begin
|
|
result := ReadProcessMemory(process, pointer({$ifdef win64}GetPeb32{$else}GetPeb{$endif}(process) + $08), @exeImage, sizeOf(exeImage), c1) and
|
|
ReadProcessMemory(process, pointer(exeImage + CENEWHDR), @nhOffset, sizeOf(nhOffset), c1) and
|
|
ReadProcessMemory(process, pointer(exeImage + nhOffset), @nh, sizeOf(nh ), c1) and
|
|
(nh.OptionalHeader.DataDirectory[14].VirtualAddress <> 0) and
|
|
(nh.OptionalHeader.DataDirectory[14].Size <> 0);
|
|
end;
|
|
|
|
function IsProcessDotNet(process: THandle) : boolean;
|
|
begin
|
|
if Is64bitProcess(process) then
|
|
result := IsProcessDotNet64(process)
|
|
else
|
|
result := IsProcessDotNet32(process);
|
|
end;
|
|
|
|
function Find32bitPatchAddress(process: THandle; module: HMODULE; const nh: TImageNtHeaders32) : pointer;
|
|
begin
|
|
{$ifndef win64}
|
|
if VmWareInjectionMode or Is64bitOS then begin
|
|
{$endif}
|
|
if nh.OptionalHeader.AddressOfEntryPoint <> 0 then
|
|
result := pointer(module + nh.OptionalHeader.AddressOfEntryPoint)
|
|
else
|
|
result := nil;
|
|
{$ifndef win64}
|
|
end else
|
|
result := NtProc(CNtTestAlert, true);
|
|
{$endif}
|
|
end;
|
|
|
|
function InjectLibraryPatchNt32(process: THandle; pid: dword; module: HMODULE; const nh: TImageNtHeaders32; dd: pointer) : integer;
|
|
// inject a dll into a newly started 32bit process
|
|
var buf : PInjectLib32;
|
|
il : TInjectLib32;
|
|
rj : TRelJump;
|
|
c1 : NativeUInt;
|
|
op : dword;
|
|
zero : dword;
|
|
begin
|
|
result := 0;
|
|
buf := AllocMemEx(2 * 4096, process);
|
|
if buf <> nil then begin
|
|
// we were able to allocate a buffer in the newly created process
|
|
{$ifndef win64}
|
|
if LdrLoadDll32 = nil then
|
|
LdrLoadDll32 := NtProc(CLdrLoadDll, true);
|
|
if NtProtectVirtualMemory32 = nil then
|
|
NtProtectVirtualMemory32 := NtProc(CNtProtectVirtualMemory, true);
|
|
if NtAllocateVirtualMemory32 = nil then
|
|
NtAllocateVirtualMemory32 := NtProc(CNtAllocateVirtualMemory, true);
|
|
if NtFreeVirtualMemory32 = nil then
|
|
NtFreeVirtualMemory32 := NtProc(CNtFreeVirtualMemory, true);
|
|
if NtQuerySystemInformation32 = nil then
|
|
NtQuerySystemInformation32 := NtProc(CNtQuerySystemInformation, true);
|
|
if NtDelayExecution32 = nil then
|
|
NtDelayExecution32 := NtProc(CNtDelayExecution, true);
|
|
{$endif}
|
|
il.movEax := $b8;
|
|
il.param := dword(buf);
|
|
il.movEcx := $b9;
|
|
il.proc := dword(@buf.injectFunc);
|
|
il.callEcx := $d1ff;
|
|
il.magic := 0;
|
|
il.next := 0;
|
|
il.pOldApi := dword(Find32bitPatchAddress(process, module, nh));
|
|
il.dll.len := lstrlenW(libW) * 2;
|
|
il.dll.maxLen := il.dll.len + 2;
|
|
il.dll.str := dword(@buf.dllBuf);
|
|
lstrcpyW(il.dllBuf, libW);
|
|
il.npvm := dword(NtProtectVirtualMemory32);
|
|
il.lld := dword(LdrLoadDll32);
|
|
il.navm := dword(NtAllocateVirtualMemory32);
|
|
il.nfvm := dword(NtFreeVirtualMemory32);
|
|
il.nqsi := dword(NtQuerySystemInformation32);
|
|
il.nde := dword(NtDelayExecution32);
|
|
// il.skip := 0;
|
|
TPAInt64(@il.injectFunc)[0] := int64($740000001000b883); // 83b80010000000 cmp dword ptr [eax + $1000], $00
|
|
TPAWord (@il.injectFunc)[4] := $5921; // 7421 jz +$21
|
|
zero := 0;
|
|
if (il.pOldApi <> 0) and
|
|
ReadProcessMemory (process, pointer(il.pOldApi), @il.oldApi, 5, c1) and (c1 = 5) and
|
|
ReadProcessMemory (process, pointer(il.pOldApi), @il.injectFunc[10], 32, c1) and (c1 = 32) and
|
|
WriteProcessMemory(process, buf, @il, sizeOf(TInjectLib32), c1) and (c1 = sizeOf(TInjectLib32)) and
|
|
WriteProcessMemory(process, pointer(NativeUInt(buf) + sizeOf(TInjectLib32)), @CInjectLibFunc32, sizeOf(CInjectLibFunc32), c1) and (c1 = sizeOf(CInjectLibFunc32)) and
|
|
WriteProcessMemory(process, pointer(NativeUInt(buf) + 4096), @zero, 4, c1) and (c1 = 4) then begin
|
|
// we've successfully initialized the buffer
|
|
if VirtualProtectEx(process, pointer(il.pOldApi), 5, PAGE_EXECUTE_READWRITE, @op) then begin
|
|
// we successfully unprotected the to-be-patched code, so now we can patch it
|
|
rj.jmp := $e9;
|
|
rj.target := NativeUInt(buf) - NativeUInt(il.pOldApi) - 5;
|
|
WriteProcessMemory(process, pointer(il.pOldApi), @rj, 5, c1);
|
|
// now restore the original page protection
|
|
VirtualProtectEx(process, pointer(il.pOldApi), 5, op, @op);
|
|
result := 1;
|
|
end;
|
|
end;
|
|
VirtualProtectEx(process, buf, 4096, PAGE_EXECUTE_READ, @op);
|
|
VirtualProtectEx(process, pointer(NativeUInt(buf) + 4096), 4096, PAGE_READWRITE, @op);
|
|
end;
|
|
end;
|
|
|
|
{$ifndef win64}
|
|
function InjectLibraryApc32(process: THandle; pid: dword; thread: THandle) : integer;
|
|
// inject a dll into a newly started 32bit process using APC
|
|
var buf : PInjectLib32;
|
|
il : TInjectLib32;
|
|
c1 : NativeUInt;
|
|
th : THandle;
|
|
op : dword;
|
|
begin
|
|
result := 0;
|
|
if @NtQueueApcThread = nil then
|
|
NtQueueApcThread := NtProc(CNtQueueApcThread);
|
|
if @NtQueueApcThread <> nil then begin
|
|
if thread = 0 then
|
|
th := OpenFirstThread(process, pid)
|
|
else
|
|
th := thread;
|
|
if th <> 0 then begin
|
|
buf := AllocMemEx(sizeOf(TInjectLib32) + sizeof(CInjectLibApcFunc32), process);
|
|
if buf <> nil then begin
|
|
// we were able to allocate a buffer in the newly created process
|
|
{$ifndef win64}
|
|
if LdrLoadDll32 = nil then
|
|
LdrLoadDll32 := NtProc(CLdrLoadDll, true);
|
|
{$endif}
|
|
ZeroMemory(@il, sizeOf(TInjectLib32));
|
|
il.dll.len := lstrlenW(libW) * 2;
|
|
il.dll.maxLen := il.dll.len + 2;
|
|
il.dll.str := dword(@buf.dllBuf);
|
|
lstrcpyW(il.dllBuf, libW);
|
|
il.lld := dword(LdrLoadDll32);
|
|
if WriteProcessMemory(process, buf, @il, sizeOf(TInjectLib32), c1) and (c1 = sizeOf(TInjectLib32)) and
|
|
WriteProcessMemory(process, @buf.injectFunc, @CInjectLibApcFunc32, sizeOf(CInjectLibApcFunc32), c1) and (c1 = sizeOf(CInjectLibApcFunc32)) and
|
|
(NtQueueApcThread(th, @buf.injectFunc, buf, nil, nil) = 0) then //QueueUserApc(@buf.injectFunc, th, buf) then
|
|
// we've successfully initialized the buffer
|
|
result := 1;
|
|
VirtualProtectEx(process, buf, sizeOf(TInjectLib32) + sizeof(CInjectLibApcFunc32), PAGE_EXECUTE_READ, @op);
|
|
end;
|
|
if thread = 0 then
|
|
CloseHandle(th);
|
|
end;
|
|
end;
|
|
end;
|
|
{$endif}
|
|
|
|
type
|
|
PInjectLib64 = ^TInjectLib64;
|
|
TInjectLib64 = packed record
|
|
movRax : word; // 0xb848
|
|
retAddr : int64; // patched API
|
|
pushRax : byte; // 0x50
|
|
pushRcx : byte; // 0x51
|
|
pushRdx : byte; // 0x52
|
|
pushR8 : word; // 0x5041
|
|
pushR9 : word; // 0x5141
|
|
subRsp28 : dword; // 0x28ec8348
|
|
movRcx : word; // 0xb948
|
|
param : int64; // pointer to "self"
|
|
movRdx : word; // 0xba48
|
|
proc : int64; // pointer to "self.injectFunc"
|
|
jmpEdx : word; // 0xe2ff
|
|
magic : dword; // "mciL" / 0x4c69636d
|
|
next : int64; // next dll (if any)
|
|
pOldApi : int64; // which code location in user land do we patch?
|
|
oldApi : TRelJump; // patch backup buffer, contains overwritten code
|
|
dll : TUnicodeStr64; // dll path/name
|
|
dllBuf : array [0..259] of WideChar; // string buffer for the dll path/name
|
|
npvm : int64; // ntdll.NtProtectVirtualMemory
|
|
lld : int64; // ntdll.LdrLoadDll
|
|
navm : int64; // ntdll.NtAllocateVirtualMemory
|
|
nfvm : int64; // ntdll.NtFreeVirtualMemory
|
|
nqsi : int64; // ntdll.NtQuerySystemInformation
|
|
nde : int64; // ntdll.NtDelayExcecution
|
|
injectFunc : array [0..3443] of byte; // will be filled with CInjectLibFunc64 (see below)
|
|
skip : dword;
|
|
end;
|
|
|
|
const CInjectLibFunc64 : array [0..537] of byte =
|
|
($4C, $8B, $DC, $53, $55, $56, $57, $41, $56, $41, $57, $48, $83, $EC, $58, $65,
|
|
$48, $8B, $04, $25, $30, $00, $00, $00, $33, $FF, $49, $83, $CE, $FF, $8B, $70,
|
|
$40, $8B, $68, $48, $48, $8B, $81, $7C, $02, $00, $00, $48, $85, $C0, $48, $8B,
|
|
$D9, $44, $8D, $7F, $05, $0F, $84, $0D, $01, $00, $00, $41, $21, $7B, $08, $4D,
|
|
$8D, $4B, $08, $45, $33, $C0, $33, $D2, $41, $8B, $CF, $FF, $D0, $8B, $84, $24,
|
|
$90, $00, $00, $00, $85, $C0, $0F, $84, $EC, $00, $00, $00, $48, $21, $BC, $24,
|
|
$A8, $00, $00, $00, $8D, $0C, $00, $4C, $8D, $8C, $24, $A0, $00, $00, $00, $48,
|
|
$89, $8C, $24, $A0, $00, $00, $00, $48, $8D, $94, $24, $A8, $00, $00, $00, $45,
|
|
$33, $C0, $49, $8B, $CE, $C7, $44, $24, $28, $04, $00, $00, $00, $C7, $44, $24,
|
|
$20, $00, $10, $00, $00, $FF, $93, $6C, $02, $00, $00, $85, $C0, $0F, $85, $A5,
|
|
$00, $00, $00, $44, $8B, $84, $24, $A0, $00, $00, $00, $48, $8B, $94, $24, $A8,
|
|
$00, $00, $00, $45, $33, $C9, $41, $8B, $CF, $FF, $93, $7C, $02, $00, $00, $85,
|
|
$C0, $75, $1F, $48, $8B, $8C, $24, $A8, $00, $00, $00, $EB, $09, $39, $39, $74,
|
|
$11, $8B, $01, $48, $03, $C8, $48, $39, $71, $50, $75, $F1, $8B, $B9, $30, $01,
|
|
$00, $00, $48, $83, $A4, $24, $A0, $00, $00, $00, $00, $4C, $8D, $84, $24, $A0,
|
|
$00, $00, $00, $48, $8D, $94, $24, $A8, $00, $00, $00, $41, $B9, $00, $80, $00,
|
|
$00, $49, $8B, $CE, $FF, $93, $74, $02, $00, $00, $85, $FF, $74, $3A, $3B, $FD,
|
|
$74, $36, $BE, $01, $00, $00, $00, $48, $8B, $4B, $37, $8A, $43, $3F, $38, $01,
|
|
$75, $08, $8B, $43, $40, $39, $41, $01, $74, $1E, $48, $8D, $54, $24, $40, $33,
|
|
$C9, $48, $C7, $44, $24, $40, $60, $79, $FE, $FF, $FF, $93, $84, $02, $00, $00,
|
|
$83, $C6, $01, $83, $FE, $64, $7C, $CF, $48, $8B, $4B, $37, $8A, $43, $3F, $38,
|
|
$01, $75, $0C, $8B, $43, $40, $39, $41, $01, $0F, $84, $A0, $00, $00, $00, $48,
|
|
$8D, $84, $24, $98, $00, $00, $00, $48, $89, $4C, $24, $38, $4C, $8D, $44, $24,
|
|
$30, $48, $8D, $54, $24, $38, $41, $B9, $40, $00, $00, $00, $49, $8B, $CE, $48,
|
|
$89, $44, $24, $20, $4C, $89, $7C, $24, $30, $FF, $93, $5C, $02, $00, $00, $85,
|
|
$C0, $75, $3E, $8B, $43, $3F, $48, $8B, $53, $37, $4C, $8D, $44, $24, $30, $89,
|
|
$02, $8A, $43, $43, $49, $8B, $CE, $88, $42, $04, $44, $8B, $8C, $24, $98, $00,
|
|
$00, $00, $48, $8D, $84, $24, $98, $00, $00, $00, $48, $8D, $54, $24, $38, $48,
|
|
$89, $44, $24, $20, $4C, $89, $7C, $24, $30, $FF, $93, $5C, $02, $00, $00, $EB,
|
|
$0A, $C7, $83, $00, $10, $00, $00, $01, $00, $00, $00, $85, $FF, $74, $04, $3B,
|
|
$FD, $75, $1C, $4C, $8D, $43, $44, $4C, $8D, $4C, $24, $30, $33, $D2, $33, $C9,
|
|
$FF, $93, $64, $02, $00, $00, $48, $8B, $5B, $2F, $48, $85, $DB, $75, $E4, $48,
|
|
$83, $C4, $58, $41, $5F, $41, $5E, $5F, $5E, $5D, $5B, // $C3};
|
|
// $48, $83, $C4, $28, $41, $59, $41, $58, $5A, $59, $C3}; // can't use this, anymore, due to CET
|
|
$48, $83, $C4, $28, $41, $59, $41, $58, $5A, $59, $41, $5B, $49, $FF, $E3); // so we replace "RET" with "POP R11 + JMP R11"
|
|
|
|
// the CInjectLib64Func data is a compilation of the following C++ code
|
|
// it got a manually created extended "tail", though (last 11 bytes)
|
|
// this user land code is copied to newly created 64bit processes to execute the dll injection
|
|
|
|
// static void InjectLib64Func(InjectLib64 *buf)
|
|
// {
|
|
// ULONG_PTR* ptib = (ULONG_PTR*) NtCurrentTeb();
|
|
// ULONG cpid = (ULONG) ptib[8];
|
|
// ULONG ctid = (ULONG) ptib[9];
|
|
//
|
|
// ULONG mtid = 0;
|
|
// if (buf->nqsi)
|
|
// {
|
|
// ULONG c1 = 0;
|
|
// buf->nqsi(SystemProcessInformation, NULL, 0, &c1);
|
|
// if (c1)
|
|
// {
|
|
// SIZE_T size = c1 * 2;
|
|
// PVOID p1 = NULL;
|
|
// if (buf->navm((HANDLE) -1, &p1, 0, &size, MEM_COMMIT, PAGE_READWRITE) == 0)
|
|
// {
|
|
// if (buf->nqsi(SystemProcessInformation, p1, (ULONG) size, NULL) == 0)
|
|
// {
|
|
// SYSTEM_PROCESS_INFORMATION* npi = (SYSTEM_PROCESS_INFORMATION*) p1;
|
|
// while (true)
|
|
// {
|
|
// if (npi->Process.UniqueProcessId == cpid)
|
|
// {
|
|
// mtid = (ULONG) npi->Process_NT5.Threads[0].ClientId.UniqueThread;
|
|
// break;
|
|
// }
|
|
// if (npi->Process.Next == 0)
|
|
// break;
|
|
// npi = (SYSTEM_PROCESS_INFORMATION*) (((ULONG_PTR) npi) + npi->Process.Next);
|
|
// }
|
|
// }
|
|
// size = 0;
|
|
// buf->nfvm((HANDLE) -1, &p1, &size, MEM_RELEASE);
|
|
// }
|
|
// }
|
|
// }
|
|
// if ((mtid) && (mtid != ctid))
|
|
// {
|
|
// // This is not the main thread! This usually doesn't happen, except sometimes in win10.
|
|
// // We "solve" this by waiting until the main thread has completed executing our loader stub.
|
|
// // Max wait time 1 second, just to be safe.
|
|
// for (int i1 = 1; i1 < 100; i1++)
|
|
// {
|
|
// if ((buf->pOldApi->jmp == buf->oldApi.jmp) && (buf->pOldApi->target == buf->oldApi.target))
|
|
// // Our loader stub patch was removed, so we assume that the main thread has completed running it.
|
|
// break;
|
|
// LONGLONG sleep = -100000; // 10 milliseconds
|
|
// buf->nde(NULL, (PLARGE_INTEGER) &sleep);
|
|
// }
|
|
// }
|
|
// if ((buf->pOldApi->jmp != buf->oldApi.jmp) || (buf->pOldApi->target != buf->oldApi.target))
|
|
// {
|
|
// PVOID p1 = (PVOID) buf->pOldApi;
|
|
// ULONG_PTR c1 = 5;
|
|
// ULONG c2;
|
|
// if (!buf->npvm((HANDLE) -1, &p1, &c1, PAGE_EXECUTE_READWRITE, &c2))
|
|
// {
|
|
// *(buf->pOldApi) = buf->oldApi;
|
|
// c1 = 5;
|
|
// buf->npvm((HANDLE) -1, &p1, &c1, c2, &c2);
|
|
// }
|
|
// else
|
|
// {
|
|
// // For some reason we can't uninstall our patch correctly.
|
|
// // That happens if "dynamic code execution" is forbidden.
|
|
// // As a workaround we keep our callback installed, but we
|
|
// // rewire it to execute our copy of the original API code.
|
|
// buf->skip = true;
|
|
// }
|
|
// if ((!mtid) || (mtid == ctid))
|
|
// do
|
|
// {
|
|
// buf->lld(0, NULL, (PUNICODE_STRING) &(buf->dll), (HMODULE*) &c1);
|
|
// buf = (InjectLib64*) buf->next;
|
|
// } while (buf);
|
|
// }
|
|
// }
|
|
|
|
function InjectLibraryPatchNt64(process: THandle) : integer;
|
|
// inject a dll into a newly started 64bit process
|
|
|
|
{$ifndef win64}
|
|
|
|
function GetModuleHandle64(const dll: UnicodeString) : HMODULE;
|
|
|
|
function GetPeb64 : int64;
|
|
asm
|
|
mov eax, gs:[$60]
|
|
mov edx, gs:[$64]
|
|
end;
|
|
|
|
var i64 : int64;
|
|
loopEnd : pointer;
|
|
mi : TPAPointer;
|
|
begin
|
|
result := 0;
|
|
try
|
|
// first let's get the 64bit PEB (Process Environment Block)
|
|
i64 := GetPeb64;
|
|
if dword(i64) = i64 then begin
|
|
// we got it - and it's only a 32bit pointer, fortunately
|
|
// now let's get the loader data
|
|
i64 := int64(pointer(i64 + $18)^);
|
|
if dword(i64) = i64 then begin
|
|
// fortunately the loader also is only a 32bit pointer
|
|
// now let's loop through the list of dlls
|
|
loopEnd := pointer(i64 + $20);
|
|
mi := loopEnd;
|
|
while (mi[0] <> loopEnd) and (mi[1] = nil) do begin
|
|
// mi[ 0] + mi[ 1] = pointer to next dll
|
|
// mi[ 8] + mi[ 9] = dll handle
|
|
// mi[16] + mi[17] = full dll file name
|
|
mi := mi[0];
|
|
if (mi[16] <> nil) and (mi[17] = nil) and (PosTextW(dll, UnicodeString(PWideChar(mi[16]))) > 0) then begin
|
|
// found the dll we're looking for
|
|
if mi[9] = nil then
|
|
// and it happens to have a 32bit handle - yippieh!
|
|
result := dword(mi[8]);
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
except end;
|
|
end;
|
|
|
|
{$endif}
|
|
|
|
var buf : PInjectLib64;
|
|
il : TInjectLib64;
|
|
rj : TRelJump;
|
|
c1 : NativeUInt;
|
|
op : dword;
|
|
{$ifndef win64}
|
|
dll : HMODULE;
|
|
{$endif}
|
|
nta64 : pointer;
|
|
npvm64 : pointer;
|
|
lld64 : pointer;
|
|
navm64 : pointer;
|
|
nfvm64 : pointer;
|
|
nqsi64 : pointer;
|
|
nde64 : pointer;
|
|
begin
|
|
result := 0;
|
|
{$ifdef win64}
|
|
nta64 := NtProc(CNtTestAlert, true);
|
|
npvm64 := NtProc(CNtProtectVirtualMemory, true);
|
|
lld64 := NtProc(CLdrLoadDll, true);
|
|
navm64 := NtProc(CNtAllocateVirtualMemory, true);
|
|
nfvm64 := NtProc(CNtFreeVirtualMemory, true);
|
|
nqsi64 := NtProc(CNtQuerySystemInformation, true);
|
|
nde64 := NtProc(CNtDelayExecution, true);
|
|
{$else}
|
|
dll := GetModuleHandle64('ntdll.dll');
|
|
nta64 := GetImageProcAddress(dll, 'NtTestAlert');
|
|
npvm64 := GetImageProcAddress(dll, 'NtProtectVirtualMemory');
|
|
lld64 := GetImageProcAddress(dll, 'LdrLoadDll');
|
|
navm64 := GetImageProcAddress(dll, 'NtAllocateVirtualMemory');
|
|
nfvm64 := GetImageProcAddress(dll, 'NtFreeVirtualMemory');
|
|
nqsi64 := GetImageProcAddress(dll, 'NtQuerySystemInformation');
|
|
nde64 := GetImageProcAddress(dll, 'NtDelayExecution');
|
|
{$endif}
|
|
buf := AllocMemEx(4 * 4096, process {$ifdef win64}, nta64 {$endif});
|
|
if buf <> nil then begin
|
|
// we were able to allocate a buffer in the newly created process
|
|
if (nta64 <> nil) and (npvm64 <> nil) and (lld64 <> nil) then begin
|
|
il.movRax := $b848;
|
|
il.retAddr := int64(nta64);
|
|
il.pushRax := $50;
|
|
il.pushRcx := $51;
|
|
il.pushRdx := $52;
|
|
il.pushR8 := $5041;
|
|
il.pushR9 := $5141;
|
|
il.subRsp28 := $28ec8348;
|
|
il.movRcx := $b948;
|
|
il.param := int64(buf);
|
|
il.movRdx := $ba48;
|
|
il.proc := int64(@buf.injectFunc);
|
|
il.jmpEdx := $e2ff;
|
|
il.magic := 0;
|
|
il.next := 0;
|
|
il.pOldApi := il.retAddr;
|
|
il.dll.len := lstrlenW(libW) * 2;
|
|
il.dll.maxLen := il.dll.len + 2;
|
|
il.dll.dummy := 0;
|
|
il.dll.str := int64(@buf.dllBuf);
|
|
lstrcpyW(il.dllBuf, libW);
|
|
il.npvm := int64(npvm64);
|
|
il.lld := int64(lld64);
|
|
il.navm := int64(navm64);
|
|
il.nfvm := int64(nfvm64);
|
|
il.nqsi := int64(nqsi64);
|
|
il.nde := int64(nde64);
|
|
il.skip := 0;
|
|
TPAInt64(@il.injectFunc)[0] := int64($740000001000b983); // 83b90010000000 cmp dword ptr [rcx + $1000], $00
|
|
TPAInt64(@il.injectFunc)[1] := int64($41594128c483482b); // 742b jz +$2b
|
|
// 4883c428 add rsp, $28
|
|
// 4159 pop r9
|
|
TPACardinal(@il.injectFunc)[4] := $58595a58; // 4158 pop r8
|
|
// 5a pop rdx
|
|
// 59 pop rcx
|
|
// 58 pop rax
|
|
Move(CInjectLibFunc64[0], il.injectFunc[20 + 32], sizeof(CInjectLibFunc64));
|
|
|
|
if ReadProcessMemory (process, pointer(il.pOldApi), @il.oldApi, 5, c1) and (c1 = 5) and
|
|
ReadProcessMemory (process, pointer(il.pOldApi), @il.injectFunc[20], 32, c1) and (c1 = 32) and
|
|
WriteProcessMemory(process, buf, @il, sizeOf(TInjectLib64), c1) and (c1 = sizeOf(TInjectLib64)) then begin
|
|
// we've successfully initialized the buffer
|
|
if VirtualProtectEx(process, pointer(il.pOldApi), 5, PAGE_EXECUTE_READWRITE, @op) then begin
|
|
// we successfully unprotected the to-be-patched code, so now we can patch it
|
|
rj.jmp := $e9;
|
|
rj.target := NativeUInt(buf) - NativeUInt(il.pOldApi) - 5;
|
|
WriteProcessMemory(process, pointer(il.pOldApi), @rj, 5, c1);
|
|
// now restore the original page protection
|
|
VirtualProtectEx(process, pointer(il.pOldApi), 5, op, @op);
|
|
result := 1;
|
|
end;
|
|
end;
|
|
end;
|
|
VirtualProtectEx(process, buf, 4096, PAGE_EXECUTE_READ, @op);
|
|
VirtualProtectEx(process, @buf.skip, 4096, PAGE_READWRITE, @op);
|
|
end;
|
|
end;
|
|
|
|
var module : HMODULE;
|
|
nh : TImageNtHeaders32;
|
|
dd : pointer;
|
|
begin
|
|
{$ifdef log}
|
|
log('InjectLibraryPatch (process: %p)', nil, nil, process);
|
|
{$endif}
|
|
if forcePatch or NotInitializedYet(process) then begin
|
|
{$ifdef log}log('NotInitializedYet (1) +');{$endif}
|
|
if GetExeModuleInfos(process, module, nh, dd) then begin
|
|
{$ifdef log}log('GetExeModuleInfos +');{$endif}
|
|
if GetVersion and $80000000 = 0 then begin
|
|
if Is64bitProcess(process) then
|
|
result := InjectLibraryPatchNt64(process)
|
|
else
|
|
{$ifndef win64}
|
|
{$ifdef InjectLibraryPatchXp}
|
|
if ((byte(GetVersion) > 5) or ((byte(GetVersion) = 5) and (byte(GetVersion shr 8) > 0))) and
|
|
(not Is64bitOS) then
|
|
result := InjectLibraryPatchXp32(process)
|
|
else
|
|
{$endif}
|
|
if IsProcessDotNet(process) then
|
|
result := InjectLibraryApc32(process, pid, thread)
|
|
else
|
|
{$endif}
|
|
result := InjectLibraryPatchNt32(process, pid, module, nh, dd);
|
|
if (result = 1) and (forcePatch or NotInitializedYet(process)) then
|
|
result := 2;
|
|
end else
|
|
result := 0;
|
|
end else result := 0;
|
|
end else result := 1;
|
|
{$ifdef log}
|
|
log('InjectLibraryPatch (process: %p) -> ' + IntToStrExA(result), nil, nil, process);
|
|
{$endif}
|
|
end;
|
|
|
|
function DoInject(process: THandle; pid: dword; firstThread: THandle; var thread: THandle) : boolean;
|
|
begin
|
|
{$ifdef log}
|
|
log('DoInject (process: %p)', nil, nil, process);
|
|
{$endif}
|
|
result := true;
|
|
thread := 0;
|
|
{$ifdef win64}
|
|
peb64 := GetPeb(process);
|
|
peb32 := 0;
|
|
{$else}
|
|
peb32 := GetPeb(process);
|
|
if Is64bitProcess(process) then
|
|
peb64 := GetPeb64(process)
|
|
else
|
|
peb64 := 0;
|
|
{$endif}
|
|
if forcePatch or NotInitializedYet(process) then begin
|
|
if mayPatch then begin
|
|
case InjectLibraryPatch(process, pid, firstThread) of
|
|
0 : result := false;
|
|
1 : begin
|
|
if mayThread then
|
|
thread := StartInjectLibraryX(process, pid, true, libA, libW)
|
|
else thread := 0;
|
|
result := thread <> 0;
|
|
end;
|
|
end;
|
|
end else
|
|
result := false;
|
|
end else begin
|
|
if mayThread then
|
|
thread := StartInjectLibraryX(process, pid, true, libA, libW)
|
|
else thread := 0;
|
|
result := thread <> 0;
|
|
end;
|
|
{$ifdef log}
|
|
log('DoInject (process: %p) -> ' + booleanToCharA(result), nil, nil, process);
|
|
{$endif}
|
|
end;
|
|
|
|
var prcs1, prcs2 : TDAProcess;
|
|
ths : array of THandle;
|
|
pids : array of dword;
|
|
pidCount : integer;
|
|
dll64 : boolean;
|
|
i1, i2 : integer;
|
|
c1 : THandle;
|
|
flags : dword;
|
|
b1, b2, b3 : boolean;
|
|
{$ifndef win64}
|
|
newMap : integer;
|
|
newMutex : boolean;
|
|
{$endif}
|
|
sla : AnsiString;
|
|
slw : AnsiString;
|
|
time : dword;
|
|
delay : boolean;
|
|
count : integer;
|
|
ws1 : UnicodeString;
|
|
cmdLine : PWideChar;
|
|
incPaths : array of UnicodeString;
|
|
incNames : array of UnicodeString;
|
|
excPaths : array of UnicodeString;
|
|
excNames : array of UnicodeString;
|
|
needFullPath : boolean;
|
|
name : UnicodeString;
|
|
path : UnicodeString;
|
|
helperBuf : PWideChar;
|
|
isSysPrc : boolean;
|
|
begin
|
|
{$ifdef log}
|
|
log('InjectLibraryX (process: ' + '%p' +
|
|
'; lib: ' + '%1' +
|
|
'; timeOut: ' + IntToStrExA(timeOut) +
|
|
')', libA, libW, process);
|
|
{$endif}
|
|
prcs1 := nil;
|
|
prcs2 := nil;
|
|
incPaths := nil;
|
|
incNames := nil;
|
|
excPaths := nil;
|
|
excNames := nil;
|
|
result := false;
|
|
time := 0;
|
|
if not CheckLibFilePath(libA, libW, sla, slw) then
|
|
exit;
|
|
EnableAllPrivileges;
|
|
InitProcs(false);
|
|
dll64 := Is64bitModule(libW);
|
|
if multiInject then begin
|
|
{$ifndef win64}
|
|
if (InjectIntoRunningProcesses or (not Is64bitOS)) and dll64 then begin
|
|
SetLastError(ERROR_NOT_SUPPORTED);
|
|
exit;
|
|
end;
|
|
{$endif}
|
|
if InjectIntoRunningProcesses and (not IsAdminAndElevated) then begin
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
exit;
|
|
end;
|
|
if UseIatDllInjection and (not DoesModuleExportOrdinal1(libW)) then begin
|
|
SetLastError(ERROR_INVALID_DLL);
|
|
exit;
|
|
end;
|
|
if (driverName = nil) or StartDllInjection(driverName, libW, session, options, includes, excludes, callback, callbackContext) then
|
|
if InjectIntoRunningProcesses then begin
|
|
if session = CURRENT_SESSION then
|
|
session := GetCurrentSessionId;
|
|
result := true;
|
|
needFullPath := @callback <> nil;
|
|
if (includes <> nil) and (includes[0] <> #0) then begin
|
|
ws1 := includes;
|
|
SetLength(incPaths, SubStrCountW(ws1));
|
|
SetLength(incNames, Length(incPaths));
|
|
for i1 := 0 to high(incPaths) do
|
|
needFullPath := SplitNamePath(SubStrW(ws1, i1 + 1), incPaths[i1], incNames[i1]) or needFullPath;
|
|
end;
|
|
if (excludes <> nil) and (excludes[0] <> #0) then begin
|
|
ws1 := excludes;
|
|
SetLength(excPaths, SubStrCountW(ws1));
|
|
SetLength(excNames, Length(excPaths));
|
|
for i1 := 0 to high(excPaths) do
|
|
needFullPath := SplitNamePath(SubStrW(ws1, i1 + 1), excPaths[i1], excNames[i1]) or needFullPath;
|
|
end;
|
|
if needFullPath then
|
|
helperBuf := pointer(LocalAlloc(LPTR, 64 * 1024))
|
|
else
|
|
helperBuf := nil;
|
|
i1 := Length(excPaths);
|
|
SetLength(excPaths, i1 + 2);
|
|
SetLength(excNames, i1 + 2);
|
|
excNames[i1 + 0] := UnicodeString(DecryptStr(CSmss));
|
|
excNames[i1 + 1] := UnicodeString(DecryptStr(CSLSvc));
|
|
delay := false;
|
|
count := 0;
|
|
pids := nil;
|
|
repeat
|
|
if delay then
|
|
Sleep(10);
|
|
delay := false;
|
|
b2 := true;
|
|
pidCount := 0;
|
|
{$ifdef log} Log('EnumProcesses'); {$endif}
|
|
prcs1 := EnumProcesses;
|
|
{$ifdef log} LogProcesses(prcs1); {$endif}
|
|
SetLength(ths, Length(prcs1));
|
|
for i1 := 0 to high(prcs1) do begin
|
|
ths[i1] := 0;
|
|
b1 := true;
|
|
for i2 := 0 to high(prcs2) do
|
|
if prcs2[i2].id = prcs1[i1].id then begin
|
|
b1 := false;
|
|
break;
|
|
end;
|
|
if excludePIDs <> nil then begin
|
|
i2 := 0;
|
|
while (excludePIDs[i2] <> 0) and (excludePIDs[i2] <> prcs1[i1].id) do
|
|
inc(i2);
|
|
if excludePIDs[i2] <> 0 then
|
|
b1 := false;
|
|
end;
|
|
if b1 and (prcs1[i1].id > 8) then begin
|
|
if (not SplitNamePath(prcs1[i1].exeFile, path, name)) and
|
|
needFullPath and
|
|
ProcessIdToFileNameW(prcs1[i1].id, helperBuf, 32 * 1024) then
|
|
SplitNamePath(helperBuf, path, name);
|
|
if ((incPaths = nil) or MatchStrArray(path, name, incPaths, incNames)) and
|
|
(not MatchStrArray(path, name, excPaths, excNames)) then begin
|
|
b2 := false;
|
|
c1 := OpenProcess(PROCESS_CREATE_THREAD or PROCESS_DUP_HANDLE or PROCESS_QUERY_INFORMATION or
|
|
PROCESS_VM_OPERATION or PROCESS_VM_READ or PROCESS_VM_WRITE, false, prcs1[i1].id);
|
|
if c1 <> 0 then begin
|
|
if dll64 = Is64bitProcess(c1) then begin
|
|
isSysPrc := IsSystemProcess(c1);
|
|
{$ifndef WIN64}
|
|
if not dll64 then
|
|
{$endif}
|
|
if ( (options and INJECT_SYSTEM_PROCESSES <> 0) or (not isSysPrc) ) and
|
|
( (options and INJECT_METRO_APPS <> 0) or (not IsMetroApp(c1)) ) and
|
|
( (session = ALL_SESSIONS) or
|
|
(prcs1[i1].session = session) or
|
|
((prcs1[i1].session = 0) and isSysPrc) ) then begin
|
|
if @callback <> nil then begin
|
|
flags := 0;
|
|
if dll64 then
|
|
flags := flags or TARGET_PROCESS_IS_64BIT;
|
|
if isSysPrc then
|
|
flags := flags or TARGET_PROCESS_IS_SYSTEM;
|
|
if IsElevatedProcess(c1) then
|
|
flags := flags or TARGET_PROCESS_IS_ELEVATED;
|
|
if IsProtectedProcess(c1) then
|
|
flags := flags or TARGET_PROCESS_IS_PROTECTED;
|
|
ws1 := GetProcessCommandLine(c1, dll64);
|
|
if ws1 <> '' then
|
|
cmdLine := PWideChar(ws1)
|
|
else
|
|
cmdLine := nil;
|
|
b3 := callback(callbackContext, prcs1[i1].id, prcs1[i1].parentId, prcs1[i1].session, flags, helperBuf, cmdLine);
|
|
end else
|
|
b3 := true;
|
|
if b3 and DoInject(c1, prcs1[i1].id, 0, ths[i1]) and (@callback <> nil) then begin
|
|
if (pids = nil) or (pidCount = Length(pids)) then
|
|
SetLength(pids, pidCount + 256);
|
|
pids[pidCount] := prcs1[i1].id;
|
|
inc(pidCount);
|
|
end;
|
|
end;
|
|
end;
|
|
CloseHandle(c1);
|
|
end else begin
|
|
c1 := GetLastError;
|
|
{$ifdef log}
|
|
log(' OpenProcess (' + IntToHexExA(prcs1[i1].id) + ') -> error ' + IntToHexExA(c1));
|
|
{$endif}
|
|
if (prcs1[i1].id <> 0) and (c1 <> ERROR_ACCESS_DENIED) then begin
|
|
prcs1[i1].id := 0;
|
|
delay := true;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
if (pidCount > 0) and (driverName <> nil) then begin
|
|
SetLength(pids, pidCount);
|
|
ReportInjectedPidsToDriver(driverName, pids, libW, session, options, includes, excludes);
|
|
end;
|
|
if time = 0 then
|
|
time := GetTickCount;
|
|
for i1 := 0 to high(ths) do
|
|
if ths[i1] <> 0 then
|
|
WaitForInjectLibraryX(ths[i1], timeOut, time);
|
|
prcs2 := prcs1;
|
|
{$ifdef log}
|
|
if not b2 then
|
|
log('Did we miss some processes? Check again...');
|
|
{$endif}
|
|
inc(count);
|
|
until b2 or (count = 5);
|
|
if helperBuf <> nil then
|
|
LocalFree(HLOCAL(helperBuf));
|
|
end else
|
|
result := true;
|
|
end else
|
|
result := (dll64 = Is64bitProcess(process)) and {$ifndef win64} (forcePatch or (not dll64)) and {$endif}
|
|
DoInject(process, ProcessHandleToId(process), thread, c1) and
|
|
( (c1 = 0) or WaitForInjectLibraryX(c1, timeOut, GetTickCount) );
|
|
{$ifdef log}
|
|
log('InjectLibraryX (process: ' + '%p' +
|
|
'; lib: ' + libA +
|
|
'; timeOut: ' + IntToStrExA(timeOut) +
|
|
') -> ' + booleanToCharA(result), nil, nil, process);
|
|
{$endif}
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
function UninjectLibraryX(owner : HMODULE;
|
|
libA : PAnsiChar;
|
|
libW : PWideChar;
|
|
multiInject : boolean;
|
|
process : THandle;
|
|
driverName : PWideChar;
|
|
session : dword;
|
|
options : dword;
|
|
includes : PWideChar;
|
|
excludes : PWideChar;
|
|
excludePIDs : TPACardinal;
|
|
timeOut : dword) : boolean;
|
|
var prcs1, prcs2 : TDAProcess;
|
|
ths : TDANativeUInt;
|
|
dll64 : boolean;
|
|
ph : THandle;
|
|
i1, i2 : integer;
|
|
c1 : THandle;
|
|
sla : AnsiString;
|
|
slw : AnsiString;
|
|
time : dword;
|
|
b1, b2 : boolean;
|
|
ws1 : UnicodeString;
|
|
incPaths : array of UnicodeString;
|
|
incNames : array of UnicodeString;
|
|
excPaths : array of UnicodeString;
|
|
excNames : array of UnicodeString;
|
|
needFullPath : boolean;
|
|
name : UnicodeString;
|
|
path : UnicodeString;
|
|
helperBuf : PWideChar;
|
|
pidsValid : boolean;
|
|
pids : TDACardinal;
|
|
begin
|
|
{$ifdef log}
|
|
log('UninjectLibraryX (process: ' + '%p' +
|
|
'; lib: ' + '%1' +
|
|
'; timeOut: ' + IntToStrExA(timeOut) +
|
|
')', libA, libW, process);
|
|
{$endif}
|
|
prcs1 := nil;
|
|
prcs2 := nil;
|
|
incPaths := nil;
|
|
incNames := nil;
|
|
excPaths := nil;
|
|
excNames := nil;
|
|
result := false;
|
|
time := 0;
|
|
if not CheckLibFilePath(libA, libW, sla, slw) then
|
|
exit;
|
|
EnableAllPrivileges;
|
|
InitProcs(false);
|
|
dll64 := Is64bitModule(libW);
|
|
if multiInject then begin
|
|
{$ifndef win64}
|
|
if (UninjectFromRunningProcesses or (not Is64bitOS)) and dll64 then begin
|
|
SetLastError(ERROR_NOT_SUPPORTED);
|
|
exit;
|
|
end;
|
|
{$endif}
|
|
if UninjectFromRunningProcesses and (not IsAdminAndElevated) then begin
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
exit;
|
|
end;
|
|
if (driverName <> nil) and (not StopDllInjection(driverName, libW, session, options, includes, excludes, pidsValid, pids)) then
|
|
exit;
|
|
if UninjectFromRunningProcesses then begin
|
|
result := true;
|
|
if pidsValid then begin
|
|
SetLength(ths, Length(pids));
|
|
for i1 := 0 to high(pids) do begin
|
|
ths[i1] := 0;
|
|
ph := OpenProcess(PROCESS_ALL_ACCESS, false, pids[i1]);
|
|
if ph <> 0 then begin
|
|
ths[i1] := StartInjectLibraryX(ph, pids[i1], false, libA, libW);
|
|
CloseHandle(ph);
|
|
end;
|
|
end;
|
|
time := GetTickCount;
|
|
for i1 := 0 to high(ths) do
|
|
if ths[i1] <> 0 then
|
|
WaitForInjectLibraryX(ths[i1], timeOut, time);
|
|
end else begin
|
|
if session = CURRENT_SESSION then
|
|
session := GetCurrentSessionId;
|
|
needFullPath := false;
|
|
if (includes <> nil) and (includes[0] <> #0) then begin
|
|
ws1 := includes;
|
|
SetLength(incPaths, SubStrCountW(ws1));
|
|
SetLength(incNames, Length(incPaths));
|
|
for i1 := 0 to high(incPaths) do
|
|
needFullPath := SplitNamePath(SubStrW(ws1, i1 + 1), incPaths[i1], incNames[i1]) or needFullPath;
|
|
end;
|
|
if (excludes <> nil) and (excludes[0] <> #0) then begin
|
|
ws1 := excludes;
|
|
SetLength(excPaths, SubStrCountW(ws1));
|
|
SetLength(excNames, Length(excPaths));
|
|
for i1 := 0 to high(excPaths) do
|
|
needFullPath := SplitNamePath(SubStrW(ws1, i1 + 1), excPaths[i1], excNames[i1]) or needFullPath;
|
|
end;
|
|
if needFullPath then
|
|
helperBuf := pointer(LocalAlloc(LPTR, 64 * 1024))
|
|
else
|
|
helperBuf := nil;
|
|
i1 := Length(excPaths);
|
|
SetLength(excPaths, i1 + 2);
|
|
SetLength(excNames, i1 + 2);
|
|
excNames[i1 + 0] := UnicodeString(DecryptStr(CSmss));
|
|
excNames[i1 + 1] := UnicodeString(DecryptStr(CSLSvc));
|
|
repeat
|
|
b2 := true;
|
|
{$ifdef log} Log('EnumProcesses'); {$endif}
|
|
prcs1 := EnumProcesses;
|
|
{$ifdef log} LogProcesses(prcs1); {$endif}
|
|
SetLength(ths, Length(prcs1));
|
|
for i1 := 0 to high(prcs1) do begin
|
|
ths[i1] := 0;
|
|
b1 := true;
|
|
for i2 := 0 to high(prcs2) do
|
|
if prcs2[i2].id = prcs1[i1].id then begin
|
|
b1 := false;
|
|
break;
|
|
end;
|
|
if excludePIDs <> nil then begin
|
|
i2 := 0;
|
|
while (excludePIDs[i2] <> 0) and (excludePIDs[i2] <> prcs1[i1].id) do
|
|
inc(i2);
|
|
if excludePIDs[i2] <> 0 then
|
|
b1 := false;
|
|
end;
|
|
if b1 and (prcs1[i1].id > 8) then begin
|
|
if (not SplitNamePath(prcs1[i1].exeFile, path, name)) and
|
|
needFullPath and
|
|
ProcessIdToFileNameW(prcs1[i1].id, helperBuf, 32 * 1024) then
|
|
SplitNamePath(helperBuf, path, name);
|
|
if ((incPaths = nil) or MatchStrArray(path, name, incPaths, incNames)) and
|
|
(not MatchStrArray(path, name, excPaths, excNames)) then begin
|
|
b2 := false;
|
|
ph := OpenProcess(PROCESS_ALL_ACCESS, false, prcs1[i1].id);
|
|
if ph <> 0 then begin
|
|
if (dll64 = Is64bitProcess(ph)) and
|
|
( (options and INJECT_SYSTEM_PROCESSES <> 0) or (not IsSystemProcess(ph)) ) and
|
|
( (options and INJECT_METRO_APPS <> 0) or (not IsMetroApp(ph)) ) and
|
|
( (session = ALL_SESSIONS) or
|
|
(GetProcessSessionId(prcs1[i1].id) = session) or
|
|
((GetProcessSessionId(prcs1[i1].id) = 0) and IsSystemProcess(ph)) ) then
|
|
ths[i1] := StartInjectLibraryX(ph, prcs1[i1].id, false, libA, libW);
|
|
CloseHandle(ph);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
if time = 0 then
|
|
time := GetTickCount;
|
|
for i1 := 0 to high(ths) do
|
|
if ths[i1] <> 0 then
|
|
WaitForInjectLibraryX(ths[i1], timeOut, time);
|
|
prcs2 := prcs1;
|
|
{$ifdef log}
|
|
if not b2 then
|
|
log('Did we miss some processes? Check again...');
|
|
{$endif}
|
|
until b2;
|
|
if helperBuf <> nil then
|
|
LocalFree(HLOCAL(helperBuf));
|
|
end;
|
|
end else
|
|
result := true;
|
|
end else
|
|
if {$ifdef win64} (dll64 = {$else} (not dll64) and (not {$endif} Is64bitProcess(process)) then begin
|
|
c1 := StartInjectLibraryX(process, ProcessHandleToId(process), false, libA, libW);
|
|
result := (c1 <> 0) and WaitForInjectLibraryX(c1, timeOut, GetTickCount);
|
|
end;
|
|
{$ifdef log}
|
|
log('UninjectLibraryX (process: ' + '%p' +
|
|
'; lib: ' + libA +
|
|
'; timeOut: ' + IntToStrExA(timeOut) +
|
|
') -> ' + booleanToCharA(result), nil, nil, process);
|
|
{$endif}
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
function UninjectAllLibrariesW(driverName: PWideChar; excludePIDs: TPCardinal = nil; timeOutPerUninject: dword = 7000) : bool; stdcall;
|
|
var uir : TDAInjectionRequest;
|
|
i1 : integer;
|
|
incl, excl : PWideChar;
|
|
begin
|
|
if (driverName = nil) or (driverName[0] = #0) then begin
|
|
result := false;
|
|
exit;
|
|
end;
|
|
result := true;
|
|
uir := EnumInjectionRequests(driverName);
|
|
for i1 := 0 to high(uir) do begin
|
|
if uir[i1].IncludeLen > 0 then
|
|
incl := uir[i1].Data
|
|
else
|
|
incl := nil;
|
|
if uir[i1].ExcludeLen > 0 then
|
|
excl := pointer(NativeUInt(@uir[i1].Data) + uir[i1].IncludeLen * 2 + 2)
|
|
else
|
|
excl := nil;
|
|
if not UninjectLibraryX(GetCallingModule, nil, uir[i1].Name, true, 0, driverName, uir[i1].Session, uir[i1].Flags, incl, excl, TPACardinal(excludePIDs), timeOutPerUninject) then
|
|
result := false;
|
|
UnpatchCreateRemoteThread;
|
|
LocalFree(HLOCAL(uir[i1]));
|
|
end;
|
|
end;
|
|
|
|
function UninjectAllLibrariesA(driverName: PAnsiChar; excludePIDs: TPCardinal = nil; timeOutPerUninject: dword = 7000) : bool; stdcall;
|
|
var driverNameW : PWideChar;
|
|
begin
|
|
if (driverName = nil) or (driverName[0] = #0) then begin
|
|
result := false;
|
|
exit;
|
|
end;
|
|
if (driverName <> nil) and (driverName[0] <> #0) then begin
|
|
driverNameW := pointer(LocalAlloc(LPTR, lstrlenA(driverName) * 2 + 2));
|
|
AnsiToWide2(driverName, driverNameW);
|
|
end else
|
|
driverNameW := nil;
|
|
result := UninjectAllLibrariesW(driverNameW, excludePIDs, timeOutPerUninject);
|
|
end;
|
|
|
|
function UninjectAllLibraries(driverName: PAnsiChar; excludePIDs: TPCardinal = nil; timeOutPerUninject: dword = 7000) : bool; stdcall;
|
|
begin
|
|
result := UninjectAllLibrariesA(driverName, excludePIDs, timeOutPerUninject);
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
function CPInject(owner: HMODULE; pi: TProcessInformation; flags: dword; libA: PAnsiChar; libW: PWideChar) : boolean;
|
|
begin
|
|
result := InjectLibraryX(owner, libA, libW, false, pi.hProcess, nil, 0, 0, nil, nil, nil, nil, nil, INFINITE, true, false, true, pi.hThread);
|
|
if result then begin
|
|
if flags and CREATE_SUSPENDED = 0 then
|
|
ResumeThread(pi.hThread);
|
|
end else begin
|
|
TerminateProcess(pi.hProcess, 0);
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread );
|
|
end;
|
|
end;
|
|
|
|
function CreateProcessExA(applicationName, commandLine : PAnsiChar;
|
|
processAttr, threadAttr : PSecurityAttributes;
|
|
inheritHandles : bool;
|
|
creationFlags : dword;
|
|
environment : pointer;
|
|
currentDirectory : PAnsiChar;
|
|
const startupInfo : {$ifdef UNICODE} TStartupInfoA; {$else} TStartupInfo; {$endif}
|
|
var processInfo : TProcessInformation;
|
|
loadLibrary : PAnsiChar ) : bool; stdcall;
|
|
begin
|
|
{$ifdef log}
|
|
log('CreateProcessExA (lib: ' + loadLibrary + ')');
|
|
{$endif}
|
|
result := CreateProcessA(applicationName, commandLine, processAttr, threadAttr,
|
|
inheritHandles, creationFlags or CREATE_SUSPENDED,
|
|
environment, currentDirectory, startupInfo, processInfo) and
|
|
CPInject(GetCallingModule, processInfo, creationFlags, loadLibrary, nil);
|
|
{$ifdef log}
|
|
log('CreateProcessExA (lib: ' + loadLibrary + ') -> ' + booleanToCharA(result));
|
|
{$endif}
|
|
end;
|
|
|
|
function CreateProcessExW(applicationName, commandLine : PWideChar;
|
|
processAttr, threadAttr : PSecurityAttributes;
|
|
inheritHandles : bool;
|
|
creationFlags : dword;
|
|
environment : pointer;
|
|
currentDirectory : PWideChar;
|
|
const startupInfo : {$ifdef UNICODE} TStartupInfoW; {$else} TStartupInfo; {$endif}
|
|
var processInfo : TProcessInformation;
|
|
loadLibrary : PWideChar ) : bool; stdcall;
|
|
var cpw : function (app, cmd, pattr, tattr: pointer; inherit: bool; flags: dword;
|
|
env, dir: pointer; const si: {$ifdef UNICODE} TStartupInfoW; {$else} TStartupInfo; {$endif} var pi: TProcessInformation) : bool; stdcall;
|
|
begin
|
|
{$ifdef log}
|
|
log('CreateProcessExW (lib: %1)', nil, loadLibrary);
|
|
{$endif}
|
|
cpw := @CreateProcessW;
|
|
{$ifdef log}
|
|
log('CreateProcessExW (lib: %1)', nil, loadLibrary);
|
|
{$endif}
|
|
result := cpw(applicationName, commandLine, processAttr, threadAttr,
|
|
inheritHandles, creationFlags or CREATE_SUSPENDED,
|
|
environment, currentDirectory, startupInfo, processInfo) and
|
|
CPInject(GetCallingModule, processInfo, creationFlags, nil, loadLibrary);
|
|
{$ifdef log}
|
|
log('CreateProcessExW (lib: %1) -> ' + booleanToCharA(result), nil, loadLibrary);
|
|
{$endif}
|
|
end;
|
|
|
|
function CreateProcessEx(applicationName, commandLine : PAnsiChar;
|
|
processAttr, threadAttr : PSecurityAttributes;
|
|
inheritHandles : bool;
|
|
creationFlags : dword;
|
|
environment : pointer;
|
|
currentDirectory : PAnsiChar;
|
|
const startupInfo : {$ifdef UNICODE} TStartupInfoA; {$else} TStartupInfo; {$endif}
|
|
var processInfo : TProcessInformation;
|
|
loadLibrary : AnsiString ) : boolean;
|
|
begin
|
|
{$ifdef log}
|
|
log('CreateProcessEx (lib: ' + loadLibrary + ')');
|
|
{$endif}
|
|
result := CreateProcessA(applicationName, commandLine, processAttr, threadAttr,
|
|
inheritHandles, creationFlags or CREATE_SUSPENDED,
|
|
environment, currentDirectory, startupInfo, processInfo) and
|
|
CPInject(GetCallingModule, processInfo, creationFlags, PAnsiChar(loadLibrary), nil);
|
|
{$ifdef log}
|
|
log('CreateProcessEx (lib: ' + loadLibrary + ') -> ' + booleanToCharA(result));
|
|
{$endif}
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
function InjectLibraryA(libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall;
|
|
begin
|
|
result := InjectLibraryX(GetCallingModule, libFileName, nil, false, processHandle, nil, 0, 0, nil, nil, nil, nil, nil, timeOut, true, true, false, 0);
|
|
UnpatchCreateRemoteThread;
|
|
end;
|
|
|
|
function InjectLibraryW(libFileName: PWideChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall;
|
|
begin
|
|
result := InjectLibraryX(GetCallingModule, nil, libFileName, false, processHandle, nil, 0, 0, nil, nil, nil, nil, nil, timeOut, true, true, false, 0);
|
|
UnpatchCreateRemoteThread;
|
|
end;
|
|
|
|
function InjectLibrary(libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall;
|
|
begin
|
|
result := InjectLibraryX(GetCallingModule, libFileName, nil, false, processHandle, nil, 0, 0, nil, nil, nil, nil, nil, timeOut, true, true, false, 0);
|
|
UnpatchCreateRemoteThread;
|
|
end;
|
|
|
|
function UninjectLibraryA(libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall;
|
|
begin
|
|
result := UninjectLibraryX(GetCallingModule, libFileName, nil, false, processHandle, nil, 0, 0, nil, nil, nil, timeOut);
|
|
UnpatchCreateRemoteThread;
|
|
end;
|
|
|
|
function UninjectLibraryW(libFileName: PWideChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall;
|
|
begin
|
|
result := UninjectLibraryX(GetCallingModule, nil, libFileName, false, processHandle, nil, 0, 0, nil, nil, nil, timeOut);
|
|
UnpatchCreateRemoteThread;
|
|
end;
|
|
|
|
function UninjectLibrary(libFileName: PAnsiChar; processHandle: THandle; timeOut: dword = 7000) : bool; stdcall;
|
|
begin
|
|
result := UninjectLibraryX(GetCallingModule, libFileName, nil, false, processHandle, nil, 0, 0, nil, nil, nil, timeOut);
|
|
UnpatchCreateRemoteThread;
|
|
end;
|
|
|
|
function InjectLibraryA(driverName : PAnsiChar;
|
|
libFileName : PAnsiChar;
|
|
session : dword;
|
|
options : dword;
|
|
includeMask : PAnsiChar = nil;
|
|
excludeMask : PAnsiChar = nil;
|
|
excludePIDs : TPCardinal = nil;
|
|
callback : TInjectApprovalCallbackRoutine = nil;
|
|
callbackContext : pointer = nil;
|
|
timeOut : dword = 7000) : bool; stdcall;
|
|
var driverNameW : PWideChar;
|
|
includesW : PWideChar;
|
|
excludesW : PWideChar;
|
|
begin
|
|
if (driverName <> nil) and (driverName[0] <> #0) then begin
|
|
driverNameW := pointer(LocalAlloc(LPTR, lstrlenA(driverName) * 2 + 2));
|
|
AnsiToWide2(driverName, driverNameW);
|
|
end else
|
|
driverNameW := nil;
|
|
if (includeMask <> nil) and (includeMask[0] <> #0) then begin
|
|
includesW := pointer(LocalAlloc(LPTR, lstrlenA(includeMask) * 2 + 2));
|
|
AnsiToWide2(includeMask, includesW);
|
|
end else
|
|
includesW := nil;
|
|
if (excludeMask <> nil) and (excludeMask[0] <> #0) then begin
|
|
excludesW := pointer(LocalAlloc(LPTR, lstrlenA(excludeMask) * 2 + 2));
|
|
AnsiToWide2(excludeMask, excludesW);
|
|
end else
|
|
excludesW := nil;
|
|
result := InjectLibraryX(GetCallingModule, libFileName, nil, true, 0, driverNameW, session, options, includesW, excludesW, TPACardinal(excludePIDs), callback, callbackContext, timeOut, true, true, false, 0);
|
|
UnpatchCreateRemoteThread;
|
|
if driverNameW <> nil then
|
|
LocalFree(HLOCAL(driverNameW));
|
|
if includesW <> nil then
|
|
LocalFree(HLOCAL(includesW));
|
|
if excludesW <> nil then
|
|
LocalFree(HLOCAL(excludesW));
|
|
end;
|
|
|
|
function InjectLibraryW(driverName : PWideChar;
|
|
libFileName : PWideChar;
|
|
session : dword;
|
|
options : dword;
|
|
includeMask : PWideChar = nil;
|
|
excludeMask : PWideChar = nil;
|
|
excludePIDs : TPCardinal = nil;
|
|
callback : TInjectApprovalCallbackRoutine = nil;
|
|
callbackContext : pointer = nil;
|
|
timeOut : dword = 7000) : bool; stdcall;
|
|
begin
|
|
result := InjectLibraryX(GetCallingModule, nil, libFileName, true, 0, driverName, session, options, includeMask, excludeMask, TPACardinal(excludePIDs), callback, callbackContext, timeOut, true, true, false, 0);
|
|
UnpatchCreateRemoteThread;
|
|
end;
|
|
|
|
function InjectLibrary(driverName : PAnsiChar;
|
|
libFileName : PAnsiChar;
|
|
session : dword;
|
|
options : dword;
|
|
includeMask : PAnsiChar = nil;
|
|
excludeMask : PAnsiChar = nil;
|
|
excludePIDs : TPCardinal = nil;
|
|
callback : TInjectApprovalCallbackRoutine = nil;
|
|
callbackContext : pointer = nil;
|
|
timeOut : dword = 7000) : bool; stdcall;
|
|
begin
|
|
result := InjectLibraryA(driverName, libFileName, session, options, includeMask, excludeMask, excludePIDs, callback, callbackContext, timeOut);
|
|
end;
|
|
|
|
function UninjectLibraryA(driverName : PAnsiChar;
|
|
libFileName : PAnsiChar;
|
|
session : dword;
|
|
options : dword;
|
|
includeMask : PAnsiChar = nil;
|
|
excludeMask : PAnsiChar = nil;
|
|
excludePIDs : TPCardinal = nil;
|
|
timeOut : dword = 7000) : bool; stdcall;
|
|
var driverNameW : PWideChar;
|
|
includesW : PWideChar;
|
|
excludesW : PWideChar;
|
|
begin
|
|
if (driverName <> nil) and (driverName[0] <> #0) then begin
|
|
driverNameW := pointer(LocalAlloc(LPTR, lstrlenA(driverName) * 2 + 2));
|
|
AnsiToWide2(driverName, driverNameW);
|
|
end else
|
|
driverNameW := nil;
|
|
if (includeMask <> nil) and (includeMask[0] <> #0) then begin
|
|
includesW := pointer(LocalAlloc(LPTR, lstrlenA(includeMask) * 2 + 2));
|
|
AnsiToWide2(includeMask, includesW);
|
|
end else
|
|
includesW := nil;
|
|
if (excludeMask <> nil) and (excludeMask[0] <> #0) then begin
|
|
excludesW := pointer(LocalAlloc(LPTR, lstrlenA(excludeMask) * 2 + 2));
|
|
AnsiToWide2(excludeMask, excludesW);
|
|
end else
|
|
excludesW := nil;
|
|
result := UninjectLibraryX(GetCallingModule, libFileName, nil, true, 0, driverNameW, session, options, includesW, excludesW, TPACardinal(excludePIDs), timeOut);
|
|
UnpatchCreateRemoteThread;
|
|
if driverNameW <> nil then
|
|
LocalFree(HLOCAL(driverNameW));
|
|
if includesW <> nil then
|
|
LocalFree(HLOCAL(includesW));
|
|
if excludesW <> nil then
|
|
LocalFree(HLOCAL(excludesW));
|
|
end;
|
|
|
|
function UninjectLibraryW(driverName : PWideChar;
|
|
libFileName : PWideChar;
|
|
session : dword;
|
|
options : dword;
|
|
includeMask : PWideChar = nil;
|
|
excludeMask : PWideChar = nil;
|
|
excludePIDs : TPCardinal = nil;
|
|
timeOut : dword = 7000) : bool; stdcall;
|
|
begin
|
|
result := UninjectLibraryX(GetCallingModule, nil, libFileName, true, 0, driverName, session, options, includeMask, excludeMask, TPACardinal(excludePIDs), timeOut);
|
|
UnpatchCreateRemoteThread;
|
|
end;
|
|
|
|
function UninjectLibrary(driverName : PAnsiChar;
|
|
libFileName : PAnsiChar;
|
|
session : dword;
|
|
options : dword;
|
|
includeMask : PAnsiChar = nil;
|
|
excludeMask : PAnsiChar = nil;
|
|
excludePIDs : TPCardinal = nil;
|
|
timeOut : dword = 7000) : bool; stdcall;
|
|
begin
|
|
result := UninjectLibraryA(driverName, libFileName, session, options, includeMask, excludeMask, excludePIDs, timeOut);
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
type
|
|
TIpcAnswer = record
|
|
map : THandle;
|
|
buf : pointer;
|
|
len : dword;
|
|
event1 : THandle;
|
|
event2 : THandle;
|
|
end;
|
|
|
|
procedure CloseIpcAnswer(var answer: TIpcAnswer);
|
|
begin
|
|
if answer.buf <> nil then
|
|
UnmapViewOfFile(answer.buf);
|
|
if answer.map <> 0 then begin
|
|
CloseHandle(answer.map);
|
|
answer.map := 0;
|
|
end;
|
|
if answer.event1 <> 0 then begin
|
|
CloseHandle(answer.event1);
|
|
answer.event1 := 0;
|
|
end;
|
|
if answer.event2 <> 0 then begin
|
|
CloseHandle(answer.event2);
|
|
answer.event2 := 0;
|
|
end;
|
|
end;
|
|
|
|
var NtQueryInformationToken : function (token: THandle; infoType: NativeUInt; buf: pointer; bufSize: dword; retLen: TPCardinal) : HRESULT; stdcall = nil;
|
|
function InitIpcAnswer(create: boolean; name: AnsiString; counter, pid: dword; var answer: TIpcAnswer; session: dword = 0) : boolean;
|
|
|
|
function GetMetroPath(pid: dword) : AnsiString;
|
|
const TokenBnoIsolation = $2c;
|
|
type
|
|
TOKEN_BNO_ISOLATION_INFORMATION = record
|
|
IsolationPrefix : PWideChar;
|
|
IsolationEnabled : boolean;
|
|
end;
|
|
var gacnop : function (token: THandle; appContainerSid: PSid; bufLen: dword; buf: PWideChar; var retLen: dword) : bool; stdcall;
|
|
ph, th : THandle;
|
|
arrChW : array [0..MAX_PATH] of WideChar;
|
|
arrChA : array [0..MAX_PATH] of AnsiChar;
|
|
len : dword;
|
|
buf : ^TOKEN_BNO_ISOLATION_INFORMATION;
|
|
begin
|
|
result := '';
|
|
gacnop := GetProcAddress(GetModuleHandleA(PAnsiChar(DecryptStr(CKernelbase))), PAnsiChar(DecryptStr(CGetAppCtnrNamedObjectPath)));
|
|
if @gacnop <> nil then begin
|
|
ph := OpenProcess(PROCESS_QUERY_INFORMATION, false, pid);
|
|
if ph <> 0 then begin
|
|
if OpenProcessToken(ph, TOKEN_QUERY, th) then begin
|
|
if gacnop(th, nil, MAX_PATH, arrChW, len) then begin
|
|
// Metro apps run under a special private namespace.
|
|
// Fortunately there's on official API which gives us the necessary information.
|
|
WideToAnsi(arrChW, arrChA);
|
|
result := arrChA;
|
|
end else
|
|
if not DisableIpcFix2 then begin
|
|
{$ifdef win64}
|
|
len := $120;
|
|
{$else}
|
|
len := $118;
|
|
{$endif}
|
|
if @NtQueryInformationToken = nil then
|
|
NtQueryInformationToken := NtProc(CNtQueryInformationToken);
|
|
if @NtQueryInformationToken <> nil then begin
|
|
buf := pointer(LocalAlloc(LPTR, len));
|
|
if (NtQueryInformationToken(th, TokenBnoIsolation, buf, len, @len) = 0) and (buf.IsolationEnabled) then begin
|
|
// RuntimeBroker.exe in Windows 10 runs under private object namespace.
|
|
// It's all rather weird. After some disassembling I found "TokenBnoSolution".
|
|
// It's in the latest "winnt.h", but otherwise completely undocumented.
|
|
// It gives us the private namespace prefix of the target process.
|
|
WideToAnsi(buf.IsolationPrefix, arrChA);
|
|
result := arrChA;
|
|
end;
|
|
LocalFree(HLOCAL(buf));
|
|
end;
|
|
end;
|
|
CloseHandle(th);
|
|
end;
|
|
CloseHandle(ph);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var s1, s2 : AnsiString;
|
|
ch1 : AnsiChar;
|
|
mp : AnsiString;
|
|
begin
|
|
if answer.len > 0 then begin
|
|
s1 := name + DecryptStr(CAnswerBuf) + IntToStrExA(counter);
|
|
if pid <> 0 then
|
|
s1 := s1 + IntToHexExA(pid);
|
|
if create then begin
|
|
answer.map := InternalCreateFileMapping(PAnsiChar(s1 + DecryptStr(CMap)), answer.len, true, false);
|
|
ch1 := '1'; answer.event1 := CreateGlobalEvent(PAnsiChar(s1 + DecryptStr(CEvent) + ch1), false, false);
|
|
ch1 := '2'; answer.event2 := CreateGlobalEvent(PAnsiChar(s1 + DecryptStr(CEvent) + ch1), false, false);
|
|
end else begin
|
|
s2 := DecryptStr(CSession) + IntToStrExA(session) + '\';
|
|
mp := '';
|
|
answer.map := OpenGlobalFileMapping(PAnsiChar(s1 + DecryptStr(CMap)), true);
|
|
if answer.map = 0 then begin
|
|
// if the IPC sender doesn't have the SeCreateGlobalPrivilege in w2k3
|
|
// or in xp sp2 or higher then the objects were not created in global
|
|
// namespace but in the namespace of the sender
|
|
// so if opening of the file map didn't work, we try to open the
|
|
// objects in the session of the sender
|
|
answer.map := OpenGlobalFileMapping(PAnsiChar(s2 + s1 + DecryptStr(CMap)), true);
|
|
if (answer.map = 0) and IsMetro then begin
|
|
mp := GetMetroPath(pid);
|
|
if mp <> '' then begin
|
|
s2 := s2 + mp + '\';
|
|
answer.map := OpenGlobalFileMapping(PAnsiChar(s2 + s1 + DecryptStr(CMap)), true);
|
|
end;
|
|
end;
|
|
end;
|
|
ch1 := '1'; answer.event1 := OpenGlobalEvent(PAnsiChar(s1 + DecryptStr(CEvent) + ch1));
|
|
ch1 := '2'; answer.event2 := OpenGlobalEvent(PAnsiChar(s1 + DecryptStr(CEvent) + ch1));
|
|
if answer.event1 = 0 then begin
|
|
ch1 := '1'; answer.event1 := OpenGlobalEvent(PAnsiChar(s2 + s1 + DecryptStr(CEvent) + ch1));
|
|
ch1 := '2'; answer.event2 := OpenGlobalEvent(PAnsiChar(s2 + s1 + DecryptStr(CEvent) + ch1));
|
|
end;
|
|
end;
|
|
if answer.map <> 0 then
|
|
answer.buf := MapViewOfFile(answer.map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
result := (answer.event1 <> 0) and (answer.event2 <> 0) and (answer.buf <> nil);
|
|
if result then begin
|
|
if create then
|
|
ZeroMemory(answer.buf, answer.len);
|
|
end else
|
|
CloseIpcAnswer(answer);
|
|
end else begin
|
|
answer.map := 0;
|
|
answer.buf := nil;
|
|
answer.event1 := 0;
|
|
answer.event2 := 0;
|
|
result := true;
|
|
end;
|
|
end;
|
|
|
|
type
|
|
TPipedIpcRec = record
|
|
map : THandle;
|
|
pid : dword;
|
|
rp, wp : THandle;
|
|
callback : TIpcCallback;
|
|
context : pointer;
|
|
mxThreads : dword;
|
|
mxQueue : dword;
|
|
flags : dword;
|
|
th : THandle;
|
|
name : array [0..MAX_PATH] of AnsiChar;
|
|
counter : dword;
|
|
msg : record
|
|
buf : PAnsiChar;
|
|
len : dword;
|
|
end;
|
|
answer : TIpcAnswer;
|
|
end;
|
|
|
|
function OpenPipedIpcMap(name: AnsiString; var ir: TPipedIpcRec; pmutex: TPNativeUInt = nil; destroy: boolean = false) : boolean;
|
|
var buf : ^TPipedIpcRec;
|
|
mutex : THandle;
|
|
map : THandle;
|
|
begin
|
|
result := false;
|
|
mutex := CreateGlobalMutex(PAnsiChar(name + DecryptStr(CIpc) + DecryptStr(CMutex)));
|
|
if mutex <> 0 then begin
|
|
WaitForSingleObject(mutex, INFINITE);
|
|
try
|
|
map := OpenGlobalFileMapping(PAnsiChar(name + DecryptStr(CIpc) + DecryptStr(CMap)), true);
|
|
if map <> 0 then begin
|
|
buf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
if buf <> nil then begin
|
|
result := true;
|
|
inc(buf^.counter);
|
|
ir := buf^;
|
|
if destroy then
|
|
if buf^.pid = GetCurrentProcessID then
|
|
CloseHandle(buf^.map)
|
|
else
|
|
result := false;
|
|
UnmapViewOfFile(buf);
|
|
end;
|
|
CloseHandle(map);
|
|
end;
|
|
finally
|
|
if (not result) or (pmutex = nil) then begin
|
|
ReleaseMutex(mutex);
|
|
CloseHandle(mutex);
|
|
end else
|
|
pmutex^ := mutex;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
type
|
|
TSecurityQualityOfService = packed record
|
|
size : dword;
|
|
dummy : array [0..1] of dword;
|
|
end;
|
|
TLpcMessageData = array [0..59] of dword;
|
|
TLpcMessagePrivate = packed record
|
|
pid : dword;
|
|
msgLen : dword;
|
|
counter : dword;
|
|
session : dword;
|
|
answerLen : dword;
|
|
data : TLpcMessageData;
|
|
end;
|
|
TPLpcMessage = ^TLpcMessage;
|
|
TLpcMessage = packed record
|
|
next : TPLpcMessage;
|
|
name : AnsiString;
|
|
callback : TIpcCallback;
|
|
context : pointer;
|
|
answer : TIpcAnswer;
|
|
prv : dword;
|
|
actualMessageLength : word;
|
|
totalMessageLength : word;
|
|
messageType : word;
|
|
dataInfoOffset : word;
|
|
clientProcessId : dword;
|
|
clientThreadId : dword;
|
|
messageId : dword;
|
|
sharedSectionSize : dword;
|
|
end;
|
|
TLpcSectionInfo = packed record
|
|
size : dword;
|
|
{$ifdef win64}
|
|
dummy1 : dword;
|
|
{$endif}
|
|
sectionHandle : THandle;
|
|
param1 : dword;
|
|
{$ifdef win64}
|
|
dummy2 : dword;
|
|
{$endif}
|
|
sectionSize : dword;
|
|
{$ifdef win64}
|
|
dummy3 : dword;
|
|
{$endif}
|
|
clientBaseAddress : pointer;
|
|
serverBaseAddress : pointer;
|
|
end;
|
|
TLpcSectionMapInfo = packed record
|
|
size : dword;
|
|
{$ifdef win64}
|
|
dummy1 : dword;
|
|
{$endif}
|
|
sectionSize : dword;
|
|
{$ifdef win64}
|
|
dummy2 : dword;
|
|
{$endif}
|
|
serverBaseAddress : pointer;
|
|
end;
|
|
{$ifndef win64}
|
|
TLpcSectionMapInfo64 = packed record
|
|
size : dword;
|
|
dummy1 : dword;
|
|
sectionSize : dword;
|
|
dummy2 : dword;
|
|
serverBaseAddress : pointer;
|
|
dummy3 : dword;
|
|
end;
|
|
{$endif}
|
|
|
|
const LPC_CONNECTION_REQUEST = $a;
|
|
|
|
var
|
|
NtCreatePort : function (var portHandle: THandle; var objAttr: TObjectAttributes; maxConnectInfoLen, maxDataLen, unknown: NativeUInt) : dword; stdcall;
|
|
NtConnectPort : function (var portHandle: THandle; var portName: TUnicodeStr; var qs: TSecurityQualityOfService; sectionInfo, mapInfo, unknown2, connectInfo: pointer; var connectInfoLen: dword) : dword; stdcall;
|
|
NtReplyWaitReceivePort : function (portHandle: THandle; portContext: pointer; replyMessage, receiveMessage: pointer) : dword; stdcall;
|
|
NtReplyWaitReceivePortEx : function (portHandle: THandle; portContext: pointer; replyMessage, receiveMessage: pointer; var timeOut: int64) : dword; stdcall;
|
|
NtAcceptConnectPort : function (var portHandle: THandle; unknown1: NativeUInt; msg: pointer; acceptIt, unknown2: NativeUInt; mapInfo: pointer) : dword; stdcall;
|
|
NtCompleteConnectPort : function (portHandle: THandle) : dword; stdcall;
|
|
|
|
type
|
|
TPLpcWorkerThread = ^TLpcWorkerThread;
|
|
TLpcWorkerThread = record
|
|
handle : THandle;
|
|
parentSemaphore : THandle;
|
|
event : THandle;
|
|
pm : TPLpcMessage;
|
|
lastActive : dword;
|
|
shutdown : boolean;
|
|
freed : ^boolean;
|
|
end;
|
|
|
|
TPLpcQueue = ^TLpcQueue;
|
|
TLpcQueue = record
|
|
name : AnsiString;
|
|
callback : TIpcCallback;
|
|
context : pointer;
|
|
maxThreadCount : dword;
|
|
madQueueLen : dword;
|
|
port : THandle;
|
|
counter : dword;
|
|
pm : TPLpcMessage;
|
|
section : TRtlCriticalSection;
|
|
semaphore : THandle;
|
|
shutdown : boolean;
|
|
portThread : THandle;
|
|
dispatchThread : THandle;
|
|
workerThreads : array of TPLpcWorkerThread;
|
|
end;
|
|
|
|
var
|
|
LpcSection : TRTLCriticalSection;
|
|
LpcReady : boolean = false;
|
|
LpcList : array of TPLpcQueue;
|
|
LpcCounterBuf : ^integer = nil;
|
|
|
|
procedure InitLpcName(const ipc: AnsiString; var ws: AnsiString; var uniStr: TUnicodeStr);
|
|
begin
|
|
ws := AnsiToWideEx(DecryptStr(CRpcControlIpc) + ipc);
|
|
uniStr.maxLen := length(ws);
|
|
uniStr.len := uniStr.maxLen - 2;
|
|
uniStr.str := pointer(ws);
|
|
end;
|
|
|
|
procedure InitLpcFuncs;
|
|
begin
|
|
if @NtCreatePort = nil then begin
|
|
NtCreatePort := NtProc(CNtCreatePort);
|
|
NtConnectPort := NtProc(CNtConnectPort);
|
|
NtReplyWaitReceivePort := NtProc(CNtReplyWaitReceivePort);
|
|
NtReplyWaitReceivePortEx := NtProc(CNtReplyWaitReceivePortEx);
|
|
NtAcceptConnectPort := NtProc(CNtAcceptConnectPort);
|
|
NtCompleteConnectPort := NtProc(CNtCompleteConnectPort);
|
|
end;
|
|
end;
|
|
|
|
function LpcWorkerThread(var wt: TLpcWorkerThread) : integer; stdcall;
|
|
// the worker thread handles one message after the other, if it's told to
|
|
var pm2 : pointer;
|
|
prv : ^TLpcMessagePrivate;
|
|
begin
|
|
result := 0;
|
|
with wt do
|
|
while true do begin
|
|
if shutdown or (WaitForSingleObject(event, INFINITE) <> WAIT_OBJECT_0) or shutdown or (pm = nil) then
|
|
break;
|
|
prv := pointer(NativeUInt(pm) + pm.prv);
|
|
pm.callback(PAnsiChar(pm.name), @prv.data, prv.msgLen, pm.answer.buf, pm.answer.len, pm.context);
|
|
if pm.answer.len <> 0 then begin
|
|
SetEvent(pm.answer.event2);
|
|
CloseIpcAnswer(pm.answer);
|
|
end;
|
|
lastActive := GetTickCount;
|
|
pm^.name := '';
|
|
pm2 := pm;
|
|
pm := nil;
|
|
LocalFree(HLOCAL(pm2));
|
|
ReleaseSemaphore(parentSemaphore, 1, nil);
|
|
end;
|
|
EnterCriticalSection(LpcSection);
|
|
if wt.freed <> nil then
|
|
wt.freed^ := true;
|
|
CloseHandle(wt.event);
|
|
CloseHandle(wt.handle);
|
|
LocalFree(HLOCAL(@wt));
|
|
LeaveCriticalSection(LpcSection);
|
|
end;
|
|
|
|
function LpcDispatchThread(var iq: TLpcQueue) : integer; stdcall;
|
|
// this thread takes all ipc messages and feeds them to the worker threads
|
|
// worker threads are dynamically created and deleted, if it makes sense
|
|
var pm : TPLpcMessage;
|
|
tid : dword;
|
|
i1, i2 : integer;
|
|
begin
|
|
while true do begin
|
|
WaitForSingleObject(iq.semaphore, INFINITE);
|
|
if iq.shutdown then
|
|
break;
|
|
EnterCriticalSection(iq.section);
|
|
try
|
|
pm := iq.pm;
|
|
if pm <> nil then
|
|
iq.pm := iq.pm^.next;
|
|
finally LeaveCriticalSection(iq.section) end;
|
|
while pm <> nil do begin
|
|
// loop until we found someone who is willing to handle this message
|
|
for i1 := 0 to high(iq.workerThreads) do begin
|
|
if iq.workerThreads[i1].pm = nil then begin
|
|
iq.workerThreads[i1].pm := pm;
|
|
SetEvent(iq.workerThreads[i1].event);
|
|
pm := nil;
|
|
break;
|
|
end;
|
|
end;
|
|
if pm <> nil then begin
|
|
// we still have a message to handle, but all worker threads are busy
|
|
if iq.maxThreadCount > dword(Length(iq.workerThreads)) then begin
|
|
// let's create a new worker thread
|
|
i1 := Length(iq.workerThreads);
|
|
SetLength(iq.workerThreads, i1 + 1);
|
|
iq.workerThreads[i1] := pointer(LocalAlloc(LPTR, sizeOf(TLpcWorkerThread)));
|
|
iq.workerThreads[i1].parentSemaphore := iq.semaphore;
|
|
iq.workerThreads[i1].event := CreateEvent(nil, false, true, nil);
|
|
iq.workerThreads[i1].pm := pm;
|
|
iq.workerThreads[i1].handle := CreateThread(nil, 0, @LpcWorkerThread, iq.workerThreads[i1], 0, tid);
|
|
if iq.workerThreads[i1].handle <> 0 then begin
|
|
SetThreadPriority(iq.workerThreads[i1].handle, THREAD_PRIORITY_NORMAL);
|
|
pm := nil;
|
|
end else begin
|
|
CloseHandle(iq.workerThreads[i1].event);
|
|
LocalFree(HLOCAL(iq.workerThreads[i1]));
|
|
SetLength(iq.workerThreads, i1 - 1);
|
|
end;
|
|
end;
|
|
if pm <> nil then begin
|
|
// we still have a message to handle, so we wait
|
|
// until a worker thread is ready to handle this message
|
|
WaitForSingleObject(iq.semaphore, INFINITE);
|
|
if iq.shutdown then
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
if iq.shutdown then
|
|
break;
|
|
i2 := Length(iq.workerThreads);
|
|
for i1 := high(iq.workerThreads) downto 0 do
|
|
if ((i1 > 0) or (i2 > 1)) and (iq.workerThreads[i1].pm = nil) and (GetTickCount - iq.workerThreads[i1].lastActive > 100) then begin
|
|
// this thread was idle for more than 100ms, so we close it down
|
|
iq.workerThreads[i1].shutdown := true;
|
|
SetEvent(iq.workerThreads[i1].event);
|
|
dec(i2);
|
|
iq.workerThreads[i1] := iq.workerThreads[i2];
|
|
iq.workerThreads[i2] := nil;
|
|
end;
|
|
if i2 <> Length(iq.workerThreads) then
|
|
SetLength(iq.workerThreads, i2);
|
|
end;
|
|
result := 0;
|
|
end;
|
|
|
|
function LpcPortThread(var iq: TLpcQueue) : integer; stdcall;
|
|
// this thread receives ipc messages from the LPC port
|
|
// all incoming messages are stored in a pointer list
|
|
var port : THandle;
|
|
pm1, pm2 : TPLpcMessage;
|
|
ppm : ^TPLpcMessage;
|
|
mi : ^TLpcSectionMapInfo;
|
|
prv : ^TLpcMessagePrivate;
|
|
prevPort : THandle;
|
|
retVal : dword;
|
|
timeOut : int64;
|
|
begin
|
|
result := 0;
|
|
prevPort := 0;
|
|
while true do begin
|
|
pm1 := pointer(LocalAlloc(LPTR, sizeof(TLpcMessage) + sizeof(TLpcMessagePrivate) + 128));
|
|
if (@NtReplyWaitReceivePortEx <> nil) and (not DisableIpcFix1) then begin
|
|
repeat
|
|
timeOut := -100000; // 10ms
|
|
retVal := NtReplyWaitReceivePortEx(iq.port, nil, nil, @pm1.actualMessageLength, timeOut);
|
|
until (retVal <> STATUS_TIMEOUT) or iq.shutdown;
|
|
end else
|
|
retVal := NtReplyWaitReceivePort(iq.port, nil, nil, @pm1.actualMessageLength);
|
|
if prevPort <> 0 then begin
|
|
CloseHandle(prevPort);
|
|
prevPort := 0;
|
|
end;
|
|
if (retVal = 0) and (pm1.messageType = LPC_CONNECTION_REQUEST) and (pm1.actualMessageLength > 0) then begin
|
|
pm1.prv := NativeUInt(@pm1.actualMessageLength) - NativeUInt(pm1) + pm1.totalMessageLength - sizeOf(TLpcMessagePrivate);
|
|
prv := pointer(NativeUInt(pm1) + pm1.prv);
|
|
if not IsBadReadPtr(prv, 20) then begin
|
|
if iq.shutdown then begin
|
|
NtAcceptConnectPort(port, 0, @pm1.actualMessageLength, 0, 0, nil);
|
|
LocalFree(HLOCAL(pm1));
|
|
break;
|
|
end;
|
|
// due to 32/64 bit LPC incompatabilities, pMsg->ClientProcessId is not reliable, so we extract the PID from our own transport buffer
|
|
if prv.pid <> 0 then
|
|
pm1.clientProcessId := prv.pid;
|
|
// set the process ID. The client checks this value as verification that the command completed
|
|
prv.pid := GetCurrentProcessId + 1;
|
|
port := 0;
|
|
{$ifdef win64}
|
|
mi := pointer(LocalAlloc(LPTR, sizeOf(TLpcSectionMapInfo)));
|
|
mi.size := sizeOf(TLpcSectionMapInfo);
|
|
{$else}
|
|
mi := pointer(LocalAlloc(LPTR, sizeOf(TLpcSectionMapInfo64)));
|
|
if Is64bitOS then
|
|
mi.size := sizeOf(TLpcSectionMapInfo64)
|
|
else mi.size := sizeOf(TLpcSectionMapInfo );
|
|
{$endif}
|
|
if NtAcceptConnectPort(port, 0, @pm1.actualMessageLength, 1, 0, mi) = 0 then begin
|
|
{$ifndef win64}
|
|
if Is64bitOS then begin
|
|
mi.sectionSize := TLpcSectionMapInfo64(pointer(mi)^).sectionSize;
|
|
mi.serverBaseAddress := TLpcSectionMapInfo64(pointer(mi)^).serverBaseAddress;
|
|
end;
|
|
{$endif}
|
|
if (mi.sectionSize > 0) and (prv.msgLen > 0) then begin
|
|
if prv.msgLen > mi.sectionSize then
|
|
prv.msgLen := mi.sectionSize;
|
|
pm2 := pointer(LocalAlloc(LPTR, NativeUInt(@prv.data) - NativeUInt(pm1) + prv.msgLen));
|
|
Move(pm1^, pm2^, NativeUInt(@prv.data) - NativeUInt(pm1));
|
|
NativeUInt(prv) := NativeUInt(pm2) + pm2.prv; // pm2.prv -> read access nil?
|
|
Move(mi.serverBaseAddress^, prv.data, prv.msgLen);
|
|
LocalFree(HLOCAL(pm1));
|
|
pm1 := pm2;
|
|
end else
|
|
if prv.msgLen > sizeOf(prv.data) then
|
|
prv.msgLen := sizeOf(prv.data);
|
|
NtCompleteConnectPort(port);
|
|
prevPort := port;
|
|
pm1.answer.len := prv.answerLen;
|
|
if InitIpcAnswer(false, iq.name, prv.counter, pm1.clientProcessId, pm1.answer, prv.session) then begin
|
|
if pm1.answer.len <> 0 then
|
|
SetEvent(pm1.answer.event1);
|
|
pm1.name := iq.name;
|
|
pm1.callback := iq.callback;
|
|
pm1.context := iq.context;
|
|
EnterCriticalSection(iq.section);
|
|
try
|
|
ppm := @iq.pm;
|
|
while ppm^ <> nil do
|
|
ppm := @ppm^^.next;
|
|
ppm^ := pm1;
|
|
finally LeaveCriticalSection(iq.section) end;
|
|
ReleaseSemaphore(iq.semaphore, 1, nil);
|
|
end else
|
|
LocalFree(HLOCAL(pm1));
|
|
end else begin
|
|
prv.pid := 0;
|
|
NtAcceptConnectPort(port, 0, @pm1.actualMessageLength, 0, 0, nil);
|
|
LocalFree(HLOCAL(pm1));
|
|
end;
|
|
LocalFree(HLOCAL(mi));
|
|
end else begin
|
|
LocalFree(HLOCAL(pm1));
|
|
if iq.shutdown and (not DisableIpcFix1) then
|
|
break;
|
|
end;
|
|
end else begin
|
|
LocalFree(HLOCAL(pm1));
|
|
if iq.shutdown and (not DisableIpcFix1) then
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure HandlePipedIpcMessage(var ir: TPipedIpcRec);
|
|
begin
|
|
ir.callback(ir.name, ir.msg.buf, ir.msg.len, ir.answer.buf, ir.answer.len, ir.context);
|
|
if ir.answer.len <> 0 then begin
|
|
SetEvent(ir.answer.event2);
|
|
CloseIpcAnswer(ir.answer);
|
|
end;
|
|
LocalFree(HLOCAL(ir.msg.buf));
|
|
end;
|
|
|
|
function PipedIpcThread2(var ir: TPipedIpcRec) : integer; stdcall;
|
|
begin
|
|
result := 0;
|
|
HandlePipedIpcMessage(ir);
|
|
LocalFree(HLOCAL(@ir));
|
|
end;
|
|
|
|
function PipedIpcThread1(var ir: TPipedIpcRec) : integer; stdcall;
|
|
var c1 : dword;
|
|
h1 : THandle;
|
|
pir : ^TPipedIpcRec;
|
|
tid : dword;
|
|
session : dword;
|
|
begin
|
|
result := 0;
|
|
while ReadFile(ir.rp, ir.msg.len, 4, c1, nil) and (c1 = 4) do begin
|
|
ir.msg.buf := pointer(LocalAlloc(LPTR, ir.msg.len));
|
|
if ir.msg.buf <> nil then begin
|
|
if ReadFile(ir.rp, ir.counter, 4, c1, nil) and (c1 = 4) and
|
|
ReadFile(ir.rp, session, 4, c1, nil) and (c1 = 4) and
|
|
ReadFile(ir.rp, ir.answer.len, 4, c1, nil) and (c1 = 4) and
|
|
ReadFile(ir.rp, ir.msg.buf^, ir.msg.len, c1, nil) and (c1 = ir.msg.len) and
|
|
InitIpcAnswer(false, ir.name, ir.counter, 0, ir.answer, session) then begin
|
|
if ir.answer.len <> 0 then
|
|
SetEvent(ir.answer.event1);
|
|
if ir.mxThreads > 1 then begin
|
|
pir := pointer(LocalAlloc(LPTR, sizeOf(ir)));
|
|
pir^ := ir;
|
|
h1 := CreateThread(nil, 0, @PipedIpcThread2, pir, 0, tid);
|
|
if h1 <> 0 then begin
|
|
SetThreadPriority(h1, THREAD_PRIORITY_NORMAL);
|
|
CloseHandle(h1);
|
|
end else begin
|
|
LocalFree(HLOCAL(pir));
|
|
LocalFree(HLOCAL(ir.msg.buf));
|
|
CloseIpcAnswer(ir.answer);
|
|
end;
|
|
end else
|
|
HandlePipedIpcMessage(ir);
|
|
end else
|
|
LocalFree(HLOCAL(ir.msg.buf));
|
|
end;
|
|
end;
|
|
LocalFree(HLOCAL(@ir));
|
|
end;
|
|
|
|
function CreateIpcQueue(ipc: PAnsiChar; callback: TIpcCallback; context: pointer; maxThreadCount, maxQueueLen: dword; securityDesc: PSecurityDescriptor) : bool; stdcall;
|
|
|
|
function CreateLpcQueue : boolean;
|
|
// new ipc queue logic based on NT native LPC (local procedure call) APIs
|
|
// implemented mainly for Vista, cause Vista doesn't like the old approach
|
|
// probably in a later version I'll also use the new approach for older OSs
|
|
// but the new approach shall prove itself first
|
|
var objAttr : ^TObjectAttributes;
|
|
uniStr : TUnicodeStr;
|
|
sa : TSecurityAttributes;
|
|
sd : TSecurityDescriptor;
|
|
ws : AnsiString;
|
|
tid : dword;
|
|
i1 : integer;
|
|
port : THandle;
|
|
ph : THandle;
|
|
begin
|
|
if DuplicateHandle(GetCurrentProcess, GetCurrentProcess, GetCurrentProcess, @ph, 0, false, DUPLICATE_SAME_ACCESS) then begin
|
|
AddAccessForEveryone(ph, SYNCHRONIZE);
|
|
CloseHandle(ph);
|
|
end;
|
|
if not LpcReady then begin
|
|
InitializeCriticalSection(LpcSection);
|
|
LpcReady := true;
|
|
end;
|
|
result := true;
|
|
EnterCriticalSection(LpcSection);
|
|
try
|
|
for i1 := 0 to high(LpcList) do
|
|
if IsTextEqualA(LpcList[i1].name, ipc) then begin
|
|
result := false;
|
|
break;
|
|
end;
|
|
finally LeaveCriticalSection(LpcSection) end;
|
|
if result then begin
|
|
InitLpcFuncs;
|
|
InitLpcName(ipc, ws, uniStr);
|
|
objAttr := pointer(LocalAlloc(LPTR, sizeOf(TObjectAttributes)));
|
|
objAttr.len := sizeOf(TObjectAttributes);
|
|
objAttr.objName := @uniStr;
|
|
if securityDesc = nil then begin
|
|
InitSecAttr(sa, sd, LimitedIpcPort, false);
|
|
objAttr.sd := @sd;
|
|
end else begin
|
|
sd.Dacl := nil;
|
|
objAttr.sd := securityDesc;
|
|
end;
|
|
port := INVALID_HANDLE_VALUE;
|
|
result := NtCreatePort(port, objAttr^, 260, 40 + 260, 0) = 0;
|
|
LocalFree(HLOCAL(objAttr));
|
|
if sd.Dacl <> nil then
|
|
LocalFree(HLOCAL(sd.Dacl));
|
|
if result then begin
|
|
if maxThreadCount > 64 then
|
|
maxThreadCount := 64;
|
|
EnterCriticalSection(LpcSection);
|
|
try
|
|
i1 := Length(LpcList);
|
|
SetLength(LpcList, i1 + 1);
|
|
LpcList[i1] := pointer(LocalAlloc(LPTR, sizeOf(TLpcQueue)));
|
|
InitializeCriticalSection(LpcList[i1].section);
|
|
LpcList[i1].name := ipc;
|
|
LpcList[i1].callback := callback;
|
|
LpcList[i1].context := context;
|
|
LpcList[i1].maxThreadCount := maxThreadCount;
|
|
LpcList[i1].madQueueLen := maxQueueLen;
|
|
LpcList[i1].port := port;
|
|
LpcList[i1].semaphore := CreateSemaphore(nil, 0, maxInt, nil);
|
|
LpcList[i1].portThread := CreateThread(nil, 0, @LpcPortThread, LpcList[i1], 0, tid);
|
|
LpcList[i1].dispatchThread := CreateThread(nil, 0, @LpcDispatchThread, LpcList[i1], 0, tid);
|
|
SetThreadPriority(LpcList[i1]. portThread, 7);
|
|
SetThreadPriority(LpcList[i1].dispatchThread, THREAD_PRIORITY_ABOVE_NORMAL);
|
|
finally LeaveCriticalSection(LpcSection) end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function CreatePipedIpcQueue : boolean;
|
|
// this is the old IPC solution based on (unnamed) pipes
|
|
var mutex : THandle;
|
|
map : THandle;
|
|
buf1, buf2 : ^TPipedIpcRec;
|
|
s1 : AnsiString;
|
|
tid : dword;
|
|
ph : THandle;
|
|
begin
|
|
result := false;
|
|
if DuplicateHandle(GetCurrentProcess, GetCurrentProcess, GetCurrentProcess, @ph, 0, false, DUPLICATE_SAME_ACCESS) then begin
|
|
AddAccessForEveryone(ph, PROCESS_DUP_HANDLE or SYNCHRONIZE);
|
|
CloseHandle(ph);
|
|
end;
|
|
s1 := ipc;
|
|
if Length(s1) > MAX_PATH then
|
|
SetLength(s1, MAX_PATH);
|
|
mutex := CreateGlobalMutex(PAnsiChar(s1 + DecryptStr(CIpc) + DecryptStr(CMutex)));
|
|
if mutex <> 0 then begin
|
|
WaitForSingleObject(mutex, INFINITE);
|
|
try
|
|
map := CreateGlobalFileMapping(PAnsiChar(s1 + DecryptStr(CIpc) + DecryptStr(CMap)), sizeOf(TPipedIpcRec));
|
|
if map <> 0 then
|
|
if GetLastError <> ERROR_ALREADY_EXISTS then begin
|
|
buf1 := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
if buf1 <> nil then begin
|
|
result := true;
|
|
ZeroMemory(buf1, sizeOf(TPipedIpcRec));
|
|
CreatePipe(buf1^.rp, buf1^.wp, @UnlimitedSa, 0);
|
|
buf1^.map := map;
|
|
buf1^.pid := GetCurrentProcessID;
|
|
buf1^.callback := callback;
|
|
buf1^.context := context;
|
|
buf1^.mxThreads := maxThreadCount;
|
|
buf1^.mxQueue := maxQueueLen;
|
|
Move(PAnsiChar(s1)^, buf1^.name, Length(s1) + 1);
|
|
buf2 := pointer(LocalAlloc(LPTR, sizeOf(buf2^)));
|
|
buf2^ := buf1^;
|
|
buf1^.th := CreateThread(nil, 0, @PipedIpcThread1, buf2, 0, tid);
|
|
SetThreadPriority(buf1^.th, 7);
|
|
buf1^.counter := 0;
|
|
UnmapViewOfFile(buf1);
|
|
end else
|
|
CloseHandle(map);
|
|
end else
|
|
CloseHandle(map);
|
|
finally
|
|
ReleaseMutex(mutex);
|
|
CloseHandle(mutex);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
result := false;
|
|
EnableAllPrivileges;
|
|
if maxThreadCount = 0 then
|
|
maxThreadCount := 16;
|
|
if @callback <> nil then
|
|
if UseNewIpcLogic then
|
|
result := CreateLpcQueue
|
|
else
|
|
result := CreatePipedIpcQueue;
|
|
end;
|
|
|
|
function SendIpcMessage(ipc: PAnsiChar; messageBuf: pointer; messageLen: dword;
|
|
answerBuf: pointer = nil; answerLen: dword = 0;
|
|
answerTimeOut: dword = INFINITE; handleMessages: bool = false) : bool; stdcall;
|
|
|
|
function WaitFor(h1, h2: THandle; timeOut: dword) : boolean;
|
|
var handles : array [0..1] of THandle;
|
|
msg : TMsg;
|
|
begin
|
|
result := false;
|
|
handles[0] := h1;
|
|
handles[1] := h2;
|
|
if handleMessages then begin
|
|
while true do
|
|
case MsgWaitForMultipleObjects(2, handles, false, timeOut, QS_ALLINPUT) of
|
|
WAIT_OBJECT_0 : begin
|
|
result := true;
|
|
break;
|
|
end;
|
|
WAIT_OBJECT_0 + 2 : while PeekMessage(msg, 0, 0, 0, PM_REMOVE) do begin
|
|
TranslateMessage(msg);
|
|
DispatchMessage(msg);
|
|
end;
|
|
else break;
|
|
end;
|
|
end else
|
|
result := WaitForMultipleObjects(2, @handles, false, timeOut) = WAIT_OBJECT_0;
|
|
end;
|
|
|
|
function GetLpcCounter : dword;
|
|
|
|
function InterlockedIncrementEx(var value: integer) : integer;
|
|
asm
|
|
{$ifdef win64}
|
|
mov rdx, value
|
|
mov eax, dword ptr [rdx]
|
|
@loop:
|
|
mov ecx, eax
|
|
inc ecx
|
|
lock cmpxchg dword ptr [rdx], ecx
|
|
jnz @loop
|
|
{$else}
|
|
mov edx, value
|
|
mov eax, [edx]
|
|
@loop:
|
|
mov ecx, eax
|
|
inc ecx
|
|
lock cmpxchg [edx], ecx
|
|
jnz @loop
|
|
{$endif}
|
|
end;
|
|
|
|
var s1 : AnsiString;
|
|
map : THandle;
|
|
mutex : THandle;
|
|
newMap : boolean;
|
|
begin
|
|
if LpcCounterBuf = nil then begin
|
|
s1 := DecryptStr(CIpc) + DecryptStr(CCounter) + IntToHexExA(GetCurrentProcessId);
|
|
mutex := CreateMutexA(nil, false, PAnsiChar(s1 + DecryptStr(CMutex)));
|
|
if mutex <> 0 then begin
|
|
WaitForSingleObject(mutex, INFINITE);
|
|
try
|
|
map := CreateFileMappingA(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, 4, PAnsiChar(s1));
|
|
if map <> 0 then begin
|
|
newMap := GetLastError <> ERROR_ALREADY_EXISTS;
|
|
LpcCounterBuf := MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
if newMap and (LpcCounterBuf <> nil) then
|
|
LpcCounterBuf^ := 0;
|
|
CloseHandle(map);
|
|
end;
|
|
finally
|
|
ReleaseMutex(mutex);
|
|
CloseHandle(mutex);
|
|
end;
|
|
end;
|
|
end;
|
|
if LpcCounterBuf <> nil then
|
|
result := dword(InterlockedIncrementEx(LpcCounterBuf^))
|
|
else
|
|
result := 0;
|
|
end;
|
|
|
|
var {$ifdef CheckRecursion}
|
|
recTestBuf : array [0..3] of pointer;
|
|
{$endif}
|
|
port : THandle;
|
|
uniStr : TUnicodeStr;
|
|
qs : TSecurityQualityOfService;
|
|
buf : TLpcMessagePrivate;
|
|
bufLen : dword;
|
|
ws : AnsiString;
|
|
c1 : dword;
|
|
si : TLpcSectionInfo;
|
|
mi : TLpcSectionMapInfo;
|
|
psi : ^TLpcSectionInfo;
|
|
pmi : ^TLpcSectionMapInfo;
|
|
ph : THandle;
|
|
ir : TPipedIpcRec;
|
|
mutex : THandle;
|
|
error : dword;
|
|
b1 : boolean;
|
|
session : dword;
|
|
t1, t2 : dword;
|
|
begin
|
|
error := GetLastError;
|
|
result := false;
|
|
{$ifdef CheckRecursion}
|
|
if NotRecursedYet(@SendIpcMessage, @recTestBuf) then begin
|
|
{$endif}
|
|
ph := 0;
|
|
ZeroMemory(@ir, sizeOf(ir));
|
|
if UseNewIpcLogic then begin
|
|
ZeroMemory(@buf, sizeOf(buf));
|
|
buf.counter := GetLpcCounter;
|
|
buf.pid := GetCurrentProcessId;
|
|
ir.answer.len := answerLen;
|
|
b1 := InitIpcAnswer(true, ipc, buf.counter, GetCurrentProcessId, ir.answer);
|
|
if b1 then begin
|
|
InitLpcFuncs;
|
|
InitLpcName(ipc, ws, uniStr);
|
|
ZeroMemory(@si, sizeOf(si));
|
|
ZeroMemory(@qs, sizeOf(qs));
|
|
port := 0;
|
|
buf.msgLen := messageLen;
|
|
buf.session := GetCurrentSessionId;
|
|
buf.answerLen := answerLen;
|
|
if messageLen > sizeOf(buf.data) then begin
|
|
// bufLen := sizeOf(buf) - sizeOf(buf.data);
|
|
ZeroMemory(@mi, sizeOf(mi));
|
|
si.size := sizeOf(si);
|
|
si.sectionHandle := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, messageLen, nil);
|
|
si.sectionSize := messageLen;
|
|
si.clientBaseAddress := MapViewOfFile(si.sectionHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
|
|
if si.clientBaseAddress <> nil then begin
|
|
Move(messageBuf^, si.clientBaseAddress^, messageLen);
|
|
UnmapViewOfFile(si.clientBaseAddress);
|
|
si.clientBaseAddress := nil;
|
|
end;
|
|
mi.size := sizeOf(mi);
|
|
mi.sectionSize := messageLen;
|
|
psi := @si;
|
|
pmi := @mi;
|
|
end else begin
|
|
// bufLen := sizeOf(buf) - sizeOf(buf.data) + messageLen;
|
|
Move(messageBuf^, buf.data, messageLen);
|
|
psi := nil;
|
|
pmi := nil;
|
|
end;
|
|
bufLen := sizeOf(buf);
|
|
// Calling NtConnectPort can sometimes fail with "unsufficient resources".
|
|
// This can happen if a *lot* of threads send IPCs at the same time.
|
|
// So we call it in a loop until it succeeds, timeout = 250ms.
|
|
t1 := GetTickCount();
|
|
while true do begin
|
|
port := 0;
|
|
c1 := NtConnectPort(port, uniStr, qs, psi, pmi, nil, @buf, bufLen);
|
|
// There is a race condition on closing the handle between this thread and the Lpc port thread.
|
|
// If the other thread closes the handle first, then NtConnectPort returns C0000034, but still works.
|
|
if (c1 = 0) or (c1 = $C0000034) or (port <> 0) then
|
|
break;
|
|
t2 := GetTickCount();
|
|
if (t2 < t1) or (t2 - t1 >= 300) then
|
|
break;
|
|
Sleep(0);
|
|
end;
|
|
if port <> 0 then
|
|
CloseHandle(port);
|
|
if si.sectionHandle <> 0 then
|
|
CloseHandle(si.sectionHandle);
|
|
result := (buf.pid <> 0) and (buf.pid <> GetCurrentProcessId);
|
|
if result and (answerLen <> 0) then
|
|
ph := OpenProcess(SYNCHRONIZE, false, buf.pid - 1);
|
|
end;
|
|
end else begin
|
|
b1 := OpenPipedIpcMap(ipc, ir, @mutex);
|
|
if b1 then begin
|
|
ir.answer.len := answerLen;
|
|
b1 := InitIpcAnswer(true, ipc, ir.counter, 0, ir.answer);
|
|
if b1 then begin
|
|
ph := OpenProcess(PROCESS_DUP_HANDLE or SYNCHRONIZE, false, ir.pid);
|
|
if (ph <> 0) and
|
|
DuplicateHandle(ph, ir.wp, GetCurrentProcess, @ir.wp, 0, false, DUPLICATE_SAME_ACCESS) then begin
|
|
session := GetCurrentSessionId;
|
|
result := WriteFile(ir.wp, messageLen, 4, c1, nil) and (c1 = 4) and
|
|
WriteFile(ir.wp, ir.counter, 4, c1, nil) and (c1 = 4) and
|
|
WriteFile(ir.wp, session, 4, c1, nil) and (c1 = 4) and
|
|
WriteFile(ir.wp, answerLen, 4, c1, nil) and (c1 = 4) and
|
|
WriteFile(ir.wp, messageBuf^, messageLen, c1, nil) and (c1 = messageLen);
|
|
CloseHandle(ir.wp);
|
|
end;
|
|
end;
|
|
ReleaseMutex(mutex);
|
|
CloseHandle(mutex);
|
|
end;
|
|
end;
|
|
if ph <> 0 then begin
|
|
if result and (answerLen <> 0) then
|
|
if WaitFor(ir.answer.event1, ph, InternalIpcTimeout) then begin
|
|
if WaitFor(ir.answer.event2, ph, answerTimeOut) then begin
|
|
Move(ir.answer.buf^, answerBuf^, answerLen);
|
|
end else
|
|
result := false;
|
|
end else
|
|
result := false;
|
|
CloseHandle(ph);
|
|
end;
|
|
if b1 then
|
|
CloseIpcAnswer(ir.answer);
|
|
{$ifdef CheckRecursion}
|
|
recTestBuf[3] := nil;
|
|
end;
|
|
{$endif}
|
|
SetLastError(error);
|
|
end;
|
|
|
|
function DestroyIpcQueue(ipc: PAnsiChar) : bool; stdcall;
|
|
|
|
function DestroyLpcQueue : boolean;
|
|
var port : THandle;
|
|
uniStr : TUnicodeStr;
|
|
qs : TSecurityQualityOfService;
|
|
bufLen : dword;
|
|
ws : AnsiString;
|
|
iq : TPLpcQueue;
|
|
i1 : integer;
|
|
freed : boolean;
|
|
workerTh : THandle;
|
|
begin
|
|
result := false;
|
|
if LpcReady then begin
|
|
EnterCriticalSection(LpcSection);
|
|
try
|
|
iq := nil;
|
|
for i1 := 0 to high(LpcList) do
|
|
if IsTextEqualA(LpcList[i1].name, ipc) then begin
|
|
iq := LpcList[i1];
|
|
LpcList[i1] := LpcList[high(LpcList)];
|
|
SetLength(LpcList, high(LpcList));
|
|
break;
|
|
end;
|
|
finally LeaveCriticalSection(LpcSection) end;
|
|
if iq <> nil then begin
|
|
iq.shutdown := true;
|
|
ReleaseSemaphore(iq.semaphore, 1, nil);
|
|
InitLpcName(iq.name, ws, uniStr);
|
|
ZeroMemory(@qs, sizeOf(qs));
|
|
if DisableIpcFix1 then begin
|
|
port := 0;
|
|
bufLen := 0;
|
|
NtConnectPort(port, uniStr, qs, nil, nil, nil, nil, bufLen);
|
|
end;
|
|
if WaitForSingleObject(iq.portThread, 1000) = WAIT_TIMEOUT then
|
|
TerminateThread(iq.portThread, 0);
|
|
CloseHandle(iq.portThread);
|
|
if WaitForSingleObject(iq.dispatchThread, 1000) = WAIT_TIMEOUT then
|
|
TerminateThread(iq.dispatchThread, 0);
|
|
CloseHandle(iq.dispatchThread);
|
|
for i1 := 0 to high(iq.workerThreads) do
|
|
if iq.workerThreads[i1] <> nil then begin
|
|
freed := false;
|
|
iq.workerThreads[i1].freed := @freed;
|
|
iq.workerThreads[i1].shutdown := true;
|
|
workerTh := 0;
|
|
if DuplicateHandle(GetCurrentProcess, iq.workerThreads[i1].handle, GetCurrentProcess, @workerTh, 0, FALSE, DUPLICATE_SAME_ACCESS) then begin
|
|
SetEvent(iq.workerThreads[i1].event);
|
|
WaitForSingleObject(workerTh, 1000);
|
|
CloseHandle(workerTh);
|
|
end else begin
|
|
SetEvent(iq.workerThreads[i1].event);
|
|
WaitForSingleObject(iq.workerThreads[i1].handle, 1000);
|
|
end;
|
|
if not freed then begin
|
|
EnterCriticalSection(LpcSection);
|
|
if not freed then begin
|
|
TerminateThread(iq.workerThreads[i1].handle, 0);
|
|
CloseHandle(iq.workerThreads[i1].event);
|
|
CloseHandle(iq.workerThreads[i1].handle);
|
|
if iq.workerThreads[i1].pm <> nil then begin
|
|
iq.workerThreads[i1].pm.name := '';
|
|
LocalFree(HLOCAL(iq.workerThreads[i1].pm));
|
|
end;
|
|
LocalFree(HLOCAL(iq.workerThreads[i1]));
|
|
end;
|
|
LeaveCriticalSection(LpcSection);
|
|
end;
|
|
end;
|
|
iq.workerThreads := nil;
|
|
CloseHandle(iq.port);
|
|
CloseHandle(iq.semaphore);
|
|
DeleteCriticalSection(iq.section);
|
|
iq.name := '';
|
|
LocalFree(HLOCAL(iq));
|
|
result := true;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function DestroyPipedIpcQueue : boolean;
|
|
var ir : TPipedIpcRec;
|
|
begin
|
|
result := OpenPipedIpcMap(ipc, ir, nil, true);
|
|
if result then begin
|
|
CloseHandle(ir.wp);
|
|
if WaitForSingleObject(ir.th, 100) = WAIT_TIMEOUT then
|
|
TerminateThread(ir.th, 0);
|
|
CloseHandle(ir.rp);
|
|
CloseHandle(ir.th);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
if UseNewIpcLogic then
|
|
result := DestroyLpcQueue
|
|
else
|
|
result := DestroyPipedIpcQueue;
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
function AddAccessForEveryone(processOrService: THandle; access: dword) : bool; stdcall;
|
|
const CEveryoneSia : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 1));
|
|
CSecurityAppPackageAuthority : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 15));
|
|
type
|
|
TTrustee = record
|
|
multiple : pointer;
|
|
multop : dword;
|
|
form : dword;
|
|
type_ : dword;
|
|
sid : PSid;
|
|
end;
|
|
TExplicitAccess = record
|
|
access : dword;
|
|
mode : dword;
|
|
inherit : dword;
|
|
trustee : TTrustee;
|
|
end;
|
|
var sd : PSecurityDescriptor;
|
|
ea : array [0..1] of TExplicitAccess;
|
|
oldDacl, newDacl : PAcl;
|
|
sid1, sid2 : PSid;
|
|
dll : HMODULE;
|
|
gsi : function (handle: THandle; objectType, securityInfo: dword; owner, group, dacl, sacl, sd: pointer) : dword; stdcall;
|
|
ssi : function (handle: THandle; objectType, securityInfo: dword; owner, group: PSid; dacl, sacl: PAcl) : dword; stdcall;
|
|
seia : function (count: dword; ea: pointer; oldAcl: PAcl; var newAcl: PAcl) : dword; stdcall;
|
|
typ : dword;
|
|
count : integer;
|
|
begin
|
|
result := false;
|
|
dll := LoadLibraryA(PAnsiChar(DecryptStr(CAdvApi32)));
|
|
gsi := GetProcAddress(dll, PAnsiChar(DecryptStr(CGetSecurityInfo)));
|
|
ssi := GetProcAddress(dll, PAnsiChar(DecryptStr(CSetSecurityInfo)));
|
|
seia := GetProcAddress(dll, PAnsiChar(DecryptStr(CSetEntriesInAclA)));
|
|
if (@gsi <> nil) and (@ssi <> nil) and (@seia <> nil) then begin
|
|
if gsi(processOrService, 6, DACL_SECURITY_INFORMATION, nil, nil, @oldDacl, nil, @sd) = 0 then typ := 6
|
|
else if gsi(processOrService, 2, DACL_SECURITY_INFORMATION, nil, nil, @oldDacl, nil, @sd) = 0 then typ := 2
|
|
else typ := 0;
|
|
if typ <> 0 then begin
|
|
if AllocateAndInitializeSid(CEveryoneSia, 1, 0, 0, 0, 0, 0, 0, 0, 0, sid1) then begin
|
|
ZeroMemory(@ea, sizeOf(ea));
|
|
ea[0].access := access;
|
|
ea[0].mode := 2; // SET_ACCESS
|
|
ea[0].trustee.form := 0; // TRUSTEE_IS_SID
|
|
ea[0].trustee.type_ := 1; // TRUSTEE_IS_USER
|
|
ea[0].trustee.sid := sid1;
|
|
count := 1;
|
|
if IsMetro and AllocateAndInitializeSid(CSecurityAppPackageAuthority, 2, 2, 1, 0, 0, 0, 0, 0, 0, sid2) then begin
|
|
// this adds access for metro "AppContainer" apps
|
|
ea[1].access := access;
|
|
ea[1].mode := 2; // SET_ACCESS
|
|
ea[1].trustee.form := 0; // TRUSTEE_IS_SID
|
|
ea[1].trustee.type_ := 2; // TRUSTEE_IS_GROUP
|
|
ea[1].trustee.sid := sid2;
|
|
inc(count);
|
|
end else
|
|
sid2 := nil;
|
|
if seia(count, @ea, oldDacl, newDacl) = 0 then begin
|
|
result := ssi(processOrService, typ, DACL_SECURITY_INFORMATION, nil, nil, newDacl, nil) = 0;
|
|
LocalFree(HLOCAL(newDacl));
|
|
end;
|
|
FreeSid(sid1);
|
|
if sid2 <> nil then
|
|
FreeSid(sid2);
|
|
end;
|
|
LocalFree(HLOCAL(sd));
|
|
end;
|
|
end;
|
|
FreeLibrary(dll);
|
|
end;
|
|
|
|
// ***************************************************************
|
|
|
|
(*function CheckSLSvc(process: THandle) : boolean;
|
|
const MEMBER_ACCESS = 1;
|
|
const idAuthorityNt : TSidIdentifierAuthority = (value: (0, 0, 0, 0, 0, 5));
|
|
var prcToken : THandle;
|
|
impToken : THandle;
|
|
sid : PSid;
|
|
sd : TSecurityDescriptor;
|
|
dacl : PAcl;
|
|
daclSize : dword;
|
|
genMap : TGenericMapping;
|
|
privBuf : array [0..sizeOf(TPrivilegeSet) + 3 * sizeof(TLuidAndAttributes) - 1] of byte;
|
|
priv : PPrivilegeSet;
|
|
privLen : dword;
|
|
accGranted : dword;
|
|
accStatus : bool;
|
|
begin
|
|
result := false;
|
|
if OpenProcessToken(process, TOKEN_QUERY or TOKEN_DUPLICATE, prcToken) then begin
|
|
if DuplicateToken(prcToken, SecurityImpersonation, @impToken) then begin
|
|
if AllocateAndInitializeSid(idAuthorityNt, 6, 80, 2119565420, 4155874467, 2934723793, 509086461, 374458824, 0, 0, sid) then begin
|
|
daclSize := sizeOf(TAcl) + 12 + 3 * GetLengthSid(sid);
|
|
dacl := pointer(LocalAlloc(LMEM_ZEROINIT, daclSize));
|
|
if dacl <> nil then begin
|
|
genMap.GenericRead := STANDARD_RIGHTS_READ or MEMBER_ACCESS;
|
|
genMap.GenericWrite := STANDARD_RIGHTS_EXECUTE;
|
|
genMap.GenericExecute := STANDARD_RIGHTS_WRITE;
|
|
genMap.GenericAll := STANDARD_RIGHTS_ALL or MEMBER_ACCESS;
|
|
priv := @privBuf;
|
|
privLen := sizeOf(privBuf);
|
|
accGranted := 0;
|
|
accStatus := false;
|
|
result := InitializeSecurityDescriptor(@sd, SECURITY_DESCRIPTOR_REVISION) and
|
|
SetSecurityDescriptorOwner(@sd, sid, false) and
|
|
SetSecurityDescriptorGroup(@sd, sid, false) and
|
|
InitializeAcl(dacl^, daclSize, 2) and
|
|
AddAccessAllowedAce(dacl^, 2, MEMBER_ACCESS, sid) and
|
|
SetSecurityDescriptorDacl(@sd, true, dacl, false) and
|
|
AccessCheck(@sd, impToken, MEMBER_ACCESS, genMap, priv^, privLen, accGranted, accStatus) and
|
|
accStatus and (accGranted = MEMBER_ACCESS);
|
|
LocalFree(HLOCAL(dacl));
|
|
end;
|
|
FreeSid(sid);
|
|
end;
|
|
CloseHandle(impToken);
|
|
end;
|
|
CloseHandle(prcToken);
|
|
end;
|
|
end;*)
|
|
|
|
// ***************************************************************
|
|
|
|
var PrivilegesEnabled : boolean = false;
|
|
procedure EnableAllPrivileges;
|
|
type TTokenPrivileges = record
|
|
PrivilegeCount : dword;
|
|
Privileges : array [0..maxInt shr 4 - 1] of TLUIDAndAttributes;
|
|
end;
|
|
var token : THandle;
|
|
c2 : dword;
|
|
i1 : integer;
|
|
ptp : ^TTokenPrivileges;
|
|
backup, restore, owner : int64;
|
|
begin
|
|
if PrivilegesEnabled then
|
|
exit;
|
|
if OpenProcessToken(windows.GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, token) then
|
|
try
|
|
c2 := 0;
|
|
GetTokenInformation(token, TokenPrivileges, nil, 0, c2);
|
|
if c2 <> 0 then begin
|
|
ptp := pointer(LocalAlloc(LPTR, c2 * 2));
|
|
if GetTokenInformation(token, TokenPrivileges, ptp, c2 * 2, c2) then begin
|
|
// enabling backup/restore privileges breaks Explorer's Samba support
|
|
if not LookupPrivilegeValueA(nil, PAnsiChar(DecryptStr(CSeBackupPrivilege )), backup ) then backup := 0;
|
|
if not LookupPrivilegeValueA(nil, PAnsiChar(DecryptStr(CSeRestorePrivilege )), restore) then restore := 0;
|
|
if not LookupPrivilegeValueA(nil, PAnsiChar(DecryptStr(CSeTakeOwnershipPrivilege)), owner ) then owner := 0;
|
|
for i1 := 0 to integer(ptp^.PrivilegeCount) - 1 do
|
|
if (ptp^.Privileges[i1].Luid <> backup ) and
|
|
(ptp^.Privileges[i1].Luid <> restore) and
|
|
(ptp^.Privileges[i1].Luid <> owner ) then
|
|
ptp^.Privileges[i1].Attributes := ptp^.Privileges[i1].Attributes or SE_PRIVILEGE_ENABLED;
|
|
AdjustTokenPrivileges(token, false, PTokenPrivileges(ptp)^, c2, PTokenPrivileges(nil)^, dword(pointer(nil)^));
|
|
end;
|
|
LocalFree(HLOCAL(ptp));
|
|
end;
|
|
finally CloseHandle(token) end;
|
|
PrivilegesEnabled := true;
|
|
end;
|
|
|
|
procedure InitializeMadCHook;
|
|
begin
|
|
IsMultiThread := true;
|
|
{$ifdef log}
|
|
log('initialization begin');
|
|
{$endif}
|
|
InitTryExceptFinally;
|
|
madTools.NeedModuleFileMapEx := madCodeHook.CollectCache_NeedModuleFileMap;
|
|
IsWine := NtProc(CWineGetVersion) <> nil;
|
|
madTools.IsWine := IsWine;
|
|
InitSecAttr(LimitedSa, LimitedSd, true, true);
|
|
InitSecAttr(UnlimitedSa, UnlimitedSd, false, true);
|
|
InitializeCriticalSection(CollectSection);
|
|
SetLastError(0);
|
|
{$ifdef log}
|
|
log('initialization end');
|
|
{$endif}
|
|
end;
|
|
|
|
procedure FinalizeMadCHook;
|
|
begin
|
|
{$ifdef log}
|
|
log('finalization begin');
|
|
{$endif}
|
|
AutoUnhook2(HMODULE(-1), false);
|
|
{$ifdef forbiddenAPIs}
|
|
if forbiddenMutex <> 0 then begin
|
|
CloseHandle(forbiddenMutex);
|
|
forbiddenMutex := 0;
|
|
end;
|
|
{$endif}
|
|
if InjectApprovalCallbackReady then begin
|
|
CancelAllInjectApprovalCallbacks;
|
|
InjectApprovalCallbackList := nil;
|
|
CloseHandle(InjectApprovalCallbackEmptyEvent);
|
|
InjectApprovalCallbackEmptyEvent := 0;
|
|
DeleteCriticalSection(InjectApprovalCallbackSection);
|
|
InjectApprovalCallbackReady := false;
|
|
end;
|
|
if HookReady and (HookList = nil) then begin
|
|
HookReady := false;
|
|
DeleteCriticalSection(HookSection);
|
|
end;
|
|
if LpcReady then begin
|
|
LpcReady := false;
|
|
DeleteCriticalSection(LpcSection);
|
|
end;
|
|
if LpcCounterBuf <> nil then
|
|
UnmapViewOfFile(LpcCounterBuf);
|
|
if LimitedSd.Dacl <> nil then
|
|
LocalFree(HLOCAL(LimitedSd.Dacl));
|
|
if UnlimitedSd.Dacl <> nil then
|
|
LocalFree(HLOCAL(UnlimitedSd.Dacl));
|
|
DeleteCriticalSection(CollectSection);
|
|
{$ifdef log}
|
|
log('finalization end');
|
|
{$endif}
|
|
end;
|
|
|
|
// ***************************************************************
|
|
(*
|
|
type
|
|
TRelJump = packed record
|
|
jmp : byte; // $e9
|
|
target : dword;
|
|
end;
|
|
PInjectLib32 = ^TInjectLib32;
|
|
TInjectLib32 = packed record
|
|
movEax : byte; // 0xb8
|
|
param : dword; // pointer // pointer to "self"
|
|
movEcx : byte; // 0xb9
|
|
proc : dword; // pointer // pointer to "self.injectFunc"
|
|
callEcx : word; // 0xd1ff
|
|
magic : dword; // "mciL" / 0x4c69636d
|
|
next : dword; // PInjectLib32Old // next dll (if any)
|
|
pOldApi : ^TRelJump; // which code location in user land do we patch?
|
|
oldApi : TRelJump; // patch backup buffer, contains overwritten code
|
|
// align : byte;
|
|
dll : TUnicodeStr32; // dll path/name
|
|
dllBuf : array [0..259] of WideChar; // string buffer for the dll path/name
|
|
npvm : function (ProcessHandle: THandle; var baseAddress: pointer; var numberofBytesToProtect: dword; newAccess: dword; var oldAccess: dword) : dword; stdcall;
|
|
lld : function (path: PWideChar; var flags: dword; var moduleFileName: TUnicodeStr32; var ModuleHandle: THandle) : dword; stdcall;
|
|
navm : function (ProcessHandle: THandle; var baseAddress: pointer; zeroBits: NativeUInt; var RegionSize: NativeInt; allocationType, protect: dword) : dword; stdcall;
|
|
nfvm : function (ProcessHandle: THandle; var baseAddress: pointer; var regionSize: NativeInt; freeType: dword) : dword; stdcall;
|
|
nqsi : function (infoClass: NativeUInt; buffer: pointer; bufSize: dword; returnSize: TPCardinal) : dword; stdcall;
|
|
nde : function (alertable: pointer; var delayInterval: int64) : dword; stdcall;
|
|
injectFunc : array [0..3514] of byte;
|
|
skip : bool;
|
|
end;
|
|
TNtProcessInfo = packed record
|
|
offset : dword;
|
|
numThreads : integer;
|
|
d1 : array [0..11] of dword;
|
|
d2 : pointer;
|
|
name : PWideChar;
|
|
d3 : pointer;
|
|
pid : NativeUInt;
|
|
parentPid : NativeUInt;
|
|
handleCount : dword;
|
|
sessionId : dword;
|
|
d4 : dword;
|
|
d5 : array [0..11] of pointer;
|
|
d6 : array [0..5] of dword;
|
|
d7 : NativeUInt;
|
|
threads : array [0..maxInt shr 7 - 1] of packed record
|
|
startAddr_nt4 : pointer;
|
|
pid_nt4 : dword;
|
|
tid_nt4 : dword;
|
|
d8 : array [44..52] of dword;
|
|
startAddr_nt5 : pointer;
|
|
pid_nt5 : NativeUInt;
|
|
tid_nt5 : NativeUInt;
|
|
d9 : dword;
|
|
end;
|
|
end;
|
|
TPNtModuleInfo = ^TNtModuleInfo;
|
|
TNtModuleInfo = record
|
|
next : TPNtModuleInfo;
|
|
d1 : array [0..2] of pointer;
|
|
handle : cardinal;
|
|
d2 : array [0..1] of pointer;
|
|
d3 : word;
|
|
nameLen : word;
|
|
name : PWideChar;
|
|
end;
|
|
|
|
procedure InjectLibFunc32OldStatic(buf: PInjectLib32);
|
|
var ctid, mtid : dword; // current and main thread ids
|
|
cpid : dword;
|
|
ebp_ : dword;
|
|
c1, c2 : dword;
|
|
size : NativeInt;
|
|
p1 : pointer;
|
|
sleep : int64;
|
|
npi : ^TNtProcessInfo;
|
|
begin
|
|
asm
|
|
mov eax, fs:[$18]
|
|
mov eax, [eax+$20]
|
|
mov cpid, eax
|
|
mov eax, fs:[$18]
|
|
mov eax, [eax+$24]
|
|
mov ctid, eax
|
|
mov ebp_, ebp
|
|
end;
|
|
TPPointer(ebp_ + 4)^ := pointer(buf.pOldApi);
|
|
mtid := 0;
|
|
if @buf.nqsi <> nil then begin
|
|
c1 := 0;
|
|
buf.nqsi(5, nil, 0, @c1);
|
|
if c1 <> 0 then begin
|
|
size := c1 * 2;
|
|
p1 := nil;
|
|
if buf.navm(THandle(-1), p1, 0, size, MEM_COMMIT, PAGE_READWRITE) = 0 then begin
|
|
if buf.nqsi(5, p1, c1, nil) = 0 then begin
|
|
npi := p1;
|
|
while true do begin
|
|
if npi^.pid = cpid then begin
|
|
mtid := npi^.threads[0].tid_nt5;
|
|
break;
|
|
end;
|
|
if npi^.offset = 0 then
|
|
break;
|
|
npi := pointer(NativeUInt(npi) + npi^.offset);
|
|
end;
|
|
end;
|
|
size := 0;
|
|
buf.nfvm(THandle(-1), p1, size, MEM_RELEASE);
|
|
end;
|
|
end;
|
|
end;
|
|
if (mtid <> 0) and (mtid <> ctid) then begin
|
|
// This is not the main thread! This usually doesn't happen, except sometimes in win10.
|
|
// We "solve" this by waiting until the main thread has completed executing our loader stub.
|
|
// Max wait time 1 second, just to be safe.
|
|
for c1 := 1 to 100 do begin
|
|
if (buf.pOldApi^.jmp = buf.oldApi.jmp) and (buf.pOldApi^.target = buf.oldApi.target) then
|
|
// Our loader stub patch was removed, so we assume that the main thread has completed running it.
|
|
break;
|
|
sleep := -100000;// 10 milliseconds
|
|
buf.nde(nil, sleep);
|
|
end;
|
|
end;
|
|
if (buf.pOldApi^.jmp <> buf.oldApi.jmp) or (buf.pOldApi^.target <> buf.oldApi.target) then begin
|
|
// step 3: uninstall our patch
|
|
p1 := buf.pOldApi;
|
|
c1 := 5;
|
|
if buf.npvm(dword(-1), p1, c1, PAGE_EXECUTE_READWRITE, c2) = 0 then begin
|
|
buf.pOldApi^ := buf.oldApi;
|
|
c1 := 5;
|
|
buf.npvm(dword(-1), p1, c1, c2, c2);
|
|
end else begin
|
|
// For some reason we can't uninstall our patch correctly.
|
|
// That happens if "dynamic code execution" is forbidden.
|
|
// As a workaround we keep our callback installed, but we
|
|
// rewire it to execute our copy of the original API code.
|
|
buf.skip := true;
|
|
end;
|
|
// step 4: finally load the to-be-injected dll - but only if we're in the context of the main thread
|
|
if (mtid = 0) or (mtid = ctid) then
|
|
repeat
|
|
buf.lld(nil, dword(nil^), buf.dll, c1);
|
|
buf := pointer(buf.next);
|
|
until buf = nil;
|
|
end;
|
|
end;
|
|
|
|
procedure End_InjectLibFunc32OldStatic(buf: PInjectLib32); begin end;
|
|
|
|
procedure InjectLibFunc32OldDynamic(buf: PInjectLib32);
|
|
var ctid, mtid : dword; // current and main thread ids
|
|
cpid : dword;
|
|
ebp_ : dword;
|
|
size : NativeInt;
|
|
c1, c2 : dword;
|
|
peb : dword;
|
|
p1 : pointer;
|
|
loopEnd : TPNtModuleInfo;
|
|
mi : TNtModuleInfo;
|
|
len : dword;
|
|
ntdll : dword;
|
|
nh : PImageNtHeaders;
|
|
ed : PImageExportDirectory;
|
|
i1 : integer;
|
|
api : pchar;
|
|
npi : ^TNtProcessInfo;
|
|
sleep : int64;
|
|
npvm : function (process: THandle; var addr: pointer; var size: NativeUInt; newProt: dword; var oldProt: dword) : dword; stdcall;
|
|
lld : function (path, flags, name: pointer; var handle: HMODULE) : dword; stdcall;
|
|
navm : function (process: THandle; var addr: pointer; zeroBits: NativeUInt; var regionSize: NativeInt; allocationType, protect: dword) : dword; stdcall;
|
|
nfvm : function (process: THandle; var addr: pointer; var regionSize: NativeInt; freeType: dword) : dword; stdcall;
|
|
nqsi : function (infoClass: dword; buffer: pointer; bufSize: dword; returnSize: TPCardinal) : dword; stdcall;
|
|
nde : function (alertable: pointer; var delayInterval: int64) : dword; stdcall;
|
|
begin
|
|
asm
|
|
mov eax, fs:[$30]
|
|
mov peb, eax
|
|
mov eax, fs:[$18]
|
|
mov eax, [eax+$20]
|
|
mov cpid, eax
|
|
mov eax, fs:[$18]
|
|
mov eax, [eax+$24]
|
|
mov ctid, eax
|
|
mov ebp_, ebp
|
|
end;
|
|
TPPointer(ebp_ + 4)^ := buf.pOldApi;
|
|
|
|
npvm := nil;
|
|
lld := nil;
|
|
navm := nil;
|
|
nfvm := nil;
|
|
nqsi := nil;
|
|
nde := nil;
|
|
|
|
// step 1: locate ntdll.dll
|
|
loopEnd := pointer(dword(pointer(peb + $C)^) + $14);
|
|
mi := loopEnd^;
|
|
while mi.next <> loopEnd do begin
|
|
mi := mi.next^;
|
|
if mi.name <> nil then begin
|
|
len := 0;
|
|
while mi.name[len] <> #0 do
|
|
inc(len);
|
|
if (int64(pointer(@mi.name[len - 9])^) = $006c00640074006e) and // ntdl
|
|
(int64(pointer(@mi.name[len - 5])^) = $006c0064002e006c) then begin // l.dl
|
|
// found it!
|
|
ntdll := mi.handle;
|
|
|
|
// step 2: locate LdrLoadDll and NtProtectVirtualMemory
|
|
nh := pointer(ntdll + dword(pointer(ntdll + $3C)^));
|
|
dword(ed) := ntdll + nh^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
|
for i1 := 0 to ed^.NumberOfNames - 1 do begin
|
|
api := pointer(ntdll + TPACardinal(ntdll + ed^.AddressOfNames)^[i1]);
|
|
if (api <> nil) and
|
|
( ( (int64(pointer(@api[ 0])^) = $4464616f4c72644c) and // LdrLoadD
|
|
(dword(pointer(@api[ 8])^) and $00ffffff = $00006c6c ) ) or // ll
|
|
( (int64(pointer(@api[ 0])^) = $6365746f7250744e) and // NtProtec
|
|
(int64(pointer(@api[ 8])^) = $6c61757472695674) and // tVirtual
|
|
(int64(pointer(@api[16])^) and $00ffffffffffffff = $000079726f6d654d) ) or // Memory
|
|
( (int64(pointer(@api[ 0])^) = $61636F6C6C41744E) and // NtAlloca
|
|
(int64(pointer(@api[ 8])^) = $6175747269566574) and // teVirtua
|
|
(int64(pointer(@api[16])^) = $0079726F6D654D6C) ) or // lMemory
|
|
( (int64(pointer(@api[ 0])^) = $695665657246744E) and // NtFreeVi
|
|
(int64(pointer(@api[ 8])^) = $6D654D6C61757472) and // rtualMem
|
|
(dword(pointer(@api[16])^) = $0079726F ) ) or // ory
|
|
( (int64(pointer(@api[ 0])^) = $537972657551744E) and // NtQueryS
|
|
(int64(pointer(@api[ 8])^) = $666E496D65747379) and // ystemInf
|
|
(int64(pointer(@api[16])^) = $6E6F6974616D726F) and // ormation
|
|
(byte (pointer(@api[24])^) = $00 ) ) or // ormation
|
|
( (int64(pointer(@api[ 0])^) = $4579616c6544744e) and // NtDelayE
|
|
(int64(pointer(@api[ 8])^) = $6e6f697475636578) and // xecution
|
|
(byte (pointer(@api[16])^) = $00 ) ) ) then begin
|
|
c1 := TPAWord(ntdll + ed^.AddressOfNameOrdinals)^[i1];
|
|
c1 := TPACardinal(ntdll + ed^.AddressOfFunctions)^[c1];
|
|
case api[2] of
|
|
'r': lld := pointer(ntdll + c1);
|
|
'P': npvm := pointer(ntdll + c1);
|
|
'A': navm := pointer(ntdll + c1);
|
|
'F': nfvm := pointer(ntdll + c1);
|
|
'Q': nqsi := pointer(ntdll + c1);
|
|
'D': nde := pointer(ntdll + c1);
|
|
end;
|
|
end;
|
|
end;
|
|
if (@lld <> nil) and (@npvm <> nil) then begin
|
|
// found both APIs!
|
|
mtid := 0;
|
|
if (@navm <> nil) and (@nfvm <> nil) and (@nqsi <> nil) and (@nde <> nil) then begin
|
|
c1 := 0;
|
|
nqsi(5, nil, 0, @c1);
|
|
if c1 <> 0 then begin
|
|
size := c1 * 2;
|
|
p1 := nil;
|
|
if navm(THandle(-1), p1, 0, size, MEM_COMMIT, PAGE_READWRITE) = 0 then begin
|
|
if nqsi(5, p1, size, nil) = 0 then begin
|
|
npi := p1;
|
|
while true do begin
|
|
if npi^.pid = cpid then begin
|
|
mtid := npi^.threads[0].tid_nt5;
|
|
break;
|
|
end;
|
|
if npi^.offset = 0 then
|
|
break;
|
|
npi := pointer(NativeUInt(npi) + npi^.offset);
|
|
end;
|
|
end;
|
|
size := 0;
|
|
nfvm(THandle(-1), p1, size, MEM_RELEASE);
|
|
end;
|
|
end;
|
|
end;
|
|
if (mtid <> 0) and (mtid <> ctid) then begin
|
|
// This is not the main thread! This usually doesn't happen, except sometimes in win10.
|
|
// We "solve" this by waiting until the main thread has completed executing our loader stub.
|
|
// Max wait time 1 second, just to be safe.
|
|
for c1 := 1 to 100 do begin
|
|
if (buf.pOldApi^.jmp = buf.oldApi.jmp) and (buf.pOldApi^.target = buf.oldApi.target) then
|
|
// Our loader stub patch was removed, so we assume that the main thread has completed running it.
|
|
break;
|
|
sleep := -100000; // 10 milliseconds
|
|
nde(nil, sleep);
|
|
end;
|
|
end;
|
|
if (buf.pOldApi^.jmp <> buf.oldApi.jmp) or (buf.pOldApi^.target <> buf.oldApi.target) then begin
|
|
// step 3: uninstall our patch
|
|
p1 := buf.pOldApi;
|
|
c1 := 5;
|
|
if npvm(dword(-1), p1, c1, PAGE_EXECUTE_READWRITE, c2) = 0 then begin
|
|
buf.pOldApi^ := buf.oldApi;
|
|
c1 := 5;
|
|
npvm(dword(-1), p1, c1, c2, c2);
|
|
end else begin
|
|
// For some reason we can't uninstall our patch correctly.
|
|
// That happens if "dynamic code execution" is forbidden.
|
|
// As a workaround we keep our callback installed, but we
|
|
// rewire it to execute our copy of the original API code.
|
|
buf.skip := true;
|
|
end;
|
|
// step 4: finally load the to-be-injected dll - but only if we're in the context of the main thread
|
|
if (mtid = 0) or (mtid = ctid) then
|
|
repeat
|
|
lld(nil, nil, @buf.dll, c1);
|
|
buf := pointer(buf.next);
|
|
until buf = nil;
|
|
end;
|
|
end;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure End_InjectLibFunc32OldDynamic(buf: PInjectLib32); begin end;
|
|
|
|
procedure CreateStub_InjectLibFunc32OldStatic;
|
|
// compile with Delphi 7 in release mode
|
|
var fh : THandle;
|
|
c1 : dword;
|
|
begin
|
|
fh := CreateFile('D:\Desktop\InjectLibFunc32OldStatic.bin', GENERIC_READ or GENERIC_WRITE, 0, nil, CREATE_ALWAYS, 0, 0);
|
|
WriteFile(fh, InjectLibFunc32OldStatic, dword(@End_InjectLibFunc32OldStatic) - dword(@InjectLibFunc32OldStatic), c1, nil);
|
|
CloseHandle(fh);
|
|
end;
|
|
|
|
procedure CreateStub_InjectLibFunc32OldDynamic;
|
|
// compile with Delphi 7 in release mode
|
|
var fh : THandle;
|
|
c1 : dword;
|
|
begin
|
|
fh := CreateFile('D:\Desktop\InjectLibFunc32OldDynamic.bin', GENERIC_READ or GENERIC_WRITE, 0, nil, CREATE_ALWAYS, 0, 0);
|
|
WriteFile(fh, InjectLibFunc32OldDynamic, dword(@End_InjectLibFunc32OldDynamic) - dword(@InjectLibFunc32OldDynamic), c1, nil);
|
|
CloseHandle(fh);
|
|
end;
|
|
*)
|
|
// ***************************************************************
|
|
|
|
initialization
|
|
InitializeMadCHook;
|
|
|
|
// CreateStub_InjectLibFunc32OldStatic;
|
|
// CreateStub_InjectLibFunc32OldDynamic;
|
|
|
|
(* fh := CreateFileA('c:\desktop\inUseStub', GENERIC_WRITE, 0, nil, CREATE_ALWAYS, 0, 0);
|
|
WriteFile(fh, InUse_ThreadState, NativeUInt(@InUse_ThreadStateEnd) - NativeUInt(@InUse_ThreadState), c1, nil);
|
|
CloseHandle(fh);
|
|
ExitProcess(0); *)
|
|
|
|
finalization
|
|
FinalizeMadCHook;
|
|
|
|
end. |