NASM - win64调用ExitProcess不用提供阴影区的原因

NASM - win64调用ExitProcess不用提供阴影区的原因

概述

通常来说,win64位程序调用API时,必须提供阴影区(shadow space), 这是win64 API调用的约定。
但是发现一个特例 ExitProcess()

.Exit:
 sub RSP, 32 ; 加上阴影区再调用API好一些,也没有负面影响
 xor   ECX, ECX
 call  ExitProcess ; 退出的函数是API, 为什么不做栈平衡, 是ExitProcess内部不用阴影区么? ExitProcess是特例?
 add RSP, 32

调用ExitProcess时,原来的例子是没有阴影区的。
尝试在调用ExitProcess的前后,加上阴影区,如上代码。
但是编译完, 用IDA看exe,调用ExitProcess的后面,原来加的 add RSP, 32 被优化掉了, golink干的?
用IDA看obj, 调用后,也没有恢复栈平衡的代码,原来是被NASM优化掉了。

// 这是obj的反汇编
fn_Start_Exit:
sub     rsp, 20h
xor     ecx, ecx
call    ExitProcess
; // 这里没有自己加的恢复栈的代码了
fn_Start endp

看来,不用加阴影区, ExitProcess就可以正常工作。
那还是恢复原来的实现,如下:

.Exit:
 xor   ECX, ECX
 call  ExitProcess ; 退出的函数是API, 为什么不做栈平衡, 是ExitProcess内部不用阴影区么? ExitProcess是特例?

用IDA看反汇编如下

public start
start proc near
sub     rsp, 8
sub     rsp, 20h
xor     ecx, ecx        ; lpModuleName
call    GetModuleHandleA
mov     cs:hInstance, rax
add     rsp, 20h
call    sub_401026
xor     ecx, ecx        ; uExitCode
call    ExitProcess ; // break here
start endp

在 call ExitProcess处下断点,单步调试一下,看看为啥调用ExitProcess不用给阴影区。

笔记

.text:0000000000401000 ; Attributes: noreturn
.text:0000000000401000
.text:0000000000401000 public start
.text:0000000000401000 start proc near
.text:0000000000401000 sub     rsp, 8
.text:0000000000401004 sub     rsp, 20h
.text:0000000000401008 xor     ecx, ecx        ; lpModuleName
.text:000000000040100A call    GetModuleHandleA
.text:000000000040100F mov     cs:hInstance, rax
.text:0000000000401016 add     rsp, 20h
.text:000000000040101A call    sub_401026
.text:000000000040101F xor     ecx, ecx        ; uExitCode
.text:0000000000401021 call    ExitProcess ; // 断住了, F7单步
.text:0000000000401021 start endp
.idata:000000000040310E ; void __stdcall __noreturn ExitProcess(UINT uExitCode)
.idata:000000000040310E ExitProcess proc near
.idata:000000000040310E jmp     cs:__imp_ExitProcess ; // here
.idata:000000000040310E ExitProcess endp
KERNEL32:000000000042E3E0 kernel32_ExitProcess:
KERNEL32:000000000042E3E0 sub     rsp, 28h ; 可以看到 kernel32_ExitProcess 入口处给了32字节的阴影区和一个未知的8字节栈空间
KERNEL32:000000000042E3E4 call    cs:off_495DF8 ; F7
KERNEL32:000000000042E3EB nop     dword ptr [rax+rax+00h] ; 但是并没有在call 之后平衡栈, 因为已经到不了这里了。
ntdll:00007FFD9280EED0 ntdll_RtlExitUserProcess:
ntdll:00007FFD9280EED0 push    rbx                             ; CODE XREF: KERNEL32:kernel32_ExitProcess+4↑p
ntdll:00007FFD9280EED0                                         ; DATA XREF: KERNEL32:off_495DF8↑o
ntdll:00007FFD9280EED2 sub     rsp, 20h
ntdll:00007FFD9280EED6 mov     ebx, ecx
ntdll:00007FFD9280EED8 call    near ptr unk_7FFD92831F90
ntdll:00007FFD9280EEDD mov     rax, gs:30h
ntdll:00007FFD9280EEE6 movzx   ecx, word ptr [rax+17EEh]
ntdll:00007FFD9280EEED shr     ecx, 0Ch
ntdll:00007FFD9280EEF0 and     ecx, 1
ntdll:00007FFD9280EEF3 call    near ptr unk_7FFD9280FEC4
ntdll:00007FFD9280EEF8 call    near ptr unk_7FFD927EE6C4
ntdll:00007FFD9280EEFD lea     rcx, unk_7FFD9291C0E0
ntdll:00007FFD9280EF04 call    near ptr ntdll_RtlEnterCriticalSection
ntdll:00007FFD9280EF09 mov     rcx, gs:60h
ntdll:00007FFD9280EF12 mov     rcx, [rcx+30h]
ntdll:00007FFD9280EF16 call    near ptr ntdll_RtlLockHeap
ntdll:00007FFD9280EF1B mov     edx, ebx
ntdll:00007FFD9280EF1D xor     ecx, ecx
ntdll:00007FFD9280EF1F call    near ptr ntdll_ZwTerminateProcess
ntdll:00007FFD9280EF24 test    eax, eax
ntdll:00007FFD9280EF26 js      loc_7FFD9286CE08
ntdll:00007FFD9280EF2C call    near ptr unk_7FFD9280F5A0
ntdll:00007FFD9280EF31 mov     rax, gs:30h
ntdll:00007FFD9280EF3A lea     rcx, unk_7FFD9291C0E0
ntdll:00007FFD9280EF41 mov     rdx, [rax+48h]
ntdll:00007FFD9280EF45 and     cs:qword_7FFD9291C0F8, 0
ntdll:00007FFD9280EF4D mov     cs:qword_7FFD9291C0F0, rdx
ntdll:00007FFD9280EF54 mov     cs:dword_7FFD9291C0E8, 0FFFFFFFEh
ntdll:00007FFD9280EF5E mov     cs:dword_7FFD9291C0EC, 1
ntdll:00007FFD9280EF68 call    near ptr ntdll_RtlLeaveCriticalSection
ntdll:00007FFD9280EF6D mov     edx, ebx
ntdll:00007FFD9280EF6F or      rcx, 0FFFFFFFFFFFFFFFFh
ntdll:00007FFD9280EF73 call    near ptr ntdll_RtlReportSilentProcessExit
ntdll:00007FFD9280EF78 call    near ptr ntdll_LdrShutdownProcess
ntdll:00007FFD9280EF7D mov     edx, ebx
ntdll:00007FFD9280EF7F or      rcx, 0FFFFFFFFFFFFFFFFh
ntdll:00007FFD9280EF83 call    near ptr ntdll_ZwTerminateProcess ; // 执行完这句, 程序就结束了。
ntdll:00007FFD9280EF88
ntdll:00007FFD9280EF88 loc_7FFD9280EF88:                       ; CODE XREF: ntdll:ntdll_memset+18841↓j
ntdll:00007FFD9280EF88 add     rsp, 20h ; // 这里有函数本身的栈平衡,但是到不了这里了。
ntdll:00007FFD9280EF8C pop     rbx
ntdll:00007FFD9280EF8D retn

结论

为API准备阴影区,是方便API可能要往栈上的阴影区中存放4个寄存器(RCX, RDX, R8, R9)的副本用于调试,异常等用途.
但是通过调试,并没有发现ExitProcess向阴影区写入4个寄存器副本的操作。
这就是调用ExitProcess时,可以不用准备阴影区的原因。

在x64程序中,调用其他API, 如果不准备阴影区也不是一定不行(调用完API没有不良影响就行)。其中有一部分API是不用阴影区的,但是谁还有能力记住或探究哪个API调用时是不用准备阴影区的?

因为API数量巨大,如果不是很肯定(e.g. ExitProcess就不用阴影区), 那么还是按照API调用约定,在调用API之前,为函数准备栈上的阴影区。加上也不会错,而且调用API的方式一致。

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值