本仓库包含所有源码(包括设置脚本),你需要这些来让 difuze 正常运行。
Ubuntu >= 14.04.5 LTS
参考 readme
如我们的论文所述,difuze 包含两个主要组件:接口恢复 和 模糊测试引擎
接口恢复机制基于 LLVM 分析过程。接口恢复的每个步骤都编写为独立的过程。按照以下说明操作,让 接口恢复 正常运行。
此步骤负责安装 LLVM 和 c2xml:
首先,确保已安装 libxml(c2xml 所需):
sudo apt-get install libxml2-dev
sudo pip install lxml
接下来,我们创建了一个单一脚本,用于下载并构建所有必需的工具。
cd helper_scripts
python setup_difuze.py --help
usage: setup_difuze.py [-h] [-b TARGET_BRANCH] [-o OUTPUT_FOLDER]
optional arguments:
-h, --help show this help message and exit
-b TARGET_BRANCH Branch (i.e. version) of the LLVM to setup. Default:
release_38 e.g., release_38
-o OUTPUT_FOLDER Folder where everything needs to be setup.
示例:
python setup_difuze.py -o difuze_deps
要完成设置,你还需要修改本地的 PATH 环境变量。设置脚本会给出你需要做的具体更改。
此步骤依赖于 设置 的成功完成。 我们有一个能构建所有内容的单一脚本,不用客气。
cd InterfaceHandlers
./build.sh
此步骤依赖于 构建 的成功完成。 要在内核驱动上运行接口恢复组件,我们首先需要将驱动转换为 LLVM 位码。
首先,我们需要一个可构建的内核。这意味着你应该能够使用常规构建设置(即 make)编译内核。
我们首先捕获 make 命令的输出,然后从该输出中提取精确的编译命令。
make 的输出bear make <所有 make 选项>
bear make -j8这将在当前目录中生成一个 compile_commands.json 文件。
只需传递 V=1 并将输出重定向到文件。
示例:
make V=1 O=out ARCH=arm64 > makeout.txt 2>&1
注意:不要使用多个进程,即 -j。多进程模式会搞乱输出文件,因为多个进程会尝试写入同一个输出文件。
就这样。接下来,在后续步骤中,我们的脚本会使用生成的 makeout.txt,并在所有已识别的驱动上运行接口恢复。
接口恢复的所有步骤都封装在单个脚本 helper_scripts/run_all.py 中。
如何运行:
cd helper_scripts
python run_all.py --help
usage: run_all.py [-h] [-l LLVM_BC_OUT] [-a CHIPSET_NUM] [-m MAKEOUT]
[-c COMPJSON] [-g COMPILER_NAME] [-n ARCH_NUM] [-o OUT]
[-k KERNEL_SRC_DIR] [-isclang] [-clangp CLANG_PATH]
[-llvmlinkp LLVMLINK_PATH] [-skb] [-skl] [-skp] [-skP]
[-ske] [-skI] [-ski] [-skv] [-skd] [-f IOCTL_FINDER_OUT]
optional arguments:
-h, --help show this help message and exit
-l LLVM_BC_OUT Destination directory where all the generated bitcode
files should be stored.
-a CHIPSET_NUM Chipset number. Valid chipset numbers are:
1(mediatek)|2(qualcomm)|3(huawei)|4(samsung)
-m MAKEOUT Path to the makeout.txt file.
-c COMPJSON Path to the compile_commands_json generated by Bear.
-g COMPILER_NAME Name of the compiler used in the makeout.txt, This is
needed to filter out compilation commands. Ex: aarch64
-linux-android-gcc
-n ARCH_NUM Destination architecture, 32 bit (1) or 64 bit (2).
-o OUT Path to the out folder. This is the folder, which
could be used as output directory during compiling
some kernels.
-k KERNEL_SRC_DIR Base directory of the kernel sources.
-isclang flag to indicate that clang was used to built the
kernel
-clangp CLANG_PATH Absolute path to the clang binary (if not provided,
the one available in the path will be used)
-llvmlinkp LLVMLINK_PATH
Absolute path to the llvm-link binary (if not
provided, the one available in the path will be used)
-skb Skip LLVM Build (default: not skipped).
-skl Skip Dr Linker (default: not skipped).
-skp Skip Parsing Headers (default: not skipped).
-skP Skip Generating Preprocessed files (default: not
skipped).
-ske Skip Entry point identification (default: not
skipped).
-skI Skip Generate Includes (default: not skipped).
-ski Skip IoctlCmdParser run (default: not skipped).
-skv Skip V4L2 ioctl processing (default: not skipped).
-skd Skip Device name finder (default: not skipped).
-f IOCTL_FINDER_OUT Path to the output folder where the ioctl command
finder output should be stored.
该脚本会在所有已识别的驱动上构建、链接并运行接口恢复,因此可能需要相当长的时间(45 分钟到 90 分钟)。
该脚本以多处理模式执行以下任务,以充分利用所有 CPU 核心:
生成的所有位码文件都将放置在通过参数 -l 提供的文件夹中。
此步骤需要相当长的时间,具体取决于你的核心数。
因此,如果你已经完成了此步骤,可以通过传递 -skb 跳过它。
此步骤执行链接,它会遍历所有位码文件,识别需要链接的相关位码文件,并将它们(使用 llvm-link)链接到一个合并的位码文件中(该文件将存储在相应的位码文件旁边)。
与上一步类似,你可以通过传递 -skl 跳过此步骤。
此步骤在头文件中查找入口点声明,并将其配置存储在 LLVM 构建目录下的 hdr_file_config.txt 文件中。
要跳过:-skp
此步骤识别所有驱动合并位码文件中的所有入口点。
输出将存储在 LLVM 构建目录下的 entry_point_out.txt 文件中。
entry_point_out.txt 文件内容示例:
IOCTL:msm_lsm_ioctl:/home/difuze/kernels/pixel/msm/sound/soc/msm/qdsp6v2/msm-lsm-client.c:msm_lsm_ioctl.txt:/home/difuze/pixel/llvm_out/sound/soc/msm/qdsp6v2/llvm_link_final/final_to_check.bc
IOCTL:msm_pcm_ioctl:/home/difuze/kernels/pixel/msm/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c:msm_pcm_ioctl.txt:/home/difuze/pixel/llvm_out/sound/soc/msm/qdsp6v2/llvm_link_final/final_to_check.bc
要跳过:-ske
此步骤将在 entry_point_out.txt 文件中的所有入口点上运行主要的接口恢复组件(IoctlCmdParser)。每个入口点的输出将存储在通过选项 -f 提供的文件夹中。
要跳过:-ski
现在,我们将展示一个从拥有内核源码到获取接口恢复结果的示例。
我们上传了一个 mediatek 内核 33.2.A.3.123.tar.bz2。 首先下载并解压上述文件。
假设你将上述文件解压到名为 ~/mediatek_kernel 的文件夹中。
安装 Bear 并按照以下步骤操作:
cd ~/mediatek_kernel
source ./env.sh
cd kernel-3.18
# 以下步骤根据内核可能不需要
mkdir out
make O=out ARCH=arm64 tubads_defconfig
# 生成 compile_commands.json
bear make -j8 O=out ARCH=arm64
cd <仓库路径>/helper_scripts
python run_all.py -l ~/mediatek_kernel/llvm_bitcode_out -a 1 -c ~/mediatek_kernel/kernel-3.18/compile_commands.json -n 2 -o ~/mediatek_kernel/kernel-3.18/out -k ~/mediatek_kernel/kernel-3.18 -f ~/mediatek_kernel/ioctl_finder_out
上述命令需要一些时间(30 分钟到 1 小时)。
首先,所有分析结果都在文件夹 ~/mediatek_kernel/ioctl_finder_out(通过选项 -f 提供的参数) 中,每个入口点会创建一个 .txt 文件,其中包含有关恢复接口的所有信息。
如果你只关心接口的信息而不关心其他内容,我们建议你使用 parse_interface_output.py 脚本。此脚本将接口恢复过程的复杂输出转换为干净的 json 文件,格式统一且清晰。
cd <仓库路径>/helper_scripts
python parse_interface_output.py <ioctl_finder_out_dir> <输出文件夹用于存放 json 文件>
这里的 <ioctl_finder_out_dir> 应该与提供给 -f 选项的文件夹相同,<输出文件夹用于存放 json 文件> 是创建 json 文件的文件夹。
你可以使用对应的 json 文件来恢复相应 ioctl 的接口。
-g 的值(仅在使用 makeout.txt 时)要为选项 -g 提供值,你需要知道用于编译内核的 *-gcc 二进制文件的名称。
一种简单的方法是,在 makeout.txt 中 grep 查找 gcc,你会看到编译器命令,从中可以知道 *-gcc 二进制文件的名称。
对于上面的示例,如果你在示例构建中对 makeout.txt 执行 grep gcc,你会看到很多类似下面的行:
aarch64-linux-android-gcc -Wp,-MD,fs/jbd2/.transaction.o.d -nostdinc -isystem ...
因此,-g 的值应该是 aarch64-linux-android-gcc。
如果要构建的内核是 32 位的,那么该二进制文件很可能是 arm-eabi-gcc。
对于 Qualcomm(或 msm)芯片组,你可能会看到 *gcc-wrapper.py 而不是 *.gcc,在这种情况下,你应该提供 *gcc-wrapper.py。
-a 的值根据芯片组类型,你需要提供对应的数字。
-o 的值这是在内核构建期间提供给 make 命令的选项 O= 的文件夹路径。
并非所有内核都需要单独的 out 路径。如果你在构建内核时没有提供选项 O,那么在运行 run_all.py 时不应为该选项提供值。
对于使用 clang 构建的内核,除了上述选项外,请指定以下选项(假设你使用了 compile_commands.json):
-isclang -clangp <用于构建内核的 CLANG 路径> -llvmlinkp <LLVM_LINK 路径(将与 clang 位于同一文件夹)>
在开始模糊测试之前,我们需要使用我们的研究级别(抱歉)解析器对输出进行一些处理。
这些解析器位于这里。要运行的主脚本是 run_all.py:
$ python run_all.py --help
usage: run_all.py [-h] -f F -o O [-n {manual,auto,hybrid}] [-m M]
run_all options
optional arguments:
-h, --help show this help message and exit
-f F Filename of the ioctl analysis output OR the entire
output directory created by the system
-o O Output directory to store the results. If this
directory does not exist it will be created
-n {manual,auto,hybrid}
Specify devname options. You can choose manual
(specify every name manually), auto (skip anything that
we don't identify a name for), or hybrid (if we
detected a name, we use it, else we ask the user)
-m M Enable multi-device output most ioctls only have one
applicable device node, but some may have multiple. (0
to disable)
你需要将 -f 传递给 ioctl 分析的输出目录,例如 ~/mediatek_kernel/ioctl_finder_out。
-o 是存储后处理结果的目录。这些将是易于理解的 XML 文件(jpits)。
-n 指定系统在多大程度上依赖我们的设备名称恢复。
如果你不想做任何工作/名称查找,可以指定 auto。
当然,这会导致跳过任何我们未恢复名称的设备。如果你想保持谨慎,不信任我们的任何恢复工作(完全合理),可以使用 manual 选项自行命名每个设备。
hybrid 则是两者的组合——我们会在可能时为你命名设备,并在失败时回退给你。
-m 有时 ioctl 可能对应多个设备(例如 v4l2/subdev ioctl 很常见)。默认情况下支持此功能,但需要用户交互来指定每个设备的设备数量。如果这让你感到烦恼,可以通过传递 -m 0 禁用提示(我们将假定每个 ioctl 对应单个设备)。
运行后,在你的输出文件夹中,每个 ioctl 应该有一个对应的文件夹。
MangoFuzz 是我们的简单原型模糊测试器,基于 Peach(特别是 MozPeach)开发。
它不是一个特别复杂的模糊测试器,但它能发现漏洞。 它也被设计为易于扩展。 该模糊测试器有 2 个组件:模糊引擎和执行器。 执行器可以在这里找到,模糊引擎可以在这里找到。
执行器在手机上运行,监听模糊引擎将发送给它的数据。
只需为你的手机架构编译它,通过 adb push 推送到手机上,然后使用你想要监听的端口执行即可!
与 MangoFuzz 交互非常简单。你需要一个 Engine 对象和一个 Parser 对象,将你的引擎传递给 Parser。
然后,使用 Parser 解析 jpits,再运行 Engine。简单吧!
我们提供了一些简单的运行脚本来帮助你入门。
要针对特定驱动程序运行,你可以使用 runner.py,并指定输出目录(由我们的后处理脚本创建)中的一个 ioctl 文件夹。
例如:./runner.py -f honor8/out/chb -num 1000。这将指示 MangoFuzz 对与 chb ioctl/驱动程序相关的所有 ioctl 命令值对运行 1000 次迭代。
如果你想要针对整个设备(手机)运行,可以使用 dev_runner.py。例如:./dev_runner.py -f honor8/out -num 100。
这将持续循环遍历驱动程序文件,每次迭代随机切换,总共运行 100 次迭代。
请注意,在模糊引擎与手机通信之前,你需要使用 ADB 设置端口转发,例如:adb forward tcp:2022 tcp:2022