ユーザ用ツール

サイト用ツール


サイドバー

ctf:lesson:第6回

第6回資料

  • 2018/7/4
  • 暑い
  • 北海道楽しかった…お土産あるよ…
  • 今回の内容: Androidアプリを解析してみる, ソースコードを読むのに慣れる

事前準備

扱うファイル

APKを見てみる

  • .apkという拡張子・形式は初めて見ると思います
    • Android端末で動作するアプリの形式です
  • バイナリを見てみよう
snippet.bash
$ xxd pinstore.apk | head -n 5
0000000: 504b 0304 1400 0808 0800 457b 4a4a 82d1  PK........E{JJ..
0000010: 5b22 ec02 0000 2808 0000 1300 0400 416e  ["....(.......An
0000020: 6472 6f69 644d 616e 6966 6573 742e 786d  droidManifest.xm
0000030: 6cfe ca00 00ad 543d 6fd4 4010 7d3e df05  [email protected]}>..                                                                    
0000040: 1f26 e4f2 fd71 49a0 a088 90ce 0488 00d1  .&...qI.........
  • 先頭(シグネチャ)に注目→実は.apk = .zip
  • 展開してみる
snippet.bash
$ unzip pinstore.apk
...snip...
$ ls
AndroidManifest.xml  assets/  classes.dex  META-INF/  pinstore.apk  res/  resources.arsc
  • assets/→アセット, 画像とか音声とかデータ
  • res/→いろいろなアプリで使いまわされるデータ
  • META-INF/ AndroidManifest.xml →アプリに関する情報(識別子、バージョンetc)
  • resources.arsc→メッセージなどに使う文字列
  • classes.dex→動作させるための.jar(後述)が入っているDEX形式のファイル
    • ここを中心に解析していきます

あなたとJava、いますぐダウンロード

  • 踏み込む前に、少しJavaについて書いておきます
  • Java言語は知っていますか?
  • 実行の流れに特徴がある
    • 普通のコンパイラではソースコードを入れると機械語を吐き出します
    • JavaはJVM (Java Virtual Machine)を利用しているので、少し実行の仕方が違います
      • Minecraftやる人ならJDKとかJREとかダウンロードしたことがあると思いますが、あれらの中にJVMが含まれています
      • まず、ソースコードを部分ごとに.classという拡張子を持つバイトコードに変換(コンパイル)します
      • 次に、実行環境のJVMでバイトコードを機械語に変換&実行(JITコンパイル)して、実際にプログラムが動きます
      • どうしてこんなまどろっこしいことをするのかは「Write Once, Run Anywhere」で検索すると見つかるかもしれません
  • AndroidのアプリはJVM系の言語で書かれることが多いです
    • 「もともとの言語が異なっても、JVMで動くバイトコードさえ出力できればあとは同じじゃん」という発想
    • 実際、Androidの公式の開発言語はKotlinという別のJVM言語です
    • スピードが要求される場面ではNDK (Native Development Kit)を使ってC/C++で書くこともあります

APKのソースコードを見てみよう

  • dex2jarを使ってclasses.dex内の.class(→バイトコード)を見てみます
snippet.bash
$ /path/to/dex2jar-2.0/d2j-dex2jar.sh classes.dex
  • バイトコードを配布用にまとめたのが.jarファイルで、これも正体はZIPです
snippet.bash
$ unzip classes-dex2jar.jar
$ cd pinlock/ctf/pinlock/com/pinstore/
$ ls	
BuildConfig.class        MainActivity.class  R.class  ...
  • バイトコードをそのまま読んで動作を解析しても良いのですが、もっとラクな方法を紹介します
  • デコンパイル (decompile) です
    • コンパイルの逆
    • 機械語・アセンブラから元のソースコードを復元するのは難しい(らしい)のですが、バイトコードからだとわりとサクっとできてしまいます
  • jadを使ってデコンパイルしてみましょう
    • *.classのようにするとそこにあるすべての.classファイルを指定できます
snippet.bash
$ /path/to/jad *.class
  • .jadがいろいろ生成されますね、これがデコンパイル結果です

いざ解析

  • MainActivity.jadを読んでみましょう
    • Androidでは、大体の処理はここから始まります
snippet.java
public void onClick(View view) {
    String s;
    String s2;
    s2 = pinEditText.getText().toString();
    view = null;
    s = null;
    String s1 = (new DatabaseUtilities(getApplicationContext())).fetchPin();
    view = s1;
_L1:
    s1 = CryptoUtilities.getHash(s2);
    s = s1;
_L2:
    Object obj;
    if(view.equalsIgnoreCase(s)) {
        view = new Intent(MainActivity.this, pinlock/ctf/pinlock/com/pinstore/SecretDisplay);
        view.putExtra("pin", s2);
        startActivity(view);
        return;
    } else {
        pinEditText.setText("");
        Toast.makeText(MainActivity.this, "Incorrect Pin, try again", 1).show();
        return;
    }
  • 入力された文字列(PIN)のハッシュとDatabaseUtilitiesから取得した文字列を比較していますね
  • 一致していればSecretDisplayにpinを渡して処理を移動しています
  • 次にSecretDisplay.jadを見てみましょう
snippet.java
String s = getIntent().getStringExtra("pin");
try {
    DatabaseUtilities databaseutilities = new DatabaseUtilities(getApplicationContext());
    textview.setText((new CryptoUtilities("v1", s)).decrypt(databaseutilities.fetchSecret()));
} catch (Exception exception) {
    Log.e("Pinlock", "exception", exception);
}
Toast.makeText(bundle, s, 1);
  • 受け取ったpinでCryptoUtilitiesなるものを生成してから、DatabaseUtilitiesで取り出してからdecrypt(=復号・解読)していますね
  • 解読結果を表示しているみたいです
  • おおまかな流れが掴めたので、DatabaseUtilitiesの内容も読んでみましょう
  • DatabaseUtilities
snippet.bash
public String fetchPin() { // MainActivityから呼び出される
    openDB();
    Cursor cursor = db.rawQuery("SELECT pin FROM pinDB", null); // pinDBテーブルからデータを取り出す
    ...
    return s;
}
 
public String fetchSecret() { // SecretDisplayから呼び出される
    openDB();
    Cursor cursor = db.rawQuery("SELECT entry FROM secretsDBv1", null); // secretsDBv1テーブルからデータを取り出す
    ...
    return s;
}
 
...
 
private static String dbName = "pinlock.db";
private static String pathToDB = "/data/data/pinlock.ctf.pinlock.com.pinstore/databases/";

- データベースからいろいろ読み出しているみたいです

データベースを見てみる

  • pinlock.dbというのがデータベースのファイルっぽいので、中身のデータを取り出してみましょう
    • findコマンドでファイルを検索できます
snippet.bash
$ find -name pinlock.db
./assets/pinlock.db
$ sqlitebrowser ./assets/pinlock.db &
  • Browse DataからTableを選択するとデータが表示できます

ハッシュ関数について

  • 暗号化について前少し話しましたが、ハッシュ関数は「復号できない暗号化」です
  • ハッシュ関数は「ある値を入れると、それに応じたハッシュ値を出力する」ものです
    • 入力が異なれば、必ずハッシュ値は異なったものになる
    • ハッシュ値から入力を推定することはできない
  • SHA256, SHA1, MD5など種類があります
  • どう便利なのかイマイチわからないので、ざっくり認証での例を紹介します
    • パスワードhogehogeを持つユーザーのデータを保存したいとします
      • このとき、hogehogeという文字列をそのまま保存すると、データが流出した際にパスワードが漏れてしまいます
      • 代わりに、hogehogeをハッシュ関数に入力したものを保存することにします
        • ハッシュ関数(SHA256)の出力→4c716d4cf211c7b7d2f3233c941771ad0507ea5bacf93b492766aa41ae9f720d
    • このユーザーでログインしたいときは、ユーザーが入力したパスワードをハッシュ関数に入力します
      • 入力したパスワードが正しければ、ハッシュ値は4c716d4cf211c7…となって上と合致します
      • 誤っていれば、「入力が異なれば、必ずハッシュ値は異なったものになる」という性質より、上のハッシュ値とは合致しません

PINを解読しよう

  • でも、「password」や「114514」といった有名・簡単(数字だけ・短い)な文字列に対するハッシュ値はまとめられている
  • CrackStation にハッシュ値を入れてみましょう
  • PINは7498ですね!

ソースコードを読むときのコツ

  • ある関数がどこから呼び出されたり、どこに定義されているかわからない→ファイルを横断した検索を活用しましょう
    • VSCodeならサイドバーの虫眼鏡を押すと検索できます
    • コマンドラインならgrepやripgrepを使いましょう
  • ctagsなどもありますが詳しくないのでわかりません

発展

  • これはCTFの問題ですが、FLAGが出てきていません→実は問題を解いたのではなくて、PINを見つけるのはそのステップの一つ
    • あとは自分でやってみてください、ここがヒントになるかも
  • Wiki書いてくれるととても嬉しい
    • 僕が嬉しくなるのはどうでもいいんですが、将来内容を思い出すとき・誰かに教えるときにそういうメモが役立ったりします
  • バイトコード
  • 今回はSQLiteという種類のデータベースでしたが、他にもいろいろあります
    • 途中にあったSELECT * FROM pinDBなどはSQL言語で書かれたデータベースを操作するためのコードです
    • PostgreSQL, MySQL, mongoDBなどが有名
  • Android
  • awesome-mobile-CTF おもしろそうなスマホ系の問題がたくさん載っています、一緒にやろう!
  • ハッシュ関数はbitcoinのマイニングにも使われてたりします
ctf/lesson/第6回.txt · 最終更新: 2018/08/15 16:49 (外部編集)