
このリポジトリには、difuze をセットアップして実行するために必要なすべてのソース(セットアップスクリプトを含む)が含まれています。
Ubuntu >= 14.04.5 LTS
詳細はreadmeを参照してください。
論文で説明されているように、difuze には2つの主要コンポーネントがあります:インターフェース回復 とファジングエンジン です。
インターフェース回復メカニズムは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
このステップでは、主要なインターフェース回復コンポーネント(IoctlCmdParser)を entry_point_out.txt ファイル内のすべてのエントリポイントに対して実行します。各エントリポイントの出力は、-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 <repo_path>/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 <repo_path>/helper_scripts
python parse_interface_output.py <ioctl_finder_out_dir> <output_directory_for_json_files>
ここで、<ioctl_finder_out_dir> は -f オプションに指定したフォルダと同じで、<output_directory_for_json_files> はJSONファイルを作成するフォルダです。
対応するioctlのインターフェース回復には、対応するJSONファイルを使用できます。
-g の値(makeout.txtを使用する場合のみ)-g オプションの値を指定するには、カーネルのコンパイルに使用された *-gcc バイナリの名前を知る必要があります。
簡単な確認方法は、makeout.txt 内で gcc を grep することです。コンパイラコマンドが表示され、そこから *-gcc バイナリ名がわかります。
上記の例では、grep gcc makeout.txt を実行すると、以下のような行が多数表示されます:
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 の代わりに *gcc-wrapper.py が表示されることがあり、その場合は *gcc-wrapper.py を指定する必要があります。
-a の値チップセットの種類に応じて、対応する番号を指定する必要があります。
-o の値これは、カーネルビルド時に make コマンドの O= オプションに指定したフォルダのパスです。
すべてのカーネルが個別の出力パスを必要とするわけではありません。O オプションを指定せずにカーネルをビルドすることもできます。その場合、run_all.py の実行時にそのオプションに値を指定してはいけません。
clang を使用してビルドされたカーネルの場合、上記のオプションに加えて、以下のオプションを指定してください(compile_commands.json を使用した場合):
-isclang -clangp <カーネルのビルドに使用したCLANGへのパス> -llvmlinkp <LLVMリンクへのパス(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 オブジェクトが必要で、エンジンにパーサーを渡します。
その後、パーサーで jpit を解析し、エンジンを実行します。簡単です!
開始するためのシンプルな実行スクリプトをいくつか用意しています。
特定のドライバに対して実行するには、出力ディレクトリ(後処理スクリプトで作成)内の ioctl フォルダの1つに対して runner.py を使用できます。
例: ./runner.py -f honor8/out/chb -num 1000。これは、chb ioctl/ドライバに関連するすべての ioctl コマンド値ペアに対して1000回の反復で MangoFuzz を実行します。
代わりにデバイス(スマートフォン)全体に対して実行する場合は、dev_runner.py を使用できます。例: ./dev_runner.py -f honor8/out -num 100。
これは、ドライバファイルをループし、ランダムに切り替えながら各ファイルに対して100回の反復を実行します。
ファズエンジンがスマートフォンと通信する前に、ADB を使用してポート転送を設定する必要があることに注意してください。例: adb forward tcp:2022 tcp:2022