调用WinAPI的InternetOpenA函数时触发访问违例(Access Violation)
解决x86-64汇编调用Windows HTTP API时的0xC0000005访问违例问题
核心问题排查与修复
x86-64 Windows下调用API触发访问违例,绝大多数是违反了调用约定或内存访问规则,针对你用wininet/winhttp都出错的情况,按以下步骤逐一排查:
1. 严格遵守x86-64 Windows调用约定(Fastcall)
Windows x64的调用规则是硬性要求,违反必出问题:
- 前4个参数依次放入
RCX,RDX,R8,R9 - 第5个及以后参数从右往左压栈
- 调用前必须保证栈是16字节对齐(call指令会压入8字节返回地址,因此调用前RSP的低4位必须为0)
- 无论参数数量多少,都要预留0x20字节的shadow register空间给API使用
错误示例(未对齐栈):
call InternetOpenA ; 此时RSP未对齐,直接触发访问违例
正确做法:
sub rsp, 0x20 ; 分配shadow空间并保证栈对齐(假设初始RSP已对齐) call InternetOpenA add rsp, 0x20 ; 调用后恢复栈空间
2. 确认API函数名的正确性
静态链接时必须使用Windows API的导出原名,不能省略后缀:
- ANSI版本:
InternetOpenA,WinHttpOpenA - Unicode版本:
InternetOpenW,WinHttpOpenW
如果写错函数名(比如漏了A/W后缀),链接时可能不报错,但运行时会调用非法地址,触发访问违例。
3. 确保字符串参数的内存合法性
汇编中字符串必须放在可读写的数据段(.data或.data?),不能放在只读的.text段:
错误示例:
section .text agent db "MyAgent/1.0", 0 ; 放在.text只读段,API若尝试修改会触发访问违例
正确做法:
section .data agent db "MyAgent/1.0", 0 ; 放在可读写数据段
同时要保证传递的字符串指针指向有效内存,不能使用未初始化的寄存器或非法地址。
4. 正确处理栈帧与局部变量
如果用栈存储局部变量(比如保存API返回的句柄),要确保分配了足够的栈空间,且访问偏移正确:
sub rsp, 0x30 ; 0x20 shadow空间 + 0x10 局部变量存储区 mov [rsp+0x20], rax ; 将InternetOpenA返回的句柄保存到栈上的局部变量
5. 静态链接库的正确引用
Makefile中必须明确指定链接所需的库文件:
- 使用wininet时,链接
wininet.lib - 使用winhttp时,链接
winhttp.lib
示例Makefile片段:
LDFLAGS = -lwininet -lkernel32 -luser32
修正后的汇编示例(基于wininet的HTTP初始化)
以下是对应C参考代码的正确汇编实现:
bits 64 default rel section .data user_agent db "Win32-Assembly-HTTP/1.0", 0 server_name db "example.com", 0 section .text global main extern InternetOpenA, InternetConnectA, HttpOpenRequestA, HttpSendRequestA, InternetCloseHandle, GetLastError main: ; 栈对齐 + 分配shadow空间 sub rsp, 0x20 ; 调用InternetOpenA mov rcx, user_agent ; 参数1: User Agent mov rdx, 0 ; 参数2: INTERNET_OPEN_TYPE_DIRECT mov r8, 0 ; 参数3: 代理地址(空) mov r9, 0 ; 参数4: 代理绕过列表(空) mov qword [rsp+0x20], 0 ; 参数5: 标志位(0) call InternetOpenA test rax, rax jz exit_fail ; 句柄创建失败,退出 ; 保存session句柄到栈 mov [rsp+0x18], rax ; 调用InternetConnectA mov rcx, [rsp+0x18] ; 参数1: Session句柄 mov rdx, server_name ; 参数2: 服务器名 mov r8, 80 ; 参数3: HTTP端口 mov r9, 0 ; 参数4: 用户名(空) mov qword [rsp+0x20], 0 ; 参数5: 密码(空) mov qword [rsp+0x28], 3 ; 参数6: INTERNET_SERVICE_HTTP mov qword [rsp+0x30], 0 ; 参数7: 标志位(0) mov qword [rsp+0x38], 0 ; 参数8: 上下文(0) sub rsp, 0x20 ; 为额外参数分配栈空间 call InternetConnectA add rsp, 0x20 test rax, rax jz close_session ; 后续HttpOpenRequestA、HttpSendRequestA等调用同理,严格遵守调用约定 ; ... close_session: mov rcx, [rsp+0x18] call InternetCloseHandle exit_fail: call GetLastError ; 可获取具体错误码用于调试 exit: add rsp, 0x20 xor eax, eax ret
调试建议
- 使用x64dbg调试,在API调用处下断点,查看寄存器值是否符合预期
- 检查调用API前RSP的值是否为16字节对齐(
RSP & 0xF == 0) - 若API返回NULL,调用
GetLastError获取具体错误码(汇编中调用后错误码存在RAX中)
内容的提问来源于stack exchange,提问作者Wrench56
相关产品推荐
相关产品推荐

