├── .gitattributes ├── .gitmodules ├── Bypassing ├── SuspendMe.DenyAccess.pas ├── SuspendMe.ThreadPool.pas ├── SuspendMe.RaceCondition.pas ├── SuspendMe.dpr ├── SuspendMe.PatchCreation.pas └── SuspendMe.SelfDebug.pas ├── .gitignore ├── Injecting ├── InjectTool.Direct.pas ├── InjectTool.dpr └── InjectTool.ThreadPool.pas ├── Monitoring ├── ModeTransitionMonitor.Trace.pas ├── ModeTransitionMonitor.dpr ├── Instrumentation.Monitor.pas ├── SuspendInfo.dpr └── SuspendInfo.dproj ├── SuspendingTechniques.groupproj ├── Suspending ├── SuspendTool.DebugObject.pas ├── SuspendTool.dpr └── SuspendTool.dproj └── Readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "NtUtils"] 2 | path = NtUtils 3 | url = https://github.com/diversenok/NtUtilsLibrary 4 | -------------------------------------------------------------------------------- /Bypassing/SuspendMe.DenyAccess.pas: -------------------------------------------------------------------------------- 1 | unit SuspendMe.DenyAccess; 2 | 3 | { 4 | This module demonstrates protecting the process and thread objects with a 5 | denying DACL. 6 | } 7 | 8 | interface 9 | 10 | uses 11 | NtUtils; 12 | 13 | // Set a denying security descriptor on the current process and its threads 14 | function ProtectProcessObject: TNtxStatus; 15 | 16 | implementation 17 | 18 | uses 19 | Ntapi.WinNt, Ntapi.ntpsapi, NtUtils.Objects, NtUtils.Threads, 20 | NtUtils.Security.Acl; 21 | 22 | function ProtectProcessObject; 23 | var 24 | SD: ISecDesc; 25 | hxThread: IHandle; 26 | begin 27 | Result := RtlxAllocateDenyingSd(SD); 28 | 29 | if not Result.IsSuccess then 30 | Exit; 31 | 32 | Result := NtxSetSecurityObject(NtCurrentProcess, DACL_SECURITY_INFORMATION, 33 | SD.Data); 34 | 35 | if not Result.IsSuccess then 36 | Exit; 37 | 38 | hxThread := nil; 39 | while NtxGetNextThread(NtCurrentProcess, hxThread, WRITE_DAC).IsSuccess do 40 | NtxSetSecurityObject(hxThread.Handle, DACL_SECURITY_INFORMATION, SD.Data); 41 | 42 | writeln('Unprivileged programs should not be able to suspend this process.'); 43 | end; 44 | 45 | end. 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Uncomment these types if you want even more clean repository. But be careful. 2 | # It can make harm to an existing project source. Read explanations below. 3 | # 4 | # Resource files are binaries containing manifest, project icon and version info. 5 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 6 | *.res 7 | # 8 | # Type library file (binary). In old Delphi versions it should be stored. 9 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 10 | #*.tlb 11 | # 12 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 13 | # Uncomment this if you are not using diagrams or use newer Delphi version. 14 | #*.ddp 15 | # 16 | # Visual LiveBindings file. Added in Delphi XE2. 17 | # Uncomment this if you are not using LiveBindings Designer. 18 | #*.vlb 19 | # 20 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 21 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 22 | #*.deployproj 23 | # 24 | # C++ object files produced when C/C++ Output file generation is configured. 25 | # Uncomment this if you are not using external objects (zlib library for example). 26 | #*.obj 27 | # 28 | 29 | # Delphi compiler-generated binaries (safe to delete) 30 | *.exe 31 | *.dll 32 | *.bpl 33 | *.bpi 34 | *.dcp 35 | *.so 36 | *.apk 37 | *.drc 38 | *.map 39 | *.dres 40 | *.rsm 41 | *.tds 42 | *.dcu 43 | *.lib 44 | *.a 45 | *.o 46 | *.ocx 47 | *.dbg 48 | 49 | # Delphi autogenerated files (duplicated info) 50 | *.cfg 51 | *.hpp 52 | *Resource.rc 53 | 54 | # Delphi local files (user-specific info) 55 | *.local 56 | *.identcache 57 | *.projdata 58 | *.tvsconfig 59 | *.dsk 60 | 61 | # Delphi history and backups 62 | __history/ 63 | __recovery/ 64 | *.~* 65 | 66 | # Castalia statistics file (since XE7 Castalia is distributed with Delphi) 67 | *.stat 68 | 69 | # Boss dependency manager vendor folder https://github.com/HashLoad/boss 70 | modules/ 71 | -------------------------------------------------------------------------------- /Injecting/InjectTool.Direct.pas: -------------------------------------------------------------------------------- 1 | unit InjectTool.Direct; 2 | 3 | { 4 | The implementation for testing direct thread injection. 5 | } 6 | 7 | interface 8 | 9 | uses 10 | Ntapi.ntpsapi, NtUtils; 11 | 12 | // Inject a dummy thread into a suspended process to see how it behaves 13 | function InjectDummyThread( 14 | hProcess: THandle; 15 | Flags: TThreadCreateFlags 16 | ): TNtxStatus; 17 | 18 | implementation 19 | 20 | uses 21 | Ntapi.WinNt, Ntapi.ntdef, Ntapi.Ntstatus, NtUtils.Threads, 22 | Ntutils.Processes.Info, NtUtils.ShellCode, NtUtils.Synchronization; 23 | 24 | function InjectDummyThread; 25 | var 26 | ThreadMain: Pointer; 27 | ThreadParam: NativeUInt; 28 | IsWoW64: Boolean; 29 | hxThread: IHandle; 30 | Checkpoint: Cardinal; 31 | begin 32 | // Prevent WoW64 -> Native injection 33 | Result := RtlxAssertWoW64Compatible(hProcess, IsWoW64); 34 | 35 | if not Result.IsSuccess then 36 | Exit; 37 | 38 | // Use some simple function with a single NativeUInt parameter as the payload 39 | Result := RtlxFindKnownDllExport(ntdll, IsWoW64, 'NtAlertThread', ThreadMain); 40 | 41 | if not Result.IsSuccess then 42 | Exit; 43 | 44 | ThreadParam := NtCurrentThread; 45 | 46 | {$IFDEF Win64} 47 | if IsWoW64 then 48 | ThreadParam := Cardinal(ThreadParam); 49 | {$ENDIF} 50 | 51 | Result := NtxCreateThread(hxThread, hProcess, ThreadMain, 52 | Pointer(ThreadParam), Flags); 53 | 54 | if not Result.IsSuccess then 55 | Exit; 56 | 57 | NtxSetNameThread(hxThread.Handle, 'Thread injection test'); 58 | writeln('Successfully created a thread.'); 59 | writeln; 60 | Checkpoint := 0; 61 | 62 | repeat 63 | writeln('[#', Checkpoint, '] Waiting for it...'); 64 | Inc(Checkpoint); 65 | 66 | Result := NtxWaitForSingleObject(hxThread.Handle, 2000 * MILLISEC); 67 | until Result.Status <> STATUS_TIMEOUT; 68 | 69 | writeln; 70 | 71 | if Result.Status = STATUS_WAIT_0 then 72 | writeln('Wait completed, thread exited.'); 73 | end; 74 | 75 | end. 76 | -------------------------------------------------------------------------------- /Bypassing/SuspendMe.ThreadPool.pas: -------------------------------------------------------------------------------- 1 | unit SuspendMe.ThreadPool; 2 | 3 | { 4 | This module demonstrates how an application can resume itself by taking 5 | advantage of the scenarios when the operating system creates additional 6 | threads in the application's thread pool. 7 | } 8 | 9 | interface 10 | 11 | uses 12 | NtUtils; 13 | 14 | // Create a thread pool and wait for someone to trigger thread creation in it. 15 | function UseThreadPool: TNtxStatus; 16 | 17 | implementation 18 | 19 | uses 20 | Ntapi.ntpsapi, NtUtils.SysUtils, NtUtils.Threads, NtUtils.Threads.Worker, 21 | NtUtils.Synchronization; 22 | 23 | var 24 | hxWorkerFactory, hxMainThread: IHandle; 25 | Count: Cardinal = 0; 26 | 27 | // The function to execute on the thread pool 28 | procedure ThreadPoolMain(Context: Pointer); stdcall; 29 | var 30 | Name: String; 31 | RemainingResumes: Cardinal; 32 | i: Integer; 33 | begin 34 | Inc(Count); 35 | Name := 'Thread pool''s thread #' + RtlxUIntToStr(Count); 36 | NtxSetNameThread(NtCurrentThread, Name); 37 | 38 | writeln; 39 | writeln('Hello from ', Name); 40 | writeln; 41 | 42 | // In case there are multiple suspensions on the main thread, lift them all 43 | if Assigned(hxMainThread) then 44 | if NtxResumeThread(hxMainThread.Handle, @RemainingResumes).IsSuccess then 45 | for i := 0 to Integer(RemainingResumes) - 2 do 46 | NtxResumeThread(hxMainThread.Handle); 47 | 48 | NtxWorkerFactoryWorkerReady(hxWorkerFactory.Handle); 49 | NtxDelayExecution(NT_INFINITE) 50 | end; 51 | 52 | function UseThreadPool; 53 | var 54 | hxIoCompletion: IHandle; 55 | begin 56 | Result := NtxOpenCurrentThread(hxMainThread); 57 | 58 | if not Result.IsSuccess then 59 | Exit; 60 | 61 | Result := NtxCreateIoCompletion(hxIoCompletion); 62 | 63 | if not Result.IsSuccess then 64 | Exit; 65 | 66 | Result := NtxCreateWorkerFactory(hxWorkerFactory, hxIoCompletion.Handle, 67 | ThreadPoolMain, nil); 68 | 69 | if not Result.IsSuccess then 70 | Exit; 71 | 72 | writeln('Current PID: ', NtCurrentProcessId); 73 | writeln('Thread pool''s handle: ', RtlxUIntPtrToStr(hxWorkerFactory.Handle, 16)); 74 | writeln; 75 | writeln('Now try to trigger thread creation. You will see a message on success.'); 76 | end; 77 | 78 | end. 79 | -------------------------------------------------------------------------------- /Bypassing/SuspendMe.RaceCondition.pas: -------------------------------------------------------------------------------- 1 | unit SuspendMe.RaceCondition; 2 | 3 | { 4 | The module includes the logic for bypassing suspension by winning the race 5 | condition. 6 | } 7 | 8 | interface 9 | 10 | uses 11 | NtUtils; 12 | 13 | // Create multiple threads that will try to win the race condition within the 14 | // suspension mechanism 15 | function RaceSuspension: TNtxStatus; 16 | 17 | implementation 18 | 19 | uses 20 | Ntapi.ntdef, Ntapi.ntpsapi, NtUtils.Threads, NtUtils.Sysutils, 21 | NtUtils.Console; 22 | 23 | var 24 | AllThreads: TArray; 25 | 26 | // A function to execute on the helper threads 27 | function ThreadMain(Context: Pointer): NTSTATUS; stdcall; 28 | var 29 | CurrentIndex: NativeInt absolute Context; 30 | i: Integer; 31 | begin 32 | // Resume all other threads in a loop 33 | 34 | while True do 35 | for i := 0 to High(AllThreads) do 36 | if i <> CurrentIndex then 37 | begin 38 | Result := NtResumeThread(AllThreads[i].Handle, nil); 39 | 40 | if not NT_SUCCESS(Result) then 41 | Exit; 42 | end; 43 | end; 44 | 45 | function RaceSuspension; 46 | var 47 | Threads: TArray; 48 | Flags: TThreadCreateFlags; 49 | i, Count: Integer; 50 | begin 51 | write('Specify the number of threads (2 or more): '); 52 | Count := ReadCardinal(2, 1 shl 24); 53 | 54 | Flags := THREAD_CREATE_FLAGS_CREATE_SUSPENDED; 55 | 56 | write('Do you want to hide them from debuggers? [y/n]: '); 57 | if ReadBoolean then 58 | Flags := Flags or THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH or 59 | THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER; 60 | 61 | SetLength(Threads, Count + 1); 62 | 63 | // The first item is always the main thread. It does not run the bypass 64 | // logic, but it's there so that other threads can resume it 65 | Result := NtxOpenCurrentThread(Threads[0]); 66 | 67 | if not Result.IsSuccess then 68 | Exit; 69 | 70 | for i := 1 to High(Threads) do 71 | begin 72 | // Create other threads for circumventing the race condition 73 | Result := NtxCreateThread(Threads[i], NtCurrentProcess, ThreadMain, 74 | Pointer(i), Flags); 75 | 76 | if Result.IsSuccess then 77 | NtxSetNameThread(Threads[i].Handle, 'Resumer Thread #' + RtlxUIntToStr(i)) 78 | else 79 | Exit; 80 | end; 81 | 82 | // Here the user can adjust their priorities, etc. 83 | writeln; 84 | write('Ready? Press enter to start.'); 85 | readln; 86 | 87 | AllThreads := Threads; 88 | Result := NtxResumeThread(Threads[High(Threads)].Handle); 89 | 90 | if not Result.IsSuccess then 91 | Exit; 92 | 93 | writeln('Try suspending any/all of them.'); 94 | end; 95 | 96 | end. 97 | -------------------------------------------------------------------------------- /Bypassing/SuspendMe.dpr: -------------------------------------------------------------------------------- 1 | program SuspendMe; 2 | 3 | { 4 | This is a sample application that demonstrates how a process can try to 5 | counteract suspension. 6 | } 7 | 8 | {$APPTYPE CONSOLE} 9 | {$MINENUMSIZE 4} 10 | {$R *.res} 11 | 12 | uses 13 | Ntapi.WinNt, 14 | Ntapi.ntstatus, 15 | Ntapi.ntpsapi, 16 | NtUtils, 17 | NtUtils.Threads, 18 | NtUtils.Synchronization, 19 | NtUtils.Console, 20 | NtUiLib.Errors, 21 | SuspendMe.RaceCondition in 'SuspendMe.RaceCondition.pas', 22 | SuspendMe.ThreadPool in 'SuspendMe.ThreadPool.pas', 23 | SuspendMe.PatchCreation in 'SuspendMe.PatchCreation.pas', 24 | SuspendMe.SelfDebug in 'SuspendMe.SelfDebug.pas', 25 | SuspendMe.DenyAccess in 'SuspendMe.DenyAccess.pas'; 26 | 27 | type 28 | TActionOptions = ( 29 | aoAdjustSecurity, 30 | aoRaceSuspension, 31 | aoUseThreadPool, 32 | aoHijackThreads, 33 | aoUseSelfDebug 34 | ); 35 | 36 | function Main: TNtxStatus; 37 | var 38 | Action: TActionOptions; 39 | Checkpoint: Cardinal; 40 | hxDebugObject: IHandle; 41 | begin 42 | NtxSetNameThread(NtCurrentThread, 'Main Thread'); 43 | 44 | writeln('This is a demo application for bypassing process suspension and freezing.'); 45 | writeln; 46 | writeln('Available options:'); 47 | writeln; 48 | writeln('[', Integer(aoAdjustSecurity), '] Protect the process with a denying security descriptor'); 49 | writeln('[', Integer(aoRaceSuspension), '] Circumvent suspension using a race condition'); 50 | writeln('[', Integer(aoUseThreadPool), '] Create a thread pool for someone to trigger'); 51 | writeln('[', Integer(aoHijackThreads), '] Hijack thread execution (resume & detach debuggers on code injection)'); 52 | writeln('[', Integer(aoUseSelfDebug), '] Start self-debugging so nobody else can attach'); 53 | 54 | writeln; 55 | write('Your choice: '); 56 | Cardinal(Action) := ReadCardinal(0, Cardinal(High(TActionOptions))); 57 | writeln; 58 | 59 | case Action of 60 | aoAdjustSecurity: 61 | ProtectProcessObject; 62 | 63 | aoRaceSuspension: 64 | Result := RaceSuspension; 65 | 66 | aoUseThreadPool: 67 | Result := UseThreadPool; 68 | 69 | aoHijackThreads: 70 | Result := HijackNewThreads; 71 | 72 | aoUseSelfDebug: 73 | Result := StartSelfDebugging(hxDebugObject); 74 | else 75 | Result.Status := STATUS_INVALID_PARAMETER; 76 | Result.Location := 'Main'; 77 | Exit; 78 | end; 79 | 80 | if not Result.IsSuccess then 81 | Exit; 82 | 83 | Checkpoint := 0; 84 | writeln; 85 | 86 | repeat 87 | writeln('[#', Checkpoint, '] The main thread is active!'); 88 | Inc(Checkpoint); 89 | until not NtxDelayExecution(2000 * MILLISEC).IsSuccess; 90 | end; 91 | 92 | procedure ReportFailures(const xStatus: TNtxStatus); 93 | begin 94 | if not xStatus.IsSuccess then 95 | writeln(xStatus.Location, ': ', RtlxNtStatusName(xStatus.Status)); 96 | end; 97 | 98 | begin 99 | ReportFailures(Main); 100 | 101 | if RtlxConsoleHostState <> chInterited then 102 | readln; 103 | end. 104 | 105 | -------------------------------------------------------------------------------- /Monitoring/ModeTransitionMonitor.Trace.pas: -------------------------------------------------------------------------------- 1 | unit ModeTransitionMonitor.Trace; 2 | 3 | { 4 | This module adds support for showing a summary of the return addresses 5 | encountered during syscall monitoring. 6 | } 7 | 8 | interface 9 | 10 | uses 11 | NtUtils, Instrumentation.Monitor; 12 | 13 | // Load symbols for looking up addresses 14 | procedure InitializeSymbols; 15 | 16 | // Display the summary return addresses 17 | procedure PrintFreshTraces( 18 | Mapping: PSyscallMonitor; 19 | const PreviousCount: UInt64 20 | ); 21 | 22 | implementation 23 | 24 | uses 25 | Ntapi.ntdef, Ntapi.ntldr, NtUtils.Ldr, NtUtils.SysUtils, DelphiUtils.Arrays, 26 | NtUtils.ImageHlp.DbgHelp; 27 | 28 | var 29 | NtdllModule, Win32Module: TModuleEntry; 30 | NtdllSymbols, Win32uSymbols: TArray; 31 | 32 | procedure InitializeSymbols; 33 | var 34 | hWin32u: PDllBase; 35 | begin 36 | // We expect system call to be executed and exported from two libraries. 37 | // We can use local module enumeration since they are Known Dlls, so they 38 | // share addresses between processes. 39 | 40 | if LdrxFindModule(NtdllModule, ByBaseName(ntdll)).IsSuccess then 41 | RtlxEnumSymbols(NtdllSymbols, NtdllModule.DllBase, NtdllModule.SizeOfImage, 42 | True); 43 | 44 | if LdrxLoadDll(win32u, hWin32u).IsSuccess and 45 | LdrxFindModule(Win32Module, ByBaseName(win32u)).IsSuccess then 46 | RtlxEnumSymbols(Win32uSymbols, Win32Module.DllBase, Win32Module.SizeOfImage, 47 | True); 48 | end; 49 | 50 | function LookupAddress(Address: Pointer): String; 51 | begin 52 | // Check ntdll 53 | if Assigned(NtdllModule.DllBase) and NtdllModule.IsInRange(Address) then 54 | Result := RtlxFindBestMatchModule(NtdllModule, NtdllSymbols, 55 | UIntPtr(Address) - UIntPtr(NtdllModule.DllBase)).ToString 56 | 57 | // Check win32u 58 | else if Assigned(Win32Module.DllBase) and Win32Module.IsInRange(Address) then 59 | Result := RtlxFindBestMatchModule(Win32Module, Win32uSymbols, 60 | UIntPtr(Address) - UIntPtr(Win32Module.DllBase)).ToString 61 | 62 | else 63 | Result := RtlxPtrToStr(Address); 64 | end; 65 | 66 | procedure PrintFreshTraces; 67 | const 68 | OVERFLOW_SUFFIX: array [Boolean] of String = ('', '+'); 69 | var 70 | TraceGroups: TArray>; 71 | Overflowed: Boolean; 72 | i: Integer; 73 | begin 74 | // Group them, merging the same return addresses 75 | TraceGroups := TArray.GroupBy( 76 | Mapping.CollectNewTraces(PreviousCount, Overflowed), 77 | function (const Entry: Pointer): Pointer 78 | begin 79 | Result := Entry 80 | end, 81 | function (const A, B: Pointer): Boolean 82 | begin 83 | Result := (A = B); 84 | end 85 | ); 86 | 87 | // Sort them by the number of occurances 88 | TArray.SortInline>(TraceGroups, 89 | function (const A, B: TArrayGroup): Integer 90 | begin 91 | Result := Length(B.Values) - Length(A.Values); 92 | end 93 | ); 94 | 95 | for i := 0 to High(TraceGroups) do 96 | begin 97 | write(' ', LookupAddress(TraceGroups[i].Key)); 98 | 99 | if Overflowed or (Length(TraceGroups[i].Values) > 1) then 100 | writeln(' x ', Length(TraceGroups[i].Values), 101 | OVERFLOW_SUFFIX[Overflowed <> False], ' times') 102 | else 103 | writeln; 104 | 105 | { if i > 6 then 106 | begin 107 | writeln(' ...'); 108 | Break; 109 | end;} 110 | end; 111 | end; 112 | 113 | end. 114 | -------------------------------------------------------------------------------- /Injecting/InjectTool.dpr: -------------------------------------------------------------------------------- 1 | program InjectTool; 2 | 3 | { 4 | This is a tool for testing thread creation in other processes. It can inject 5 | threads directy or trigger it via an existing thread pool. 6 | } 7 | 8 | {$APPTYPE CONSOLE} 9 | {$MINENUMSIZE 4} 10 | {$R *.res} 11 | 12 | uses 13 | Ntapi.ntstatus, 14 | Ntapi.ntseapi, 15 | Ntapi.ntpsapi, 16 | NtUtils, 17 | NtUtils.SysUtils, 18 | NtUtils.Processes, 19 | NtUtils.Processes.Snapshots, 20 | NtUtils.Tokens, 21 | NtUtils.Console, 22 | NtUiLib.Errors, 23 | InjectTool.ThreadPool in 'InjectTool.ThreadPool.pas', 24 | InjectTool.Direct in 'InjectTool.Direct.pas'; 25 | 26 | type 27 | TInjectionAction = ( 28 | iaInjectThread, 29 | iaTriggerThreadPool 30 | ); 31 | 32 | function Main: TNtxStatus; 33 | var 34 | hxProcess: IHandle; 35 | ProcessName: String; 36 | PID: Cardinal; 37 | Action: TInjectionAction; 38 | AccessMask: TProcessAccessMask; 39 | ThreadFlags: TThreadCreateFlags; 40 | begin 41 | writeln('This is a program for testing thread creation.'); 42 | writeln; 43 | writeln('Available options:'); 44 | writeln; 45 | writeln('[', Integer(iaInjectThread) ,'] Create a thread'); 46 | writeln('[', Integer(iaTriggerThreadPool) ,'] Trigger thread pool''s thread creation'); 47 | writeln; 48 | write('Your choice: '); 49 | Cardinal(Action) := ReadCardinal(0, Cardinal(High(TInjectionAction))); 50 | ThreadFlags := 0; 51 | writeln; 52 | 53 | case Action of 54 | iaInjectThread: 55 | begin 56 | AccessMask := PROCESS_CREATE_THREAD or PROCESS_QUERY_LIMITED_INFORMATION; 57 | 58 | write('Do you want to hide it from DLLs? [y/n]: '); 59 | 60 | if ReadBoolean then 61 | ThreadFlags := ThreadFlags or THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH; 62 | 63 | write('Do you want to hide it from debuggers? [y/n]: '); 64 | 65 | if ReadBoolean then 66 | ThreadFlags := ThreadFlags or THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER; 67 | 68 | write('Do you want it to bypass process freezing? [y/n]: '); 69 | 70 | if ReadBoolean then 71 | ThreadFlags := ThreadFlags or THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE; 72 | 73 | writeln; 74 | end; 75 | 76 | iaTriggerThreadPool: 77 | AccessMask := PROCESS_DUP_HANDLE or PROCESS_QUERY_INFORMATION; 78 | else 79 | Result.Location := 'Main'; 80 | Result.Status := STATUS_INVALID_PARAMETER; 81 | Exit; 82 | end; 83 | 84 | write('PID or a unique image name: '); 85 | ProcessName := ReadString(False); 86 | writeln; 87 | 88 | NtxAdjustPrivilege(NtxCurrentEffectiveToken, SE_DEBUG_PRIVILEGE, 89 | SE_PRIVILEGE_ENABLED, True); 90 | 91 | if RtlxStrToInt(ProcessName, PID) then 92 | Result := NtxOpenProcess(hxProcess, PID, AccessMask) 93 | else 94 | Result := NtxOpenProcessByName(hxProcess, ProcessName, AccessMask, 95 | [pnAllowShortNames]); 96 | 97 | if not Result.IsSuccess then 98 | Exit; 99 | 100 | case Action of 101 | iaInjectThread: 102 | Result := InjectDummyThread(hxProcess.Handle, ThreadFlags); 103 | 104 | iaTriggerThreadPool: 105 | Result := TriggerThreadPool(hxProcess.Handle); 106 | end; 107 | end; 108 | 109 | procedure ReportFailures(const xStatus: TNtxStatus); 110 | begin 111 | if not xStatus.IsSuccess then 112 | writeln(xStatus.Location, ': ', RtlxNtStatusName(xStatus.Status)) 113 | else 114 | writeln('Success.'); 115 | end; 116 | 117 | begin 118 | ReportFailures(Main); 119 | 120 | if RtlxConsoleHostState <> chInterited then 121 | readln; 122 | end. 123 | -------------------------------------------------------------------------------- /SuspendingTechniques.groupproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {C81FC904-D8EF-43F6-9A0C-BEBE641C0AAC} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Default.Personality.12 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /Injecting/InjectTool.ThreadPool.pas: -------------------------------------------------------------------------------- 1 | unit InjectTool.ThreadPool; 2 | 3 | { 4 | The implementation for triggering thread creation via existing thread pools. 5 | } 6 | 7 | interface 8 | 9 | uses 10 | NtUtils; 11 | 12 | // Adjust the minimum number of threads in a thread pool to force the OS 13 | // to create new threads in a suspended process. 14 | function TriggerThreadPool(hProcess: THandle): TNtxStatus; 15 | 16 | implementation 17 | 18 | uses 19 | Ntapi.ntstatus, Ntapi.nttp, NtUtils.Threads.Worker, NtUtils.Objects, 20 | NtUtils.Objects.Snapshots, NtUtils.SysUtils, DelphiUtils.Arrays, 21 | NtUtils.Console, NtUiLib.Errors; 22 | 23 | type 24 | TThreadPoolEntry = record 25 | HandleEntry: TProcessHandleEntry; 26 | hxLocalHandle: IHandle; 27 | Status: TNtxStatus; 28 | Info: TWorkerFactoryBasicInformation; 29 | end; 30 | 31 | function ChooseThreadPool( 32 | hProcess: THandle; 33 | out ThreadPool: TThreadPoolEntry 34 | ): TNtxStatus; 35 | var 36 | TypeIndex: Integer; 37 | Handles: TArray; 38 | ThreadPools: TArray; 39 | i: Integer; 40 | begin 41 | // We are only interested in worker factories 42 | Result := NtxFindType('TpWorkerFactory', TypeIndex); 43 | 44 | if not Result.IsSuccess then 45 | Exit; 46 | 47 | Result := NtxEnumerateHandlesProcess(hProcess, Handles); 48 | 49 | if not Result.IsSuccess then 50 | Exit; 51 | 52 | TArray.FilterInline(Handles, ByType(TypeIndex)); 53 | 54 | if Length(Handles) = 0 then 55 | begin 56 | Result.Location := 'ChooseThreadPool'; 57 | Result.Status := STATUS_NOT_FOUND; 58 | Exit; 59 | end; 60 | 61 | // Collect information about each thread pool 62 | ThreadPools := TArray.Map(Handles, 63 | function (const HandleEntry: TProcessHandleEntry): TThreadPoolEntry 64 | begin 65 | Result.HandleEntry := HandleEntry; 66 | 67 | Result.Status := NtxDuplicateHandleFrom(hProcess, HandleEntry.HandleValue, 68 | Result.hxLocalHandle); 69 | 70 | if not Result.Status.IsSuccess then 71 | Exit; 72 | 73 | Result.Status := NtxQueryWorkerFactory(Result.hxLocalHandle.Handle, 74 | Result.Info); 75 | end 76 | ); 77 | 78 | writeln('Which thread pool should we work with?'); 79 | for i := 0 to High(ThreadPools) do 80 | begin 81 | write('[', i, '] Handle ', RtlxUIntPtrToStr(ThreadPools[i].HandleEntry. 82 | HandleValue, 16), ' '); 83 | 84 | if ThreadPools[i].Status.IsSuccess then 85 | writeln('(', 86 | 'min: ', ThreadPools[i].Info.ThreadMinimum, ', ', 87 | 'max: ', ThreadPools[i].Info.ThreadMaximum, ', ', 88 | 'current: ', ThreadPools[i].Info.TotalWorkerCount, 89 | ')') 90 | else 91 | writeln(RtlxNtStatusName(ThreadPools[i].Status.Status), ' @ ', 92 | ThreadPools[i].Status.Location); 93 | end; 94 | 95 | writeln; 96 | write('Your choice: '); 97 | ThreadPool := ThreadPools[ReadCardinal(0, High(ThreadPools))]; 98 | Result := ThreadPool.Status; 99 | end; 100 | 101 | function TriggerThreadPool(hProcess: THandle): TNtxStatus; 102 | var 103 | ThreadPool: TThreadPoolEntry; 104 | NewMinumum: Cardinal; 105 | begin 106 | Result := ChooseThreadPool(hProcess, ThreadPool); 107 | 108 | if not Result.IsSuccess then 109 | Exit; 110 | 111 | // While the user was deciding which thread pool to use, the number of active 112 | // threads might've changed; refresh it. 113 | Result := NtxQueryWorkerFactory(ThreadPool.hxLocalHandle.Handle, 114 | ThreadPool.Info); 115 | 116 | if not Result.IsSuccess then 117 | Exit; 118 | 119 | NewMinumum := ThreadPool.Info.TotalWorkerCount + 1; 120 | 121 | if NewMinumum <= ThreadPool.Info.ThreadMinimum then 122 | NewMinumum := ThreadPool.Info.ThreadMinimum + 1; 123 | 124 | // Make sure we don't overflow the maximum 125 | if (ThreadPool.Info.ThreadMaximum <> 0) and 126 | (NewMinumum > ThreadPool.Info.ThreadMaximum) then 127 | begin 128 | Result := NtxWorkerFactory.Set(ThreadPool.hxLocalHandle.Handle, 129 | WorkerFactoryThreadMaximum, NewMinumum); 130 | 131 | if not Result.IsSuccess then 132 | Exit; 133 | end; 134 | 135 | // Adjust the minimum 136 | Result := NtxWorkerFactory.Set(ThreadPool.hxLocalHandle.Handle, 137 | WorkerFactoryThreadMinimum, NewMinumum); 138 | end; 139 | 140 | end. 141 | -------------------------------------------------------------------------------- /Suspending/SuspendTool.DebugObject.pas: -------------------------------------------------------------------------------- 1 | unit SuspendTool.DebugObject; 2 | 3 | { 4 | The module provides logic for suspending/freezing processes via debug objects. 5 | } 6 | 7 | interface 8 | 9 | uses 10 | NtUtils; 11 | 12 | // Suspend a process via a debug object 13 | function SuspendViaDebugMain( 14 | const hxProcess: IHandle 15 | ): TNtxStatus; 16 | 17 | // An improved version for freezing a process via a debug object 18 | function FreezeViaDebugMain( 19 | hxProcess: IHandle 20 | ): TNtxStatus; 21 | 22 | implementation 23 | 24 | uses 25 | Ntapi.ntdef, Ntapi.ntstatus, Ntapi.ntdbg, Ntapi.ntpsapi, Ntapi.ntmmapi, 26 | NtUtils.Debug, NtUtils.Processes.Info, NtUtils.Memory, 27 | NtUtils.Threads, NtUtils.Console, NtUiLib.Errors; 28 | 29 | function SuspendViaDebugMain; 30 | var 31 | hxDebugObject: IHandle; 32 | begin 33 | Result := NtxCreateDebugObject(hxDebugObject); 34 | 35 | if not Result.IsSuccess then 36 | Exit; 37 | 38 | Result := NtxDebugProcess(hxProcess.Handle, hxDebugObject.Handle); 39 | 40 | if not Result.IsSuccess then 41 | Exit; 42 | 43 | write('The process was suspended via a debug object. Press enter to undo...'); 44 | readln; 45 | 46 | Result := NtxDebugProcessStop(hxProcess.Handle, hxDebugObject.Handle); 47 | end; 48 | 49 | // Protecting the page with the MZ header blocks external thread creation 50 | function PreventThreadInjection( 51 | const hxProcess: IHandle; 52 | out Reverter: IAutoReleasable 53 | ): TNtxStatus; 54 | var 55 | Info: TProcessBasicInformation; 56 | ImageBase: Pointer; 57 | begin 58 | // Locate PEB 59 | Result := NtxProcess.Query(hxProcess.Handle, ProcessBasicInformation, Info); 60 | 61 | if not Result.IsSuccess then 62 | Exit; 63 | 64 | // Read the address of the image base 65 | Result := NtxMemory.Read(hxProcess.Handle, 66 | @Info.PebBaseAddress.ImageBaseAddress, ImageBase); 67 | 68 | if not Result.IsSuccess then 69 | Exit; 70 | 71 | // Protect the first page 72 | Result := NtxProtectMemoryAuto(hxProcess, ImageBase, 1, PAGE_READONLY or 73 | PAGE_GUARD, Reverter); 74 | end; 75 | 76 | function FreezeViaDebugMain; 77 | var 78 | ObjAttributes: IObjectAttributes; 79 | hxDebugObject, hxThread: IHandle; 80 | WaitState: TDbgxWaitState; 81 | WaitHandles: TDbgxHandles; 82 | DelayedAllowInjection: IAutoReleasable; 83 | PreventInjection: Boolean; 84 | begin 85 | write('Do you want to prevent detaching? [y/n]: '); 86 | if ReadBoolean then 87 | ObjAttributes := AttributeBuilder.UseAttributes(OBJ_EXCLUSIVE) 88 | else 89 | ObjAttributes := nil; 90 | 91 | write('Do you want to prevent thread injection? [y/n]: '); 92 | PreventInjection := ReadBoolean; 93 | 94 | Result := NtxCreateDebugObject(hxDebugObject, False, ObjAttributes ); 95 | 96 | if not Result.IsSuccess then 97 | Exit; 98 | 99 | Result := NtxDebugProcess(hxProcess.Handle, hxDebugObject.Handle); 100 | 101 | if not Result.IsSuccess then 102 | Exit; 103 | 104 | // Retrieve the first debug event without waiting 105 | Result := NtxDebugWait(hxDebugObject.Handle, WaitState, WaitHandles, 0); 106 | 107 | if Result.IsFailOrTimeout then 108 | Exit; 109 | 110 | // The first event should be process creation 111 | if (WaitState.NewState <> DbgCreateProcessStateChange) or 112 | not Assigned(WaitHandles.hxProcess) then 113 | begin 114 | Result.Location := '[Unexpected debug event]'; 115 | Result.Status := STATUS_UNSUCCESSFUL; 116 | Exit; 117 | end; 118 | 119 | // Now we got full access 120 | hxProcess := WaitHandles.hxProcess; 121 | 122 | // Injecting a thread causes the system to freeze other threads 123 | // No need to specify a valid start address since it will never run 124 | Result := NtxCreateThread(hxThread, hxProcess.Handle, nil, nil, 125 | THREAD_CREATE_FLAGS_CREATE_SUSPENDED); 126 | 127 | if not Result.IsSuccess then 128 | Exit; 129 | 130 | NtxSetNameThread(hxThread.Handle, 'Debugger-injected thread'); 131 | Result := NtxTerminateThread(hxThread.Handle, DBG_TERMINATE_THREAD); 132 | 133 | if not Result.IsSuccess then 134 | Exit; 135 | 136 | if PreventInjection then 137 | begin 138 | // Prevent tools like Process Explorer from injecting hide-from-debugger 139 | // threads (optional) 140 | Result := PreventThreadInjection(hxProcess, DelayedAllowInjection); 141 | 142 | if not Result.IsSuccess then 143 | writeln('Cannot prevent thread injection: ', Result.Location, ': ', 144 | RtlxNtStatusName(Result.Status)); 145 | end; 146 | 147 | write('The process was frozen via a debug object. Press enter to undo...'); 148 | readln; 149 | 150 | // Undo memory protection changes before stopping the debug session 151 | DelayedAllowInjection := nil; 152 | 153 | // Exiting the function will stop debugging through closing the handle within 154 | // the hxDebugObject variable. 155 | end; 156 | 157 | end. 158 | -------------------------------------------------------------------------------- /Bypassing/SuspendMe.PatchCreation.pas: -------------------------------------------------------------------------------- 1 | unit SuspendMe.PatchCreation; 2 | 3 | { 4 | This module demonstrates how a suspended program can resume itself by taking 5 | advantage of the threads that some tools (for example, Process Explorer) 6 | inject into processes. 7 | } 8 | 9 | interface 10 | 11 | uses 12 | NtUtils; 13 | 14 | // Patch local thread initialization to execute our payload. The payload resumes 15 | // the main thread and (optionally) detaches the process from its debugger. 16 | function HijackNewThreads: TNtxStatus; 17 | 18 | implementation 19 | 20 | uses 21 | Ntapi.WinNt, Ntapi.ntdef, Ntapi.ntstatus, Ntapi.ntrtl, Ntapi.ntmmapi, 22 | DelphiApi.Reflection, DelphiUtils.ExternalImport, NtUtils.Processes, 23 | NtUtils.Threads, NtUtils.Memory, NtUtils.Debug, NtUiLib.Errors; 24 | 25 | var 26 | hxMainThread: IHandle; 27 | 28 | // The function to execute on thread creation 29 | function Payload: TNtxStatus; 30 | var 31 | RemainingResumes: Cardinal; 32 | i: Integer; 33 | hxDbgObj: IHandle; 34 | begin 35 | if not Assigned(hxMainThread) then 36 | begin 37 | Result.Location := 'Payload'; 38 | Result.Status := STATUS_INVALID_HANDLE; 39 | Exit; 40 | end; 41 | 42 | Result := NtxResumeThread(hxMainThread.Handle, @RemainingResumes); 43 | 44 | if not Result.IsSuccess then 45 | Exit; 46 | 47 | // In case there are multiple suspensions on the main thread, lift them all 48 | for i := 0 to Integer(RemainingResumes) - 2 do 49 | begin 50 | Result := NtxResumeThread(hxMainThread.Handle); 51 | 52 | if not Result.IsSuccess then 53 | Break; 54 | end; 55 | 56 | // If we are frozen via a debug object, removing it will unfreeze us 57 | Result := NtxOpenDebugObjectProcess(hxDbgObj, NtCurrentProcess); 58 | 59 | if Result.IsSuccess then 60 | Result := NtxDebugProcessStop(NtCurrentProcess, hxDbgObj.Handle) 61 | else if Result.Status = STATUS_PORT_NOT_SET then 62 | Result.Status := STATUS_SUCCESS; // Nothing to do 63 | end; 64 | 65 | procedure RunPayload; 66 | var 67 | Result: TNtxStatus; 68 | begin 69 | // When using Delphi's I/O functions (writeln, etc.), the compiler emits the 70 | // calls to System.__IOTest that uses TLS and crashes our process with access 71 | // violation if executed from a thread that skipped attaching to DLLs. 72 | // So, suppress these calls. 73 | 74 | {$IOCHECKS OFF} 75 | writeln; 76 | writeln('Hijacking injected thread...'); 77 | 78 | Result := Payload; 79 | 80 | if not Result.IsSuccess then 81 | writeln(Result.Location, ': ', RtlxNtStatusName(Result.Status)) 82 | else 83 | writeln('Success'); 84 | 85 | writeln; 86 | {$IOCHECKS ON} 87 | end; 88 | 89 | // The function that RtlUserThreadStart usually forwards the call to 90 | function BaseThreadInitThunk( 91 | [Reserved] Reserved: Cardinal; 92 | [in] Func: TUserThreadStartRoutine; 93 | [in, opt] Parameter: Pointer 94 | ): NTSTATUS; stdcall; external kernel32; 95 | 96 | // A patched version of RtlUserThreadStart 97 | procedure PatchedThreadStartRoutine( 98 | [in] Func: TUserThreadStartRoutine; 99 | [in, opt] Parameter: Pointer 100 | ); stdcall; 101 | begin 102 | // Note that we call RunPayload instead of inlining it because Delphi 103 | // often emits code into function epilogues that cleans up strings and other 104 | // resources. Since BaseThreadInitThunk never returns, we need to make sure 105 | // our callback cleans up before calling it. 106 | RunPayload; 107 | BaseThreadInitThunk(0, Func, Parameter); 108 | end; 109 | 110 | exports 111 | // Help resolving the name without symbols 112 | PatchedThreadStartRoutine; 113 | 114 | type 115 | TFarJump = packed record 116 | InstructionStart: Word; 117 | Address: UInt64; 118 | JumpRax: Word; 119 | end; 120 | PFarJump = ^TFarJump; 121 | 122 | function HijackNewThreads; 123 | const 124 | JMP_LOOP = $FEEB; // jmp short 0 (aka infinite loop) 125 | MOV_RAX = $B848; // mov rax, ... 126 | JMP_RAX = $E0FF; // jmp rax 127 | var 128 | pCode: PPointer; 129 | Code: PFarJump; 130 | UndoProtection: IAutoReleasable; 131 | begin 132 | Result := NtxOpenCurrentThread(hxMainThread); 133 | 134 | if not Result.IsSuccess then 135 | Exit; 136 | 137 | // Find the code to patch 138 | pCode := ExternalImportTarget(@RtlUserThreadStart); 139 | 140 | if not Assigned(pCode) then 141 | begin 142 | Result.Location := 'HijackThreadCreation'; 143 | Result.Status := STATUS_INVALID_IMAGE_FORMAT; 144 | Exit; 145 | end; 146 | 147 | Code := pCode^; 148 | 149 | // Make it writable 150 | Result := NtxProtectMemoryAuto(NtxCurrentProcess, Code, SizeOf(TFarJump), 151 | PAGE_EXECUTE_READWRITE, UndoProtection); 152 | 153 | if not Result.IsSuccess then 154 | Exit; 155 | 156 | // Make sure nobody enters the code while we patch it 157 | Code.InstructionStart := JMP_LOOP; 158 | 159 | Code.Address := UIntPtr(@PatchedThreadStartRoutine); 160 | Code.JumpRax := JMP_RAX; 161 | Code.InstructionStart := MOV_RAX; 162 | 163 | UndoProtection := nil; 164 | NtxFlushInstructionCache(NtCurrentProcess, Code, SizeOf(TFarJump)); 165 | 166 | writeln('Now suspend the process and try injecting some code or inspecting ' + 167 | 'the list of threads in Process Explorer.'); 168 | end; 169 | 170 | end. 171 | -------------------------------------------------------------------------------- /Monitoring/ModeTransitionMonitor.dpr: -------------------------------------------------------------------------------- 1 | program ModeTransitionMonitor; 2 | 3 | { 4 | This is a sample application that counts program's transitions from kernel 5 | to user mode via an instrumentation callback. 6 | } 7 | 8 | {$APPTYPE CONSOLE} 9 | {$R *.res} 10 | 11 | uses 12 | Ntapi.WinNt, 13 | Ntapi.ntstatus, 14 | Ntapi.ntpsapi, 15 | Ntapi.ntseapi, 16 | Ntapi.Versions, 17 | DelphiUtils.AutoObjects, 18 | NtUtils, 19 | NtUtils.SysUtils, 20 | NtUtils.Processes, 21 | NtUtils.Processes.Snapshots, 22 | NtUtils.Processes.Info, 23 | NtUtils.Processes.Info.Remote, 24 | NtUtils.Synchronization, 25 | NtUtils.Tokens, 26 | NtUtils.Console, 27 | NtUiLib.Errors, 28 | Instrumentation.Monitor in 'Instrumentation.Monitor.pas', 29 | ModeTransitionMonitor.Trace in 'ModeTransitionMonitor.Trace.pas'; 30 | 31 | { 32 | The idea is the following: 33 | 34 | 1. Map a shared memory region with the target 35 | 2. Write a small shellcode to it that counts the number of its invocations 36 | and records returns addresses into a circular buffer. 37 | 3. Install it as the instrumentation callback (either by setting it 38 | directly using the Debug privilege, or via injecting a thread that does 39 | that on the target's behalf). The system invokes the callback every time 40 | a thread within the process transitions to user-mode. 41 | 4. Pull the counter and return addresses via a local memory mapping. 42 | } 43 | 44 | function Main: TNtxStatus; 45 | const 46 | AccessMask = PROCESS_SET_INSTRUMENTATION or SYNCHRONIZE; 47 | TraceMagnitude: array [Boolean] of TTraceMagnitude = (TRACE_MAGNITUDE_LOW, 48 | TRACE_MAGNITUDE_MEDIUM); 49 | var 50 | ProcessName: String; 51 | PID: Cardinal; 52 | hxProcess: IHandle; 53 | CaptureTraces, TargetIsWoW64, IsIdle: Boolean; 54 | CurrentCount, PreviousCount: UInt64; 55 | LocalMapping: IMemory; 56 | begin 57 | writeln('This program allows monitoring kernel-to-user mode transitions in a ' 58 | + 'context of a specific process via the instrumentation callback.'); 59 | writeln; 60 | 61 | // Try enabling the debug privilege. Note that it is not strictly necessary 62 | // starting from Windows 8.1 since we can set the instrumentation callback 63 | // on the target's behalf by injecting a thread. 64 | Result := NtxAdjustPrivilege(NtxCurrentEffectiveToken, SE_DEBUG_PRIVILEGE, 65 | SE_PRIVILEGE_ENABLED, RtlOsVersion >= OsWin81); 66 | 67 | if not Result.IsSuccess then 68 | Exit; 69 | 70 | if Result.Status = STATUS_NOT_ALL_ASSIGNED then 71 | writeln('WARNING: Debug Privilege is not available; will use thread ' + 72 | 'injection instead.' + #$D#$A); 73 | 74 | write('Do you want to capture return addresses? [y/n]: '); 75 | CaptureTraces := ReadBoolean; 76 | 77 | if CaptureTraces then 78 | begin 79 | writeln('Loading symbols...'); 80 | InitializeSymbols; 81 | end; 82 | 83 | writeln; 84 | write('Target''s PID or a unique image name: '); 85 | ProcessName := ReadString(False); 86 | 87 | // Open the target 88 | if RtlxStrToInt(ProcessName, PID) then 89 | Result := NtxOpenProcess(hxProcess, PID, AccessMask) 90 | else 91 | Result := NtxOpenProcessByName(hxProcess, ProcessName, AccessMask, 92 | [pnAllowShortNames]); 93 | 94 | if not Result.IsSuccess then 95 | Exit; 96 | 97 | // Instrumentation callbacks do not seem to work under WoW64 98 | Result := NtxQueryIsWoW64Process(hxProcess.Handle, TargetIsWoW64); 99 | 100 | if not Result.IsSuccess then 101 | Exit; 102 | 103 | if TargetIsWoW64 then 104 | begin 105 | Result.Location := 'Target runs under WoW64'; 106 | Result.Status := STATUS_NOT_SUPPORTED; 107 | Exit; 108 | end; 109 | 110 | writeln('Setting up monitoring...'); 111 | Result := StartMonitoring(hxProcess, TraceMagnitude[CaptureTraces], 112 | LocalMapping); 113 | 114 | if not Result.IsSuccess then 115 | Exit; 116 | 117 | writeln; 118 | IsIdle := False; 119 | PreviousCount := 0; 120 | Result.Status := STATUS_TIMEOUT; 121 | 122 | repeat 123 | CurrentCount := LocalMapping.Data.SyscallCount; 124 | 125 | if (CurrentCount <> PreviousCount) or not IsIdle then 126 | begin 127 | writeln('Transitions / second: ', CurrentCount - PreviousCount); 128 | 129 | if CaptureTraces then 130 | begin 131 | if CurrentCount <> PreviousCount then 132 | PrintFreshTraces(LocalMapping.Data, PreviousCount); 133 | 134 | writeln; 135 | end; 136 | end; 137 | 138 | IsIdle := (CurrentCount = PreviousCount); 139 | PreviousCount := CurrentCount; 140 | 141 | if Result.Status <> STATUS_TIMEOUT then 142 | Break; 143 | 144 | Result := NtxWaitForSingleObject(hxProcess.Handle, 1000 * MILLISEC); 145 | until False; 146 | 147 | if Result.Status <> STATUS_WAIT_0 then 148 | Exit; 149 | 150 | writeln('Target process exited. Transitions detected: ', PreviousCount); 151 | end; 152 | 153 | procedure ReportFailures(const xStatus: TNtxStatus); 154 | begin 155 | if not xStatus.IsSuccess then 156 | writeln(xStatus.Location, ': ', RtlxNtStatusName(xStatus.Status)) 157 | end; 158 | 159 | begin 160 | ReportFailures(Main); 161 | 162 | if RtlxConsoleHostState <> chInterited then 163 | readln; 164 | end. 165 | 166 | -------------------------------------------------------------------------------- /Suspending/SuspendTool.dpr: -------------------------------------------------------------------------------- 1 | program SuspendTool; 2 | 3 | { 4 | A small tool for testing various techniques for suspending/freezing processes. 5 | } 6 | 7 | {$APPTYPE CONSOLE} 8 | {$MINENUMSIZE 4} 9 | {$R *.res} 10 | 11 | uses 12 | Ntapi.ntstatus, 13 | Ntapi.ntseapi, 14 | Ntapi.ntpsapi, 15 | NtUtils, 16 | NtUtils.Threads, 17 | NtUtils.Tokens, 18 | NtUtils.Processes, 19 | NtUtils.Processes.Snapshots, 20 | NtUtils.SysUtils, 21 | NtUtils.Jobs, 22 | NtUtils.Console, 23 | NtUiLib.Errors, 24 | SuspendTool.DebugObject in 'SuspendTool.DebugObject.pas'; 25 | 26 | type 27 | TSuspendAction = ( 28 | saSuspendThreads, 29 | saResumeThreads, 30 | saSuspendProcess, 31 | saResumeProcess, 32 | saSuspendViaDebug, 33 | saFreezeViaDebug, 34 | saFreezeViaJob, 35 | saFreezeViaState 36 | ); 37 | 38 | function Main: TNtxStatus; 39 | var 40 | hxProcess, hxThread, hxJob, hxProcessState: IHandle; 41 | AccessMask: TProcessAccessMask; 42 | ProcessName: String; 43 | Action: TSuspendAction; 44 | PID: Cardinal; 45 | JobInfo: TJobObjectFreezeInformation; 46 | begin 47 | writeln('This program implements various techniques for suspending processes.'); 48 | writeln; 49 | writeln('Available options:'); 50 | writeln; 51 | writeln('[', Integer(saSuspendThreads), '] Enumerate & suspend all threads'); 52 | writeln('[', Integer(saResumeThreads), '] Enumerate & resume all threads'); 53 | writeln('[', Integer(saSuspendProcess), '] Suspend via NtSuspendProcess'); 54 | writeln('[', Integer(saResumeProcess), '] Resume via NtResumeProcess'); 55 | writeln('[', Integer(saSuspendViaDebug), '] Suspend via a debug object'); 56 | writeln('[', Integer(saFreezeViaDebug), '] Freeze via a debug object'); 57 | writeln('[', Integer(saFreezeViaJob), '] Freeze via a job object'); 58 | writeln('[', Integer(saFreezeViaState), '] Freeze via a state change object'); 59 | writeln; 60 | write('Your choice: '); 61 | Cardinal(Action) := ReadCardinal(0, Cardinal(High(TSuspendAction))); 62 | writeln; 63 | 64 | case Action of 65 | saSuspendThreads, saResumeThreads: 66 | AccessMask := PROCESS_QUERY_INFORMATION; 67 | 68 | saSuspendProcess, saResumeProcess, saSuspendViaDebug, saFreezeViaDebug: 69 | AccessMask := PROCESS_SUSPEND_RESUME; 70 | 71 | saFreezeViaJob: 72 | AccessMask := PROCESS_ASSIGN_TO_JOB; 73 | 74 | saFreezeViaState: 75 | AccessMask := PROCESS_CHANGE_STATE; 76 | else 77 | Result.Location := 'Main'; 78 | Result.Status := STATUS_INVALID_PARAMETER; 79 | Exit; 80 | end; 81 | 82 | write('PID or a unique image name: '); 83 | ProcessName := ReadString(False); 84 | writeln; 85 | 86 | // Enable the Debug Privilege if available 87 | NtxAdjustPrivilege(NtxCurrentEffectiveToken, SE_DEBUG_PRIVILEGE, 88 | SE_PRIVILEGE_ENABLED, True); 89 | 90 | if RtlxStrToInt(ProcessName, PID) then 91 | Result := NtxOpenProcess(hxProcess, PID, AccessMask) 92 | else 93 | Result := NtxOpenProcessByName(hxProcess, ProcessName, AccessMask, 94 | [pnAllowShortNames]); 95 | 96 | if not Result.IsSuccess then 97 | Exit; 98 | 99 | hxThread := nil; 100 | 101 | case Action of 102 | saSuspendThreads: 103 | while NtxGetNextThread(hxProcess.Handle, hxThread, 104 | THREAD_SUSPEND_RESUME).Save(Result) do 105 | if not NtxSuspendThread(hxThread.Handle).Save(Result) then 106 | Exit; 107 | 108 | saResumeThreads: 109 | while NtxGetNextThread(hxProcess.Handle, hxThread, 110 | THREAD_SUSPEND_RESUME).Save(Result) do 111 | if not NtxResumeThread(hxThread.Handle).Save(Result) then 112 | Exit; 113 | 114 | saSuspendProcess: 115 | Result := NtxSuspendProcess(hxProcess.Handle); 116 | 117 | saResumeProcess: 118 | Result := NtxResumeProcess(hxProcess.Handle); 119 | 120 | saSuspendViaDebug: 121 | Result := SuspendViaDebugMain(hxProcess); 122 | 123 | saFreezeViaDebug: 124 | Result := FreezeViaDebugMain(hxProcess); 125 | 126 | saFreezeViaJob: 127 | begin 128 | Result := NtxCreateJob(hxJob); 129 | 130 | if not Result.IsSuccess then 131 | Exit; 132 | 133 | JobInfo := Default(TJobObjectFreezeInformation); 134 | JobInfo.Flags := JOB_OBJECT_OPERATION_FREEZE; 135 | JobInfo.Freeze := True; 136 | 137 | Result := NtxJob.Set(hxJob.Handle, JobObjectFreezeInformation, JobInfo); 138 | 139 | if not Result.IsSuccess then 140 | Exit; 141 | 142 | Result := NtxAssignProcessToJob(hxProcess.Handle, hxJob.Handle); 143 | 144 | if not Result.IsSuccess then 145 | Exit; 146 | 147 | write('The process was frozen via a job object. Press enter to undo...'); 148 | readln; 149 | 150 | JobInfo.Freeze := False; 151 | 152 | Result := NtxJob.Set(hxJob.Handle, JobObjectFreezeInformation, JobInfo); 153 | end; 154 | 155 | saFreezeViaState: 156 | begin 157 | Result := NtxCreateProcessState(hxProcessState, hxProcess.Handle); 158 | 159 | if not Result.IsSuccess then 160 | Exit; 161 | 162 | Result := NtxChageStateProcess(hxProcessState.Handle, hxProcess.Handle, 163 | ProcessStateChangeSuspend); 164 | 165 | if not Result.IsSuccess then 166 | Exit; 167 | 168 | write('The process was frozen via a state change. Press enter to undo...'); 169 | readln; 170 | 171 | Result := NtxChageStateProcess(hxProcessState.Handle, hxProcess.Handle, 172 | ProcessStateChangeResume); 173 | end; 174 | end; 175 | end; 176 | 177 | procedure ReportFailures(const xStatus: TNtxStatus); 178 | begin 179 | if not xStatus.IsSuccess then 180 | writeln(xStatus.Location, ': ', RtlxNtStatusName(xStatus.Status)) 181 | else 182 | writeln('Success.'); 183 | end; 184 | 185 | begin 186 | ReportFailures(Main); 187 | 188 | if RtlxConsoleHostState <> chInterited then 189 | readln; 190 | end. 191 | 192 | -------------------------------------------------------------------------------- /Monitoring/Instrumentation.Monitor.pas: -------------------------------------------------------------------------------- 1 | unit Instrumentation.Monitor; 2 | 3 | { 4 | This module allows monitoring and recording mode transitions happening in a 5 | context of other processes via the instrumentation callback. 6 | } 7 | 8 | interface 9 | 10 | uses 11 | Ntapi.WinNt, NtUtils, DelphiUtils.AutoObjects; 12 | 13 | const 14 | TRACE_MAGNITUDE_LOW = 8; // record 256 addresses or 2 KiB of data 15 | TRACE_MAGNITUDE_MEDIUM = 16; // record 65k addresses or 512 KiB of data 16 | TRACE_MAGNITUDE_HIGH = 20; // record 4kk addresses or 32 MiB of data 17 | 18 | type 19 | // Defines number of entries in the buffer as 2**Magnitude 20 | TTraceMagnitude = 0..27; 21 | 22 | TSyscallMonitor = record 23 | Code: array [0..7] of UInt64; 24 | TaceSlotMask: Cardinal; // 2**Magnitude - 1 25 | SyscallCount: UInt64; 26 | TraceSlots: TAnysizeArray; 27 | function CollectNewTraces( 28 | const PreviousCount: UInt64; 29 | out Overflowed: Boolean 30 | ): TArray; 31 | end; 32 | PSyscallMonitor = ^TSyscallMonitor; 33 | 34 | // Install the instrumentation shellcode into a process and start monitoring 35 | function StartMonitoring( 36 | hxProcess: IHandle; 37 | TraceMagnitude: TTraceMagnitude; 38 | out LocalMapping: IMemory; 39 | const Timeout: Int64 = NT_INFINITE 40 | ): TNtxStatus; 41 | 42 | // A helper function for bebugging the shellcode locally 43 | function DebugShellcode: TNtxStatus; 44 | 45 | implementation 46 | 47 | uses 48 | Ntapi.crt, Ntapi.ntstatus, Ntapi.ntmmapi, NtUtils.Shellcode, 49 | NtUtils.Processes, NtUtils.Memory, NtUtils.Processes.Info.Remote; 50 | 51 | // The shellcode for injecting into the target process 52 | // Note: be consistent with the definition of the structure 53 | procedure SyscallMonitor; 54 | asm 55 | push rcx 56 | push r8 57 | lea r8, @@TraceSlots 58 | mov ecx, 1 59 | lock xadd qword ptr [@@SyscallCount], rcx // Increment the counter 60 | and ecx, dword ptr [@@TaceSlotMask] // Infer the slot index 61 | mov qword ptr [r8 + rcx * 8], r10 // Save the return address 62 | pop r8 63 | pop rcx 64 | jmp r10 65 | 66 | // Add some padding to align the buffer 67 | dq $CCCCCCCCCCCCCCCC 68 | dq $CCCCCCCCCCCCCCCC 69 | dq $CCCCCCCCCCCCCCCC 70 | 71 | @@TaceSlotMask: 72 | dd 0 // 2**Magnitude - 1 (aka NumberOfSlots - 1) 73 | dd 0 74 | @@SyscallCount: 75 | dq 0 76 | @@TraceSlots: 77 | dq 0 78 | end; 79 | 80 | procedure DebugShellcodeLoop; 81 | asm 82 | lea r10, SyscallMonitor 83 | jmp r10 84 | end; 85 | 86 | function DebugShellcode; 87 | var 88 | Reverter: IAutoReleasable; 89 | begin 90 | // Make sure the memory is writable 91 | Result := NtxProtectMemoryAuto(NtxCurrentProcess, @SyscallMonitor, 92 | Sizeof(TSyscallMonitor), PAGE_EXECUTE_READWRITE, Reverter); 93 | 94 | if not Result.IsSuccess then 95 | Exit; 96 | 97 | Reverter.AutoRelease := False; 98 | Reverter := nil; 99 | 100 | // Jump to the shellcode 101 | DebugShellcodeLoop; 102 | end; 103 | 104 | function StartMonitoring; 105 | var 106 | RemoteMapping: IMemory; 107 | SlotMask: Cardinal; 108 | begin 109 | if TraceMagnitude > High(TTraceMagnitude) then 110 | begin 111 | Result.Location := 'StartMonitoring'; 112 | Result.Status := STATUS_BUFFER_OVERFLOW; 113 | Exit; 114 | end; 115 | 116 | // The number of slots for recording addresses minus one 117 | SlotMask := (1 shl TraceMagnitude) - 1; 118 | 119 | // Map a shared memory region for the shellcode and its trace 120 | Result := RtlxMapSharedMemory( 121 | hxProcess, 122 | SizeOf(TSyscallMonitor) + SlotMask * SizeOf(Pointer), 123 | IMemory(LocalMapping), 124 | RemoteMapping, 125 | [mmAllowWrite, mmAllowExecute] 126 | ); 127 | 128 | if not Result.IsSuccess then 129 | Exit; 130 | 131 | // Write the shellcode 132 | memmove(LocalMapping.Data, @SyscallMonitor, SizeOf(TSyscallMonitor)); 133 | LocalMapping.Data.TaceSlotMask := SlotMask; 134 | 135 | // Set it as the instrumentation callback 136 | Result := NtxSetInstrumentationProcess(hxProcess, RemoteMapping.Data, 137 | Timeout); 138 | 139 | // Make sure we don't unmap the shellcode when we exit. Even if we reset the 140 | // callback later, we don't know when its safe to unmap the code because there 141 | // can be threads still executing or being suspended in the middle of it. 142 | if Result.IsSuccess then 143 | RemoteMapping.AutoRelease := False; 144 | end; 145 | 146 | function TSyscallMonitor.CollectNewTraces; 147 | var 148 | CurrentCount: UInt64; 149 | FreshEntries: UInt64; 150 | PreviousIndex, UntilWrap: Cardinal; 151 | begin 152 | // Make a defensive copy since the value can change rapidly 153 | CurrentCount := SyscallCount; 154 | 155 | FreshEntries := CurrentCount - PreviousCount; 156 | PreviousIndex := PreviousCount and TaceSlotMask; 157 | Overflowed := False; 158 | 159 | if FreshEntries > TaceSlotMask + 1 then 160 | begin 161 | // The trace grew bigger than can fit into the buffer, so it wrapped around 162 | // Some entries are lost. 163 | Overflowed := True; 164 | SetLength(Result, TaceSlotMask + 1); 165 | Move(TraceSlots, Result[0], Length(Result) * SizeOf(Pointer)); 166 | end 167 | else if FreshEntries + PreviousIndex <= TaceSlotMask + 1 then 168 | begin 169 | // The buffer did not wrap since the last time 170 | SetLength(Result, FreshEntries); 171 | 172 | {$R-} 173 | Move(TraceSlots[PreviousIndex], Result[0], SizeOf(Pointer) * FreshEntries); 174 | {$R+} 175 | end 176 | else 177 | begin 178 | // The buffer wrapped since the last time, but not entirely 179 | SetLength(Result, FreshEntries); 180 | UntilWrap := TaceSlotMask + 1 - PreviousIndex; 181 | 182 | // Copy the portion from the last position to the right boundary 183 | {$R-} 184 | Move(TraceSlots[PreviousIndex], Result[0], SizeOf(Pointer) * UntilWrap); 185 | 186 | // Copy from the left boundary to the new poition 187 | Move(TraceSlots, Result[UntilWrap], SizeOf(Pointer) * 188 | (Length(Result) - UntilWrap)); 189 | {$R+} 190 | end; 191 | end; 192 | 193 | end. 194 | -------------------------------------------------------------------------------- /Monitoring/SuspendInfo.dpr: -------------------------------------------------------------------------------- 1 | program SuspendInfo; 2 | 3 | { 4 | This program determines suspension and freezing of processes and threads. 5 | } 6 | 7 | {$APPTYPE CONSOLE} 8 | {$R *.res} 9 | 10 | uses 11 | Ntapi.WinNt, 12 | Ntapi.ntstatus, 13 | Ntapi.ntseapi, 14 | Ntapi.ntpsapi, 15 | Ntapi.ntexapi, 16 | Ntapi.Versions, 17 | DelphiUtils.Arrays, 18 | NtUtils, 19 | NtUtils.Processes, 20 | NtUtils.Processes.Info, 21 | NtUtils.Processes.Snapshots, 22 | NtUtils.Threads, 23 | NtUtils.Tokens, 24 | NtUtils.SysUtils, 25 | NtUtils.Console, 26 | NtUiLib.Errors; 27 | 28 | function ChooseProcess( 29 | out Process: TProcessEntry 30 | ): TNtxStatus; 31 | var 32 | Processes: TArray; 33 | ProcessName: String; 34 | PID: Cardinal; 35 | begin 36 | write('Target''s PID or a unique image name: '); 37 | ProcessName := ReadString(False); 38 | writeln; 39 | 40 | Result := NtxEnumerateProcesses(Processes); 41 | 42 | if not Result.IsSuccess then 43 | Exit; 44 | 45 | if RtlxStrToInt(ProcessName, PID) then 46 | TArray.FilterInline(Processes, ByPid(PID)) 47 | else 48 | TArray.FilterInline(Processes, ByImage(ProcessName, 49 | [pfAllowShortNames])); 50 | 51 | Result.Location := 'ChooseProcess'; 52 | case Length(Processes) of 53 | 0: Result.Status := STATUS_NOT_FOUND; 54 | 1: Process := Processes[0]; 55 | else 56 | Result.Status := STATUS_OBJECT_NAME_COLLISION; 57 | end; 58 | end; 59 | 60 | procedure PrintProcessFreeze(PID: TProcessId); 61 | var 62 | hxProcess: IHandle; 63 | Info: TProcessBasicInformationEx; 64 | begin 65 | Info := Default(TProcessBasicInformationEx); 66 | Info.Size := SizeOf(TProcessBasicInformationEx); 67 | 68 | write('Frozen: '); 69 | if NtxOpenProcess(hxProcess, PID, PROCESS_QUERY_LIMITED_INFORMATION).IsSuccess 70 | and NtxProcess.Query(hxProcess.Handle, ProcessBasicInformation, Info).IsSuccess then 71 | if LongBool(Info.Flags and PROCESS_BASIC_FLAG_FROZEN) then 72 | begin 73 | writeln('Yes'); 74 | writeln('Deep-Frozen: Unknown'); 75 | end 76 | else 77 | writeln('No') 78 | else 79 | writeln('Unknown'); 80 | end; 81 | 82 | function QueryUnbaisedSuspendCount( 83 | TID: TThreadId; 84 | out UnbaisedSuspendCount: Cardinal 85 | ): TNtxStatus; 86 | var 87 | hxThread: IHandle; 88 | begin 89 | Result := NtxOpenThread(hxThread, TID, THREAD_SUSPEND_RESUME); 90 | 91 | if not Result.IsSuccess then 92 | Exit; 93 | 94 | // Retrieve the last suspend count via suspension 95 | Result := NtxSuspendThread(hxThread.Handle, @UnbaisedSuspendCount); 96 | 97 | if Result.IsSuccess then 98 | NtxResumeThread(hxThread.Handle); 99 | 100 | if Result.Status = STATUS_THREAD_IS_TERMINATING then 101 | begin 102 | Result.Status := STATUS_SUCCESS; 103 | UnbaisedSuspendCount := 0; 104 | end 105 | else if Result.Status = STATUS_SUSPEND_COUNT_EXCEEDED then 106 | begin 107 | Result.Status := STATUS_SUCCESS; 108 | UnbaisedSuspendCount := 127; 109 | end; 110 | end; 111 | 112 | procedure PrintThreadInfo(const Thread: TThreadEntry); 113 | const 114 | // We could've use reflection instead, but there is no point of bringing it 115 | // for converting a single enumeration 116 | WaitReasonStrings: array [TWaitReason] of String = ('Executive', 117 | 'FreePage', 'PageIn', 'PoolAllocation', 'DelayExecution', 'Suspended', 118 | 'UserRequest', 'WrExecutive', 'WrFreePage', 'WrPageIn', 'WrPoolAllocation', 119 | 'WrDelayExecution', 'WrSuspended', 'WrUserRequest', 'WrEventPair', 120 | 'WrQueue', 'WrLpcReceive', 'WrLpcReply', 'WrVirtualMemory', 'WrPageOut', 121 | 'WrRendezvous', 'WrKeyedEvent', 'WrTerminated', 'WrProcessInSwap', 122 | 'WrCpuRateControl', 'WrCalloutStack', 'WrKernel', 'WrResource', 123 | 'WrPushLock', 'WrMutex', 'WrQuantumEnd', 'WrDispatchInt', 'WrPreempted', 124 | 'WrYieldExecution', 'WrFastMutex', 'WrGuardedMutex', 'WrRundown', 125 | 'WrAlertByThreadId', 'WrDeferredPreempt'); 126 | var 127 | hxThread: IHandle; 128 | BaisedSuspendCount, UnbaisedSuspendCount: Cardinal; 129 | BaisedCountKnown, UnbaisedCountKnown: Boolean; 130 | ThreadName: String; 131 | begin 132 | ThreadName := ''; 133 | BaisedCountKnown := False; 134 | 135 | // Naming threads and querying suspend count directly requires Win 8.1+ 136 | if RtlOsVersionAtLeast(OsWin81) and 137 | NtxOpenThread(hxThread, Thread.Basic.ClientID.UniqueThread, 138 | THREAD_QUERY_LIMITED_INFORMATION).IsSuccess then 139 | begin 140 | NtxQueryNameThread(hxThread.Handle, ThreadName); 141 | 142 | BaisedCountKnown := NtxThread.Query(hxThread.Handle, ThreadSuspendCount, 143 | BaisedSuspendCount).IsSuccess; 144 | end; 145 | 146 | // Another way to retrieve the count is via suspension 147 | UnbaisedCountKnown := QueryUnbaisedSuspendCount( 148 | Thread.Basic.ClientID.UniqueThread, UnbaisedSuspendCount).IsSuccess; 149 | 150 | if ThreadName = '' then 151 | ThreadName := 'Unnamed'; 152 | 153 | writeln('Thread: ', Thread.Basic.ClientID.UniqueThread, ' [', ThreadName, ']'); 154 | 155 | if Thread.Basic.WaitReason <= High(TWaitReason) then 156 | writeln('Wait Reason: ', WaitReasonStrings[Thread.Basic.WaitReason]) 157 | else 158 | writeln('Wait Reason: ', Cardinal(Thread.Basic.WaitReason), ' (Unknown)'); 159 | 160 | write('Suspend Count: '); 161 | if BaisedCountKnown and UnbaisedCountKnown then 162 | if BaisedSuspendCount = UnbaisedSuspendCount then 163 | writeln(UnbaisedSuspendCount) 164 | else 165 | writeln(UnbaisedSuspendCount, ' (+', BaisedSuspendCount - 166 | UnbaisedSuspendCount, ' from freezing)') 167 | else if UnbaisedCountKnown then 168 | writeln(UnbaisedSuspendCount, ' (not including freezing)') 169 | else if BaisedCountKnown then 170 | writeln(BaisedSuspendCount, ' (including freezing)') 171 | else 172 | writeln('Unknown'); 173 | 174 | write('Frozen: '); 175 | if BaisedCountKnown and UnbaisedCountKnown then 176 | if BaisedSuspendCount = UnbaisedSuspendCount then 177 | writeln('No') 178 | else 179 | writeln('Yes') 180 | else 181 | writeln('Unknown'); 182 | 183 | writeln; 184 | end; 185 | 186 | function Main: TNtxStatus; 187 | var 188 | Process: TProcessEntry; 189 | i: Integer; 190 | begin 191 | writeln('This is a program for querying process and thread suspension state.'); 192 | writeln; 193 | 194 | Result := ChooseProcess(Process); 195 | 196 | if not Result.IsSuccess then 197 | Exit; 198 | 199 | NtxAdjustPrivilege(NtxCurrentEffectiveToken, SE_DEBUG_PRIVILEGE, 200 | SE_PRIVILEGE_ENABLED, True); 201 | 202 | writeln('-------- Process --------'); 203 | writeln('Process: ', Process.Basic.ProcessID, ' [', Process.ImageName, ']'); 204 | writeln('Threads: ', Length(Process.Threads)); 205 | PrintProcessFreeze(Process.Basic.ProcessID); 206 | writeln; 207 | writeln('-------- Threads --------'); 208 | 209 | for i := 0 to High(Process.Threads) do 210 | PrintThreadInfo(Process.Threads[i]); 211 | end; 212 | 213 | procedure ReportFailures(const xStatus: TNtxStatus); 214 | begin 215 | if not xStatus.IsSuccess then 216 | writeln(xStatus.Location, ': ', RtlxNtStatusName(xStatus.Status)) 217 | end; 218 | 219 | begin 220 | ReportFailures(Main); 221 | 222 | if RtlxConsoleHostState <> chInterited then 223 | readln; 224 | end. 225 | 226 | -------------------------------------------------------------------------------- /Bypassing/SuspendMe.SelfDebug.pas: -------------------------------------------------------------------------------- 1 | unit SuspendMe.SelfDebug; 2 | 3 | { 4 | This module demonstrates how a process can registered as a debugger for 5 | itself, so that no other program can attach to it. 6 | } 7 | 8 | interface 9 | 10 | uses 11 | Ntapi.WinNt, NtUtils; 12 | 13 | const 14 | DEFAULT_SELF_DEBUG_TIMEOUT = 8000 * MILLISEC; 15 | 16 | // Attach a debug port to the current process and suppress further debug events 17 | function StartSelfDebugging( 18 | out hxDebugObject: IHandle; 19 | const Timeout: Int64 = DEFAULT_SELF_DEBUG_TIMEOUT 20 | ): TNtxStatus; 21 | 22 | implementation 23 | 24 | uses 25 | Ntapi.ntdef, Ntapi.ntstatus, Ntapi.ntrtl, Ntapi.ntmmapi, Ntapi.ntpsapi, 26 | Ntapi.ntldr, NtUtils.Debug, NtUtils.Processes, NtUtils.Sections, 27 | NtUtils.SysUtils, NtUtils.Processes.Create, NtUtils.Processes.Create.Native, 28 | NtUtils.Memory, NtUtils.Threads, NtUtils.Ldr, NtUtils.ImageHlp, 29 | NtUtils.Synchronization, NtUtils.Objects, NtUtils.Security.Acl, 30 | NtUtils.Console, DelphiUtils.AutoObjects; 31 | 32 | function NtCreateThreadEx( 33 | out ThreadHandle: THandle; 34 | DesiredAccess: TThreadAccessMask; 35 | [in, opt] ObjectAttributes: PObjectAttributes; 36 | ProcessHandle: THandle; 37 | StartRoutine: TUserThreadStartRoutine; 38 | [in, opt] Argument: Pointer; 39 | CreateFlags: TThreadCreateFlags; 40 | ZeroBits: NativeUInt; 41 | StackSize: NativeUInt; 42 | MaximumStackSize: NativeUInt; 43 | [in, opt] AttributeList: PPsAttributeList 44 | ): NTSTATUS; stdcall; 45 | begin 46 | // Forward the parameters making sure to hide the thread from debugger 47 | Result := Ntapi.ntpsapi.NtCreateThreadEx(ThreadHandle, DesiredAccess, 48 | ObjectAttributes, ProcessHandle, StartRoutine, Argument, CreateFlags or 49 | THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER, ZeroBits, StackSize, 50 | MaximumStackSize, AttributeList); 51 | end; 52 | 53 | exports 54 | // Help resolving names without symbols 55 | NtCreateThreadEx; 56 | 57 | // Make sure local libraries always use our version of thread creation that 58 | // toggles the hide-from-debugger flag 59 | procedure PatchThreadCreation(ModuleBase: PDllBase; Size: Cardinal); 60 | var 61 | Import: TArray; 62 | i, j: Integer; 63 | IAT: Pointer; 64 | UndoProtection: IAutoReleasable; 65 | begin 66 | if RtlxEnumerateImportImage(Import, ModuleBase, Size, True).IsSuccess then 67 | for i := 0 to High(Import) do 68 | begin 69 | if RtlxCompareAnsiStrings(Import[i].DllName, ntdll) <> 0 then 70 | Continue; 71 | 72 | for j := 0 to High(Import[i].Functions) do 73 | begin 74 | if not Import[i].Functions[j].ImportByName or (RtlxCompareAnsiStrings( 75 | Import[i].Functions[j].Name, 'NtCreateThreadEx') <> 0) then 76 | Continue; 77 | 78 | IAT := PByte(ModuleBase) + Import[i].IAT + 79 | Cardinal(j) * SizeOf(Pointer); 80 | 81 | if NtxProtectMemoryAuto(NtxCurrentProcess, IAT, SizeOf(Pointer), 82 | PAGE_READWRITE, UndoProtection).IsSuccess then 83 | Pointer(IAT^) := @NtCreateThreadEx; 84 | end; 85 | end; 86 | end; 87 | 88 | // Make sure we avoid generating debug event as much as possible 89 | procedure SuppressDebugEvents; 90 | var 91 | hxThread: IHandle; 92 | Module: TModuleEntry; 93 | UndoProtection: IAutoReleasable; 94 | begin 95 | hxThread := nil; 96 | 97 | // Mark existing threads so they don't generate debug events 98 | while NtxGetNextThread(NtCurrentProcess, hxThread, THREAD_SET_INFORMATION) 99 | .IsSuccess do 100 | NtxSetThread(hxThread.Handle, ThreadHideFromDebugger, nil, 0); 101 | 102 | // Patch local IATs to use our thread creation that suppresses debug events 103 | for Module in LdrxEnumerateModules do 104 | if Module.DllBase <> PDllBase(@ImageBase) then 105 | PatchThreadCreation(Module.DllBase, Module.SizeOfImage); 106 | 107 | // Protecting the page with the MZ header blocks external thread creations 108 | if NtxProtectMemoryAuto(NtxCurrentProcess, @ImageBase, 1, PAGE_READONLY or 109 | PAGE_GUARD, UndoProtection).IsSuccess then 110 | UndoProtection.AutoRelease := False; 111 | end; 112 | 113 | // The function we execute in a fork (since someone need to respond to the debug 114 | // event out-of-process) 115 | function ForkMain( 116 | hProcess: THandle; 117 | hDebugObject: THandle 118 | ): TNtxStatus; 119 | var 120 | Wait: TDbgxWaitState; 121 | Handles: TDbgxHandles; 122 | begin 123 | // Start debugging the parent process 124 | Result := NtxDebugProcess(hProcess, hDebugObject); 125 | 126 | if not Result.IsSuccess then 127 | Exit; 128 | 129 | // Drain the queue of debug messages without waiting 130 | repeat 131 | Result := NtxDebugWait(hDebugObject, Wait, Handles, 0); 132 | 133 | // A timeout means that we removed all messages from the queue, 134 | // so it should be considered successful 135 | if not Result.IsSuccess or (Result.Status = STATUS_TIMEOUT) then 136 | Break; 137 | 138 | Result := NtxDebugContinue(hDebugObject, Wait.AppClientId); 139 | until not Result.IsSuccess; 140 | 141 | // Cancel debugging on failure 142 | if not Result.IsSuccess then 143 | NtxDebugProcessStop(hProcess, hDebugObject); 144 | end; 145 | 146 | type 147 | TSharedContext = record 148 | Status: NTSTATUS; 149 | Location: PWideChar; 150 | end; 151 | PSharedContext = ^TSharedContext; 152 | 153 | // All brought together 154 | function StartSelfDebugging; 155 | var 156 | ObjAttributes: IObjectAttributes; 157 | PreventDetaching: Boolean; 158 | hxSection, hxProcess: IHandle; 159 | Mapping: IMemory; 160 | Info: TProcessInfo; 161 | begin 162 | write('Do you want to prevent detaching? [y/n]: '); 163 | PreventDetaching := ReadBoolean; 164 | 165 | ObjAttributes := AttributeBuilder.UseAttributes(OBJ_INHERIT); 166 | 167 | if PreventDetaching then 168 | ObjAttributes := ObjAttributes.UseSecurity(RtlxAllocateDenyingSd); 169 | 170 | // Create a shared a debug port 171 | Result := NtxCreateDebugObject(hxDebugObject, False, ObjAttributes); 172 | 173 | if not Result.IsSuccess then 174 | Exit; 175 | 176 | // The fork will also need a handle to the parent (us) 177 | Result := NtxOpenCurrentProcess(hxProcess, MAXIMUM_ALLOWED, OBJ_INHERIT); 178 | 179 | if not Result.IsSuccess then 180 | Exit; 181 | 182 | Result := NtxCreateSection(hxSection, SizeOf(TSharedContext)); 183 | 184 | if not Result.IsSuccess then 185 | Exit; 186 | 187 | // And a shared memory region 188 | Result := NtxMapViewOfSection(IMemory(Mapping), hxSection.Handle, 189 | NtxCurrentProcess); 190 | 191 | if not Result.IsSuccess then 192 | Exit; 193 | 194 | Mapping.Data.Location := 'Main'; 195 | Mapping.Data.Status := STATUS_UNSUCCESSFUL; 196 | 197 | SuppressDebugEvents; 198 | 199 | // Start a fork that will attach as a debugger 200 | Result := RtlxCloneCurrentProcess(Info); 201 | 202 | if not Result.IsSuccess then 203 | Exit; 204 | 205 | if Result.Status = STATUS_PROCESS_CLONED then 206 | try 207 | // Executing within the fork 208 | Result := ForkMain(hxProcess.Handle, hxDebugObject.Handle); 209 | Mapping.Data.Status := Result.Status; 210 | 211 | // Two processes share the same constant strings; no need to marshal them 212 | if StringRefCount(Result.Location) <= 0 then 213 | Mapping.Data.Location := PWideChar(Result.Location); 214 | finally 215 | NtxTerminateProcess(NtCurrentProcess, STATUS_PROCESS_CLONED); 216 | end; 217 | 218 | // Wait for fork's completion 219 | Result := NtxWaitForSingleObject(Info.hxProcess.Handle, Timeout); 220 | 221 | if not Result.IsSuccess then 222 | Exit; 223 | 224 | if Result.Status = STATUS_TIMEOUT then 225 | begin 226 | // Terminate and forward the error 227 | NtxTerminateProcess(Info.hxProcess.Handle, STATUS_TIMEOUT); 228 | Result.Status := STATUS_WAIT_TIMEOUT; 229 | Exit; 230 | end; 231 | 232 | // Forward the status from the fork 233 | Result.Location := 'Fork::' + String(Mapping.Data.Location); 234 | Result.Status := Mapping.Data.Status; 235 | 236 | if not Result.IsSuccess then 237 | Exit; 238 | 239 | // Prevent debug object's inheritance and (optionally) closing 240 | NtxSetFlagsHandle(hxDebugObject.Handle, False, PreventDetaching); 241 | 242 | writeln; 243 | writeln('Note that we blocked Ctrl+C because new threads can deadlock us. ' + 244 | 'You can still close the console or terminate the process from an ' + 245 | 'external tool.'); 246 | end; 247 | 248 | end. 249 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Suspending-Techniques 2 | 3 | This is a repository that includes several tools for experimenting with various techniques for suspending processes on Windows. The idea of this project arose from a [discussion at Process Hacker](https://github.com/processhacker/processhacker/issues/856). 4 | 5 | I performed most experiments on Windows 10 20H2, but the topics I describe here apply at least starting from Windows 7 (except for the functionality that did not exist yet). If you are using Windows Insider builds, see the notes below the next section because there are some changes in the behavior. 6 | 7 | # Idea 8 | 9 | While we can [argue on the exact definition](https://github.com/processhacker/processhacker/issues/856#issuecomment-813092041) of what it means for a thread to be in a suspended state, conceptually, it requires trapping it in the kernel mode and, therefore, preventing it from executing any user-mode code. Since processes do not execute code anyway (they are merely containers for resources), we refer to them as being suspended when all their threads are. What is interesting for us is to control suspension from an external tool such as Process Hacker. 10 | 11 | Under the hood, the system maintains a ***suspend count*** for each thread (stored in `KTHREAD`), which we can increment and decrement through [`SuspendThread`](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread) and [`ResumeThread`](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-resumethread), respectively. Besides that, suspending queues a kernel APC that always executes before the thread switches modes. Thus, a thread can never execute user-mode code unless its suspend count is zero. Check out [this comment](https://github.com/microsoft/terminal/issues/9704#issuecomment-814398869) for some interesting insights on the pitfalls of interoperation between suspension and non-alertable synchronous I/O. 12 | 13 | The second mechanism we are going to cover here is called ***freezing***. Overall, it acts like suspension but cannot be undone via `ResumeThread`. Remember that suspension is an entirely per-thread feature? Freezing, on the other hand, is somewhat hybrid. Each thread stores a dedicated bit within its `KTHREAD` structure (as part of `ThreadFlags`), but the actual counter resides in the process object (`KPROCESS`). 14 | 15 | [`NtQueryInformationThread`](https://github.com/processhacker/processhacker/blob/000a748b3c2bf75cff03212cbc59a30cd67c2043/phnt/include/ntpsapi.h#L1360-L1369) exposes the value of the suspend counter via the `ThreadSuspendCount` info class. Note that the function increments the output (originating from `KTHREAD`'s `SuspendCount`) by one for frozen processes. So, if we ever encounter a thread with a `ThreadSuspendCount` of one that we can increment but cannot decrement - it is definitely frozen. 16 | 17 | Finally, starting from Windows 8, there is ***deep freezing***, a completely per-process concept controlled by a dedicated flag in the `KPROCESS` structure. Unlike ordinary freezing, it guarantees that new threads created in a deep-frozen process immediately become frozen as well. This feature proves to be the most reliable option when it comes to preventing code execution. 18 | 19 | Interestingly, Microsoft recently introduced some changes to these mechanisms that made freezing and deep freezing indistinguishable, as far as my user-mode experiments can tell. It happened somewhere between Insider builds 20231 and 21286. If you are using Windows Insider, you'll notice that injecting threads into a frozen process freezes them as if the process is actually deep-frozen. While it yields some of the demonstrations I prepared less exciting, it does make multiple techniques more reliable. 20 | 21 | If you want to know more technical details regarding these mechanisms, check out `PsSuspendThread` and `PsFreezeProcess` with their cross-references in ntoskrnl, and read [Windows Internals](https://books.google.nl/books?id=V4kjnwEACAAJ). 22 | 23 | # Research Questions 24 | 25 | 1. *What are the available options that allow suspending or freezing other processes?* 26 | 2. *What are their benefits and shortcomings?* 27 | 3. *How might someone bypass them?* 28 | 29 | # Tools Overview 30 | 31 | *For more details, navigate to the [corresponding section](#tools). To download the tools, see the [releases page](https://github.com/diversenok/Suspending-Techniques/releases).* 32 | 33 | I wrote several tools that we can use to experiment and reproduce my observations: 34 | 35 | - **SuspendTool** is a program that can suspend/freeze processes using several different methods. I will cover the techniques it implements in the next section. 36 | - **ModeTransitionMonitor** is a program that detects all kernel-to-user mode transitions happening within a specific process. If you are interested in how it works, check out the [dedicated section](#modetransitionmonitor). 37 | - **SuspendInfo** is a small tool that queries the state of suspension and freezing. 38 | - **InjectTool** is a program for injecting dummy threads (either directly or via a thread pool) into a process. 39 | - **SuspendMe** is a test application that demonstrates several approaches for bypassing suspension. 40 | 41 | # Techniques 42 | 43 | - [Snapshot & Suspend Threads (Not Covered)](#snapshot--suspend-threads-not-covered) 44 | - [Enumerate & Suspend Threads](#enumerate--suspend-threads) 45 | - [Suspend via NtSuspendProcess](#suspend-via-ntsuspendprocess) 46 | - [Suspend via a Debug Object](#suspend-via-a-debug-object) 47 | - [Freeze via a Debug Object with Thread Injection](#freeze-via-a-debug-object-with-thread-injection) 48 | - [Freezing via a Job Object](#freezing-via-a-job-object) 49 | - [Freezing via a State Change Object](#freezing-via-a-state-change-object) 50 | 51 | ## Snapshot & Suspend Threads (Not Covered) 52 | 53 | ### Idea 54 | It appears that the documented way to suspend a process is to snapshot the list of its threads via [`CreateToolhelp32Snapshot`](https://docs.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot) and then suspend each one of them using [`SuspendThread`](https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread). This approach sounds like a terrible idea for several reasons: 55 | 56 | 1. It requires passing access checks on all target threads when obtaining their handles. 57 | 2. This method is full of inherent race conditions. It (**A**) suspends threads one-by-one, (**B**) does not detect their creation (that can happen between snapshotting and completing suspension), and (**C**) does not detect their termination. The last part implies a relatively small but non-zero chance to suspend a thread in a completely unrelated process if the original one terminates and its ID gets reused within a short time. 58 | 3. The documented function for making process/thread snapshots introduces a significant overhead compared to its native counterpart. 59 | 4. It requires the caller to have at least medium integrity for snapshotting. Which is, to be fair, rarely an issue. 60 | 61 | ## Enumerate & Suspend Threads 62 | 63 | ### Idea 64 | We can improve the previous approach by replacing snapshotting with a loop of [`NtGetNextThread`](https://github.com/processhacker/processhacker/blob/c28efff632e76f1cb60aeb798a4cceae1289f3dd/phnt/include/ntpsapi.h#L1253-L1263)'s. The result is still somewhat discouraging because of items **1** and **2A**, but at least it resolves **2B**, **2C**, **3**, and **4** from the list above. `NtGetNextThread` does iterate through the threads created after the enumeration started, so item **2B** does not apply. Additionally, using handles instead of Thread IDs prevents the most destructive scenario **2C**. 65 | 66 | ### Bypasses 67 | Aside from protecting objects with security descriptors that deny specific actions, a program can also exploit the race condition that appears because we don't perform suspension as an atomic operation. The code in the target process runs concurrently with our algorithm, so if it manages to resume at least one thread before we complete their enumeration, it wins. The **SuspendMe** tool includes this functionality as one of the options. The implementation for it is straightforward: just several threads resuming each other in a tight loop. You might find it surprising, but the tool counteracts suspension quite effectively, especially on multi-processor systems. 68 | 69 | Additionally, this method does not account for the future threads that might appear in the process while it's suspended. I know at least two scenarios of when it can happen. First of all, thread pools. They allow a variable number of threads to balance the load when dealing with a set of tasks. Every process on Windows includes at least one of them because of the module loader in ntdll, but other components use the infrastructure they provide as well. If the system notices that a thread pool cannot keep up with the upcoming tasks (of course, we are suspended!), it might create additional threads to help us. It already lifts suspension on the scale of the process (by definition), but a specially crafted program can take advantage of its thread pools to resume itself. You can experiment with this idea with a pair of tools: select an option for creating a thread pool in **SuspendMe**, and then use **InjectTool** to adjust the minimum number of threads, triggering their creation. 70 | 71 | Secondly, some external tools can create threads to execute code within the process's context. Most of the time, it requires explicit user action (for example, when injecting DLLs) but can also happen unexpectedly. **Process Explorer**, for example, uses thread injection to retrieve debugging information when the user merely navigates to the threads page in the process's properties. Someone might argue that the thread exists temporarily and only executes predefined code, but, again, a specially crafted application can take advantage of it. **SuspendMe** includes a pair of options that patch `RtlUserThreadStart` - a function from ntdll where almost any thread starts - and hijack its execution, resuming the process. You can try the following sequence with [Process Explorer](https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer): 72 | 73 | 1. Start **SuspendMe** and select the corresponding option for patching. 74 | 2. Suspend the process via the context menu in the process list. You will see that that it was indeed suspended. 75 | 3. Double-click it to inspect the properties, switch to the Threads page. 76 | 4. **SuspendMe** should hijack the thread and resume itself. 77 | 78 | In the next section, we will discuss how it is possible despite **Process Explorer** using a different suspension technique. Fortunately, this behavior does not apply to **Process Hacker**. 79 | 80 | ### Overview 81 | Pros | Cons 82 | ----------------------------------------------------------- | ---- 83 | Does not require keeping any handles to maintain suspension | Requires passing access checks on threads 84 | _-_ | Does not prevent race conditions 85 | _-_ | Does not suspend future threads 86 | 87 | ## Suspend via NtSuspendProcess 88 | 89 | ### Idea 90 | A pair of functions called [`NtSuspendProcess`](https://github.com/processhacker/processhacker/blob/c28efff632e76f1cb60aeb798a4cceae1289f3dd/phnt/include/ntpsapi.h#L1195-L1200) and [`NtResumeProcess`](https://github.com/processhacker/processhacker/blob/c28efff632e76f1cb60aeb798a4cceae1289f3dd/phnt/include/ntpsapi.h#L1202-L1207) provides an exceptionally straightforward and easy-to-use solution. This is the most widely used method that powers suspension functionality in **Windows Resource Monitor**, **Process Explorer**, **Process Hacker**, and a handful of other tools. 91 | 92 | ### Bypasses 93 | Unfortunately for us, it also suffers from almost the same set of problems. The method itself is surprisingly similar to the previous one with a single significant difference - it executes in the kernel mode and does not require passing additional access checks. Essentially, it uses a loop of `PsGetNextProcessThread` + `PsSuspendThread` as opposed to `NtGetNextThread` + `NtSuspendThread` we had in the previous case. Thus, it fails to provide atomicity and falls victim to the race condition from item **2A**. And again, you can demonstrate this behavior with any of the tools mentioned above by trying to suspend the **SuspendMe** program when it works in the race-condition-bypassing mode. The examples with thread pools and thread hijacking also apply here; feel free to experiment yourself. 94 | 95 | ### Overview 96 | Pros | Cons 97 | ----------------------------------------------------------- | ---- 98 | Does not require keeping any handles to maintain suspension | Does not prevent race conditions 99 | _-_ | Does not suspend future threads 100 | 101 | ## Suspend via a Debug Object 102 | 103 | ### Idea 104 | How about taking an alternative path? Debugging is essentially a fancy inter-process synchronization mechanism compatible with any application out-of-the-box. If you are not familiar with its internals, here is a quick recap. First, a debugger creates a debug object (aka debug port) and then attaches it to the target process. Starting from this point, every time an event of interest occurs in the target process (be it thread creation, exception, or a breakpoint hit), the system pauses its execution and posts a message to the debug port, waiting for an acknowledgment. Additionally, attaching itself generates a process creation and a few module loading events. Luckily for us, the system does not enforce any time constraints on the responses, so we can delay them indefinitely, keeping the target paused. 105 | 106 | In terms of Native API, we call [`NtCreateDebugObject`](https://github.com/processhacker/processhacker/blob/c28efff632e76f1cb60aeb798a4cceae1289f3dd/phnt/include/ntdbg.h#L231-L239), followed by [`NtDebugActiveProcess`](https://github.com/processhacker/processhacker/blob/c28efff632e76f1cb60aeb798a4cceae1289f3dd/phnt/include/ntdbg.h#L241-L247) (which requires `PROCESS_SUSPEND_RESUME` access to the process). Typically, debuggers implement a loop of [`NtWaitForDebugEvent`](https://github.com/processhacker/processhacker/blob/c28efff632e76f1cb60aeb798a4cceae1289f3dd/phnt/include/ntdbg.h#L277-L285) plus [`NtDebugContinue`](https://github.com/processhacker/processhacker/blob/c28efff632e76f1cb60aeb798a4cceae1289f3dd/phnt/include/ntdbg.h#L249-L256), but we don't need that since we are not interested in debugging per se. Instead, we wait until it's time to resume the process and either close the debug object or call [`NtRemoveProcessDebug`](https://github.com/processhacker/processhacker/blob/c28efff632e76f1cb60aeb798a4cceae1289f3dd/phnt/include/ntdbg.h#L258-L264) to detach altogether. As you can see, this method has a slight disadvantage: it requires keeping a handle to the debug object. Closing it resumes the target automatically unless we explicitly configure the kill-on-close flag that will terminate it instead. If we want to keep the process suspended after the debugger exits, we can, however, store this handle within the target process. 107 | 108 | Interestingly, while malicious programs often implement various anti-debugging techniques, almost none of them interfere with our approach because we don't let the application execute any code. Still, a process can have only a single debug port, so if it manages to attach one to itself, it will prevent us from doing the same. I implemented an option for starting a self-debugging session in the **SuspendMe** tool but, to be honest, I did it mainly because I find it a peculiar challenge rather than a demonstration of a plausible attack vector. 109 | 110 | ### Bypasses 111 | I was deliberately avoiding the question of whether this technique provides suspension, freezing, or deep freezing. Confusingly, it has the properties of all of them. I believe the best way to explain it is to let you experiment with the tools yourself. We'll need **SuspendTool** with option #4, **InjectTool**, all techniques from **SuspendMe**, and, optionally, **ModeTransitionMonitor** with **SuspendInfo**. You should be able to reproduce the following results: 112 | 113 | 1. There is still a race condition with suspension. 114 | 2. Creating and terminating threads freezes the process. 115 | 3. However, it does not occur when using the *hide-from-debugger* flag. 116 | 4. Yet, existing threads with this flag can get frozen. 117 | 118 | *Note that item #3 does not apply to recent Insider builds.* 119 | 120 | The functionality of hiding threads from debuggers is not exposed through the documented API, so the last two observations are somewhat exotic. To create such a thread, supply `THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER` to [`NtCreateThreadEx`](https://github.com/processhacker/processhacker/blob/c28efff632e76f1cb60aeb798a4cceae1289f3dd/phnt/include/ntpsapi.h#L1831-L1846); to hide an existing one, use `ThreadHideFromDebugger` info class with [`NtSetInformationThread`](https://github.com/processhacker/processhacker/blob/c28efff632e76f1cb60aeb798a4cceae1289f3dd/phnt/include/ntpsapi.h#L1371-L1379). 121 | 122 | As you can see, there is something sophisticated going on. Fortunately, with some knowledge about the internals of debugging, we can break it down and explain based on several simple rules: 123 | 124 | 1. Process creation and module loading events (that we receive while attaching) merely suspend the process. This suspension is subject to race conditions because, for some reason, it does not involve freezing. 125 | 2. Thread-creation and termination events, on the other hand, do a way better job: they freeze all existing threads. Technically, it is still ordinary freezing. But since it uses such convenient triggers, it is almost as good as deep freezing. 126 | 3. Hidden threads do not trigger debugging events, so they are free to execute even in a frozen process, but only if created after the freezing occurred. 127 | 128 | *Again, note that item #3 does not apply to recent Insider builds.* 129 | 130 | ### Overview 131 | Pros | Cons 132 | ---------------------- | ---- 133 | Freezes future threads | Requires keeping a handle open 134 | _-_ | Does not prevent race conditions 135 | 136 | ## Freeze via a Debug Object with Thread Injection 137 | 138 | ### Idea 139 | So, if the thread creation event is so helpful, why don't we generate one ourselves? We can inject and immediately terminate a dummy suspended thread to freeze the target. Technically, we can even avoid opening the target for `PROCESS_CREATE_THREAD` access because the kernel gives us a full-access handle after we acknowledge the process creation notification. Additionally, we can include a few other improvements, such as protecting the debug object (so nobody can detach it) and blocking remote thread creation (to mitigate the impact of injected hidden threads). 140 | 141 | Yes, Process Explorer does hide the threads it injects from debuggers but also appears to be the only tool I know that does that. So, unless you are running Insider Preview builds, a program might exploit them to execute arbitrary code from a frozen process. I noticed that creating remote threads from user mode always looks up at the first page of the process's image (the one with the MZ header), so protecting it for the duration of suspension does the trick. 142 | 143 | ### Bypasses 144 | Finally, we are getting somewhere: there is little a program can do to bypass this technique. It successfully prevents race conditions and the thread pool-based bypass. As far as I can tell, the only options that still might work are the following: 145 | 146 | 1. Protect the process and thread objects with a denying DACL. This approach, obviously, works against unprivileged tools but won't interfere with administrators that have the Debug privilege. 147 | 2. Occupying the debug port beforehand and thus, preventing anyone from using it. **SuspendMe** combines it with injection prevention, so freezing it via debugging would require overcoming both obstacles. 148 | 3. Other techniques that can prevent debuggers from attaching or injecting threads. 149 | 150 | ### Overview 151 | Pros | Cons 152 | ---------------------- | ---- 153 | Freezes future threads | Requires keeping a handle open 154 | 155 | ## Freezing via a Job Object 156 | 157 | ### Idea 158 | Job objects provide a mechanism for manipulating and monitoring a group of processes as a single entity. Additionally, they allow enforcing various limits and constraints on their execution, configurable through the [`NtSetInformationJobObject`](https://github.com/processhacker/processhacker/blob/c28efff632e76f1cb60aeb798a4cceae1289f3dd/phnt/include/ntpsapi.h#L2062-L2070) function. Starting from Windows 8, jobs also support freezing processes through the corresponding `JobObjectFreezeInformation` info class. The primary advantage of this technique is that it relies on deep freezing - an operation that is not susceptible to race conditions and takes care of the new threads out-of-the-box. 159 | 160 | Before we can freeze a process, we need to put it into a job using [`NtAssignProcessToJobObject`](https://github.com/processhacker/processhacker/blob/c28efff632e76f1cb60aeb798a4cceae1289f3dd/phnt/include/ntpsapi.h#L2027-L2033). Note that this operation is irreversible and, therefore, should be taken with care. Fortunately, starting from Windows 8, a process can be part of multiple jobs. Although they must form a hierarchy, we are unlikely to run into conflicts between the restrictions they enforce as long as we don't configure any. 161 | 162 | As you can guess, this technique also requires keeping a handle open. While we already encountered a similar problem with debugging, here it's more severe: closing the last handle to a frozen job makes it impossible to unfreeze the processes within it. The system does not expose any functions for opening a job aside from doing it by name, and names get disassociated with the last closed handle. Given enough access, we can, of course, store a backup copy in the target's handle table to prevent this scenario from happening. 163 | 164 | ### Bypasses 165 | Deep freezing is designed to provide substantial reliability guarantees. I didn't manage to find any weaknesses that allow the process to execute code in a deep-frozen state, so there aren't many options left. Looking into the possibilities for preventing freezing from happening, we can try the following ideas: 166 | 167 | 1. Protect the process and thread objects with a denying DACL. Again, it won't stop administrators that have the Debug privilege. 168 | 2. Craft and employ a specific job hierarchy that will conflict with the new job, failing its assignment. I didn't manage to exploit this attack vector when we don't enforce any additional limits, but it might be possible, considering the assignment logic. 169 | 170 | ### Overview 171 | Pros | Cons 172 | -----------------------| ---- 173 | Freezes future threads | Requires keeping a handle open 174 | _-_ | Requires Windows 8 and above 175 | _-_ | Permanently assigns the process to a job 176 | 177 | ## Freezing via a State Change Object 178 | 179 | ### Idea 180 | Suspending threads and processes via functions like `NtSuspendThread` and `NtSuspendProcess` looks somewhat similar to synchronizing with external resources: it requires an explicit release operation. What happens when a process that, say, acquired a shared mutex crashes unexpectedly? The system releases the ownership automatically when it destroys the process's handle table. Despite similarities, it does not happen with acquired suspension. Not long ago, Microsoft, apparently, decided to address this issue by introducing an alternative approach for dealing with suspension. [Windows Insider Preview 20190 introduced](https://twitter.com/hFireF0X/status/1295995982409236480) a new **ProcessStateChange** type for kernel objects, followed by a similar **ThreadStateChange** that [appeared in 20226](https://twitter.com/hFireF0X/status/1311528429112754176). The new syscalls [documented here](https://windows-internals.com/thread-and-process-state-change/) tie suspend and resume actions to these objects. Because these objects record performed operations, the system can undo them automatically when it destroys the object. In practice, you call `NtCreateProcessStateChange`, then apply suspension via `NtChangeProcessState`. To resume the process, either call `NtCreateProcessStateChange` again specifying the corresponding action or merely close the object and let the system handle everything on its own. 181 | 182 | Interestingly, this functionality initially worked on top of the same routines that power the ordinary suspension (`PsSuspendProcess` and `PsSuspendThread`) and, therefore, was vulnerable to the entire spectrum of attacks we discussed earlier. However, somewhere between builds 20231 and 21286, they replaced process-wide suspension with freezing (via `PsFreezeProcess`), making it significantly more reliable. Considering that Microsoft made freezing and deep-freezing essentially equivalent around the same time, this technique has great potential for powering system tools that require high reliability in the future. 183 | 184 | ### Bypasses 185 | Yet again, out of the methods I included with the repository, nothing really breaks freezing in a form implemented in recent Insider builds. There, of course, might be something I missed that still differentiates freezing from deep-freezing and, therefore, allows creating active threads in a frozen process. Although, I don't see any working options other than the most boring one we already mentioned multiple times: preventing the process from being opened by an unprivileged caller with a denying DACL. 186 | 187 | ### Overview 188 | Pros | Cons 189 | -----------------------| ---- 190 | Freezes future threads | Requires keeping a handle open 191 | _-_ | Requires Windows Insider Preview 192 | 193 | # Tools 194 | 195 | You can download the tools from the [releases page](https://github.com/diversenok/Suspending-Techniques/releases). 196 | 197 | ## SuspendTool 198 | 199 | The tool implements all of the techniques for suspending and freezing processes I discuss above. 200 | 201 | ```text 202 | Available options: 203 | 204 | [0] Enumerate & suspend all threads 205 | [1] Enumerate & resume all threads 206 | [2] Suspend via NtSuspendProcess 207 | [3] Resume via NtResumeProcess 208 | [4] Suspend via a debug object 209 | [5] Freeze via a debug object 210 | [6] Freeze via a job object 211 | [7] Freeze via a state change object 212 | ``` 213 | 214 | ## SuspendMe 215 | 216 | This program tries its best to bypass or at least counteract specific suspension methods. 217 | 218 | ```text 219 | Available options: 220 | 221 | [0] Protect the process with a denying security descriptor 222 | [1] Circumvent suspension using a race condition 223 | [2] Create a thread pool for someone to trigger 224 | [3] Hijack thread execution (resume & detach debuggers on code injection) 225 | [4] Start self-debugging so nobody else can attach 226 | ``` 227 | 228 | ## InjectTool 229 | 230 | You can use this tool to check how a specific technique responds to thread creation. Additionally, you can use it to help the **SuspendMe** tool escape when it works in thread-hijacking mode. When used for direct injection, the thread will execute [`NtAlertThread`](https://github.com/processhacker/processhacker/blob/d6e5d36d2c6c2523d55a6f07a6447bf9eca569db/phnt/include/ntpsapi.h#L1445-L1450). I chose this function because it matches the expected prototype and exits immediately. 231 | 232 | ```text 233 | Available options: 234 | 235 | [0] Create a thread 236 | [1] Create a thread (hide from DLLs & debuggers) 237 | [2] Trigger thread pool's thread creation 238 | ``` 239 | 240 | ## ModeTransitionMonitor 241 | 242 | Process statistics don't provide enough information to reliably identify user-mode code execution. **UserTime** is not precise enough to detect running a single line of code, while **CycleTime** does not distinguish between user and kernel modes. Of course, if a program spins in a tight loop and consumes 100% of the CPU, we don't need any sophisticated tricks. As for the rest, I wrote a program that installs the **Instrumentation Callback** within the target process (see [slides by Alex Ionescu](https://github.com/ionescu007/HookingNirvana/blob/9e4e8e326b9dfd10a7410986486e567e5980f913/Esoteric%20Hooks.pdf) and a [blog post by Antonio Cocomazzi](https://splintercod3.blogspot.com/p/weaponizing-mapping-injection-with.html)). The system invokes this callback every time it returns from the kernel mode, making it possible to identify when any wait completes. As a bonus, we can record return addresses and get a better insight into what happens within the target. 243 | 244 | Technically, we need the Debug privilege to install the instrumentation callback for another process. But since setting it on the current one does not require anything, we can easily bypass this requirement by injecting a thread that installs the callback on the target's behalf. 245 | 246 | ```text 247 | Do you want to capture return addresses? [y/n]: y 248 | Loading symbols... 249 | 250 | Target's PID or a unique image name: SuspendMe 251 | Setting up monitoring... 252 | 253 | Transitions / second: 0 254 | 255 | Transitions / second: 6 256 | ntdll.dll!ZwQueryInformationThread+0x14 x 3 times 257 | ntdll.dll!ZwAlertThread+0x14 258 | ntdll.dll!NtTestAlert+0x14 259 | ntdll.dll!LdrInitializeThunk 260 | 261 | Transitions / second: 0 262 | ``` 263 | 264 | ## SuspendInfo 265 | 266 | **SuspendInfo** is a small program that inspects and displays suspension/freezing info for all threads in a process. 267 | 268 | # Conclusion 269 | 270 | I was surprised to learn that the most commonly used techniques utilized by both first- and third-party tools have reliability issues that allow a specially crafted program to circumvent them. We saw that Microsoft takes the steps in the right direction: first, they introduced job-based deep-freezing, then significantly improved ordinary freezing and included a great alternative solution. The debugging-based technique turned out to be full of peculiar pitfalls and weaknesses, but with some tweaking, it might be a better option than using `NtSuspendProcess` in tools like Process Hacker. 271 | 272 | Feel free to use the [Discussions page](https://github.com/diversenok/Suspending-Techniques/discussions) for sharing your ideas on improving, bypassing, or utilizing suspension techniques. 273 | -------------------------------------------------------------------------------- /Monitoring/SuspendInfo.dproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {4772B3D2-531D-400E-8430-D2E2E46C39BC} 4 | 19.2 5 | None 6 | SuspendInfo.dpr 7 | True 8 | Release 9 | Win64 10 | 3 11 | Console 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | Base 29 | true 30 | 31 | 32 | true 33 | Cfg_1 34 | true 35 | true 36 | 37 | 38 | true 39 | Base 40 | true 41 | 42 | 43 | ..\$(Platform)\$(Config)\dcu 44 | ..\$(Platform)\$(Config) 45 | false 46 | false 47 | false 48 | false 49 | false 50 | System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) 51 | SuspendInfo 52 | ..\NtUtils;..\NtUtils\Headers;$(DCC_UnitSearchPath) 53 | 1033 54 | CompanyName=diversenok;FileDescription=A program for checking process suspension and freezing.;FileVersion=1.0.0.0;InternalName=;LegalCopyright=diversenok;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= 55 | true 56 | 3 57 | true 58 | 59 | 60 | DBXSqliteDriver;IndyIPCommon;RESTComponents;bindcompdbx;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;svnui;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;FireDAC;vcltouch;vcldb;bindcompfmx;svn;FireDACSqliteDriver;FireDACPgDriver;inetdb;VirtualTreesDR;soaprtl;DbxCommonDriver;fmx;FireDACIBDriver;fmxdae;xmlrtl;soapmidas;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;VCLRESTComponents;soapserver;dbxcds;VclSmp;adortl;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;IndyProtocols;inetdbxpress;VclExtension;FireDACCommonODBC;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage) 61 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 62 | Debug 63 | true 64 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png 65 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png 66 | (None) 67 | 68 | 69 | DBXSqliteDriver;IndyIPCommon;RESTComponents;bindcompdbx;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;FireDAC;vcltouch;vcldb;bindcompfmx;FireDACSqliteDriver;FireDACPgDriver;inetdb;VirtualTreesDR;soaprtl;DbxCommonDriver;fmx;FireDACIBDriver;fmxdae;xmlrtl;soapmidas;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;VCLRESTComponents;soapserver;dbxcds;VclSmp;adortl;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;IndyProtocols;inetdbxpress;VclExtension;FireDACCommonODBC;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage) 70 | true 71 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png 72 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png 73 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) 74 | Debug 75 | (None) 76 | 77 | 78 | DEBUG;$(DCC_Define) 79 | true 80 | false 81 | true 82 | true 83 | true 84 | true 85 | true 86 | 87 | 88 | false 89 | 90 | 91 | false 92 | RELEASE;$(DCC_Define) 93 | 0 94 | 0 95 | 96 | 97 | 98 | MainSource 99 | 100 | 101 | Cfg_2 102 | Base 103 | 104 | 105 | Base 106 | 107 | 108 | Cfg_1 109 | Base 110 | 111 | 112 | 113 | Delphi.Personality.12 114 | Application 115 | 116 | 117 | 118 | SuspendInfo.dpr 119 | 120 | 121 | Microsoft Office 2000 Sample Automation Server Wrapper Components 122 | Microsoft Office XP Sample Automation Server Wrapper Components 123 | 124 | 125 | 126 | 127 | 128 | true 129 | 130 | 131 | 132 | 133 | true 134 | 135 | 136 | 137 | 138 | true 139 | 140 | 141 | 142 | 143 | 1 144 | 145 | 146 | Contents\MacOS 147 | 1 148 | 149 | 150 | 0 151 | 152 | 153 | 154 | 155 | classes 156 | 1 157 | 158 | 159 | classes 160 | 1 161 | 162 | 163 | 164 | 165 | res\xml 166 | 1 167 | 168 | 169 | res\xml 170 | 1 171 | 172 | 173 | 174 | 175 | library\lib\armeabi-v7a 176 | 1 177 | 178 | 179 | 180 | 181 | library\lib\armeabi 182 | 1 183 | 184 | 185 | library\lib\armeabi 186 | 1 187 | 188 | 189 | 190 | 191 | library\lib\armeabi-v7a 192 | 1 193 | 194 | 195 | 196 | 197 | library\lib\mips 198 | 1 199 | 200 | 201 | library\lib\mips 202 | 1 203 | 204 | 205 | 206 | 207 | library\lib\armeabi-v7a 208 | 1 209 | 210 | 211 | library\lib\arm64-v8a 212 | 1 213 | 214 | 215 | 216 | 217 | library\lib\armeabi-v7a 218 | 1 219 | 220 | 221 | 222 | 223 | res\drawable 224 | 1 225 | 226 | 227 | res\drawable 228 | 1 229 | 230 | 231 | 232 | 233 | res\values 234 | 1 235 | 236 | 237 | res\values 238 | 1 239 | 240 | 241 | 242 | 243 | res\values-v21 244 | 1 245 | 246 | 247 | res\values-v21 248 | 1 249 | 250 | 251 | 252 | 253 | res\values 254 | 1 255 | 256 | 257 | res\values 258 | 1 259 | 260 | 261 | 262 | 263 | res\drawable 264 | 1 265 | 266 | 267 | res\drawable 268 | 1 269 | 270 | 271 | 272 | 273 | res\drawable-xxhdpi 274 | 1 275 | 276 | 277 | res\drawable-xxhdpi 278 | 1 279 | 280 | 281 | 282 | 283 | res\drawable-xxxhdpi 284 | 1 285 | 286 | 287 | res\drawable-xxxhdpi 288 | 1 289 | 290 | 291 | 292 | 293 | res\drawable-ldpi 294 | 1 295 | 296 | 297 | res\drawable-ldpi 298 | 1 299 | 300 | 301 | 302 | 303 | res\drawable-mdpi 304 | 1 305 | 306 | 307 | res\drawable-mdpi 308 | 1 309 | 310 | 311 | 312 | 313 | res\drawable-hdpi 314 | 1 315 | 316 | 317 | res\drawable-hdpi 318 | 1 319 | 320 | 321 | 322 | 323 | res\drawable-xhdpi 324 | 1 325 | 326 | 327 | res\drawable-xhdpi 328 | 1 329 | 330 | 331 | 332 | 333 | res\drawable-mdpi 334 | 1 335 | 336 | 337 | res\drawable-mdpi 338 | 1 339 | 340 | 341 | 342 | 343 | res\drawable-hdpi 344 | 1 345 | 346 | 347 | res\drawable-hdpi 348 | 1 349 | 350 | 351 | 352 | 353 | res\drawable-xhdpi 354 | 1 355 | 356 | 357 | res\drawable-xhdpi 358 | 1 359 | 360 | 361 | 362 | 363 | res\drawable-xxhdpi 364 | 1 365 | 366 | 367 | res\drawable-xxhdpi 368 | 1 369 | 370 | 371 | 372 | 373 | res\drawable-xxxhdpi 374 | 1 375 | 376 | 377 | res\drawable-xxxhdpi 378 | 1 379 | 380 | 381 | 382 | 383 | res\drawable-small 384 | 1 385 | 386 | 387 | res\drawable-small 388 | 1 389 | 390 | 391 | 392 | 393 | res\drawable-normal 394 | 1 395 | 396 | 397 | res\drawable-normal 398 | 1 399 | 400 | 401 | 402 | 403 | res\drawable-large 404 | 1 405 | 406 | 407 | res\drawable-large 408 | 1 409 | 410 | 411 | 412 | 413 | res\drawable-xlarge 414 | 1 415 | 416 | 417 | res\drawable-xlarge 418 | 1 419 | 420 | 421 | 422 | 423 | res\values 424 | 1 425 | 426 | 427 | res\values 428 | 1 429 | 430 | 431 | 432 | 433 | 1 434 | 435 | 436 | Contents\MacOS 437 | 1 438 | 439 | 440 | 0 441 | 442 | 443 | 444 | 445 | Contents\MacOS 446 | 1 447 | .framework 448 | 449 | 450 | Contents\MacOS 451 | 1 452 | .framework 453 | 454 | 455 | 0 456 | 457 | 458 | 459 | 460 | 1 461 | .dylib 462 | 463 | 464 | 1 465 | .dylib 466 | 467 | 468 | 1 469 | .dylib 470 | 471 | 472 | Contents\MacOS 473 | 1 474 | .dylib 475 | 476 | 477 | Contents\MacOS 478 | 1 479 | .dylib 480 | 481 | 482 | 0 483 | .dll;.bpl 484 | 485 | 486 | 487 | 488 | 1 489 | .dylib 490 | 491 | 492 | 1 493 | .dylib 494 | 495 | 496 | 1 497 | .dylib 498 | 499 | 500 | Contents\MacOS 501 | 1 502 | .dylib 503 | 504 | 505 | Contents\MacOS 506 | 1 507 | .dylib 508 | 509 | 510 | 0 511 | .bpl 512 | 513 | 514 | 515 | 516 | 0 517 | 518 | 519 | 0 520 | 521 | 522 | 0 523 | 524 | 525 | 0 526 | 527 | 528 | 0 529 | 530 | 531 | Contents\Resources\StartUp\ 532 | 0 533 | 534 | 535 | Contents\Resources\StartUp\ 536 | 0 537 | 538 | 539 | 0 540 | 541 | 542 | 543 | 544 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 545 | 1 546 | 547 | 548 | 549 | 550 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 551 | 1 552 | 553 | 554 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 555 | 1 556 | 557 | 558 | 559 | 560 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 561 | 1 562 | 563 | 564 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 565 | 1 566 | 567 | 568 | 569 | 570 | 1 571 | 572 | 573 | 1 574 | 575 | 576 | 1 577 | 578 | 579 | 580 | 581 | 1 582 | 583 | 584 | 1 585 | 586 | 587 | 1 588 | 589 | 590 | 591 | 592 | 1 593 | 594 | 595 | 1 596 | 597 | 598 | 1 599 | 600 | 601 | 602 | 603 | 1 604 | 605 | 606 | 1 607 | 608 | 609 | 1 610 | 611 | 612 | 613 | 614 | 1 615 | 616 | 617 | 1 618 | 619 | 620 | 1 621 | 622 | 623 | 624 | 625 | 1 626 | 627 | 628 | 1 629 | 630 | 631 | 1 632 | 633 | 634 | 635 | 636 | 1 637 | 638 | 639 | 1 640 | 641 | 642 | 1 643 | 644 | 645 | 646 | 647 | 1 648 | 649 | 650 | 1 651 | 652 | 653 | 1 654 | 655 | 656 | 657 | 658 | 1 659 | 660 | 661 | 1 662 | 663 | 664 | 1 665 | 666 | 667 | 668 | 669 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 670 | 1 671 | 672 | 673 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 674 | 1 675 | 676 | 677 | 678 | 679 | 1 680 | 681 | 682 | 1 683 | 684 | 685 | 1 686 | 687 | 688 | 689 | 690 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 691 | 1 692 | 693 | 694 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 695 | 1 696 | 697 | 698 | 699 | 700 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 701 | 1 702 | 703 | 704 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 705 | 1 706 | 707 | 708 | 709 | 710 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 711 | 1 712 | 713 | 714 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 715 | 1 716 | 717 | 718 | 719 | 720 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 721 | 1 722 | 723 | 724 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 725 | 1 726 | 727 | 728 | 729 | 730 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 731 | 1 732 | 733 | 734 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 735 | 1 736 | 737 | 738 | 739 | 740 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 741 | 1 742 | 743 | 744 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 745 | 1 746 | 747 | 748 | 749 | 750 | 1 751 | 752 | 753 | 1 754 | 755 | 756 | 1 757 | 758 | 759 | 760 | 761 | 1 762 | 763 | 764 | 1 765 | 766 | 767 | 1 768 | 769 | 770 | 771 | 772 | 1 773 | 774 | 775 | 1 776 | 777 | 778 | 1 779 | 780 | 781 | 782 | 783 | 1 784 | 785 | 786 | 1 787 | 788 | 789 | 1 790 | 791 | 792 | 793 | 794 | 1 795 | 796 | 797 | 1 798 | 799 | 800 | 1 801 | 802 | 803 | 804 | 805 | 1 806 | 807 | 808 | 1 809 | 810 | 811 | 1 812 | 813 | 814 | 815 | 816 | 1 817 | 818 | 819 | 1 820 | 821 | 822 | 1 823 | 824 | 825 | 826 | 827 | 1 828 | 829 | 830 | 1 831 | 832 | 833 | 1 834 | 835 | 836 | 837 | 838 | 1 839 | 840 | 841 | 1 842 | 843 | 844 | 1 845 | 846 | 847 | 848 | 849 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 850 | 1 851 | 852 | 853 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 854 | 1 855 | 856 | 857 | 858 | 859 | 1 860 | 861 | 862 | 1 863 | 864 | 865 | 1 866 | 867 | 868 | 869 | 870 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 871 | 1 872 | 873 | 874 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 875 | 1 876 | 877 | 878 | 879 | 880 | 1 881 | 882 | 883 | 1 884 | 885 | 886 | 1 887 | 888 | 889 | 890 | 891 | 1 892 | 893 | 894 | 1 895 | 896 | 897 | 1 898 | 899 | 900 | 901 | 902 | 1 903 | 904 | 905 | 1 906 | 907 | 908 | 1 909 | 910 | 911 | 912 | 913 | 1 914 | 915 | 916 | 1 917 | 918 | 919 | 1 920 | 921 | 922 | 923 | 924 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 925 | 1 926 | 927 | 928 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 929 | 1 930 | 931 | 932 | 933 | 934 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 935 | 1 936 | 937 | 938 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 939 | 1 940 | 941 | 942 | 943 | 944 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 945 | 1 946 | 947 | 948 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 949 | 1 950 | 951 | 952 | 953 | 954 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 955 | 1 956 | 957 | 958 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 959 | 1 960 | 961 | 962 | 963 | 964 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 965 | 1 966 | 967 | 968 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 969 | 1 970 | 971 | 972 | 973 | 974 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 975 | 1 976 | 977 | 978 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 979 | 1 980 | 981 | 982 | 983 | 984 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 985 | 1 986 | 987 | 988 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 989 | 1 990 | 991 | 992 | 993 | 994 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 995 | 1 996 | 997 | 998 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 999 | 1 1000 | 1001 | 1002 | 1003 | 1004 | 1 1005 | 1006 | 1007 | 1 1008 | 1009 | 1010 | 1011 | 1012 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1013 | 1 1014 | 1015 | 1016 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1017 | 1 1018 | 1019 | 1020 | 1021 | 1022 | 1 1023 | 1024 | 1025 | 1 1026 | 1027 | 1028 | 1029 | 1030 | ..\ 1031 | 1 1032 | 1033 | 1034 | ..\ 1035 | 1 1036 | 1037 | 1038 | 1039 | 1040 | 1 1041 | 1042 | 1043 | 1 1044 | 1045 | 1046 | 1 1047 | 1048 | 1049 | 1050 | 1051 | ..\$(PROJECTNAME).launchscreen 1052 | 64 1053 | 1054 | 1055 | ..\$(PROJECTNAME).launchscreen 1056 | 64 1057 | 1058 | 1059 | 1060 | 1061 | 1 1062 | 1063 | 1064 | 1 1065 | 1066 | 1067 | 1 1068 | 1069 | 1070 | 1071 | 1072 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1073 | 1 1074 | 1075 | 1076 | 1077 | 1078 | ..\ 1079 | 1 1080 | 1081 | 1082 | ..\ 1083 | 1 1084 | 1085 | 1086 | 1087 | 1088 | Contents 1089 | 1 1090 | 1091 | 1092 | Contents 1093 | 1 1094 | 1095 | 1096 | 1097 | 1098 | Contents\Resources 1099 | 1 1100 | 1101 | 1102 | Contents\Resources 1103 | 1 1104 | 1105 | 1106 | 1107 | 1108 | library\lib\armeabi-v7a 1109 | 1 1110 | 1111 | 1112 | library\lib\arm64-v8a 1113 | 1 1114 | 1115 | 1116 | 1 1117 | 1118 | 1119 | 1 1120 | 1121 | 1122 | 1 1123 | 1124 | 1125 | 1 1126 | 1127 | 1128 | Contents\MacOS 1129 | 1 1130 | 1131 | 1132 | Contents\MacOS 1133 | 1 1134 | 1135 | 1136 | 0 1137 | 1138 | 1139 | 1140 | 1141 | library\lib\armeabi-v7a 1142 | 1 1143 | 1144 | 1145 | 1146 | 1147 | 1 1148 | 1149 | 1150 | 1 1151 | 1152 | 1153 | 1154 | 1155 | Assets 1156 | 1 1157 | 1158 | 1159 | Assets 1160 | 1 1161 | 1162 | 1163 | 1164 | 1165 | Assets 1166 | 1 1167 | 1168 | 1169 | Assets 1170 | 1 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | 1177 | 1178 | 1179 | 1180 | 1181 | 1182 | 1183 | 1184 | 1185 | True 1186 | True 1187 | 1188 | 1189 | 12 1190 | 1191 | 1192 | 1193 | 1194 | 1195 | -------------------------------------------------------------------------------- /Suspending/SuspendTool.dproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {0EB4852A-204A-410C-937C-49303424E396} 4 | 19.2 5 | None 6 | SuspendTool.dpr 7 | True 8 | Release 9 | Win64 10 | 3 11 | Console 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | Base 29 | true 30 | 31 | 32 | true 33 | Cfg_1 34 | true 35 | true 36 | 37 | 38 | true 39 | Base 40 | true 41 | 42 | 43 | ..\$(Platform)\$(Config)\dcu 44 | ..\$(Platform)\$(Config) 45 | false 46 | false 47 | false 48 | false 49 | false 50 | System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) 51 | SuspendTool 52 | ..\NtUtils;..\NtUtils\Headers;$(DCC_UnitSearchPath) 53 | 1033 54 | CompanyName=diversenok;FileDescription=A tool for suspending and freezing processes.;FileVersion=1.0.0.0;InternalName=;LegalCopyright=diversenok;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= 55 | true 56 | 3 57 | true 58 | 59 | 60 | DBXSqliteDriver;IndyIPCommon;RESTComponents;bindcompdbx;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;svnui;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;FireDAC;vcltouch;vcldb;bindcompfmx;svn;FireDACSqliteDriver;FireDACPgDriver;inetdb;VirtualTreesDR;soaprtl;DbxCommonDriver;fmx;FireDACIBDriver;fmxdae;xmlrtl;soapmidas;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;VCLRESTComponents;soapserver;dbxcds;VclSmp;adortl;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;IndyProtocols;inetdbxpress;VclExtension;FireDACCommonODBC;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage) 61 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 62 | Debug 63 | true 64 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png 65 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png 66 | (None) 67 | 68 | 69 | DBXSqliteDriver;IndyIPCommon;RESTComponents;bindcompdbx;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;FireDAC;vcltouch;vcldb;bindcompfmx;FireDACSqliteDriver;FireDACPgDriver;inetdb;VirtualTreesDR;soaprtl;DbxCommonDriver;fmx;FireDACIBDriver;fmxdae;xmlrtl;soapmidas;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;VCLRESTComponents;soapserver;dbxcds;VclSmp;adortl;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;IndyProtocols;inetdbxpress;VclExtension;FireDACCommonODBC;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage) 70 | true 71 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png 72 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png 73 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) 74 | Debug 75 | (None) 76 | 77 | 78 | DEBUG;$(DCC_Define) 79 | true 80 | false 81 | true 82 | true 83 | true 84 | true 85 | true 86 | 87 | 88 | false 89 | 90 | 91 | false 92 | RELEASE;$(DCC_Define) 93 | 0 94 | 0 95 | 96 | 97 | 98 | MainSource 99 | 100 | 101 | 102 | Cfg_2 103 | Base 104 | 105 | 106 | Base 107 | 108 | 109 | Cfg_1 110 | Base 111 | 112 | 113 | 114 | Delphi.Personality.12 115 | Application 116 | 117 | 118 | 119 | SuspendTool.dpr 120 | 121 | 122 | Microsoft Office 2000 Sample Automation Server Wrapper Components 123 | Microsoft Office XP Sample Automation Server Wrapper Components 124 | 125 | 126 | 127 | 128 | 129 | true 130 | 131 | 132 | 133 | 134 | true 135 | 136 | 137 | 138 | 139 | true 140 | 141 | 142 | 143 | 144 | 1 145 | 146 | 147 | Contents\MacOS 148 | 1 149 | 150 | 151 | 0 152 | 153 | 154 | 155 | 156 | classes 157 | 1 158 | 159 | 160 | classes 161 | 1 162 | 163 | 164 | 165 | 166 | res\xml 167 | 1 168 | 169 | 170 | res\xml 171 | 1 172 | 173 | 174 | 175 | 176 | library\lib\armeabi-v7a 177 | 1 178 | 179 | 180 | 181 | 182 | library\lib\armeabi 183 | 1 184 | 185 | 186 | library\lib\armeabi 187 | 1 188 | 189 | 190 | 191 | 192 | library\lib\armeabi-v7a 193 | 1 194 | 195 | 196 | 197 | 198 | library\lib\mips 199 | 1 200 | 201 | 202 | library\lib\mips 203 | 1 204 | 205 | 206 | 207 | 208 | library\lib\armeabi-v7a 209 | 1 210 | 211 | 212 | library\lib\arm64-v8a 213 | 1 214 | 215 | 216 | 217 | 218 | library\lib\armeabi-v7a 219 | 1 220 | 221 | 222 | 223 | 224 | res\drawable 225 | 1 226 | 227 | 228 | res\drawable 229 | 1 230 | 231 | 232 | 233 | 234 | res\values 235 | 1 236 | 237 | 238 | res\values 239 | 1 240 | 241 | 242 | 243 | 244 | res\values-v21 245 | 1 246 | 247 | 248 | res\values-v21 249 | 1 250 | 251 | 252 | 253 | 254 | res\values 255 | 1 256 | 257 | 258 | res\values 259 | 1 260 | 261 | 262 | 263 | 264 | res\drawable 265 | 1 266 | 267 | 268 | res\drawable 269 | 1 270 | 271 | 272 | 273 | 274 | res\drawable-xxhdpi 275 | 1 276 | 277 | 278 | res\drawable-xxhdpi 279 | 1 280 | 281 | 282 | 283 | 284 | res\drawable-xxxhdpi 285 | 1 286 | 287 | 288 | res\drawable-xxxhdpi 289 | 1 290 | 291 | 292 | 293 | 294 | res\drawable-ldpi 295 | 1 296 | 297 | 298 | res\drawable-ldpi 299 | 1 300 | 301 | 302 | 303 | 304 | res\drawable-mdpi 305 | 1 306 | 307 | 308 | res\drawable-mdpi 309 | 1 310 | 311 | 312 | 313 | 314 | res\drawable-hdpi 315 | 1 316 | 317 | 318 | res\drawable-hdpi 319 | 1 320 | 321 | 322 | 323 | 324 | res\drawable-xhdpi 325 | 1 326 | 327 | 328 | res\drawable-xhdpi 329 | 1 330 | 331 | 332 | 333 | 334 | res\drawable-mdpi 335 | 1 336 | 337 | 338 | res\drawable-mdpi 339 | 1 340 | 341 | 342 | 343 | 344 | res\drawable-hdpi 345 | 1 346 | 347 | 348 | res\drawable-hdpi 349 | 1 350 | 351 | 352 | 353 | 354 | res\drawable-xhdpi 355 | 1 356 | 357 | 358 | res\drawable-xhdpi 359 | 1 360 | 361 | 362 | 363 | 364 | res\drawable-xxhdpi 365 | 1 366 | 367 | 368 | res\drawable-xxhdpi 369 | 1 370 | 371 | 372 | 373 | 374 | res\drawable-xxxhdpi 375 | 1 376 | 377 | 378 | res\drawable-xxxhdpi 379 | 1 380 | 381 | 382 | 383 | 384 | res\drawable-small 385 | 1 386 | 387 | 388 | res\drawable-small 389 | 1 390 | 391 | 392 | 393 | 394 | res\drawable-normal 395 | 1 396 | 397 | 398 | res\drawable-normal 399 | 1 400 | 401 | 402 | 403 | 404 | res\drawable-large 405 | 1 406 | 407 | 408 | res\drawable-large 409 | 1 410 | 411 | 412 | 413 | 414 | res\drawable-xlarge 415 | 1 416 | 417 | 418 | res\drawable-xlarge 419 | 1 420 | 421 | 422 | 423 | 424 | res\values 425 | 1 426 | 427 | 428 | res\values 429 | 1 430 | 431 | 432 | 433 | 434 | 1 435 | 436 | 437 | Contents\MacOS 438 | 1 439 | 440 | 441 | 0 442 | 443 | 444 | 445 | 446 | Contents\MacOS 447 | 1 448 | .framework 449 | 450 | 451 | Contents\MacOS 452 | 1 453 | .framework 454 | 455 | 456 | 0 457 | 458 | 459 | 460 | 461 | 1 462 | .dylib 463 | 464 | 465 | 1 466 | .dylib 467 | 468 | 469 | 1 470 | .dylib 471 | 472 | 473 | Contents\MacOS 474 | 1 475 | .dylib 476 | 477 | 478 | Contents\MacOS 479 | 1 480 | .dylib 481 | 482 | 483 | 0 484 | .dll;.bpl 485 | 486 | 487 | 488 | 489 | 1 490 | .dylib 491 | 492 | 493 | 1 494 | .dylib 495 | 496 | 497 | 1 498 | .dylib 499 | 500 | 501 | Contents\MacOS 502 | 1 503 | .dylib 504 | 505 | 506 | Contents\MacOS 507 | 1 508 | .dylib 509 | 510 | 511 | 0 512 | .bpl 513 | 514 | 515 | 516 | 517 | 0 518 | 519 | 520 | 0 521 | 522 | 523 | 0 524 | 525 | 526 | 0 527 | 528 | 529 | 0 530 | 531 | 532 | Contents\Resources\StartUp\ 533 | 0 534 | 535 | 536 | Contents\Resources\StartUp\ 537 | 0 538 | 539 | 540 | 0 541 | 542 | 543 | 544 | 545 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 546 | 1 547 | 548 | 549 | 550 | 551 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 552 | 1 553 | 554 | 555 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 556 | 1 557 | 558 | 559 | 560 | 561 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 562 | 1 563 | 564 | 565 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 566 | 1 567 | 568 | 569 | 570 | 571 | 1 572 | 573 | 574 | 1 575 | 576 | 577 | 1 578 | 579 | 580 | 581 | 582 | 1 583 | 584 | 585 | 1 586 | 587 | 588 | 1 589 | 590 | 591 | 592 | 593 | 1 594 | 595 | 596 | 1 597 | 598 | 599 | 1 600 | 601 | 602 | 603 | 604 | 1 605 | 606 | 607 | 1 608 | 609 | 610 | 1 611 | 612 | 613 | 614 | 615 | 1 616 | 617 | 618 | 1 619 | 620 | 621 | 1 622 | 623 | 624 | 625 | 626 | 1 627 | 628 | 629 | 1 630 | 631 | 632 | 1 633 | 634 | 635 | 636 | 637 | 1 638 | 639 | 640 | 1 641 | 642 | 643 | 1 644 | 645 | 646 | 647 | 648 | 1 649 | 650 | 651 | 1 652 | 653 | 654 | 1 655 | 656 | 657 | 658 | 659 | 1 660 | 661 | 662 | 1 663 | 664 | 665 | 1 666 | 667 | 668 | 669 | 670 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 671 | 1 672 | 673 | 674 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 675 | 1 676 | 677 | 678 | 679 | 680 | 1 681 | 682 | 683 | 1 684 | 685 | 686 | 1 687 | 688 | 689 | 690 | 691 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 692 | 1 693 | 694 | 695 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 696 | 1 697 | 698 | 699 | 700 | 701 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 702 | 1 703 | 704 | 705 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 706 | 1 707 | 708 | 709 | 710 | 711 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 712 | 1 713 | 714 | 715 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 716 | 1 717 | 718 | 719 | 720 | 721 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 722 | 1 723 | 724 | 725 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 726 | 1 727 | 728 | 729 | 730 | 731 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 732 | 1 733 | 734 | 735 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 736 | 1 737 | 738 | 739 | 740 | 741 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 742 | 1 743 | 744 | 745 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 746 | 1 747 | 748 | 749 | 750 | 751 | 1 752 | 753 | 754 | 1 755 | 756 | 757 | 1 758 | 759 | 760 | 761 | 762 | 1 763 | 764 | 765 | 1 766 | 767 | 768 | 1 769 | 770 | 771 | 772 | 773 | 1 774 | 775 | 776 | 1 777 | 778 | 779 | 1 780 | 781 | 782 | 783 | 784 | 1 785 | 786 | 787 | 1 788 | 789 | 790 | 1 791 | 792 | 793 | 794 | 795 | 1 796 | 797 | 798 | 1 799 | 800 | 801 | 1 802 | 803 | 804 | 805 | 806 | 1 807 | 808 | 809 | 1 810 | 811 | 812 | 1 813 | 814 | 815 | 816 | 817 | 1 818 | 819 | 820 | 1 821 | 822 | 823 | 1 824 | 825 | 826 | 827 | 828 | 1 829 | 830 | 831 | 1 832 | 833 | 834 | 1 835 | 836 | 837 | 838 | 839 | 1 840 | 841 | 842 | 1 843 | 844 | 845 | 1 846 | 847 | 848 | 849 | 850 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 851 | 1 852 | 853 | 854 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 855 | 1 856 | 857 | 858 | 859 | 860 | 1 861 | 862 | 863 | 1 864 | 865 | 866 | 1 867 | 868 | 869 | 870 | 871 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 872 | 1 873 | 874 | 875 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 876 | 1 877 | 878 | 879 | 880 | 881 | 1 882 | 883 | 884 | 1 885 | 886 | 887 | 1 888 | 889 | 890 | 891 | 892 | 1 893 | 894 | 895 | 1 896 | 897 | 898 | 1 899 | 900 | 901 | 902 | 903 | 1 904 | 905 | 906 | 1 907 | 908 | 909 | 1 910 | 911 | 912 | 913 | 914 | 1 915 | 916 | 917 | 1 918 | 919 | 920 | 1 921 | 922 | 923 | 924 | 925 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 926 | 1 927 | 928 | 929 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 930 | 1 931 | 932 | 933 | 934 | 935 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 936 | 1 937 | 938 | 939 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 940 | 1 941 | 942 | 943 | 944 | 945 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 946 | 1 947 | 948 | 949 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 950 | 1 951 | 952 | 953 | 954 | 955 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 956 | 1 957 | 958 | 959 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 960 | 1 961 | 962 | 963 | 964 | 965 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 966 | 1 967 | 968 | 969 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 970 | 1 971 | 972 | 973 | 974 | 975 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 976 | 1 977 | 978 | 979 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 980 | 1 981 | 982 | 983 | 984 | 985 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 986 | 1 987 | 988 | 989 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 990 | 1 991 | 992 | 993 | 994 | 995 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 996 | 1 997 | 998 | 999 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1000 | 1 1001 | 1002 | 1003 | 1004 | 1005 | 1 1006 | 1007 | 1008 | 1 1009 | 1010 | 1011 | 1012 | 1013 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1014 | 1 1015 | 1016 | 1017 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1018 | 1 1019 | 1020 | 1021 | 1022 | 1023 | 1 1024 | 1025 | 1026 | 1 1027 | 1028 | 1029 | 1030 | 1031 | ..\ 1032 | 1 1033 | 1034 | 1035 | ..\ 1036 | 1 1037 | 1038 | 1039 | 1040 | 1041 | 1 1042 | 1043 | 1044 | 1 1045 | 1046 | 1047 | 1 1048 | 1049 | 1050 | 1051 | 1052 | ..\$(PROJECTNAME).launchscreen 1053 | 64 1054 | 1055 | 1056 | ..\$(PROJECTNAME).launchscreen 1057 | 64 1058 | 1059 | 1060 | 1061 | 1062 | 1 1063 | 1064 | 1065 | 1 1066 | 1067 | 1068 | 1 1069 | 1070 | 1071 | 1072 | 1073 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 1074 | 1 1075 | 1076 | 1077 | 1078 | 1079 | ..\ 1080 | 1 1081 | 1082 | 1083 | ..\ 1084 | 1 1085 | 1086 | 1087 | 1088 | 1089 | Contents 1090 | 1 1091 | 1092 | 1093 | Contents 1094 | 1 1095 | 1096 | 1097 | 1098 | 1099 | Contents\Resources 1100 | 1 1101 | 1102 | 1103 | Contents\Resources 1104 | 1 1105 | 1106 | 1107 | 1108 | 1109 | library\lib\armeabi-v7a 1110 | 1 1111 | 1112 | 1113 | library\lib\arm64-v8a 1114 | 1 1115 | 1116 | 1117 | 1 1118 | 1119 | 1120 | 1 1121 | 1122 | 1123 | 1 1124 | 1125 | 1126 | 1 1127 | 1128 | 1129 | Contents\MacOS 1130 | 1 1131 | 1132 | 1133 | Contents\MacOS 1134 | 1 1135 | 1136 | 1137 | 0 1138 | 1139 | 1140 | 1141 | 1142 | library\lib\armeabi-v7a 1143 | 1 1144 | 1145 | 1146 | 1147 | 1148 | 1 1149 | 1150 | 1151 | 1 1152 | 1153 | 1154 | 1155 | 1156 | Assets 1157 | 1 1158 | 1159 | 1160 | Assets 1161 | 1 1162 | 1163 | 1164 | 1165 | 1166 | Assets 1167 | 1 1168 | 1169 | 1170 | Assets 1171 | 1 1172 | 1173 | 1174 | 1175 | 1176 | 1177 | 1178 | 1179 | 1180 | 1181 | 1182 | 1183 | 1184 | 1185 | 1186 | True 1187 | True 1188 | 1189 | 1190 | 12 1191 | 1192 | 1193 | 1194 | 1195 | 1196 | --------------------------------------------------------------------------------