记录一只金丝雀

金丝雀案例代码

复制代码
#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)
代码分步解析

  1. 获取金丝雀值 (第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) 的位置。

  2. 初始化局部变量(第6步)

    assembly

    复制代码
    11a6:       31 c0                   xor    %eax,%eax

    这里将 %eax 清零,这是一个常见的操作,用于消除金丝雀值在寄存器中的痕迹,提高一点安全性。

  3. 函数核心功能(第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

    这部分就是 strcpyprintf 的调用,处理你的 buffer 数组(位于 -0x10(%rbp))和输入字符串。

  4. 检查金丝雀(第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 字节,编译器判定存在风险,所以为该函数插入了完整的金丝雀机制。