金丝雀案例代码
#include <stdio.h>
#include <string.h>
void vulnerable_function(char *input) {
char buffer[8]; // 在栈上分配64字节的局部数组
strcpy(buffer, input); // 危险操作!未检查长度,可能溢出
printf("Input: %s\n", buffer);
}
int main(int argc, char **argv) {
if (argc < 2) return 0;
vulnerable_function(argv[1]); // 从命令行接收输入
return 0;
}
编译g++ a.cp p -fstack-protector
这是没有金丝雀的样子, 上面的buffer长度改成7
0000000000001189 <_Z19vulnerable_functionPc>:
1189: f3 0f 1e fa endbr64
118d: 55 push %rbp
118e: 48 89 e5 mov %rsp,%rbp
1191: 48 83 ec 20 sub $0x20,%rsp
1195: 48 89 7d e8 mov %rdi,-0x18(%rbp)
1199: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
11a0: 00 00
11a2: 48 89 45 f8 mov %rax,-0x8(%rbp)
11a6: 31 c0 xor %eax,%eax
11a8: 48 8b 55 e8 mov -0x18(%rbp),%rdx
11ac: 48 8d 45 f0 lea -0x10(%rbp),%rax
11b0: 48 89 d6 mov %rdx,%rsi
11b3: 48 89 c7 mov %rax,%rdi
11b6: e8 b5 fe ff ff call 1070 <strcpy@plt>
11bb: 48 8d 45 f0 lea -0x10(%rbp),%rax
11bf: 48 89 c6 mov %rax,%rsi
11c2: 48 8d 05 3b 0e 00 00 lea 0xe3b(%rip),%rax # 2004 <_IO_stdin_used+0x4>
11c9: 48 89 c7 mov %rax,%rdi
11cc: b8 00 00 00 00 mov $0x0,%eax
11d1: e8 ba fe ff ff call 1090 <printf@plt>
11d6: 90 nop
11d7: 48 8b 45 f8 mov -0x8(%rbp),%rax
11db: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax
11e2: 00 00
11e4: 74 05 je 11eb <_Z19vulnerable_functionPc+0x62>
11e6: e8 95 fe ff ff call 1080 <__stack_chk_fail@plt>
11eb: c9 leave
11ec: c3 ret
这是带金丝雀的 样子
0000000000001189 <_Z19vulnerable_functionPc>:
1189: f3 0f 1e fa endbr64
118d: 55 push %rbp
118e: 48 89 e5 mov %rsp,%rbp
1191: 48 83 ec 20 sub $0x20,%rsp
1195: 48 89 7d e8 mov %rdi,-0x18(%rbp)
1199: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
11a0: 00 00
11a2: 48 89 45 f8 mov %rax,-0x8(%rbp)
11a6: 31 c0 xor %eax,%eax
11a8: 48 8b 55 e8 mov -0x18(%rbp),%rdx
11ac: 48 8d 45 f0 lea -0x10(%rbp),%rax
11b0: 48 89 d6 mov %rdx,%rsi
11b3: 48 89 c7 mov %rax,%rdi
11b6: e8 b5 fe ff ff call 1070 <strcpy@plt>
11bb: 48 8d 45 f0 lea -0x10(%rbp),%rax
11bf: 48 89 c6 mov %rax,%rsi
11c2: 48 8d 05 3b 0e 00 00 lea 0xe3b(%rip),%rax # 2004 <_IO_stdin_used+0x4>
11c9: 48 89 c7 mov %rax,%rdi
11cc: b8 00 00 00 00 mov $0x0,%eax
11d1: e8 ba fe ff ff call 1090 <printf@plt>
11d6: 90 nop
11d7: 48 8b 45 f8 mov -0x8(%rbp),%rax
11db: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax
11e2: 00 00
11e4: 74 05 je 11eb <_Z19vulnerable_functionPc+0x62>
11e6: e8 95 fe ff ff call 1080 <__stack_chk_fail@plt>
11eb: c9 leave
11ec: c3 ret
执行效果
ubuntu22@NYX:~/ardu/ardupilot_clean$ ./a.out 123123123123123
Input: 123123123123123
*** stack smashing detected ***: terminated
Aborted (core dumped)
代码分步解析
-
获取金丝雀值 (第4-5步):
assembly
1199: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 11a0: 00 00 11a2: 48 89 45 f8 mov %rax,-0x8(%rbp)-
mov %fs:0x28,%rax:从线程局部存储(%fs:0x28)中读取一个随机值,这就是金丝雀。 -
mov %rax,-0x8(%rbp):将这个值保存在栈帧中-0x8(%rbp)的位置。
-
-
初始化局部变量(第6步):
assembly
11a6: 31 c0 xor %eax,%eax这里将
%eax清零,这是一个常见的操作,用于消除金丝雀值在寄存器中的痕迹,提高一点安全性。 -
函数核心功能(第7-13步):
assembly
11a8: 48 8b 55 e8 mov -0x18(%rbp),%rdx 11ac: 48 8d 45 f0 lea -0x10(%rbp),%rax ... 11bb: 48 8d 45 f0 lea -0x10(%rbp),%rax ... 11d6: 90 nop这部分就是
strcpy和printf的调用,处理你的buffer数组(位于-0x10(%rbp))和输入字符串。 -
检查金丝雀(第15-17步):
assembly
11d7: 48 8b 45 f8 mov -0x8(%rbp),%rax 11db: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax 11e2: 00 00 11e4: 74 05 je 11eb <...> 11e6: e8 95 fe ff ff call 1080 <__stack_chk_fail@plt>-
mov -0x8(%rbp),%rax:从栈上读取之前保存的金丝雀值。 -
sub %fs:0x28,%rax:用栈上的值减去原始值。如果两者相同,结果为0,je跳转生效,函数正常返回。 -
如果结果不为0,则调用
__stack_chk_fail,触发栈溢出保护。
-
📐 内存布局与阈值验证
从 sub $0x20,%rsp 可以算出,该函数在栈上分配了 32 字节 (0x20)。结合变量的位置:
| 位置 | 内容 | 说明 |
|---|---|---|
-0x8(%rbp) |
栈金丝雀 (8 字节) | 紧邻 rbp,处于关键位置 |
-0x10(%rbp) |
buffer 数组 (16 字节) |
这就 char buffer[64] |
-0x18(%rbp) |
保存的参数指针 | 存储 input 指针 |
这表明,使用的 char buffer[64] 明确触发了金丝雀保护。阈值规则生效:因为数组长度 (64) 大于 8 字节,编译器判定存在风险,所以为该函数插入了完整的金丝雀机制。