-
Notifications
You must be signed in to change notification settings - Fork 38
/
Copy pathNtUtils.Processes.Info.Remote.pas
254 lines (205 loc) · 6.9 KB
/
NtUtils.Processes.Info.Remote.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
unit NtUtils.Processes.Info.Remote;
{
This module provides support for querying/setting process's information in the
context of the target process.
}
interface
uses
Ntapi.ntpsapi, NtUtils, NtUtils.Shellcode;
const
PROCESS_QUERY_SECTION = PROCESS_REMOTE_EXECUTE or PROCESS_DUP_HANDLE;
PROCESS_SET_INSTRUMENTATION = PROCESS_REMOTE_EXECUTE or
PROCESS_SET_INFORMATION;
// Open image section for a process even if the file was deleted
function NtxQuerySectionProcess(
out hxSection: IHandle;
[Access(PROCESS_QUERY_SECTION)] const hxProcess: IHandle;
const Timeout: Int64 = DEFAULT_REMOTE_TIMEOUT
): TNtxStatus;
// Set an instrumentation callback to execute every time process's threads
// return to user mode. Does not require the Debug Privilege since we
// are setting it via shellcode. Use "jmp r10" to return from the callback.
function NtxSetInstrumentationProcess(
[Access(PROCESS_SET_INSTRUMENTATION)] const hxProcess: IHandle;
CallbackAddress: Pointer;
const Timeout: Int64 = DEFAULT_REMOTE_TIMEOUT
): TNtxStatus;
implementation
uses
Ntapi.ntdef, Ntapi.ntstatus, Ntapi.ntobapi, NtUtils.Processes.Info,
NtUtils.Objects, DelphiUtils.AutoObjects;
{$BOOLEVAL OFF}
{$IFOPT R+}{$DEFINE R+}{$ENDIF}
{$IFOPT Q+}{$DEFINE Q+}{$ENDIF}
{ Image Section }
type
TSectionQueryContext = record
NtQueryInformationProcess: function (
ProcessHandle: THandle;
ProcessInformationClass: TProcessInfoClass;
ProcessInformation: Pointer;
ProcessInformationLength: Cardinal;
ReturnLength: PCardinal
): NTSTATUS; stdcall;
{$IFDEF Win32}WoW64Padding1: Cardinal;{$ENDIF}
hSection: THandle;
{$IFDEF Win32}WoW64Padding2: Cardinal;{$ENDIF}
end;
PSectionQueryContext = ^TSectionQueryContext;
// Function to execute remotely; keep in sync with assembly below
function QuerySectionRemote(Context: PSectionQueryContext): NTSTATUS; stdcall;
begin
Result := Context.NtQueryInformationProcess(NtCurrentProcess,
ProcessImageSection, @Context.hSection, SizeOf(Context.hSection), nil);
end;
const
// Raw assembly for injection; keep in sync with function above
{$IFDEF Win64}
QuerySectionAsm64: array [0 .. 47] of Byte = (
$48, $83, $EC, $28, $48, $89, $C8, $48, $83, $C9, $FF, $BA, $59, $00, $00,
$00, $4C, $8D, $40, $08, $41, $B9, $08, $00, $00, $00, $48, $C7, $44, $24,
$20, $00, $00, $00, $00, $FF, $10, $48, $83, $C4, $28, $C3, $CC, $CC, $CC,
$CC, $CC, $CC
);
{$ENDIF}
QuerySectionAsm32: array [0 .. 23] of Byte = (
$55, $8B, $EC, $8B, $45, $08, $6A, $00, $6A, $04, $8D, $50, $08, $52, $6A,
$59, $6A, $FF, $FF, $10, $5D, $C2, $04, $00
);
function NtxQuerySectionProcess;
var
TargetIsWoW64: Boolean;
CodeRef: TMemory;
LocalMapping: IMemory<PSectionQueryContext>;
RemoteMemory: IMemory;
begin
// Prevent WoW64 -> Native injection
Result := RtlxAssertWoW64Compatible(hxProcess, TargetIsWoW64);
if not Result.IsSuccess then
Exit;
{$IFDEF Win64}
if not TargetIsWoW64 then
CodeRef := TMemory.Reference(QuerySectionAsm64)
else
{$ENDIF}
CodeRef := TMemory.Reference(QuerySectionAsm32);
// Map shared memory with the target
Result := RtlxMapSharedMemory(hxProcess, SizeOf(TSectionQueryContext) +
CodeRef.Size, IMemory(LocalMapping), RemoteMemory, [mmAllowWrite,
mmAllowExecute]);
if not Result.IsSuccess then
Exit;
// Find dependencies
Result := RtlxFindKnownDllExport(ntdll, TargetIsWoW64,
'NtQueryInformationProcess', @LocalMapping.Data.NtQueryInformationProcess);
if not Result.IsSuccess then
Exit;
Move(CodeRef.Address^, LocalMapping.Offset(SizeOf(TSectionQueryContext))^,
CodeRef.Size);
Result := RtlxRemoteExecute(
hxProcess,
'Remote::NtQueryInformationProcess',
RemoteMemory.Offset(SizeOf(TSectionQueryContext)),
CodeRef.Size,
RemoteMemory.Data,
THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH,
Timeout,
[RemoteMemory]
);
if not Result.IsSuccess then
Exit;
// Copy the handle back
Result := NtxDuplicateHandleFrom(hxProcess, LocalMapping.Data.hSection,
hxSection, DUPLICATE_SAME_ACCESS or DUPLICATE_CLOSE_SOURCE);
end;
{ Instrumentation Callback }
// We only support 64-bits for now
{$IFDEF Win64}
type
TInstrumentationSetContext = record
NtSetInformationProcess: function (
ProcessHandle: THandle;
ProcessInformationClass: TProcessInfoClass;
[in] ProcessInformation: Pointer;
ProcessInformationLength: Cardinal
): NTSTATUS; stdcall;
Callback: Pointer;
end;
PInstrumentationSetContext = ^TInstrumentationSetContext;
// A function to execute the context of the target
function InstrumentationSetter(
Context: PInstrumentationSetContext
): NTSTATUS; stdcall;
begin
Result := Context.NtSetInformationProcess(
NtCurrentProcess,
ProcessInstrumentationCallback,
@Context.Callback,
SizeOf(Context.Callback)
);
end;
const
InstrumentationSetter64: array [0 .. 39] of Byte = (
$48, $83, $EC, $28, $48, $89, $C8, $48, $83, $C9, $FF, $BA, $28, $00, $00,
$00, $4C, $8D, $40, $08, $41, $B9, $08, $00, $00, $00, $FF, $10, $48, $83,
$C4, $28, $C3, $CC, $CC, $CC, $CC, $CC, $CC, $CC
);
function NtxSetInstrumentationProcess;
var
TargetIsWoW64: Boolean;
CodeRef: TMemory;
LocalMapping: IMemory<PInstrumentationSetContext>;
RemoteMemory: IMemory;
begin
Result := NtxQueryIsWoW64Process(hxProcess, TargetIsWoW64);
if not Result.IsSuccess then
Exit;
if TargetIsWoW64 then
begin
Result.Location := 'NtxSetInstrumentationProcess';
Result.Status := STATUS_NOT_SUPPORTED;
Exit;
end;
// Try setting it directly (requires Debug Privilege)
Result := NtxProcess.Set(hxProcess, ProcessInstrumentationCallback,
CallbackAddress);
if Result.IsSuccess then
Exit;
// It did not work; fallback to injecting a thread to do it on the target's
// behalf
CodeRef := TMemory.Reference(InstrumentationSetter64);
// Map shared memory with the target
Result := RtlxMapSharedMemory(hxProcess, SizeOf(TInstrumentationSetContext) +
CodeRef.Size, IMemory(LocalMapping), RemoteMemory, [mmAllowExecute]);
if not Result.IsSuccess then
Exit;
// Find dependencies
Result := RtlxFindKnownDllExport(ntdll, TargetIsWoW64,
'NtSetInformationProcess', @LocalMapping.Data.NtSetInformationProcess);
if not Result.IsSuccess then
Exit;
// Copy the data and the code
LocalMapping.Data.Callback := CallbackAddress;
Move(CodeRef.Address^, LocalMapping.Offset(
SizeOf(TInstrumentationSetContext))^, CodeRef.Size);
// Execute and wait
Result := RtlxRemoteExecute(
hxProcess,
'Remote::NtSetInformationProcess',
RemoteMemory.Offset(SizeOf(TInstrumentationSetContext)),
CodeRef.Size,
RemoteMemory.Data,
THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH,
Timeout,
[RemoteMemory]
);
end;
{$ELSE}
function NtxSetInstrumentationProcess;
begin
// Maybe add 32-bit support later
Result.Location := 'NtxSetInstrumentationProcess';
Result.Status := STATUS_NOT_SUPPORTED;
end;
{$ENDIF}
end.