この記事について
以前の2つの記事では、 JavaソースコードなしでAndroidアプリケーションをデバッグする方法と、ブレークポイントをインストールするいくつかの機能について説明しました 。 親愛なる読者がこれらの記事をまだ読んでいない場合は、まず始めてからこの記事を読むことを強くお勧めします。
たまたま、私はこれまで、Dalvikバイトコードのデバッグについてのみ話し、ネイティブメソッドのデバッグについては語っていませんでした。 しかし、最も巧妙なものがしばしば隠されているのはネイティブな方法です-unningな防御、興味深いマルウェア機能、ゼロデイ脆弱性。 したがって、今日は簡潔で、「水」なしで、C / C ++ソースコードなしでネイティブメソッドをデバッグする方法を教えます(まあ、何でも、読者がそこに書かれています)。
私の話から利益を得るには、少し「主題に」なる必要があります。 特に、読者は
- Smaliの構文を理解し、.smaliファイルにコードを入力し、ネイティブメソッドの宣言と呼び出しを通常のメソッドと区別でき、Apktoolを使用して.apkファイルを再コンパイルする方法を知っていました 。
- Java Native Interface(JNI)とは何か、 どのように機能するかを想像してください。
- System.load(...)およびSystem.loadLibrary(...)メソッドが使用される理由、Androidでの動作方法、およびSmaliコードのこれらのメソッドの引数により、JNI関数がそれらに対応する.soライブラリに個別に決定できることを知っていましたまたは他のネイティブメソッド。
- .soライブラリでこれらのJNI関数を見つける方法を知っていました。
- 少なくとも初期レベルでは、 ARMアセンブラー を知っていました(この記事では、ARMアーキテクチャを備えたデバイスまたはこのアーキテクチャをエミュレートするエミュレーターでデバッグが実行されることを前提としています)。
- gdbとgdbserverを使用した経験があります 。
それはおそらく読者が必要とするすべての知識とスキルです。 ツールに移りましょう。
ツール
今日必要なもの:
- Android NDKの最新バージョンのARM用gdbおよびgdbserver。 ここでインストールについて説明します 。
- 最新バージョンのAndroid SDKの
adb
ユーティリティ。 ここでインストールについて説明します 。 - デバッグが実際のAndroidデバイスで行われている場合-ルート権限が必要です。
準備に移りましょう。
準備する
読者は、次の準備手順を独立して実行するのに十分な経験があると想定されます。
- Apktoolを使用して.apkアプリケーションファイルを逆アセンブルし、
////smali
のサブディレクトリ内のファイルを調べて確認します。
- Dalvikバイトコードから呼び出されるネイティブメソッド。
- これらのJNIメソッドに対応する関数が配置されている.soライブラリ。
- この.soライブラリを、
////lib
のサブディレクトリまたはデバッグを実行するデバイスから削除し、それを調べて、Dalvikバイトコードから呼び出される特定のネイティブメソッドに対応するJNI関数が呼び出されるかどうかを調べます。 - この記事の説明に従って、Apktoolに
-d
を指定して.apkアプリケーションを再構築し、NetBeansでのデバッグ用に駆動し-d
。 - NetBeansでのデバッグが、関心のあるJNI関数で.soをロードする
System.loadLibrary(...)
またはSystem.load(...)
呼び出しの後 、ただしネイティブメソッドの最初の呼び出しの前に停止することを確認してください。 ここで書いたトリックを使用できます。 - デバッグしているデバイスまたはエミュレータにgdbserverをプッシュします。
すべての準備が完了したので、デバッグに進むことができます。
デバッグ
NetBeansに組み込まれているデバッガーの下でDalvikバイトコードをデバッグするためと、gdbの下でネイティブメソッドの呼び出しをデバッグするためだけに、2つのデバッガーの下でアプリケーションを直接駆動するという考え方です。 少し奇妙に聞こえますが、実際にはそれ自体で機能します。 常にではありませんが、次のセクション「落とし穴」を参照してください。
したがって、読者が前の「準備」セクションのすべての準備手順を完了し、デバイスまたはエミュレーターで実行中のアプリケーションを再構築した場合、NetBeansはコンピューターで開かれ、
System.load(...)
または
System.loadLibrary(...)
呼び出した後のどこかにデバッグが配置され
System.loadLibrary(...)
、ただしネイティブメソッドの最初の呼び出しの前。 さらに、読者はどのライブラリーでどのJNI関数がどのネイティブメソッドに対応するかをすでに認識しています。 ここから始めます。
次はステップバイステップの説明です。 Windows用に作成されましたが、LinuxとMacOSでも動作すると思います。 指示に正確に従ってください:
-
abd shell
コマンドを使用して、デバイスまたはエミュレーターのADBコンソールを開きます。 ADBコンソールでps
コマンドを使用して、アプリケーションプロセスのPIDを見つけます。 同じコンソールで、次のコマンドを実行します。
gdbserver :5039 --attach %PID%
%PID%
はアプリケーションプロセスのPIDです。 応答として、gdbserverは次のようなものを出力するはずです。
Attached; pid = %PID% Listening on port 5039
- コンピューターで新しいコンソールを開き、コマンドを実行します
adb forward tcp:5039 tcp:5039
- 同じコンソールで、Android NDKからgdbを実行します。
gdb libMyNativeLibrary.so
libMyNativeLibrary.so
は、関心のあるJNI関数を含む同じ.soライブラリです。 ライブラリは、コンピュータの起動時にgdbの現在のディレクトリにある必要があります。 これにより、gdbコンソールが開きます。 - gdbコンソールで、次のコマンドを入力します。
(gdb) target remote :5039
Remote debugging using :5039 0x4009d58c in ?? ()
Remote debugging from host 127.0.0.1
- gdbコンソールで
(gdb) info functions
0x5b5f7bac Java_my_app_for_debug_MainActivity_coolNativeMethod 0x5b5f7c0c Java_my_app_for_debug_MainActivity_anotherCoolNativeMethod 0x5b5f7c1c Java_my_app_for_debug_MainActivity_theCoolestNativeMethodEver
- gdbコンソールで、興味のあるJNI関数のアドレスにブレークポイントを設定します。この場合は
(gdb) break *0x5b5f7bac (gdb) break *0x5b5f7c0c (gdb) break *0x5b5f7c1c
- gdbコンソールで
c
コマンドを使用して、アプリケーションを再開します。 このコマンドを実行すると、NetBeansでのデバッグが「フリーズ」し、Dalvikバイトコードを再度デバッグできるようになります。
これで、Dalvikバイトコードがネイティブメソッド呼び出しを呼び出すたびに
const/high16 v8, 0x4100 invoke-static {v8}, Lmy/app/for/debug/MainActivity;->theCoolestNativeMethodEver(F)V
NetBeansでのデバッグはフリーズしますが、gdbは次のようなメッセージであなたを喜ばせます
Breakpoint 1, 0x5b5f7c1c in Java_my_app_for_debug_MainActivity_theCoolestNativeMethodEver () from libMyNativeLibrary.so
これは実際にそれです。 次に、
x/i $pc
、
stepi
一般に、ARM命令を次々にフォワードデバッグします(最初にARMアセンブラが必要になると言ったのを覚えていますか?-まあ...)
落とし穴
ああ、たくさんあります。 落とし穴の庭。 Ainol Auroraデバイス( まだ古いもの )でAndroid 4.0.3のGNU gdbserver(GDB)7.4.1と組み合わせてGNU gdb(GDB)7.4.1を使用したときに遭遇した最も記憶に残るグリッチを次に示します。
- しばらくして、ウォッチドッグタイムアウトによってgdbがgdbserverから定期的に落ちる場合は、コンソールでgdb
set watchdog 18000
実行します-これが役立つはずです。 -
info functions
結果として、info functions
のリストにメモリ内の関数のアドレスが表示されず、.soファイル内のオフセットが表示される場合があります。次に例を示します。
0x000c0bac Java_my_app_for_debug_MainActivity_coolNativeMethod 0x000c0c0c Java_my_app_for_debug_MainActivity_anotherCoolNativeMethod 0x000c0c1c Java_my_app_for_debug_MainActivity_theCoolestNativeMethodEver
libMyNativeLibrary.so
実際に起動時にgdbのディレクトリにあるかどうかを確認し、同じgdb libMyNativeLibrary.so
コマンドgdb libMyNativeLibrary.so
を再起動します。 -
info functions
結果として、info functions
のリストにメモリ内の関数のアドレスと.soファイル内のオフセットが表示される場合があります。次に例を示します。
0x5b5f7bac Java_my_app_for_debug_MainActivity_coolNativeMethod 0x5b5f7c0c Java_my_app_for_debug_MainActivity_anotherCoolNativeMethod 0x5b5f7c1c Java_my_app_for_debug_MainActivity_theCoolestNativeMethodEver 0x000c0bac Java_my_app_for_debug_MainActivity_coolNativeMethod 0x000c0c0c Java_my_app_for_debug_MainActivity_anotherCoolNativeMethod 0x000c0c1c Java_my_app_for_debug_MainActivity_theCoolestNativeMethodEver
- 関数名の
break
コマンドが正常に機能する場合-幸運なことに、そうでない場合は...まあ、実際にはそれが機能しないので、この記事では関数のアドレスにブレークポイントを設定します。 -
set stop-on-solib-events
ない場合があります。 私にはうまくいきません。 - 時々
Cannot access memory at address 0x1
が表示さCannot access memory at address 0x1
。 無視してください。
これは、ソースコードなしでネイティブメソッドをデバッグすることを隠すグリッチの完全なリストとはほど遠いものであり、他の研究者は、私が遭遇したことのないまったく異なるユニークなグリッチを思い付くでしょう。 他の誰かが見つけたら-コメントで共有してください。 また、コメントでは、質問をするか、テキストを技術的に修正してください。 できるだけ早く回答するよう努めますが、バカな場合は我慢してください。 私は皆に答えようとします。
ハッピーデバッグ!
PS記事を無視した人たちへのリクエストで、まったく気に入らなかったものについてのコメントを退会してください。 可能であれば、修正を試みます。
PPS記事は大きなプラスになりました-デバッグとリバースエンジニアリングのトピックが人々にとって興味深いことを意味し、私は私の経験をさらに共有します。 冒頭の記事の短所については、まだ理解していませんでした-ああ、ハブラシラのランダムな変動を相殺しましょう。
PPPS記事のトピックに関する技術的な支援が必要な場合-連絡してください、私は何をお手伝いできます。