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

x64 NASM汇编中push的dword/word尺寸修饰作用及bind调用实现疑问

核心问题一:NASM压栈的尺寸修饰和无修饰的本质区别

首先纠正一个常见的认知误区:“不带尺寸修饰的push和带dword/word修饰等价” 这个结论仅适用于16位实模式、32位保护模式环境,在x86-64长模式下完全不成立。

x64长模式下压栈指令的默认行为有明确的硬件规则:

  • 当push的操作数是寄存器/内存变量时,压栈尺寸和操作数的位宽完全一致(比如push rax会压入8字节,rsp减8;push eax会压入4字节,rsp减4)
  • 当push的操作数是立即数时,没有显式标注尺寸修饰的前提下,汇编器和硬件会默认按照64位宽度处理:将立即数符号扩展为64位,压入8字节的栈空间,rsp同步减少8

显式标注word/dword尺寸修饰的核心作用,就是强制覆盖上述默认的64位宽度规则:

  • push word 0x1234:强制将立即数截断为16位,仅压入2字节的有效数据,rsp减少2,不会做64位符号扩展
  • push dword 0x12345678:强制将立即数截断为32位,仅压入4字节的有效数据,rsp减少4,不会做64位符号扩展

为什么去掉尺寸修饰后程序“能运行但功能异常”?
因为x64下每次无修饰的立即数push都会占8字节的栈空间,会完全打乱原本紧凑的sockaddr内存布局:

  • 原本带修饰时,三次压栈总共占用8字节,刚好对应sockaddr_in结构体的前8个字段,rsi指向的位置依次排布地址族、端口、IP,完全符合内核读取的偏移要求
  • 去掉修饰后,三次压栈总共占用24字节,rsi指向的低地址位置连续排布3个8字节的数值,原本应该在偏移2位置的端口值、偏移4位置的IP值,会被挤到偏移8、偏移16的位置,内核实际读取到的端口为0,最终会让内核随机分配一个临时监听端口,和你预期的固定端口完全不匹配。这种情况下因为内存访问完全合法,不会触发报错,只是功能不符合预期。
核心问题二:当前sockaddr压栈实现的逻辑漏洞

首先明确Linux x64环境下标准sockaddr_in结构体的内存布局(总长度16字节):

  • 偏移0,2字节:sin_family,固定值为2(对应IPv4地址族)
  • 偏移2,2字节:sin_port,必须为大端(网络字节序)格式的端口号
  • 偏移4,4字节:sin_addr,必须为大端格式的IPv4地址
  • 偏移8,8字节:sin_zero,填充字段,规范要求为全0,用于和通用sockaddr结构体保持大小一致

对照上述布局,你的实现有这些特点和问题:

  • 正确的部分:压栈顺序完全符合栈向下增长的特性。因为要让最终rsi指向的低地址依次排布地址族、端口、IP,必须先压最靠高地址的IP字段,再压端口,最后压地址族,这个顺序完全正确。同时x86的小端存储特性,让你写的push word 0x560f、push dword 0x6B02A8C0刚好生成符合网络字节序要求的大端数据,没有字节序转换错误。
  • 存在的问题:
    • 传入的addrlen值不规范:你在rdx中传入的32是不合理的,实际sockaddr_in的总长度只有16字节。虽然Linux内核允许传入更大的长度(只会读取前16字节),但这个值不符合系统调用的参数规范,正确的传值应为16。
    • 未清理填充字段:你的压栈操作仅生成了前8字节的有效结构体内容,偏移8到偏移16位置的sin_zero字段使用的是栈上的历史残留数据,仅靠之前测试时残留值恰好为0才没有出现绑定异常,不具备可移植性。规范的实现需要在现有压栈序列的基础上,通过两次push dword 0,凑齐8字节的0值填充,让整个16字节的结构体完全可控。
    • 栈布局的紧凑性是正确的:x64下压word/dword操作数时,硬件只会按照操作数实际尺寸修改rsp,不会自动对齐到8字节边界,这也是你能够通过压栈实现紧凑结构体布局的硬件基础。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.25 12:45:04