mirror of
https://github.com/tdeerenberg/InlineWhispers3.git
synced 2025-07-15 00:44:17 +00:00
307 lines
9.1 KiB
Plaintext
307 lines
9.1 KiB
Plaintext
#include "syscalls.h"
|
|
|
|
//#define DEBUG
|
|
|
|
#define JUMPER
|
|
|
|
#ifdef _M_IX86
|
|
|
|
EXTERN_C PVOID internal_cleancall_wow64_gate(VOID) {
|
|
return (PVOID)__readfsdword(0xC0);
|
|
}
|
|
|
|
__declspec(naked) BOOL local_is_wow64(void)
|
|
{
|
|
__asm {
|
|
mov eax, fs:[0xc0]
|
|
test eax, eax
|
|
jne wow64
|
|
mov eax, 0
|
|
ret
|
|
wow64:
|
|
mov eax, 1
|
|
ret
|
|
}
|
|
}
|
|
|
|
|
|
#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
|
|
|
|
SW3_SYSCALL_LIST SW3_SyscallList;
|
|
|
|
// SEARCH_AND_REPLACE
|
|
#ifdef SEARCH_AND_REPLACE
|
|
// THIS IS NOT DEFINED HERE; don't know if I'll add it in a future release
|
|
EXTERN void SearchAndReplace(unsigned char[], unsigned char[]);
|
|
#endif
|
|
|
|
DWORD SW3_HashSyscall(PCSTR FunctionName)
|
|
{
|
|
DWORD i = 0;
|
|
DWORD Hash = SW3_SEED;
|
|
|
|
while (FunctionName[i])
|
|
{
|
|
WORD PartialName = *(WORD*)((ULONG_PTR)FunctionName + i++);
|
|
Hash ^= PartialName + SW3_ROR8(Hash);
|
|
}
|
|
|
|
return Hash;
|
|
}
|
|
|
|
#ifndef JUMPER
|
|
PVOID SC_Address(PVOID NtApiAddress)
|
|
{
|
|
return NULL;
|
|
}
|
|
#else
|
|
PVOID SC_Address(PVOID NtApiAddress)
|
|
{
|
|
DWORD searchLimit = 512;
|
|
PVOID SyscallAddress;
|
|
|
|
#ifdef _WIN64
|
|
// If the process is 64-bit on a 64-bit OS, we need to search for syscall
|
|
BYTE syscall_code[] = { 0x0f, 0x05, 0xc3 };
|
|
ULONG distance_to_syscall = 0x12;
|
|
#else
|
|
// If the process is 32-bit on a 32-bit OS, we need to search for sysenter
|
|
BYTE syscall_code[] = { 0x0f, 0x34, 0xc3 };
|
|
ULONG distance_to_syscall = 0x0f;
|
|
#endif
|
|
|
|
#ifdef _M_IX86
|
|
// If the process is 32-bit on a 64-bit OS, we need to jump to WOW32Reserved
|
|
if (local_is_wow64())
|
|
{
|
|
#ifdef DEBUG
|
|
// printf("[+] Running 32-bit app on x64 (WOW64)\n");
|
|
#endif
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
// we don't really care if there is a 'jmp' between
|
|
// NtApiAddress and the 'syscall; ret' instructions
|
|
SyscallAddress = SW3_RVA2VA(PVOID, NtApiAddress, distance_to_syscall);
|
|
|
|
int match = 1; // Assume a match initially
|
|
for (size_t i = 0; i < sizeof(syscall_code); i++) {
|
|
if (((BYTE*)syscall_code)[i] != ((BYTE*)SyscallAddress)[i]) {
|
|
match = 0; // Set match to 0 if any byte doesn't match
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (match) {
|
|
// we can use the original code for this system call :)
|
|
#if defined(DEBUG)
|
|
// printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
|
#endif
|
|
return SyscallAddress;
|
|
}
|
|
|
|
// the 'syscall; ret' intructions have not been found,
|
|
// we will try to use one near it, similarly to HalosGate
|
|
|
|
for (ULONG32 num_jumps = 1; num_jumps < searchLimit; num_jumps++)
|
|
{
|
|
// let's try with an Nt* API below our syscall
|
|
SyscallAddress = SW3_RVA2VA(
|
|
PVOID,
|
|
NtApiAddress,
|
|
distance_to_syscall + num_jumps * 0x20);
|
|
match = 1; // Assume a match initially
|
|
for (size_t i = 0; i < sizeof(syscall_code); i++) {
|
|
if (((BYTE*)syscall_code)[i] != ((BYTE*)SyscallAddress)[i]) {
|
|
match = 0; // Set match to 0 if any byte doesn't match
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (match) {
|
|
// we can use the original code for this system call :)
|
|
#if defined(DEBUG)
|
|
// printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
|
#endif
|
|
return SyscallAddress;
|
|
}
|
|
|
|
// let's try with an Nt* API above our syscall
|
|
SyscallAddress = SW3_RVA2VA(
|
|
PVOID,
|
|
NtApiAddress,
|
|
distance_to_syscall - num_jumps * 0x20);
|
|
|
|
match = 1; // Assume a match initially
|
|
for (size_t i = 0; i < sizeof(syscall_code); i++) {
|
|
if (((BYTE*)syscall_code)[i] != ((BYTE*)SyscallAddress)[i]) {
|
|
match = 0; // Set match to 0 if any byte doesn't match
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (match) {
|
|
// we can use the original code for this system call :)
|
|
#if defined(DEBUG)
|
|
// printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
|
#endif
|
|
return SyscallAddress;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
// printf("Syscall Opcodes not found!\n");
|
|
#endif
|
|
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
BOOL SW3_PopulateSyscallList()
|
|
{
|
|
// Return early if the list is already populated.
|
|
if (SW3_SyscallList.Count) return TRUE;
|
|
|
|
#ifdef _WIN64
|
|
PSW3_PEB Peb = (PSW3_PEB)__readgsqword(0x60);
|
|
#else
|
|
PSW3_PEB Peb = (PSW3_PEB)__readfsdword(0x30);
|
|
#endif
|
|
PSW3_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.
|
|
PSW3_LDR_DATA_TABLE_ENTRY LdrEntry;
|
|
for (LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
|
|
{
|
|
DllBase = LdrEntry->DllBase;
|
|
PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
|
|
PIMAGE_NT_HEADERS NtHeaders = SW3_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)SW3_RVA2VA(ULONG_PTR, DllBase, VirtualAddress);
|
|
|
|
// If this is NTDLL.dll, exit loop.
|
|
PCHAR DllName = SW3_RVA2VA(PCHAR, DllBase, ExportDirectory->Name);
|
|
|
|
if ((*(ULONG*)DllName | 0x20202020) != 0x6c64746e) continue;
|
|
if ((*(ULONG*)(DllName + 4) | 0x20202020) == 0x6c642e6c) break;
|
|
}
|
|
|
|
if (!ExportDirectory) return FALSE;
|
|
|
|
DWORD NumberOfNames = ExportDirectory->NumberOfNames;
|
|
PDWORD Functions = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions);
|
|
PDWORD Names = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames);
|
|
PWORD Ordinals = SW3_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals);
|
|
|
|
// Populate SW3_SyscallList with unsorted Zw* entries.
|
|
DWORD i = 0;
|
|
PSW3_SYSCALL_ENTRY Entries = SW3_SyscallList.Entries;
|
|
do
|
|
{
|
|
PCHAR FunctionName = SW3_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);
|
|
|
|
// Is this a system call?
|
|
if (*(USHORT*)FunctionName == 0x775a)
|
|
{
|
|
Entries[i].Hash = SW3_HashSyscall(FunctionName);
|
|
Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];
|
|
Entries[i].SyscallAddress = SC_Address(SW3_RVA2VA(PVOID, DllBase, Entries[i].Address));
|
|
|
|
i++;
|
|
if (i == SW3_MAX_ENTRIES) break;
|
|
}
|
|
} while (--NumberOfNames);
|
|
|
|
// Save total number of system calls found.
|
|
SW3_SyscallList.Count = i;
|
|
|
|
// Sort the list by address in ascending order.
|
|
for (DWORD i = 0; i < SW3_SyscallList.Count - 1; i++)
|
|
{
|
|
for (DWORD j = 0; j < SW3_SyscallList.Count - i - 1; j++)
|
|
{
|
|
if (Entries[j].Address > Entries[j + 1].Address)
|
|
{
|
|
// Swap entries.
|
|
SW3_SYSCALL_ENTRY TempEntry;
|
|
|
|
TempEntry.Hash = Entries[j].Hash;
|
|
TempEntry.Address = Entries[j].Address;
|
|
TempEntry.SyscallAddress = Entries[j].SyscallAddress;
|
|
|
|
Entries[j].Hash = Entries[j + 1].Hash;
|
|
Entries[j].Address = Entries[j + 1].Address;
|
|
Entries[j].SyscallAddress = Entries[j + 1].SyscallAddress;
|
|
|
|
Entries[j + 1].Hash = TempEntry.Hash;
|
|
Entries[j + 1].Address = TempEntry.Address;
|
|
Entries[j + 1].SyscallAddress = TempEntry.SyscallAddress;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash)
|
|
{
|
|
// Ensure SW3_SyscallList is populated.
|
|
if (!SW3_PopulateSyscallList()) return -1;
|
|
|
|
for (DWORD i = 0; i < SW3_SyscallList.Count; i++)
|
|
{
|
|
if (FunctionHash == SW3_SyscallList.Entries[i].Hash)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash)
|
|
{
|
|
// Ensure SW3_SyscallList is populated.
|
|
if (!SW3_PopulateSyscallList()) return NULL;
|
|
|
|
for (DWORD i = 0; i < SW3_SyscallList.Count; i++)
|
|
{
|
|
if (FunctionHash == SW3_SyscallList.Entries[i].Hash)
|
|
{
|
|
return SW3_SyscallList.Entries[i].SyscallAddress;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
DWORD LCG() {
|
|
static DWORD seed = 123987654;
|
|
return (seed >> 16) & 0x7FFF; // return a pseudo-random number
|
|
}
|
|
|
|
EXTERN_C PVOID SW3_GetRandomSyscallAddress(DWORD FunctionHash)
|
|
{
|
|
// Ensure SW3_SyscallList is populated.
|
|
if (!SW3_PopulateSyscallList()) return NULL;
|
|
|
|
DWORD index = LCG() % SW3_SyscallList.Count;
|
|
|
|
while (FunctionHash == SW3_SyscallList.Entries[index].Hash){
|
|
// Spoofing the syscall return address
|
|
index = LCG() % SW3_SyscallList.Count;
|
|
}
|
|
return SW3_SyscallList.Entries[index].SyscallAddress;
|
|
}
|