mirror of
https://github.com/bats3c/DarkLoadLibrary
synced 2026-06-06 15:14:33 +00:00
add NtAllocateVirtualMemory and NtProtectVirtualMemory syscalls
I used SysWhispers2, so it should work for all versions of Windows. This is of course to bypass userland hooks. VirtualProtect is likely to get denied by an EDR
This commit is contained in:
@@ -152,12 +152,15 @@
|
||||
<ClCompile Include="src\ldrutils.c" />
|
||||
<ClCompile Include="src\main.c" />
|
||||
<ClCompile Include="src\pebutils.c" />
|
||||
<ClCompile Include="src\syscalls.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="include\darkloadlibrary.h" />
|
||||
<ClInclude Include="include\ldrutils.h" />
|
||||
<ClInclude Include="include\pebstructs.h" />
|
||||
<ClInclude Include="include\pebutils.h" />
|
||||
<ClInclude Include="include\syscalls.h" />
|
||||
<ClInclude Include="include\syscallsstubs.asm" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
|
||||
@@ -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 <Windows.h>
|
||||
|
||||
#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
|
||||
@@ -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
|
||||
@@ -36,24 +36,30 @@ BOOL MapSections(
|
||||
);
|
||||
|
||||
// try get prefered address
|
||||
pdModule->ModuleBase = (ULONG_PTR)VirtualAlloc(
|
||||
(LPVOID)(pNtHeaders->OptionalHeader.ImageBase),
|
||||
(SIZE_T)pNtHeaders->OptionalHeader.SizeOfImage,
|
||||
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 (!pdModule->ModuleBase)
|
||||
);
|
||||
if (!NT_SUCCESS(status) || pdModule->ModuleBase != pNtHeaders->OptionalHeader.ImageBase)
|
||||
{
|
||||
pdModule->ModuleBase = (ULONG_PTR)VirtualAlloc(
|
||||
pdModule->ModuleBase = NULL;
|
||||
RegionSize = pNtHeaders->OptionalHeader.SizeOfImage;
|
||||
status = NtAllocateVirtualMemory(
|
||||
(HANDLE)-1,
|
||||
&pdModule->ModuleBase,
|
||||
0,
|
||||
(SIZE_T)pNtHeaders->OptionalHeader.SizeOfImage,
|
||||
&RegionSize,
|
||||
MEM_RESERVE | MEM_COMMIT,
|
||||
PAGE_READWRITE
|
||||
);
|
||||
}
|
||||
|
||||
if (!pdModule->ModuleBase)
|
||||
if (!NT_SUCCESS(status))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
@@ -342,13 +348,19 @@ BOOL BeginExecution(
|
||||
{
|
||||
dwProtect |= PAGE_NOCACHE;
|
||||
}
|
||||
|
||||
VirtualProtect(
|
||||
(LPVOID)(pdModule->ModuleBase + pSectionHeader->VirtualAddress),
|
||||
pSectionHeader->SizeOfRawData,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include "darkloadlibrary.h"
|
||||
#include "syscalls.h"
|
||||
|
||||
typedef DWORD (WINAPI * _ThisIsAFunction) (LPCWSTR);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user