本仓库包含所有源码(包括设置脚本),你需要这些来让 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 时不应为该选项提供值。