|
|
@@ -0,0 +1,178 @@
|
|
|
+#include "syscalls.h"
|
|
|
+#include <time.h>
|
|
|
+#include <stdint.h>
|
|
|
+
|
|
|
+// 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 };
|
|
|
+
|
|
|
+#ifdef RANDSYSCALL
|
|
|
+#ifndef _WIN64
|
|
|
+uint32_t ntdllBase = 0;
|
|
|
+#else
|
|
|
+uint64_t ntdllBase = 0;
|
|
|
+#endif
|
|
|
+#endif
|
|
|
+
|
|
|
+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(void)
|
|
|
+{
|
|
|
+ // Return early if the list is already populated.
|
|
|
+ if (SW2_SyscallList.Count) return TRUE;
|
|
|
+
|
|
|
+#if defined(_WIN64)
|
|
|
+ PSW2_PEB Peb = (PSW2_PEB)__readgsqword(0x60);
|
|
|
+#else
|
|
|
+ PSW2_PEB Peb = (PSW2_PEB)__readfsdword(0x30);
|
|
|
+#endif
|
|
|
+ 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;
|
|
|
+
|
|
|
+#ifdef RANDSYSCALL
|
|
|
+#ifdef _WIN64
|
|
|
+ ntdllBase = (uint64_t)DllBase;
|
|
|
+#else
|
|
|
+ ntdllBase = (uint64_t)DllBase;
|
|
|
+#endif
|
|
|
+#endif
|
|
|
+
|
|
|
+ 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 (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;
|
|
|
+}
|
|
|
+
|
|
|
+#ifdef RANDSYSCALL
|
|
|
+#ifdef _WIN64
|
|
|
+EXTERN_C uint64_t SW2_GetRandomSyscallAddress(void)
|
|
|
+#else
|
|
|
+EXTERN_C DWORD SW2_GetRandomSyscallAddress(int callType)
|
|
|
+#endif
|
|
|
+{
|
|
|
+ int instructOffset = 0;
|
|
|
+ int instructValue = 0;
|
|
|
+#ifndef _WIN64
|
|
|
+ // Wow64
|
|
|
+ if (callType == 0)
|
|
|
+ {
|
|
|
+ instructOffset = 0x05;
|
|
|
+ instructValue = 0x0E8;
|
|
|
+ }
|
|
|
+ // x86
|
|
|
+ else if (callType == 1)
|
|
|
+ {
|
|
|
+ instructOffset = 0x05;
|
|
|
+ instructValue = 0x0BA;
|
|
|
+ }
|
|
|
+#else
|
|
|
+ instructOffset = 0x12;
|
|
|
+ instructValue = 0x0F;
|
|
|
+#endif
|
|
|
+ srand(time(0));
|
|
|
+ do
|
|
|
+ {
|
|
|
+ int randNum = (rand() % (SW2_SyscallList.Count + 1));
|
|
|
+ if (*(unsigned char*)(ntdllBase + SW2_SyscallList.Entries[randNum].Address + instructOffset) == instructValue)
|
|
|
+ return (ntdllBase + SW2_SyscallList.Entries[randNum].Address + instructOffset);
|
|
|
+ } while(1);
|
|
|
+}
|
|
|
+#endif
|