[Fuzzing] Qiling 框架在 Ubuntu22.04 rootfs下遇到 CPU ISA level 错误的临时解决方案
问题分析
最近在尝试用 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上:
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 @@
希望能帮到路过的人。
update
Glibc 引入这个检测的原因,主要是便于通过 cpuid
指令来确定CPU是否满足一些所需的 feature 。这些 feature 的集合被用 ISA Level来描述:baseline, v2, v3 和 v4。支持某 ISA 级别意味着支持该级别和先前级别中包含的所有 feature。
目前 Unicorn 2.0 对于这些 ISA Level 以及所包含的 feature 的支持情况如下(并没有完全支持某个 Level):