尽管 pwnable.tw已经很久没更新新题,这上面的题目放到现在对我而言也仍然是很有趣的。在解 3x17 这道题的时候,用到了之前从没用过的 fini_array hijack,因此记录一下。
预分析
checksec
结果:
1 | Arch: amd64-64-little |
file
结果:
1 | 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
程序功能分析
1 | if ( byte_4B9330 == 1 ) |
程序很直接地提供了一次任意地址写的机会,又称 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
- 当程序退出时,先执行
main
函数,任意写包含于其中(尽管会检查0x4B9330
地址处是否为1,但其类型为 byte/uint8,因此1+16 == 1
,我们不必担心)。 - 然后执行
__libc_csu_fini
函数。该函数的作用就是调用所有.fini_array
中的函数,于是回到步骤1。因此形成了无限循环。
如何定位.fini_array
和__libc_csu_fini
的地址?
1 | readelf -S ./3x17 | grep .fini_array |
-S
可以显示各个段的 header 。
在 start
函数中找到:
1 | sub_401EB0( |
# 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
函数为我们创造了条件:
1 | push rbp |
以上是 __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
。
mov rsp, rbp
:rsp = rbp = .fini_array
pop rbp
:rbp = .fini_array[0]
,rsp = .fini_array + 0x8
ret
:rip = rsp = .fini_array + 0x8
因此只要令.fini_array[0]
处为leave ; ret
gadget 的地址,且从.fini_array + 0x8
开始为 ROP chain 即可 get shell 。
exploit
由于只是一道 150 分的题,根据 pwnable.tw 的规则可以公开代码用于参考:
1 | #!/usr/bin/env python3 |
条评论