汇编中为字符串添加空终止符时触发段错误的问题咨询
问题分析与解决方案
首先,你的段错误根源很明确:你尝试往只读的.text段内存写入数据。
看你的代码,Shellvariables是直接放在.text段(代码段)里的,而现代操作系统为了安全,会把.text段标记为只读(防止恶意代码篡改程序指令)。当你用movb %al, 0x9(%rsi)往这个只读区域写入空终止符时,CPU的内存保护机制就会触发段错误,阻止你修改只读内存。
正确的实现方式:把字符串放到可写的栈上
要实现你想要的相对寻址和空终止符写入,必须把字符串数据放到可写的栈内存里。这里提供两种调整思路:
思路1:用Push指令直接构建栈上字符串
这种方式不需要依赖.text段的字符串,直接把/bin/bash的字节推到栈上,栈本身是可写的,后续写入空终止符和其他数据都不会有问题:
.text .globl _start _start: xorq %rax, %rax # 清零RAX,AL=0用于空终止符 # 栈向下增长,所以逆序push字符串字节(小端存储) pushq %rax # 先压入一个空QWORD,预留后续空间 pushq $0x687361622f6e6962 # 对应ASCII: "bin/bash"(字节逆序后push,小端存储后恢复为正常顺序) pushq $0x2f # 压入 '/',现在栈顶指向完整的"/bin/bash"起始地址 movq %rsp, %rsi # RSI指向栈上的"/bin/bash" # 给字符串添加空终止符:"/bin/bash"共9个字符,偏移0x8的位置是最后一个字符'h',改为0 movb %al, 0x8(%rsi) # 构建argv数组:argv[0] = 字符串地址,argv[1] = NULL pushq %rax # 压入NULL作为argv[1] pushq %rsi # 压入字符串地址作为argv[0] movq %rsp, %rdi # RDI指向argv数组(execve的第一个参数) # envp设为NULL movq %rax, %rdx movb $59, %al # execve的系统调用号是59 syscall # 调用execve("/bin/bash", ["/bin/bash", NULL], NULL)
思路2:调整原有的Jmp-Call-Pop结构,让字符串在栈上
如果你想保留原来的跳转调用结构,那需要让call指令压栈的地址指向栈上的字符串,而不是.text段的只读字符串。不过这种方式不如push直接,这里提供一个修改示例:
.text .globl _start _start: # 先把字符串字节push到栈上 xorq %rax, %rax pushq %rax pushq $0x687361622f6e6962 pushq $0x2f movq %rsp, %rdi # 保存字符串地址 # 现在用jmp-call-pop拿到栈上的字符串地址 jmp get_string shellcode: popq %rsi # RSI现在指向栈上的字符串(可写) movb %al, 0x8(%rsi) # 写入空终止符 movq %rsi, 0x9(%rsi) # 设置argv[0] movq %rax, 0x11(%rsi) # 设置argv[1] = NULL movq %rax, 0x19(%rsi) # 设置envp = NULL movb $59, %al movq %rsi, %rdi leaq 0x9(%rsi), %rsi leaq 0x19(%rsi), %rdx syscall get_string: call shellcode # 这里的占位符不会被用到,因为我们已经把字符串push到栈上了 .ascii "placeholder"
其他补充:验证内存权限的小技巧
你可以用readelf工具查看你的目标程序的段权限:
readelf -S your_program | grep -E "(Idx|text)"
输出里的FLAGS列如果包含AX(可执行、可读)但没有W(可写),就说明.text段是只读的,这时候往里面写数据必然触发段错误。
内容的提问来源于stack exchange,提问作者Telboon
相关产品推荐
相关产品推荐

