x64汇编调用WinExec:calc.exe无效仅calc有效的问题排查
问题:WinExec调用中字符串构造的异常问题
背景
正在学习Shellcode编写,曾接触过汇编但知识点不够系统,容易遗忘。
环境配置
gcc --version gcc.exe (Rev3, Built by MSYS2 project) 13.2.0 nasm --version NASM version 2.16.01 compiled on Mar 20 2023 # 编译命令 nasm -fwin64 -o file.obj file.asm gcc -o file.exe file.obj
问题描述
编写调用WinExec的汇编程序时,传入参数calc.exe无法启动计算器,但仅传入calc时可以正常运行。尝试多种栈上构造字符串的方式,多数程序能运行但无法启动计算器,部分会触发段错误。
示例代码
; define arch bits 64 ; make pic adresses default rel segment .data segment .text global main extern ExitProcess extern WinExec extern printf main: push rbp mov rbp,rsp sub rsp, 32 push 0 push 0x636C6163 ; 用于添加'.exe'的变体代码 ;mov dword [rsp + 4], 0x6578652e mov rcx, rsp mov rdx, 1 call WinExec xor rax,rax call ExitProcess
尝试的构造方式及结果
- 先push
calc,再用mov指令添加.exe:程序正常退出但未启动计算器 - 构造完整路径
C:\Windows\System32\calc.exe:触发KernelBase.dll段错误 - 按word块push
ca、lc、.e、xe:程序正常退出但未启动计算器 - 按word块push
ca、lc:触发KernelBase.dll段错误 - 按dword push
calc:唯一能正常启动计算器的方式
疑问
为何会出现这种差异?是否与LSB(最低有效字节)有关?
解答
核心问题:64位栈操作特性、字节序与栈对齐的三重错误
你的问题本质是对x86_64(Win64)环境下的栈操作、字节序规则以及调用约定理解不到位,具体拆解如下:
1. 64位push指令的隐性扩展
Win64下,push指令对32位/16位操作数会自动零扩展为64位,压入8字节数据,而非你预期的4/2字节。
- 你写的
push 0实际压入8字节全0,push 0x636C6163实际压入0x00000000636C6163(8字节)。 - 这导致栈中字符串的布局完全偏离预期:比如你以为只加了一个空终止符,实际栈中会有大量冗余空字节;按word块push时,每个操作都会额外添加6个空字节,直接破坏字符串结构。
2. 小端序构造错误
x86架构采用小端序存储,即字符串的字节会按反向顺序存入双字/字中。你在构造长字符串时,要么字节顺序搞反,要么push顺序错误:
- 比如构造
.exe时,你用mov dword [rsp+4], 0x6578652e是正确的(小端序下,该双字会在栈中生成.exe的正确字节序列),但结合前面的8字节push操作,最终字符串会变成calc.exe后跟着4个冗余空字节——虽然这本身不影响终止符,但结合栈对齐问题就会触发异常。 - 按word块push
ca、lc时,你错误地按正向顺序push,导致栈中生成的字符串是cacl而非calc,WinExec自然找不到对应程序。
3. Win64调用约定的栈对齐要求
Win64规定,调用任何API前,栈指针rsp必须保持16字节对齐。你的代码中:
push rbp后rsp已偏离16字节对齐,后续sub rsp,32、push操作进一步破坏对齐,调用WinExec时栈处于不对齐状态,触发未定义行为——部分场景下运气好能运行(比如仅pushcalc),但多数情况会出现段错误或功能异常。
正确构造calc.exe的示例代码
直接在预留的栈空间中精确写入字符串,避免push指令的隐性扩展问题,同时保证栈对齐:
bits 64 default rel segment .text global main extern ExitProcess extern WinExec main: push rbp mov rbp,rsp sub rsp, 32 ; 预留阴影空间 ; 在阴影空间中构造calc.exe字符串(含空终止符) mov dword [rsp+16], 0x636C6163 ; 小端序存储"calc" mov dword [rsp+20], 0x6578652E ; 小端序存储".exe" mov byte [rsp+24], 0x00 ; 空终止符 mov rcx, rsp+16 ; 字符串首地址 mov rdx, 1 ; SW_SHOWNORMAL sub rsp, 8 ; 调整栈至16字节对齐 call WinExec add rsp, 8 ; 恢复栈 xor rax,rax call ExitProcess
内容的提问来源于stack exchange,提问作者Andrei
相关产品推荐
相关产品推荐

