使用反汇编分析的最小复现示例
main.c
void myfunc(char *const src, int len) {
int i;
for (i = 0; i < len; ++i) {
src[i] = 42;
}
}
int main(void) {
char arr[] = {'a', 'b', 'c', 'd'};
int len = sizeof(arr);
myfunc(arr, len + 1);
return 0;
}
GitHub 上游。
编译并运行:
gcc -fstack-protector-all -g -O0 -std=c99 main.c
ulimit -c unlimited && rm -f core
./a.out
执行失败:
*** stack smashing detected ***: terminated
Aborted (core dumped)
在Ubuntu 20.04,GCC 10.2.0上进行了测试。
在Ubuntu 16.04,GCC 6.4.0上,我可以使用-fstack-protector
代替-fstack-protector-all
来复现此问题。但是,当我根据Geng Jiawen的评论在GCC 10.2.0上进行测试时,它停止了崩溃。 man gcc
澄清了,正如选项名称所建议的那样,-all
版本会更积极地添加检查,因此可能会导致更大的性能损失:
-fstack-protector
发出额外的代码以检查缓冲区溢出,例如堆栈破坏攻击。这是通过向具有易受攻击对象的函数添加保护变量来完成的。这包括调用“alloca”的函数和具有大于或等于8个字节的缓冲区的函数。当进入函数时初始化保护变量,然后在退出函数时进行检查。如果保护检查失败,则打印错误消息并退出程序。只考虑实际在堆栈上分配的变量,优化掉的变量或在寄存器中分配的变量不计入。
-fstack-protector-all
与-fstack-protector类似,但保护所有函数。
反汇编
现在我们来看反汇编:
objdump -D a.out
其中包含:
int main (void){
400579: 55 push %rbp
40057a: 48 89 e5 mov %rsp,%rbp
# Allocate 0x10 of stack space.
40057d: 48 83 ec 10 sub $0x10,%rsp
# Put the 8 byte canary from %fs:0x28 to -0x8(%rbp),
# which is right at the bottom of the stack.
400581: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
400588: 00 00
40058a: 48 89 45 f8 mov %rax,-0x8(%rbp)
40058e: 31 c0 xor %eax,%eax
char arr[] = {'a', 'b', 'c', 'd'}
400590: c6 45 f4 61 movb $0x61,-0xc(%rbp)
400594: c6 45 f5 62 movb $0x62,-0xb(%rbp)
400598: c6 45 f6 63 movb $0x63,-0xa(%rbp)
40059c: c6 45 f7 64 movb $0x64,-0x9(%rbp)
int len = sizeof(arr)
4005a0: c7 45 f0 04 00 00 00 movl $0x4,-0x10(%rbp)
myfunc(arr, len + 1)
4005a7: 8b 45 f0 mov -0x10(%rbp),%eax
4005aa: 8d 50 01 lea 0x1(%rax),%edx
4005ad: 48 8d 45 f4 lea -0xc(%rbp),%rax
4005b1: 89 d6 mov %edx,%esi
4005b3: 48 89 c7 mov %rax,%rdi
4005b6: e8 8b ff ff ff callq 400546 <myfunc>
return 0
4005bb: b8 00 00 00 00 mov $0x0,%eax
}
# Check that the canary at -0x8(%rbp) hasn't changed after calling myfunc.
# If it has, jump to the failure point __stack_chk_fail.
4005c0: 48 8b 4d f8 mov -0x8(%rbp),%rcx
4005c4: 64 48 33 0c 25 28 00 xor %fs:0x28,%rcx
4005cb: 00 00
4005cd: 74 05 je 4005d4 <main+0x5b>
4005cf: e8 4c fe ff ff callq 400420 <__stack_chk_fail@plt>
# Otherwise, exit normally.
4005d4: c9 leaveq
4005d5: c3 retq
4005d6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
4005dd: 00 00 00
请注意,
objdump
的
人工智能模块自动添加了有用的注释。
如果你通过GDB多次运行此程序,你会发现:
- 栈末尾警卫每次都有不同的随机值。
-
myfunc
的最后一个循环正是修改栈末尾警卫地址的部分。
栈末尾警卫通过设置
%fs:0x28
来实现随机化,其中包含一个随机值,详见以下链接:
-
https://unix.stackexchange.com/questions/453749/what-sets-fs0x28-stack-canary
-
Why does this memory address %fs:0x28 ( fs[0x28] ) have a random value?
如何调试?
请参见:
Stack smashing detected。