mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
77 lines
5.7 KiB
Markdown
77 lines
5.7 KiB
Markdown
# Stack Canaries
|
||
|
||
{{#include ../../../banners/hacktricks-training.md}}
|
||
|
||
## **StackGuard 和 StackShield**
|
||
|
||
**StackGuard** 在 **EIP (扩展指令指针)** 之前插入一个特殊值,称为 **canary**,具体为 `0x000aff0d`(表示空值、换行符、EOF、回车)以防止缓冲区溢出。然而,像 `recv()`、`memcpy()`、`read()` 和 `bcopy()` 这样的函数仍然存在漏洞,并且它不保护 **EBP (基指针)**。
|
||
|
||
**StackShield** 采用比 StackGuard 更复杂的方法,通过维护一个 **全局返回栈**,存储所有返回地址 (**EIPs**)。这种设置确保任何溢出不会造成伤害,因为它允许比较存储的和实际的返回地址以检测溢出发生。此外,StackShield 可以检查返回地址与边界值,以检测 **EIP** 是否指向预期数据空间之外。然而,这种保护可以通过 Return-to-libc、ROP(面向返回的编程)或 ret2ret 等技术绕过,这表明 StackShield 也不保护局部变量。
|
||
|
||
## **Stack Smash Protector (ProPolice) `-fstack-protector`:**
|
||
|
||
该机制在 **EBP** 之前放置一个 **canary**,并重新组织局部变量以将缓冲区放置在更高的内存地址,防止它们覆盖其他变量。它还安全地复制传递到局部变量上方的堆栈参数,并使用这些副本作为参数。然而,它不保护少于 8 个元素的数组或用户结构中的缓冲区。
|
||
|
||
**canary** 是从 `/dev/urandom` 派生的随机数或默认值 `0xff0a0000`。它存储在 **TLS (线程局部存储)** 中,允许跨线程共享内存空间具有线程特定的全局或静态变量。这些变量最初从父进程复制,子进程可以在不影响父进程或兄弟进程的情况下更改其数据。然而,如果 **`fork()` 在不创建新 canary 的情况下使用,所有进程(父进程和子进程)共享相同的 canary**,使其变得脆弱。在 **i386** 架构中,canary 存储在 `gs:0x14`,在 **x86_64** 中,存储在 `fs:0x28`。
|
||
|
||
这种本地保护识别具有缓冲区易受攻击的函数,并在这些函数的开始处注入代码以放置 canary,在结束时验证其完整性。
|
||
|
||
当 Web 服务器使用 `fork()` 时,它允许通过逐字节猜测 canary 字节进行暴力攻击。然而,在 `fork()` 后使用 `execve()` 会覆盖内存空间,从而消除攻击。`vfork()` 允许子进程在尝试写入之前执行而不进行复制,此时会创建一个副本,提供了一种不同的进程创建和内存处理方法。
|
||
|
||
### 长度
|
||
|
||
在 `x64` 二进制文件中,canary cookie 是一个 **`0x8`** 字节的 qword。**前七个字节是随机的**,最后一个字节是 **空字节**。
|
||
|
||
在 `x86` 二进制文件中,canary cookie 是一个 **`0x4`** 字节的 dword。**前三个字节是随机的**,最后一个字节是 **空字节**。
|
||
|
||
> [!CAUTION]
|
||
> 两个 canary 的最低有效字节是空字节,因为它将是来自较低地址的堆栈中的第一个,因此 **读取字符串的函数将在读取之前停止**。
|
||
|
||
## 绕过
|
||
|
||
**泄露 canary** 然后用其自身的值覆盖它(例如,缓冲区溢出)。
|
||
|
||
- 如果 **canary 在子进程中被 fork**,可能可以 **逐字节暴力破解** 它:
|
||
|
||
{{#ref}}
|
||
bf-forked-stack-canaries.md
|
||
{{#endref}}
|
||
|
||
- 如果二进制文件中存在一些有趣的 **泄露或任意读取漏洞**,可能可以泄露它:
|
||
|
||
{{#ref}}
|
||
print-stack-canary.md
|
||
{{#endref}}
|
||
|
||
- **覆盖堆栈存储的指针**
|
||
|
||
易受堆栈溢出影响的堆栈可能 **包含可以被覆盖的字符串或函数的地址**,以利用该漏洞而无需到达堆栈 canary。检查:
|
||
|
||
{{#ref}}
|
||
../../stack-overflow/pointer-redirecting.md
|
||
{{#endref}}
|
||
|
||
- **修改主 canary 和线程 canary**
|
||
|
||
在受 canary 保护的线程函数中 **缓冲区溢出** 可以用来 **修改线程的主 canary**。因此,缓解措施是无效的,因为检查是使用两个相同的(尽管被修改过的)canary。
|
||
|
||
此外,在受 canary 保护的线程函数中 **缓冲区溢出** 可以用来 **修改存储在 TLS 中的主 canary**。这是因为,可能通过线程的 **堆栈中的 bof** 到达存储 TLS 的内存位置(因此,canary)。\
|
||
因此,缓解措施是无效的,因为检查是使用两个相同的(尽管被修改过的)canary。\
|
||
此攻击在以下写作中进行: [http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads](http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads)
|
||
|
||
还可以查看 [https://www.slideshare.net/codeblue_jp/master-canary-forging-by-yuki-koike-code-blue-2015](https://www.slideshare.net/codeblue_jp/master-canary-forging-by-yuki-koike-code-blue-2015) 的演示,其中提到通常 **TLS** 是通过 **`mmap`** 存储的,当 **线程** 的 **堆栈** 被创建时,它也是通过 `mmap` 生成的,这可能允许如前所述的溢出。
|
||
|
||
- **修改 `__stack_chk_fail` 的 GOT 条目**
|
||
|
||
如果二进制文件具有部分 RELRO,则可以使用任意写入来修改 **`__stack_chk_fail` 的 GOT 条目**,使其成为一个不会在 canary 被修改时阻止程序的虚拟函数。
|
||
|
||
此攻击在以下写作中进行: [https://7rocky.github.io/en/ctf/other/securinets-ctf/scrambler/](https://7rocky.github.io/en/ctf/other/securinets-ctf/scrambler/)
|
||
|
||
## 参考
|
||
|
||
- [https://guyinatuxedo.github.io/7.1-mitigation_canary/index.html](https://guyinatuxedo.github.io/7.1-mitigation_canary/index.html)
|
||
- [http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads](http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads)
|
||
- [https://7rocky.github.io/en/ctf/other/securinets-ctf/scrambler/](https://7rocky.github.io/en/ctf/other/securinets-ctf/scrambler/)
|
||
|
||
{{#include ../../../banners/hacktricks-training.md}}
|