diff --git a/DarkLoadLibrary/DarkLoadLibrary.vcxproj b/DarkLoadLibrary/DarkLoadLibrary.vcxproj index 0f5324b..50d5e2b 100644 --- a/DarkLoadLibrary/DarkLoadLibrary.vcxproj +++ b/DarkLoadLibrary/DarkLoadLibrary.vcxproj @@ -56,6 +56,7 @@ + @@ -152,14 +153,20 @@ + + + Document + + + \ No newline at end of file diff --git a/DarkLoadLibrary/DarkLoadLibrary.vcxproj.filters b/DarkLoadLibrary/DarkLoadLibrary.vcxproj.filters index 8dead88..216d072 100644 --- a/DarkLoadLibrary/DarkLoadLibrary.vcxproj.filters +++ b/DarkLoadLibrary/DarkLoadLibrary.vcxproj.filters @@ -27,6 +27,9 @@ Source Files + + Source Files + @@ -41,5 +44,13 @@ Header Files + + Header Files + + + + + Source Files + \ No newline at end of file diff --git a/DarkLoadLibrary/include/syscalls.h b/DarkLoadLibrary/include/syscalls.h new file mode 100644 index 0000000..a28a305 --- /dev/null +++ b/DarkLoadLibrary/include/syscalls.h @@ -0,0 +1,72 @@ +#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 SW2_HEADER_H_ +#define SW2_HEADER_H_ + +#include + +#define SW2_SEED 0x63191199 +#define SW2_ROL8(v) (v << 8 | v >> 24) +#define SW2_ROR8(v) (v >> 8 | v << 24) +#define SW2_ROX8(v) ((SW2_SEED % 2) ? SW2_ROL8(v) : SW2_ROR8(v)) +#define SW2_MAX_ENTRIES 500 +#define SW2_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva) + +// Typedefs are prefixed to avoid pollution. + +typedef struct _SW2_SYSCALL_ENTRY +{ + DWORD Hash; + DWORD Address; +} SW2_SYSCALL_ENTRY, *PSW2_SYSCALL_ENTRY; + +typedef struct _SW2_SYSCALL_LIST +{ + DWORD Count; + SW2_SYSCALL_ENTRY Entries[SW2_MAX_ENTRIES]; +} SW2_SYSCALL_LIST, *PSW2_SYSCALL_LIST; + +typedef struct _SW2_PEB_LDR_DATA { + BYTE Reserved1[8]; + PVOID Reserved2[3]; + LIST_ENTRY InMemoryOrderModuleList; +} SW2_PEB_LDR_DATA, *PSW2_PEB_LDR_DATA; + +typedef struct _SW2_LDR_DATA_TABLE_ENTRY { + PVOID Reserved1[2]; + LIST_ENTRY InMemoryOrderLinks; + PVOID Reserved2[2]; + PVOID DllBase; +} SW2_LDR_DATA_TABLE_ENTRY, *PSW2_LDR_DATA_TABLE_ENTRY; + +typedef struct _SW2_PEB { + BYTE Reserved1[2]; + BYTE BeingDebugged; + BYTE Reserved2[1]; + PVOID Reserved3[2]; + PSW2_PEB_LDR_DATA Ldr; +} SW2_PEB, *PSW2_PEB; + +DWORD SW2_HashSyscall(PCSTR FunctionName); +BOOL SW2_PopulateSyscallList(); +EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash); + +EXTERN_C NTSTATUS NtProtectVirtualMemory( + IN HANDLE ProcessHandle, + IN OUT PVOID * BaseAddress, + IN OUT PSIZE_T RegionSize, + IN ULONG NewProtect, + OUT PULONG OldProtect); + +EXTERN_C NTSTATUS NtAllocateVirtualMemory( + IN HANDLE ProcessHandle, + IN OUT PVOID * BaseAddress, + IN ULONG ZeroBits, + IN OUT PSIZE_T RegionSize, + IN ULONG AllocationType, + IN ULONG Protect); + +#endif diff --git a/DarkLoadLibrary/src/ldrutils.c b/DarkLoadLibrary/src/ldrutils.c index 52e44b5..fc0e4c0 100644 --- a/DarkLoadLibrary/src/ldrutils.c +++ b/DarkLoadLibrary/src/ldrutils.c @@ -1,4 +1,7 @@ #include "ldrutils.h" +#if _M_X64 +#include "syscalls.h" +#endif BOOL IsValidPE( PBYTE pbData @@ -7,8 +10,8 @@ BOOL IsValidPE( PIMAGE_NT_HEADERS pNtHeaders; pNtHeaders = RVA( - PIMAGE_NT_HEADERS, - pbData, + PIMAGE_NT_HEADERS, + pbData, ((PIMAGE_DOS_HEADER)pbData)->e_lfanew ); @@ -30,12 +33,41 @@ BOOL MapSections( PIMAGE_SECTION_HEADER pSectionHeader; pNtHeaders = RVA( - PIMAGE_NT_HEADERS, - pdModule->pbDllData, + PIMAGE_NT_HEADERS, + pdModule->pbDllData, ((PIMAGE_DOS_HEADER)pdModule->pbDllData)->e_lfanew ); // try get prefered address +#if _M_X64 + pdModule->ModuleBase = pNtHeaders->OptionalHeader.ImageBase; + SIZE_T RegionSize = pNtHeaders->OptionalHeader.SizeOfImage; + NTSTATUS status = NtAllocateVirtualMemory( + (HANDLE)-1, + &pdModule->ModuleBase, + 0, + &RegionSize, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE + ); + if (!NT_SUCCESS(status) || pdModule->ModuleBase != pNtHeaders->OptionalHeader.ImageBase) + { + pdModule->ModuleBase = NULL; + RegionSize = pNtHeaders->OptionalHeader.SizeOfImage; + status = NtAllocateVirtualMemory( + (HANDLE)-1, + &pdModule->ModuleBase, + 0, + &RegionSize, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE + ); + } + if (!NT_SUCCESS(status)) + { + return FALSE; + } +#else pdModule->ModuleBase = (ULONG_PTR)VirtualAlloc( (LPVOID)(pNtHeaders->OptionalHeader.ImageBase), (SIZE_T)pNtHeaders->OptionalHeader.SizeOfImage, @@ -57,7 +89,7 @@ BOOL MapSections( { return FALSE; } - +#endif // copy across the headers for (INT i = 0; i < pNtHeaders->OptionalHeader.SizeOfHeaders; i++) { @@ -82,8 +114,8 @@ BOOL MapSections( if ((pdModule->ModuleBase - pNtHeaders->OptionalHeader.ImageBase) && pDataDir) { pRelocation = RVA( - PIMAGE_BASE_RELOCATION, - pdModule->ModuleBase, + PIMAGE_BASE_RELOCATION, + pdModule->ModuleBase, pDataDir->VirtualAddress ); @@ -137,8 +169,8 @@ BOOL ResolveImports( LDRGETPROCADDRESS pLdrGetProcAddress = NULL; pNtHeaders = RVA( - PIMAGE_NT_HEADERS, - pdModule->pbDllData, + PIMAGE_NT_HEADERS, + pdModule->pbDllData, ((PIMAGE_DOS_HEADER)pdModule->pbDllData)->e_lfanew ); @@ -158,8 +190,8 @@ BOOL ResolveImports( if (pDataDir->Size) { pImportDesc = RVA( - PIMAGE_IMPORT_DESCRIPTOR, - pdModule->ModuleBase, + PIMAGE_IMPORT_DESCRIPTOR, + pdModule->ModuleBase, pDataDir->VirtualAddress ); @@ -172,7 +204,7 @@ BOOL ResolveImports( pImportDesc = RVA( PIMAGE_IMPORT_DESCRIPTOR, - pdModule->ModuleBase, + pdModule->ModuleBase, pDataDir->VirtualAddress ); @@ -185,14 +217,14 @@ BOOL ResolveImports( ); pFirstThunk = RVA( - PIMAGE_THUNK_DATA, - pdModule->ModuleBase, + PIMAGE_THUNK_DATA, + pdModule->ModuleBase, pImportDesc->FirstThunk ); - + pOrigFirstThunk = RVA( - PIMAGE_THUNK_DATA, - pdModule->ModuleBase, + PIMAGE_THUNK_DATA, + pdModule->ModuleBase, pImportDesc->OriginalFirstThunk ); @@ -201,30 +233,30 @@ BOOL ResolveImports( if (IMAGE_SNAP_BY_ORDINAL(pOrigFirstThunk->u1.Ordinal)) { pLdrGetProcAddress( - hLibrary, - NULL, - (WORD)pOrigFirstThunk->u1.Ordinal, - (PVOID *)&(pFirstThunk->u1.Function) + hLibrary, + NULL, + (WORD)pOrigFirstThunk->u1.Ordinal, + (PVOID*)&(pFirstThunk->u1.Function) ); } else { pImportByName = RVA( - PIMAGE_IMPORT_BY_NAME, - pdModule->ModuleBase, + PIMAGE_IMPORT_BY_NAME, + pdModule->ModuleBase, pOrigFirstThunk->u1.AddressOfData ); - + FILL_STRING( - aString, + aString, pImportByName->Name ); pLdrGetProcAddress( - hLibrary, - &aString, - 0, - (PVOID *)&(pFirstThunk->u1.Function) + hLibrary, + &aString, + 0, + (PVOID*)&(pFirstThunk->u1.Function) ); } } @@ -237,8 +269,8 @@ BOOL ResolveImports( if (pDataDir->Size) { pDelayDesc = RVA( - PIMAGE_DELAYLOAD_DESCRIPTOR, - pdModule->ModuleBase, + PIMAGE_DELAYLOAD_DESCRIPTOR, + pdModule->ModuleBase, pDataDir->VirtualAddress ); @@ -249,14 +281,14 @@ BOOL ResolveImports( HMODULE hLibrary = LoadLibraryA((LPSTR)(pdModule->ModuleBase + pDelayDesc->DllNameRVA)); pFirstThunk = RVA( - PIMAGE_THUNK_DATA, - pdModule->ModuleBase, + PIMAGE_THUNK_DATA, + pdModule->ModuleBase, pDelayDesc->ImportAddressTableRVA ); - + pOrigFirstThunk = RVA( - PIMAGE_THUNK_DATA, - pdModule->ModuleBase, + PIMAGE_THUNK_DATA, + pdModule->ModuleBase, pDelayDesc->ImportNameTableRVA ); @@ -265,30 +297,30 @@ BOOL ResolveImports( if (IMAGE_SNAP_BY_ORDINAL(pOrigFirstThunk->u1.Ordinal)) { pLdrGetProcAddress( - hLibrary, - NULL, - (WORD)pOrigFirstThunk->u1.Ordinal, - (PVOID *)&(pFirstThunk->u1.Function) + hLibrary, + NULL, + (WORD)pOrigFirstThunk->u1.Ordinal, + (PVOID*)&(pFirstThunk->u1.Function) ); } else { pImportByName = RVA( - PIMAGE_IMPORT_BY_NAME, - pdModule->ModuleBase, + PIMAGE_IMPORT_BY_NAME, + pdModule->ModuleBase, pOrigFirstThunk->u1.AddressOfData ); - + FILL_STRING( - aString, + aString, pImportByName->Name ); pLdrGetProcAddress( - hLibrary, - &aString, - 0, - (PVOID *)&(pFirstThunk->u1.Function) + hLibrary, + &aString, + 0, + (PVOID*)&(pFirstThunk->u1.Function) ); } } @@ -313,8 +345,8 @@ BOOL BeginExecution( DLLMAIN DllMain = NULL; pNtHeaders = RVA( - PIMAGE_NT_HEADERS, - pdModule->pbDllData, + PIMAGE_NT_HEADERS, + pdModule->pbDllData, ((PIMAGE_DOS_HEADER)pdModule->pbDllData)->e_lfanew ); @@ -342,13 +374,28 @@ BOOL BeginExecution( { dwProtect |= PAGE_NOCACHE; } - +#if _M_X64 + PVOID BaseAddress = pdModule->ModuleBase + pSectionHeader->VirtualAddress; + SIZE_T RegionSize = pSectionHeader->SizeOfRawData; + NTSTATUS status = NtProtectVirtualMemory( + (HANDLE)-1, + &BaseAddress, + &RegionSize, + dwProtect, + &dwProtect + ); + if (!NT_SUCCESS(status)) + { + return FALSE; + } +#else VirtualProtect( (LPVOID)(pdModule->ModuleBase + pSectionHeader->VirtualAddress), pSectionHeader->SizeOfRawData, dwProtect, &dwProtect ); +#endif } } @@ -361,12 +408,12 @@ BOOL BeginExecution( if (pDataDir->Size) { pTlsDir = RVA( - PIMAGE_TLS_DIRECTORY, - pdModule->ModuleBase, + PIMAGE_TLS_DIRECTORY, + pdModule->ModuleBase, pDataDir->VirtualAddress ); - ppCallback = (PIMAGE_TLS_CALLBACK *)(pTlsDir->AddressOfCallBacks); + ppCallback = (PIMAGE_TLS_CALLBACK*)(pTlsDir->AddressOfCallBacks); for (; *ppCallback; ppCallback++) { diff --git a/DarkLoadLibrary/src/syscalls.c b/DarkLoadLibrary/src/syscalls.c new file mode 100644 index 0000000..63303ea --- /dev/null +++ b/DarkLoadLibrary/src/syscalls.c @@ -0,0 +1,120 @@ +#include "syscalls.h" + +// 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 + +SW2_SYSCALL_LIST SW2_SyscallList; + +DWORD SW2_HashSyscall(PCSTR FunctionName) +{ + DWORD i = 0; + DWORD Hash = SW2_SEED; + + while (FunctionName[i]) + { + WORD PartialName = *(WORD*)((ULONG64)FunctionName + i++); + Hash ^= PartialName + SW2_ROR8(Hash); + } + + return Hash; +} + +BOOL SW2_PopulateSyscallList() +{ + // Return early if the list is already populated. + if (SW2_SyscallList.Count) return TRUE; + + PSW2_PEB Peb = (PSW2_PEB)__readgsqword(0x60); + PSW2_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. + PSW2_LDR_DATA_TABLE_ENTRY LdrEntry; + for (LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0]) + { + DllBase = LdrEntry->DllBase; + PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase; + PIMAGE_NT_HEADERS NtHeaders = SW2_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)SW2_RVA2VA(ULONG_PTR, DllBase, VirtualAddress); + + // If this is NTDLL.dll, exit loop. + PCHAR DllName = SW2_RVA2VA(PCHAR, DllBase, ExportDirectory->Name); + + if ((*(ULONG*)DllName | 0x20202020) != 'ldtn') continue; + if ((*(ULONG*)(DllName + 4) | 0x20202020) == 'ld.l') break; + } + + if (!ExportDirectory) return FALSE; + + DWORD NumberOfNames = ExportDirectory->NumberOfNames; + PDWORD Functions = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions); + PDWORD Names = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames); + PWORD Ordinals = SW2_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals); + + // Populate SW2_SyscallList with unsorted Zw* entries. + DWORD i = 0; + PSW2_SYSCALL_ENTRY Entries = SW2_SyscallList.Entries; + do + { + PCHAR FunctionName = SW2_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]); + + // Is this a system call? + if (*(USHORT*)FunctionName == 'wZ') + { + Entries[i].Hash = SW2_HashSyscall(FunctionName); + Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]]; + + i++; + if (i == SW2_MAX_ENTRIES) break; + } + } while (--NumberOfNames); + + // Save total number of system calls found. + SW2_SyscallList.Count = i; + + // Sort the list by address in ascending order. + for (DWORD i = 0; i < SW2_SyscallList.Count - 1; i++) + { + for (DWORD j = 0; j < SW2_SyscallList.Count - i - 1; j++) + { + if (Entries[j].Address > Entries[j + 1].Address) + { + // Swap entries. + SW2_SYSCALL_ENTRY TempEntry; + + TempEntry.Hash = Entries[j].Hash; + TempEntry.Address = Entries[j].Address; + + Entries[j].Hash = Entries[j + 1].Hash; + Entries[j].Address = Entries[j + 1].Address; + + Entries[j + 1].Hash = TempEntry.Hash; + Entries[j + 1].Address = TempEntry.Address; + } + } + } + + return TRUE; +} + +EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash) +{ + // Ensure SW2_SyscallList is populated. + if (!SW2_PopulateSyscallList()) return -1; + + for (DWORD i = 0; i < SW2_SyscallList.Count; i++) + { + if (FunctionHash == SW2_SyscallList.Entries[i].Hash) + { + return i; + } + } + + return -1; +} diff --git a/DarkLoadLibrary/src/syscallsstubs.asm b/DarkLoadLibrary/src/syscallsstubs.asm new file mode 100644 index 0000000..3a96085 --- /dev/null +++ b/DarkLoadLibrary/src/syscallsstubs.asm @@ -0,0 +1,41 @@ +.code + +EXTERN SW2_GetSyscallNumber: PROC + +NtProtectVirtualMemory PROC + mov [rsp +8], rcx ; Save registers. + mov [rsp+16], rdx + mov [rsp+24], r8 + mov [rsp+32], r9 + sub rsp, 28h + mov ecx, 0079D1B09h ; Load function hash into ECX. + call SW2_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 + syscall ; Invoke system call. + ret +NtProtectVirtualMemory ENDP + +NtAllocateVirtualMemory PROC + mov [rsp +8], rcx ; Save registers. + mov [rsp+16], rdx + mov [rsp+24], r8 + mov [rsp+32], r9 + sub rsp, 28h + mov ecx, 00B9D010Fh ; Load function hash into ECX. + call SW2_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 + syscall ; Invoke system call. + ret +NtAllocateVirtualMemory ENDP + +end \ No newline at end of file diff --git a/README.md b/README.md index b4829db..2353cd1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ `LoadLibrary` for offensive operations. -### How does is work? +### How does it work? https://www.mdsec.co.uk/2021/06/bypassing-image-load-kernel-callbacks/