Upgrade to Pro — share decks privately, control downloads, hide ads and more …

本当はこわいLLDB

 本当はこわいLLDB

iOSDC2020 Day2 16:20〜
https://fortee.jp/iosdc-japan-2020/proposal/62a95cf9-1b1b-401a-8a1c-11c0490aad00

【Special Thanks】
発表資料のレビューとリハに付き合ってくれた
ddd503(@dk31486981 )

MiyasakaKazutoshi

September 21, 2020
Tweet

More Decks by MiyasakaKazutoshi

Other Decks in Programming

Transcript

  1. 本当はこわいLLDB
    みやし

    View Slide

  2. ⾃⼰紹介
    Name: みやし

    Twitter: @po_miyasaka

    Work:minne @ GMOペパボ

    OSSの宣伝: 

    @yhkaplan⽒とVisionAPIを使ったクレカリーダーのOSSを作成し
    ました。

    iOS13以降必須ですが、card.ioよりも読み取れるカードも多く、
    UIもカスタマイズしやすいので、ぜひ活⽤してみてください!

    https://github.com/yhkaplan/credit-card-scanner
    2

    View Slide

  3. 発表内容
    Objective-Cでコンパイルされたアプリであれば、

    LLDBで実⾏中に⾊々なハックができてしまいます。

    今回の発表では、実際にアプリにアタッチしてハックしてみよ
    うと思います。

    (ちなみに、SwiftでかかれたコードではLLDBでハックできませ
    んでした。)


    3

    View Slide

  4. ⽬次
    • 下準備

    • スライドについての備考

    • 拡張コマンドのインストール

    • SIPを解除する

    • アプリにアタッチする

    • アプリ全体を眺める

    • 依存しているダイナミックライブラリの⼀覧化

    • アセンブリを書き出す

    • アプリ内でハードコードされた⽂字列を⼀覧化

    • ローカルデータベースの中身を⾒る

    • アプリ内のDocumentsやCachesの中身を⾒る

    • 表示中のView階層を⾒る
    4
    • 気になる箇所にブレークポイントを挟む

    • 正規表現でマッチする関数でブレーク

    • 関数のアドレスを指定してブレーク

    • UI操作契機でブレーク

    • ブレークした関数の引数の中身を調べる

    • 引数とレジスタの関係

    • 引数が⽂字列だった場合

    • 引数がクラスインスタンスだった場合

    • 引数が関数だった場合

    • ブレークした関数の返り値を調べる

    • ブレークした関数の引数や返り値の書き換え

    • リバースエンジニアリングの防⽌法

    • 結論

    View Slide

  5. スライドについての備考
    • ターミナルコマンドには「$」LLDBコマンドには「(lldb)」のプ
    レフィックスをつけます。
    • ⽂字数削減のため「メソッド」のことでも「関数」と表記して
    ます。
    • このスライドは私個⼈の趣味の範囲で作ったものであり、所属
    する組織団体とは関係ありません。
    下準備
    5

    View Slide

  6. LLDBの便利な拡張コマンドをインストール
    • https://github.com/facebook/chisel

      使⽤するコマンド: pvc, pviews

    • https://github.com/po-miyasaka/LLDB

    使⽤するコマンド: vinfo, cbd

    • https://github.com/DerekSelander/LLDB

    使⽤するコマンド: lsof, ls, lookup, tv, yoink

    ※ Derek⽒の「Advanced Apple Debugging & Reverse Engineering」はおすすめです。
    下準備
    6

    View Slide

  7. SIPを解除する
    MACはSIPによって守られているため、デフォルト設定では
    サードパーティのアプリにアタッチできません。

    MACをリカバリーモードにしてターミナルで以下を叩くこ
    とでSIPをDisableにできます。

    ⚠ SIPの解除はMACを脆弱にするのでしない⽅がいいです。

    試す場合は⾃⼰責任でお願いします!
     $ csrutil disable && reboot

    下準備
    7

    View Slide

  8. 任意のMACアプリにアタッチする
    Xcodeで以下のようなMy Macをターゲットに持つProjectを
    開きます。



    Debug > Attach to Process PID or Name から

    任意のアプリ名を⼊⼒してアタッチします。
    下準備
    8

    View Slide

  9. 任意のMACアプリにアタッチする②
    ターミナルじゃなくてXcodeでアタッチするのは、Xcodeが便利だからです。



    レジスタのフォーマットは、右クリックの「Edit Summary Format」で以下のように⼊⼒す
    ればOKです。以下のNSStringの部分をcharに変えたりすると⽂字列が表示されるようにな
    ります。



    下準備
    9

    View Slide

  10. 依存しているダイナミックライブラリを⼀覧化


    絶対パスで出⼒されるので、ライブラリにアクセスしやすくて便利です。

    以下のコマンドを使うとライブラリがどのようにメモリに展開されているか
    も表示してくれます。



    Static ライブラリは表示されません。
     (lldb) image list
     $ vmmap
    アプリ全体を眺める
    10

    View Slide

  11. アセンブリを書き出す


    パスは image list で出⼒されたパスをそのまま指定できま
    す。

    アセンブリを眺めると、関数名とか、⽂字列が表示されてお
    り、処理を把握するヒントになる。

    (Swiftでビルドされている場合はそのようなヒントは残らな
    い)
    $ otool -tvV /Application/Hoge/Contents/MacOS/Hoge
    アプリ全体を眺める
    11

    View Slide

  12. アプリ内でハードコードされた⽂字列を⼀覧化


    ソースコードにハードコードされた⽂字列(ascii)が表示され
    ます。IBに紐付いた変数名やプロトコル名なども表示される
    ようです。
    $ strings -t x /Application/Hoge/Contents/MacOS/Hoge
    アプリ全体を眺める
    12

    View Slide

  13. ローカルデータベースの中身を⾒る


    アプリから参照している外部ファイルのパスが⼀覧化されま
    す。その中に.sqliteや.realmがある場合は、ターミナルの
    sqliteコマンドやRealm Browserなどを使⽤することでデー
    タベースの中身を確認することができます。
    (lldb) lsof
    アプリ全体を眺める
    13

    View Slide

  14. アプリ内のDocumentsやCachesの中身を⾒る


    気になったファイルは以下のコマンドによりFinderに表示するこ
    とができます。



    (lldb) ls ./Library/Caches
    (lldb) yoink ./Library/Caches/hoge.csv
    アプリ全体を眺める
    14

    View Slide

  15. 表示中のView階層を⾒る


    Catalyst等でUIKitを使っていればVC階層も表示できる



    出⼒されたアドレスを使ってViewのisHiddenを切り替えることができます。
    viewの特定に役⽴ちます。

    (lldb) pviews

    (lldb) pvc

    (lldb) tv 0x00006000020d8510
    アプリ全体を眺める
    15

    View Slide

  16. 正規表現にマッチする関数でブレーク
    -s でモジュールを限定できる。

    以下コマンドはUIKitに含まれるすべての関数でブレークする。


    ※ シンボルがない場合は全てのシンボルが___lldb_unnamed_symbol5013$$Hogeのように
    命名されるので⽂字列指定はできない。Objective-Cであれば、後述のレジスタ内の情報か
    ら関数を判定できるが、Swiftでコンパイルされている場合はメソッド名を判別することは
    不可能
    (lldb) rbreak '\-\[UIViewController\ ' -s UIKit


    (lldb) rbreak .* -s UIKit


    気になる箇所にブレークポイントを挟む
    16

    View Slide

  17. 関数のアドレスを指定してブレーク
    前述で出⼒したアセンブリの任意の箇所でブレークしたい場合は、

    記載されているオフセットにベースアドレスを⾜したものを指定する必要があります。



    ベースアドレスは実⾏中のアプリのアセンブリ(図左)と 出⼒したアセンブリ(図右)を⾒⽐べて、

    同じに⾒える処理のアドレスの差分を計算すれば割り出せます。

    上記のような出⼒であれば以下のように差分を計算すればベースアドレスが割り出せます。




    (lldb) break set -a <ベースアドレス + オフセット>


    (lldb) p/x (0x108a03182 - 0x0000000100008182)


      0x00000000089fb000


    気になる箇所にブレークポイントを挟む
    17

    View Slide

  18. UI操作契機でブレーク①
    まずはmainターゲットのアプリの全ての関数にブレークポイントを貼る



    プロセスをContinueすると⾊んな箇所でブレークするので、以下のコマ
    ンドで⽌まった箇所のブレークポイントを⽚っ端から破棄する


    ⼀通り不要なブレークポイントを破棄したら⽬的のUIコンポーネントを
    タップする。そのタイミングで⽌まった箇所が⽬的のブレークポイント
    (lldb) rbreak .* -s Hoge


    (lldb) cbd


    気になる箇所にブレークポイントを挟む
    18

    View Slide

  19. UI操作契機でブレーク②
    まずは以下のブレークポイントを貼る。

    -Cオプションに指定したコマンドはブレークしたとき実⾏される

    -G trueは⾃動的にContinueするためのオプション


    Continueして操作したときに、以下のようなログが取れる。


    上記を解釈すると、「ToolbarItemのアクションが発⽕により- [HOGEViewController backAction]が実⾏される」

    ということなので、以下のブレークポイントを貼ることにより、ハンドリングできるようになる、



    (ブレークポイントが作成できない場合、シンボルがない可能性があります。その場合の救済処置を後のページに記載します)
    (lldb) rbreak targetForAction:to:from: -C 'p/a $arg3' -C 'po $arg4' -C 'po $arg5' -G true


    p/a $arg3


    (unsigned long) $180 = 0x0000000102ff5ed0 "backAction"


    po $arg4





    po $arg5


    identifier = "back"

    (lldb) break set -n ‘HOGEViewController backAction’


    気になる箇所にブレークポイントを挟む
    19

    View Slide

  20. 引数とレジスタの関係①
    Objective-Cからコンパイルされた任意の関数でブレークし
    た時レジスタには以下のように値がマッピングされます。


    $arg1 ( $rdi ) : 関数のレシーバ

    $arg2 ( $rsi ) : セレクタ名

    $arg3 ( $rdx ) : 第⼀引数

    $arg4 ( $rcx ) : 第⼆引数

    $arg5 ( $r8 ) : 第三引数

    $arg6 ( $r9 ) : 第四引数


    20
    ブレークした関数の引数の中身を確認する

    View Slide

  21. 引数とレジスタの関係②
    例えば以下の関数でブレークした場合は

    以下のように引数がレジスタにマッピングされます。

    AppKit`-[NSApplication targetForAction:to:from:]:


    $arg1 ( $rdi ) : 関数のレシーバ → NSApplicationのインスタンス

    $arg2 ( $rsi ) : セレクタ名   → 'targetForAction:to:from:’という⽂字列

    $arg3 ( $rdx ) : 第⼀引数    → targetForAction: に対する値

    $arg4 ( $rcx ) : 第⼆引数    → to: に対する値

    $arg5 ( $r8 ) : 第三引数    → from: に対する値

    $arg6 ( $r9 ) : 第四引数    → nil


    21
    ブレークした関数の引数の中身を確認する

    View Slide

  22. 引数とレジスタの関係③
    実際レジスタには以下のように謎い64bitの数字が⼊ってます。

    この数字をみても⼈間には何を表しているかわかりません。

    これらの数字がインスタンスか、⽂字列か、関数のアドレスを指すかなどを調べ
    るために、いろんなコマンドを使⽤して、その実態を調べる必要があります。

    次の章から値の実態の確認をしていきます。

    $arg1 ( $rdi ) : 関数のレシーバ → 0x00007f92d9105e40

    $arg2 ( $rsi ) : セレクタ名   → 0x00007fff7950ab9b

    $arg3 ( $rdx ) : 第⼀引数    → 0x00007fff24da1a1e

    $arg4 ( $rcx ) : 第⼆引数    → 0x00006000020d8510

    $arg5 ( $r8 ) : 第三引数    → 0x0000600002703d80

    $arg6 ( $r9 ) : 第四引数    → nil


    22
    ブレークした関数の引数の中身を確認する

    View Slide

  23. 引数が⽂字列(char) だった場合
    レジスタの中身が⽂字列へのポインタだった場合、以下の3つの
    どの⽅法でもその⽂字列を確認することができます。


    ※ SwiftのStringはこのように中身を確認することはできません。
    (lldb) p (char *)0x00007fff7950ab9b


    (lldb) p (char *)$rdi


    (lldb) p (char *)$arg1


    ブレークした関数の引数の中身を確認する
    23

    View Slide

  24. 引数がクラスインスタンスだった場合①
    レジスタの中身がクラスインスタンスのポインタだった場合、
    以下の3つのどの⽅法でも実態を確認することができます。


    ※ Swiftでコンパイルされた場合このように中身を確認することはで
    きません。
    (lldb) po 0x0000600002703d80


    (lldb) po $rdi


    (lldb) po $arg1


    24
    ブレークした関数の引数の中身を確認する

    View Slide

  25. 引数がクラスインスタンスだった場合②
    プロパティにアクセスするためには、インスタンス変数を作る必要があります。以下の2つの⽅法のどちらでもポインターか
    らインスタンス変数を作成することができます。変数名は $2 のように⾃動的につけられて出⼒されます。


    出⼒された変数を使えばメンバにアクセスできます。



    しかし $2.viewのようにアクセスできない場合は以下のようにメソッド形式にするとアクセスできるときがあります。
    (lldb) p (UIViewController *)$arg1


    (lldb) vinfo $arg1


    (lldb) po $3.view


    (lldb) po [$4 setHidden: 1]


    (lldb) po [$1 view]


    25
    ブレークした関数の引数の中身を確認する

    View Slide

  26. 引数がクラスインスタンスだった場合③
    インスタンス変数のメンバや関数の⼀覧は以下のように表示することができます。

    関数⼀覧
    メンバ⼀覧



    うまく表示されないときは、以下のようにモジュールをインポートするとうまく
    いくようになるかもしれません。
    (lldb) lookup NSTextField -m AppKit -l


    (lldb) type lookup NSTextField


    (lldb) po @import AppKit


    26
    ブレークした関数の引数の中身を確認する

    View Slide

  27. 引数がクラスインスタンスだった場合④
    「UI操作に対するアクションでブレーク②」でシンボルがない場合でも、レシーバとなるインスタンスのポインタ
    とセレクタ名があれば関数を実⾏することができます。
    
 

    以下のように、対象のモジュールの全ての関数にブレークポイントを貼った後に




    対象のメソッドを実⾏します。(-g オプションを使うことでLLDBコマンド契機で実⾏された関数に対してもブレー
    クすることができます。)


    -


    以下のようなシンボルのないメソッドでブレークしたら、$arg1と$arg2を確認してください。

    ⽬的の関数でブレークしていることがわかります。

    (lldb) rbreak .* -s HogeModule


    (lldb) exp -g —- [$1 hogeMethod: nil]


     HogeModule`___lldb_unnamed_symbol470$$HogeModule:


    27
    ブレークした関数の引数の中身を確認する

    View Slide

  28. 引数がクラスインスタンスだった場合⑤
    レジスタがBlockクラスのインスタンスを指していた場合、以下のよう
    にinvoke:に割り当てられているアドレスにブレークポイントを設置す
    ることで該当のBlockが実⾏されたときにブレークできます。



    (lldb) po $arg1


    <__NSStackBlock__: 0x70000ab7c018>


    signature: hogehogehumohumo


    invoke : 0x7fff43be780e (DFRFoundation`___DFRHandleHandshake_block_invoke)


    copy : 0x7fff43be9c2c (DFRFoundation`__copy_helper_block_e8_32r40r48r)


    dispose : 0x7fff43be9c76 (DFRFoundation`__destroy_helper_block_e8_32r40r48r)


    (lldb) break set -a 0x7fff43be780e


    28
    ブレークした関数の引数の中身を確認する

    View Slide

  29. 引数が関数だった場合
    レジスタの中身が関数のアドレスだった場合、

    以下の⽅法で関数かどうかがわかります。


    以下のようにSummaryに関数名が表示されます。


    (lldb) image lookup -a 0x70000ab7c018


    (lldb) image lookup -a 0x7fff27d93211


    Address: CoreFoundation[0x00007fff26c7d211]


    Summary: CoreFoundation`__27-[__NSSet:]_block_invoke


    29
    ブレークした関数の引数の中身を確認する

    View Slide

  30. 返り値を確認する
    以下のコマンドでフレームを抜けたあと、$raxレジスタを確
    認すると関数の返り値が⼊っています。




    ※ Swiftでコンパイルされた場合このように中身を確認することは基
    本できません。
    (lldb) finish


    30
    ブレークした関数の返り値の中身を確認する

    View Slide

  31. 引数や返り値を書き換える
    $arg1や$raxなどのレジスタを書き換えることで引数や返り
    値を書き換えることができます。





    (lldb) po $rax = 1


    (lldb) po $rdi = 0x00006000014dee20


    処理を制御する
    31

    View Slide

  32. アタッチされてる場合の分岐を実装
    以下のコードでアタッチを判別できるようです。

    ローカルビルドでiPhone実機とMACで動作確認しました。

    シュミレータは常にアタッチしてるものとみなされているようです。

    リリースビルドではためしてません。

    (アセンブリレベルで操作されたら意味ない気もする)


    リバースエンジニアリングの防⽌策
    let mib = UnsafeMutablePointer.allocate(capacity: 4)


    mib[0] = CTL_KERN


    mib[1] = KERN_PROC


    mib[2] = KERN_PROC_PID


    mib[3] = getpid()


    var size: Int = MemoryLayout.size


    var info: kinfo_proc? = nil


    sysctl(mib, 4, &info, &size, nil, 0)


    if (info.unsafelyUnwrapped.kp_proc.p_flag & P_TRACED) > 0 {


    print(“アタッチされているのでリターンします”)


    return 


    }


    32

    View Slide

  33. • 普通にSwiftで書いたコードは、型、シンボル等の情報はほ
    ぼ残らないので実⾏中に制御できないが、Objective-Cで書
    かれている場合はできてしまう。

    • 依存ライブラリがObjective-Cで書かれている場合も注意

    • Swiftで書かれている場合もローカルDBや外部ファイルは
    閲覧ができるのでアプリにとっての機密情報は⼊れない
    か、最低でも暗号化するのがよさそう。
    結論
    33

    View Slide