阴影空间示例

问题描述 投票:0回答:2

编辑:

我已经接受了下面的答案,并添加了我自己的答案以及我对代码的最终修订。希望它向人们展示影子空间分配的实际示例,而不是更多的文字。

编辑 2:我还设法在 YouTube 视频(所有内容)的注释中找到了一个调用约定 PDF 的链接,其中有一些关于 Linux 上的 Shadow Space 和 Red Zone 的有趣花絮。可以在这里找到:http://www.agner.org/optimize/calling_conventions.pdf

原文:

我在这里和整个互联网上查看了其他几个问题,但我似乎找不到在 64 位 Windows 程序集中调用子例程/Windows API 时分配“影子空间”的正确示例。

我的理解是这样的:

  • 呼叫者应在
    sub rsp,<bytes here>
     之前 
    call callee
  • 如果需要,被调用者应该使用它来存储寄存器(或者局部变量,如果不需要保存寄存器)
  • 来电者将其清理,例如:
    add rsp,<bytes here>
  • 分配的数量应与 32 字节对齐

考虑到这一点,这就是我尝试过的:

section .text

start:

    sub rsp,0x20 ; <---- Allocate 32 bytes of "Shadow space"

    mov rcx,msg1
    mov rdx,msg1.len
    call write

    add rsp,0x20

    mov rcx,NULL
    call ExitProcess

    ret

write:

    mov [rsp+0x08],rcx      ; <-- use the Shadow space
    mov [rsp+0x10],rdx      ; <-- and again

    mov rcx,STD_OUTPUT_HANDLE   ; Get handle to StdOut
    call GetStdHandle

    mov rcx,rax         ; hConsoleOutput
    mov rdx,[rsp+0x08]      ; lpBuffer
    mov r8,[rsp+0x10]       ; nNumberOfCharsToWrite
    mov r9,empty        ; lpNumberOfCharsWritten
    push NULL           ; lpReserved
    call WriteConsoleA

    ret

我的两个字符串是“Hello”和“World! “。这在崩溃之前成功地打印了“Hello”。我怀疑我做得正确......除了我应该以某种方式清理(而且我不知道如何)。

我做错了什么?我已经尝试过大小组合,并且在 WinAPI 调用之前也尝试过“分配影子空间”(我应该这样做吗?)。

应该指出的是,当我根本不关心阴影空间时,这工作得非常好。但是,我试图遵守 ABI,因为我的 write

 函数调用 WinAPI(因此不是叶函数)。

windows assembly x86-64 nasm stack-memory
2个回答
10
投票
必须在调用之前直接提供影子空间。将影子空间想象为旧的 stdcall/cdecl 约定的遗迹:对于

WriteFile

,您需要五次推送。阴影空间代表最后四次推送(前四个参数)。现在您需要四个寄存器、影子空间(只是空间,内容并不重要)和影子空间
之后堆栈上的一个值(实际上是第一次压入)。目前,调用者的返回地址 (start
) 位于 
WriteFile
 将用作影子空间 -> 崩溃的空间中。

参数少于 4 个的函数仍然可以获得 32 字节的影子空间供自己使用。

您可以在函数内部为 WinAPI 函数(

GetStdHandle

WriteConsoleA
)创建一个新的影子空间 
write
:

write: push rbp mov rbp, rsp sub rsp, (16 + 32) ; 5th argument of WriteConsoleA (8) + Shadow space (32) ; plus another 8 to make it a multiple of 16 (to keep stack aligned after one push aligned it after function entry) mov [rbp+16],rcx ; <-- use our Shadow space, provided by `start` mov [rbp+24],rdx ; <-- and again, to save our incoming args mov rcx, -11 ; Get handle to StdOut call GetStdHandle mov rcx,rax ; hConsoleOutput mov rdx, [rbp+16] ; lpBuffer ; reloaded saved copy of register arg mov r8, [rbp+24] ; nNumberOfCharsToWrite mov r9,empty ; lpNumberOfCharsWritten mov qword [rsp+32],0 ; lpReserved - 5th argument directly behind the shadow space call WriteConsoleA leave ret
    

4
投票
为了完整起见,我将其发布在这里,因为这就是我的最终结果。这工作得很好,据我所知,除了 Windows 上 x64 ASM 的

UNWIND_INFO

/异常处理要求,这几乎是正确的。希望评论也准确。

编辑:

这是在雷蒙德发表评论后更新的。我删除了

rbp

 的保留,因为它不是必需的,并且使我的堆栈对齐超出了我的预期。

; Windows APIs ; GetStdHandle ; ------------ ; HANDLE WINAPI GetStdHandle( ; _In_ DWORD nStdHandle ; ); extern GetStdHandle ; WriteFile ; ------------ ; BOOL WINAPI WriteFile( ; _In_ HANDLE hFile, ; _In_ LPCVOID lpBuffer, ; _In_ DWORD nNumberOfBytesToWrite, ; _Out_opt_ LPDWORD lpNumberOfBytesWritten, ; _Inout_opt_ LPOVERLAPPED lpOverlapped ; ); extern WriteFile ; ExitProcess ; ----------- ; VOID WINAPI ExitProcess( ; _In_ UINT uExitCode ; ); extern ExitProcess global start section .data STD_OUTPUT_HANDLE equ -11 NULL equ 0 msg1 db "Hello ", 0 msg1.len equ $-msg1 msg2 db "World!", 10, 0 msg2.len equ $-msg2 section .bss empty resd 1 section .text start: sub rsp,0x28 ; Allocate 32 bytes of Shadow Space + align it to 16 bytes (8 byte return address already on stack, so 8 + 40 = 16*3) mov rcx,msg1 mov rdx,msg1.len call write mov rcx,msg2 mov rdx,msg2.len call write mov rcx,NULL call ExitProcess add rsp,0x28 ; Restore the stack pointer before exiting ret write: ; Allocate another 40 bytes of stack space (the return address makes 48 total). Its 32 ; bytes of Shadow Space for the WinAPI calls + 8 more bytes for the fifth argument ; to the WriteFile API call. sub rsp,0x28 mov [rsp+0x30],rcx ; Argument 1 is 48 bytes back in the stack (40 for Shadow Space above, 8 for return address) mov [rsp+0x38],rdx ; Argument 2 is just after Argument 1 mov rcx,STD_OUTPUT_HANDLE ; Get handle to StdOut call GetStdHandle mov rcx,rax ; hFile mov rdx,[rsp+0x30] ; lpBuffer mov r8,[rsp+0x38] ; nNumberOfBytesToWrite mov r9,empty ; lpNumberOfBytesWritten ; Move the 5th argument directly behind the Shadow Space mov qword [rsp+0x20],0 ; lpOverlapped, Argument 5 (just after the Shadow Space 32 bytes back) call WriteFile add rsp,0x28 ; Restore the stack pointer (remove the Shadow Space) ret

这导致...:

© www.soinside.com 2019 - 2024. All rights reserved.