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/