问题分析
最近在尝试用 Qiling Framework AFLplusplus 进行fuzz,在ubuntu 22.04
(GLIBC版本2.35)下构建环境并测试时遇到了以下问题:
[!] 0x7ffff7dea1cf: syscall ql_syscall_rseq number = 0x14e(334) not implemented
/lib/x86_64-linux-gnu/libc.so.6: CPU ISA level is lower than required
[=] writev(fd = 0x2, vec = 0x80000000d530, vlen = 0x2) = 0x46
[=] exit_group(code = 0x7f) = ?
使用动态链接的ELF程序在初始化时会遇到ISA检查错误导致无法启动。最开始按照Qiling的提示,我以为是因为ld.so新引入的rseq
系统调用没有被正确实现所导致的,阅读了手册并添加了以下syscall hook后发现并没有效果:
def null_rseq_impl(ql: Qiling, abi: int, length: int, flags: int, sig: int):
return 0
ql.os.set_syscall('rseq', null_rseq_impl, QL_INTERCEPT.CALL)
于是翻找ld.so相关检查逻辑的代码,发现该CHECK只是读取了一些常量并进行比较,没有写操作,理论上bypass掉if判断即可:
至于bypass的方式,我想用地址hook来实现,由于Qiling不实现ASLR,ld.so的基地址固定,于是理论上只要找到相关逻辑的jz
指令进行hook即可。打开IDA好一通找,由于没有出现字符串的交叉引用,也没有相关函数符号的交叉引用,花了不少时间,最后找到了该逻辑的位置:
实现到Qiling的hook上:
代码语言:javascript复制def bypass_isa_check(ql: Qiling) -> None:
print("by_pass_isa_check():")
ql.arch.regs.rip = 0x15
pass
ql.hook_address(bypass_isa_check, ld_so_base 0x2389f)
这时程序可以正常运行。
在解决过程中,去官方的 issue 找了一下发现不少人提过同样的问题,目前还没给啥官方解决方案,于是就先用这个暴力方法解决燃眉之急。
完整脚本
Qiling的extensions模块提供了AFL的有关接口,所以完整的用于ubuntu22.04 rootfs的Fuzz脚本如下:
- warpper_fuzz.py
import unicornafl
unicornafl.monkeypatch()
import os
import sys
from typing import Optional
from qiling import *
from qiling.const import QL_VERBOSE, QL_INTERCEPT
from qiling.extensions import pipe
from qiling.extensions import afl
def main(input_file):
ql = Qiling(
["./test"], "/",
verbose=QL_VERBOSE.OFF)
# set stdin
ql.os.stdin = pipe.SimpleInStream(sys.stdin.fileno())
# get address
base = ql.loader.images[0].base
call_stk_chk_fail = base 0x1330
main_addr = base 0x11c9
def by_pass_isa_check(ql: Qiling) -> None:
print("by_pass_isa_check():")
ql.arch.regs.rip = 0x15
pass
ld_so_base = 0x7ffff7dd5000
ql.hook_address(by_pass_isa_check, ld_so_base 0x2389f)
def null_rseq_impl(ql: Qiling, abi: int, length: int, flags: int, sig: int):
return 0
ql.os.set_syscall('rseq', null_rseq_impl, QL_INTERCEPT.CALL)
def place_input_callback(ql: Qiling, input: bytes, persistent_round: int) -> Optional[bool]:
# feed fuzzed input to our mock stdin
ql.os.stdin.write(input)
# signal afl to proceed with this input
return True
def start_afl(ql: Qiling):
# Have Unicorn fork and start instrumentation.
afl.ql_afl_fuzz(ql, input_file=input_file, place_input_callback=place_input_callback, exits=[ql.os.exit_point])
# make the process crash whenever __stack_chk_fail@plt is about to be called.
# this way afl will count stack protection violations as crashes
ql.hook_address(callback=lambda x: os.abort(), address=call_stk_chk_fail)
# set afl instrumentation [re]starting point. we set it to 'main'
ql.hook_address(callback=start_afl, address=main_addr)
# entry
ql.run()
if __name__ == "__main__":
if len(sys.argv) == 1:
raise ValueError("No input file provided")
main(sys.argv[1])
- fuzz.sh
#!/bin/bash
afl-fuzz -m none -i input -o output -U python3 ./wrapper_fuzz.py @@
希望能帮到路过的人。