阴影 space 示例
Shadow space example
编辑:
我接受了下面的答案,并在我对代码的最终修订中添加了我自己的答案。希望它向人们展示了 Shadow Space 分配的实际示例,而不是更多的单词。
编辑 2:我还设法在 YouTube 视频(所有内容)的注释中找到了调用约定 PDF 的 link,其中有一些关于 Shadow Space 和Linux 上的红色区域。可以在这里找到:http://www.agner.org/optimize/calling_conventions.pdf
原文:
我在这里和整个互联网上查看了其他几个问题,但我似乎找不到在调用 subroutine/Windows [=56= 时分配 "Shadow Space" 的合适示例] 在 64 位 Windows 汇编中。
我的理解是这样的:
- 来电者应该
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!\n"。这设法在崩溃前打印 "Hello " 。我怀疑我做的是正确的......除了我应该以某种方式清理(我不确定如何)。
我做错了什么?我已经尝试了多种尺寸的组合,并且还在 WinAPI 调用之前尝试了 "allocating Shadow Space"(我应该这样做吗?)。
应该注意的是,当我根本不关心 Shadow Space 时,这工作得很好 。但是,由于我的 write
函数调用了 WinAPIs(因此不是叶函数),所以我试图与 ABI 兼容。
阴影 space 必须直接在调用之前提供。将影子 space 想象成旧 stdcall/cdecl 约定的遗物:对于 WriteFile
你需要五次推动。阴影 space 代表最后四次推送(前四个参数)。现在你需要四个寄存器,shadow space(只是 space,内容无关紧要)和堆栈上的一个值 after shadow space(这实际上是第一次推送)。当前,调用者 (start
) 的 return 地址位于 space 中,WriteFile
将用作影子 space -> 崩溃。
您可以在函数 write
:
中为 WinAPI 函数(GetStdHandle
和 WriteConsoleA
)创建一个新的影子 space
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
为了完整起见,我将其张贴在这里,因为这就是我的最终结果。这非常有效,据我所知,除了 Windows 上 x64 ASM 的 UNWIND_INFO
/异常处理要求外,这几乎是正确的。希望评论也是准确的。
编辑:
这是在 Raymonds 在下面发表评论后更新的。我删除了 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
这导致...:
编辑:
我接受了下面的答案,并在我对代码的最终修订中添加了我自己的答案。希望它向人们展示了 Shadow Space 分配的实际示例,而不是更多的单词。
编辑 2:我还设法在 YouTube 视频(所有内容)的注释中找到了调用约定 PDF 的 link,其中有一些关于 Shadow Space 和Linux 上的红色区域。可以在这里找到:http://www.agner.org/optimize/calling_conventions.pdf
原文:
我在这里和整个互联网上查看了其他几个问题,但我似乎找不到在调用 subroutine/Windows [=56= 时分配 "Shadow Space" 的合适示例] 在 64 位 Windows 汇编中。
我的理解是这样的:
- 来电者应该
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!\n"。这设法在崩溃前打印 "Hello " 。我怀疑我做的是正确的......除了我应该以某种方式清理(我不确定如何)。
我做错了什么?我已经尝试了多种尺寸的组合,并且还在 WinAPI 调用之前尝试了 "allocating Shadow Space"(我应该这样做吗?)。
应该注意的是,当我根本不关心 Shadow Space 时,这工作得很好 。但是,由于我的 write
函数调用了 WinAPIs(因此不是叶函数),所以我试图与 ABI 兼容。
阴影 space 必须直接在调用之前提供。将影子 space 想象成旧 stdcall/cdecl 约定的遗物:对于 WriteFile
你需要五次推动。阴影 space 代表最后四次推送(前四个参数)。现在你需要四个寄存器,shadow space(只是 space,内容无关紧要)和堆栈上的一个值 after shadow space(这实际上是第一次推送)。当前,调用者 (start
) 的 return 地址位于 space 中,WriteFile
将用作影子 space -> 崩溃。
您可以在函数 write
:
GetStdHandle
和 WriteConsoleA
)创建一个新的影子 space
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
为了完整起见,我将其张贴在这里,因为这就是我的最终结果。这非常有效,据我所知,除了 Windows 上 x64 ASM 的 UNWIND_INFO
/异常处理要求外,这几乎是正确的。希望评论也是准确的。
编辑:
这是在 Raymonds 在下面发表评论后更新的。我删除了 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
这导致...: