为何无溢出行为的函数调用会覆盖其他函数的栈内存?
在CSAPP的Attack Lab实验Level3的说明文档中提到:
"调用hexmatch和strncmp函数时,它们会向栈中压入数据,覆盖原本属于getbuf函数缓冲区的部分内存。因此,你需要注意cookie字符串的存放位置。"
我完成了该实验,但实在无法理解为何无溢出行为的函数调用会破坏其他函数的栈内存。注入代码将执行流切换到touch3后,getbuf的40字节栈内存似乎被覆盖了,更准确地说,是在调用hexmatch和strncmp之后发生的覆盖。
以下是相关函数的反汇编代码:
00000000004017a8 <getbuf>: 4017a8: 48 83 ec 28 sub $0x28,%rsp 4017ac: 48 89 e7 mov %rsp,%rdi 4017af: e8 8c 02 00 00 call 401a40 <Gets> 4017b4: b8 01 00 00 00 mov $0x1,%eax 4017b9: 48 83 c4 28 add $0x28,%rsp 4017bd: c3 ret 4017be: 90 nop 4017bf: 90 nop 000000000040184c <hexmatch>: 40184c: 41 54 push %r12 40184e: 55 push %rbp 40184f: 53 push %rbx 401850: 48 83 c4 80 add $0xffffffffffffff80,%rsp 401854: 41 89 fc mov %edi,%r12d 401857: 48 89 f5 mov %rsi,%rbp 40185a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 401861: 00 00 401863: 48 89 44 24 78 mov %rax,0x78(%rsp) 401868: 31 c0 xor %eax,%eax 40186a: e8 41 f5 ff ff call 400db0 <random@plt> 40186f: 48 89 c1 mov %rax,%rcx 401872: 48 ba 0b d7 a3 70 3d movabs $0xa3d70a3d70a3d70b,%rdx 401879: 0a d7 a3 40187c: 48 f7 ea imul %rdx 40187f: 48 01 ca add %rcx,%rdx 401882: 48 c1 fa 06 sar $0x6,%rdx 401886: 48 89 c8 mov %rcx,%rax 401889: 48 c1 f8 3f sar $0x3f,%rax 40188d: 48 29 c2 sub %rax,%rdx 401890: 48 8d 04 92 lea (%rdx,%rdx,4),%rax 401894: 48 8d 04 80 lea (%rax,%rax,4),%rax 401898: 48 c1 e0 02 shl $0x2,%rax 40189c: 48 29 c1 sub %rax,%rcx 40189f: 48 8d 1c 0c lea (%rsp,%rcx,1),%rbx 4018a3: 45 89 e0 mov %r12d,%r8d 4018a6: b9 e2 30 40 00 mov $0x4030e2,%ecx 4018ab: 48 c7 c2 ff ff ff ff mov $0xffffffffffffffff,%rdx 4018b2: be 01 00 00 00 mov $0x1,%esi 4018b7: 48 89 df mov %rbx,%rdi 4018ba: b8 00 00 00 00 mov $0x0,%eax 4018bf: e8 ac f5 ff ff call 400e70 <__sprintf_chk@plt> 4018c4: ba 09 00 00 00 mov $0x9,%edx 4018c9: 48 89 de mov %rbx,%rsi 4018cc: 48 89 ef mov %rbp,%rdi 4018cf: e8 cc f3 ff ff call 400ca0 <strncmp@plt> 4018d4: 85 c0 test %eax,%eax 4018d6: 0f 94 c0 sete %al 4018d9: 0f b6 c0 movzbl %al,%eax 4018dc: 48 8b 74 24 78 mov 0x78(%rsp),%rsi 4018e1: 64 48 33 34 25 28 00 xor %fs:0x28,%rsi 4018e8: 00 00 4018ea: 74 05 je 4018f1 <hexmatch+0xa5> 4018ec: e8 ef f3 ff ff call 400ce0 <__stack_chk_fail@plt> 4018f1: 48 83 ec 80 sub $0xffffffffffffff80,%rsp 4018f5: 5b pop %rbx 4018f6: 5d pop %rbp 4018f7: 41 5c pop %r12 4018f9: c3 ret 00000000004018fa <touch3>: 4018fa: 53 push %rbx 4018fb: 48 89 fb mov %rdi,%rbx 4018fe: c7 05 d4 2b 20 00 03 movl $0x3,0x202bd4(%rip) # 6044dc <vlevel> 401905: 00 00 00 401908: 48 89 fe mov %rdi,%rsi 40190b: 8b 3d d3 2b 20 00 mov 0x202bd3(%rip),%edi # 6044e4 <cookie> 401911: e8 36 ff ff ff call 40184c <hexmatch> 401916: 85 c0 test %eax,%eax 401918: 74 23 je 40193d <touch3+0x43> 40191a: 48 89 da mov %rbx,%rdx 40191d: be 38 31 40 00 mov $0x403138,%esi 401922: bf 01 00 00 00 mov $0x1,%edi 401927: b8 00 00 00 00 mov $0x0,%eax 40192c: e8 bf f4 ff ff call 400df0 <__printf_chk@plt> 401931: bf 03 00 00 00 mov $0x3,%edi 401936: e8 52 03 00 00 call 401c8d <validate> 40193b: eb 21 jmp 40195e <touch3+0x64> 40193d: 48 89 da mov %rbx,%rdx 401940: be 60 31 40 00 mov $0x403160,%esi 401945: bf 01 00 00 00 mov $0x1,%edi 40194a: b8 00 00 00 00 mov $0x0,%eax 40194f: e8 9c f4 ff ff call 400df0 <__printf_chk@plt> 401954: bf 03 00 00 00 mov $0x3,%edi 401959: e8 f1 03 00 00 call 401d4f <fail> 40195e: bf 00 00 00 00 mov $0x0,%edi 401963: e8 d8 f4 ff ff call 400e40 <exit@plt>
问题原因解析
核心原因是x86-64架构的栈向下增长特性,以及后续函数的栈帧扩展覆盖了getbuf的栈缓冲区:
getbuf的栈布局:
getbuf通过sub $0x28,%rsp分配了40字节的缓冲区,缓冲区的地址范围是[rsp_prev - 40, rsp_prev)(其中rsp_prev是进入getbuf时的rsp值)。当我们通过溢出修改getbuf的返回地址为touch3后,getbuf执行ret时会跳转到touch3,此时rsp回到rsp_prev,并在ret后加8(弹出返回地址到rip)。hexmatch的栈空间分配:
touch3调用hexmatch时,call指令会先将下一条指令的地址压栈(rsp减8)。进入hexmatch后:- 先push r12、rbp、rbx三个寄存器,rsp再减24字节;
- 执行
add $0xffffffffffffff80,%rsp,等价于sub $0x80,%rsp,直接向低地址方向分配128字节的局部空间。
此时rsp的位置为rsp_prev - 8 - 24 - 128 = rsp_prev - 160,hexmatch的局部空间范围是[rsp_prev - 160, rsp_prev - 32)。
栈空间重叠与覆盖:
getbuf的缓冲区范围是[rsp_prev - 40, rsp_prev),其中[rsp_prev - 40, rsp_prev - 32)这8字节的区域,正好和hexmatch局部空间的高地址部分重叠。当hexmatch调用__sprintf_chk向局部空间写入字符串时,就会覆盖这部分属于getbuf缓冲区的内存,也就是你观察到的getbuf栈内存被覆盖的现象。
简单来说,不是函数调用有溢出,而是后续函数的栈帧扩展直接覆盖了之前函数留在栈上的缓冲区区域——栈的向下增长特性让这种重叠成为可能。
内容的提问来源于stack exchange,提问作者shouao chen

