株式会社Ninjastars 技術研究部

「もっと楽しく安全なゲームの世界を創る」

はじめてのLLDB Android ARM64解析入門

株式会社Ninjastars
セキュリティエンジニア:一瀬健二郎

今回はAndroid ARM64でビルドされたcrackmeを対象に、lldbとlldb-serverを用いて動的解析を行います。
lldbの使い方を学べばiOSアプリケーションの解析にも応用でき、ARM64の理解はスマホアプリの脆弱性診断などでも重要です。
次世代の高機能デバッガであるlldbの使い方を学び診断に役立てましょう。
※本稿ではARM64については簡単に触れる程度にします。

f:id:Ninjastars:20200426150010p:plain
lldbでスマホアプリを診断しよう!

環境

今回はlldbとlldb-serverを用いてリモートデバッグを行います。
lldbの都合上ホストOSはUbuntuを利用しますが、これは仮想マシン上でも可能です。
またAndroid実機が必要となりますが、root化している必要はありません。

Host:
Ubuntu 20.04LTS(VMWare上等でも可能)
LLDB-10
lldb-server(arm64-v8a AndroidStudio付属)

Guest:
Android実機(64bit)
※root化している必要はありません。

導入

Ubuntu上にlldbのインストール
sudo apt update
sudo apt upgrade
sudo apt install lldb-10
Ubuntu上にadbのインストール
sudo apt install adb
lldb-serverの取得

Download Android Studio and SDK tools  |  Android Developers

上記サイトからLinux(64-bit) android-studio-ide-xxxをダウンロードして展開してください。
android-studio/bin/lldb/android/arm64-v8aにlldb-serverが存在します。
このフォルダにTerminal上で移動してください。
またAndroid実機とPCをUSBで接続し、Androidの開発者オプションでUSBデバッグをオンにしてください。

adb push lldb-server /data/local/tmp
adb shell chmod a+x /data/local/tmp/lldb-server
lldb-serverの起動

UbuntuのTerminal上で下記コマンドを入力することでAndroid実機上でlldb-serverがListen状態で起動します。

adb shell
cd /data/local/tmp
./lldb-server platform --listen *:1234
lldbの起動とlldb-serverとのコネクト

UbuntuのTerminal上で下記コマンドを入力することでlldb-serverとコネクト出来ます。

lldb-10
#lldbが起動
platform select remote-android
platform connect connect://localhost:1234
起動確認

lldbに以下コマンドを入力してみましょう。

platform process list

以下のように出力されれば成功です。

f:id:Ninjastars:20200426152855p:plain
プロセスID一覧が出力される。

crackmeの解析

以下からcrackmeをダウンロードしてください。
drive.google.com


以下コマンドでcrackmeを起動できます。

#転送+権限付与
adb push crackme /data/local/tmp
adb shell chmod a+x /data/local/tmp/crackme
#起動
adb shell
cd /data/local/tmp
./crackme

f:id:Ninjastars:20200426164801p:plain
crackmeの挙動

Please Input Key.と出力されるので、適当に入力すると終了してしまいます。
これを今からlldbとlldb-serverを用いて解析してみましょう。

まずcrackmeのプロセスIDを取得します。

platform process list

f:id:Ninjastars:20200426165138p:plain
crackmeのプロセスIDの取得
環境ごとに変わりますが、上の画像の場合20484がcrackmeのプロセスIDであると分かります。

それではlldbでcrackmeにアタッチしてみましょう。

#attach pid
attach 20484

f:id:Ninjastars:20200426165508p:plain
crackmeにアタッチ成功

crackmeにアタッチ成功すると上のような状態になります。
lldbで現在のバックトレースを取得します。

thread backtrace
#もしくは下記
bt

f:id:Ninjastars:20200426165829p:plain
バックトレースの表示

バックトレースの結果からmain関数内でscanfが呼び出され、その内部でのreadが呼び出されてる状態であると分かります。
それではmain関数を逆アセンブルしてみましょう。

disassemble --name main
#もしくは下記
di -n main

f:id:Ninjastars:20200426170414p:plain
main関数の逆アセンブル結果

ここでx*やw*等の見慣れないレジスタが表示されています。
ARM64アーキテクチャの基本については下記の方のサイトが大変参考になります。ARM64について初見の方は下記のサイトでレジスタや命令について把握してください。
www.mztn.org

まずmain関数の逆アセンブル結果からstrlen関数を呼び出した後にx0レジスタと3を比較していることが分かります。
strlen関数は文字列長を返す関数であり、ARM64において戻り値は基本的にx0レジスタに格納されます。
main+60においてb.neのジャンプ先の次の命令がexitになっているため、文字列長が3でないとプログラムが終了するということが分かります。
よって文字列長は3であると判明しました。

次にmain+92においてw10レジスタとw8レジスタを比較し、一致していなければ次のb.neでwrongにジャンプすることが分かります。
つまりこのw10レジスタとw8レジスタを一致させるような入力を与えれば良いと分かります。

lldbから一旦crackmeを終了させます。

process kill

再度lldbでcrackmeにアタッチしてください。

main+92にブレークポイントをセットします。

breakpoint set --address main+92
#もしくは下記
br s -a main+92

停止中のプログラムを再開します。

continue
#もしくは下記
c

この状態でcrackmeで適当に3文字"abc"と入力するとmain+92でブレークします。
lldbでレジスタの状態を取得します。

register read

f:id:Ninjastars:20200426173025p:plain
レジスタの状態の取得

w10レジスタ(x10レジスタの下位32bit)にはasciiコードで入力文字列であるabcが逆になったcba、w8レジスタ(x8レジスタの下位32bit)にはFLEという数値が格納されていることが分かります。
つまり入力文字列で"ELF"と与えれば良いということが分かります。

f:id:Ninjastars:20200426173500p:plain
フラグが現れた!
Flag:ninja{4nti_che4t_s0lution}

最後になりますが、lldbのコマンド等は下記公式サイトの一覧が分かりやすく纏まっています。
lldb.llvm.org

まとめ

lldbについて今回は簡潔に解説させていただきましたが、実際は非常に多機能に渡ります。
私自身も勉強中であり、常に情報をキャッチアップし脆弱性診断などに的確に利用して行きたいと思います。
本稿が読者の皆様の診断・学習の手助けとなれば幸いです。

注意事項

本レポートに記載されている内容を許可されていないソフトウェアで行うと、場合によっては犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたソフトウェアに対してのみ実施するようにしてください。

本レポートについて
お問い合せ
E-mail:ichise@ninjastars-net.com

株式会社Ninjastarsエンジニア
一瀬健二郎