You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows x86-64 Shellcode中栈上字符串调用printf的异常原因探究

问题:Windows 10 x64下栈上存储字符串调用printf的异常原因

在Windows 10 x64环境中,尝试调用printf("String: %s\n", "Hello World")并将字符串"Hello World"存在栈上时,发现只有当字符串存放在距rsp至少32字节的位置时才能正常输出,否则会出现乱码或无输出。已了解x64 ABI的16字节对齐要求,但无法解释该现象。

可行代码示例:

bits 64
default rel

segment .data
    msg db "String: %s", 0xd, 0xa, 0

segment .text
global main
extern ExitProcess

extern printf

main:
    push rbp
    mov  rbp, rsp
    sub  rsp, 64  ; 调整栈大小(Number 1)

    mov  rdx, rsp
    add  rdx, 32  ; 字符串在栈上的偏移(Number 2)
    mov  dword [rdx],     "Hell"
    mov  dword [rdx + 4], "o Wo"
    mov  dword [rdx + 8], "rld"
    
    lea  rcx, [msg]
    call printf

    xor  rcx, rcx
    call ExitProcess

测试结果表格:

Number 1Number 2是否可行?输出结果
640否
648否x¨/(☻
6416否Pf{tè☻
6424否Ϩ;í
6432是Hello World
6440是Hello World
6448是Hello World
6456是Hello World
6464是Hello World
320否
328否°²øRg
3216否ákG×│☺
3224否↑°wd¢
3232是Hello World

原因分析:Windows x64 ABI的影子空间(Shadow Space)要求

你遇到的问题核心是Windows x64 ABI强制要求的32字节影子空间,和16字节对齐是两个独立规则:

  1. 影子空间的定义
    Windows x64规定,调用任何函数时,必须在栈上预留32字节的"影子空间"(也叫寄存器参数区)。这块区域是给被调用函数用的,用来保存前4个寄存器参数(rcx、rdx、r8、r9)的值——很多C标准库函数(比如printf)会无条件将这四个寄存器的值写入到这块栈空间中。

  2. 你的代码中影子空间的位置
    在main函数执行sub rsp, 64后,rsp指向栈帧的起始位置。此时,从rsp到rsp+31的32字节区域,就是你为调用printf预留的影子空间。当你把字符串存在rsp+0到rsp+24的位置时,这些数据正好落在影子空间范围内。

  3. 乱码的本质
    当printf被调用后,它会立即将rcx(指向msg的指针)、rdx(指向栈上字符串的指针)等寄存器的值写入影子空间,直接覆盖了你存在这里的"Hello World"字符串。等printf去读取rdx指向的地址时,原来的字符串已经被破坏,自然输出乱码或无内容。

  4. 偏移32及以上正常的原因
    当字符串存放在rsp+32及以上的位置时,这块区域不在影子空间范围内(影子空间是rsp到rsp+31),不会被printf写入寄存器值的操作覆盖,所以字符串能保持完整,printf可以正确读取输出。


内容的提问来源于stack exchange,提问作者Lumen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 15:05:20