Share
Embed
本当はこわい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
Transcript
33 slides
-
⾃⼰紹介 Name: みやし Twitter: @po_miyasaka Work:minne @ GMOペパボ OSSの宣伝:
@yhkaplan⽒とVisionAPIを使ったクレカリーダーのOSSを作成し ました。 iOS13以降必須ですが、card.ioよりも読み取れるカードも多く、 UIもカスタマイズしやすいので、ぜひ活⽤してみてください! https://github.com/yhkaplan/credit-card-scanner 2 -
⽬次 • 下準備 • スライドについての備考 • 拡張コマンドのインストール • SIPを解除する •
アプリにアタッチする • アプリ全体を眺める • 依存しているダイナミックライブラリの⼀覧化 • アセンブリを書き出す • アプリ内でハードコードされた⽂字列を⼀覧化 • ローカルデータベースの中身を⾒る • アプリ内のDocumentsやCachesの中身を⾒る • 表示中のView階層を⾒る 4 • 気になる箇所にブレークポイントを挟む • 正規表現でマッチする関数でブレーク • 関数のアドレスを指定してブレーク • UI操作契機でブレーク • ブレークした関数の引数の中身を調べる • 引数とレジスタの関係 • 引数が⽂字列だった場合 • 引数がクラスインスタンスだった場合 • 引数が関数だった場合 • ブレークした関数の返り値を調べる • ブレークした関数の引数や返り値の書き換え • リバースエンジニアリングの防⽌法 • 結論 -
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 -
任意のMACアプリにアタッチする Xcodeで以下のようなMy Macをターゲットに持つProjectを 開きます。 Debug > Attach to
Process PID or Name から 任意のアプリ名を⼊⼒してアタッチします。 下準備 8 -
アセンブリを書き出す パスは image list で出⼒されたパスをそのまま指定できま す。 アセンブリを眺めると、関数名とか、⽂字列が表示されてお り、処理を把握するヒントになる。 (Swiftでビルドされている場合はそのようなヒントは残らな い)
$ otool -tvV /Application/Hoge/Contents/MacOS/Hoge アプリ全体を眺める 11 -
表示中のView階層を⾒る Catalyst等でUIKitを使っていればVC階層も表示できる 出⼒されたアドレスを使ってViewのisHiddenを切り替えることができます。 viewの特定に役⽴ちます。 (lldb) pviews (lldb) pvc
(lldb) tv 0x00006000020d8510 アプリ全体を眺める 15 -
正規表現にマッチする関数でブレーク -s でモジュールを限定できる。 以下コマンドはUIKitに含まれるすべての関数でブレークする。 ※ シンボルがない場合は全てのシンボルが___lldb_unnamed_symbol5013$$Hogeのように 命名されるので⽂字列指定はできない。Objective-Cであれば、後述のレジスタ内の情報か ら関数を判定できるが、Swiftでコンパイルされている場合はメソッド名を判別することは 不可能 (lldb)
rbreak '\-\[UIViewController\ ' -s UIKit (lldb) rbreak .* -s UIKit 気になる箇所にブレークポイントを挟む 16 -
関数のアドレスを指定してブレーク 前述で出⼒したアセンブリの任意の箇所でブレークしたい場合は、 記載されているオフセットにベースアドレスを⾜したものを指定する必要があります。 ベースアドレスは実⾏中のアプリのアセンブリ(図左)と 出⼒したアセンブリ(図右)を⾒⽐べて、 同じに⾒える処理のアドレスの差分を計算すれば割り出せます。 上記のような出⼒であれば以下のように差分を計算すればベースアドレスが割り出せます。 (lldb) break
set -a <ベースアドレス + オフセット> (lldb) p/x (0x108a03182 - 0x0000000100008182) 0x00000000089fb000 気になる箇所にブレークポイントを挟む 17 -
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 <HOGEViewController: 0x6000008e6490> po $arg5 <ToolbarItem: 0x60000312cbb0> identifier = "back" (lldb) break set -n ‘HOGEViewController backAction’ 気になる箇所にブレークポイントを挟む 19 -
引数とレジスタの関係① Objective-Cからコンパイルされた任意の関数でブレークし た時レジスタには以下のように値がマッピングされます。 $arg1 ( $rdi ) : 関数のレシーバ
$arg2 ( $rsi ) : セレクタ名 $arg3 ( $rdx ) : 第⼀引数 $arg4 ( $rcx ) : 第⼆引数 $arg5 ( $r8 ) : 第三引数 $arg6 ( $r9 ) : 第四引数 20 ブレークした関数の引数の中身を確認する -
引数とレジスタの関係② 例えば以下の関数でブレークした場合は 以下のように引数がレジスタにマッピングされます。 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 ブレークした関数の引数の中身を確認する -
引数とレジスタの関係③ 実際レジスタには以下のように謎い64bitの数字が⼊ってます。 この数字をみても⼈間には何を表しているかわかりません。 これらの数字がインスタンスか、⽂字列か、関数のアドレスを指すかなどを調べ るために、いろんなコマンドを使⽤して、その実態を調べる必要があります。 次の章から値の実態の確認をしていきます。 $arg1 ( $rdi )
: 関数のレシーバ → 0x00007f92d9105e40 $arg2 ( $rsi ) : セレクタ名 → 0x00007fff7950ab9b $arg3 ( $rdx ) : 第⼀引数 → 0x00007fff24da1a1e $arg4 ( $rcx ) : 第⼆引数 → 0x00006000020d8510 $arg5 ( $r8 ) : 第三引数 → 0x0000600002703d80 $arg6 ( $r9 ) : 第四引数 → nil 22 ブレークした関数の引数の中身を確認する -
引数が⽂字列(char) だった場合 レジスタの中身が⽂字列へのポインタだった場合、以下の3つの どの⽅法でもその⽂字列を確認することができます。 ※ SwiftのStringはこのように中身を確認することはできません。 (lldb) p (char *)0x00007fff7950ab9b
(lldb) p (char *)$rdi (lldb) p (char *)$arg1 ブレークした関数の引数の中身を確認する 23 -
引数がクラスインスタンスだった場合① レジスタの中身がクラスインスタンスのポインタだった場合、 以下の3つのどの⽅法でも実態を確認することができます。 ※ Swiftでコンパイルされた場合このように中身を確認することはで きません。 (lldb) po 0x0000600002703d80 (lldb)
po $rdi (lldb) po $arg1 24 ブレークした関数の引数の中身を確認する -
引数がクラスインスタンスだった場合② プロパティにアクセスするためには、インスタンス変数を作る必要があります。以下の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 ブレークした関数の引数の中身を確認する -
引数がクラスインスタンスだった場合③ インスタンス変数のメンバや関数の⼀覧は以下のように表示することができます。 関数⼀覧 メンバ⼀覧 うまく表示されないときは、以下のようにモジュールをインポートするとうまく いくようになるかもしれません。 (lldb) lookup NSTextField -m
AppKit -l (lldb) type lookup NSTextField (lldb) po @import AppKit 26 ブレークした関数の引数の中身を確認する -
引数がクラスインスタンスだった場合④ 「UI操作に対するアクションでブレーク②」でシンボルがない場合でも、レシーバとなるインスタンスのポインタ とセレクタ名があれば関数を実⾏することができます。 以下のように、対象のモジュールの全ての関数にブレークポイントを貼った後に 対象のメソッドを実⾏します。(-g オプションを使うことでLLDBコマンド契機で実⾏された関数に対してもブレー クすることができます。) -
以下のようなシンボルのないメソッドでブレークしたら、$arg1と$arg2を確認してください。 ⽬的の関数でブレークしていることがわかります。 (lldb) rbreak .* -s HogeModule (lldb) exp -g —- [$1 hogeMethod: nil] HogeModule`___lldb_unnamed_symbol470$$HogeModule: 27 ブレークした関数の引数の中身を確認する -
引数がクラスインスタンスだった場合⑤ レジスタが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 ブレークした関数の引数の中身を確認する -
引数が関数だった場合 レジスタの中身が関数のアドレスだった場合、 以下の⽅法で関数かどうかがわかります。 以下のようにSummaryに関数名が表示されます。 (lldb) image lookup -a 0x70000ab7c018 (lldb)
image lookup -a 0x7fff27d93211 Address: CoreFoundation[0x00007fff26c7d211] Summary: CoreFoundation`__27-[__NSSet:]_block_invoke 29 ブレークした関数の引数の中身を確認する -
引数や返り値を書き換える $arg1や$raxなどのレジスタを書き換えることで引数や返り 値を書き換えることができます。 (lldb) po $rax = 1 (lldb) po
$rdi = 0x00006000014dee20 処理を制御する 31 -
アタッチされてる場合の分岐を実装 以下のコードでアタッチを判別できるようです。 ローカルビルドでiPhone実機とMACで動作確認しました。 シュミレータは常にアタッチしてるものとみなされているようです。 リリースビルドではためしてません。 (アセンブリレベルで操作されたら意味ない気もする) リバースエンジニアリングの防⽌策 let mib
= UnsafeMutablePointer<Int32>.allocate(capacity: 4) mib[0] = CTL_KERN mib[1] = KERN_PROC mib[2] = KERN_PROC_PID mib[3] = getpid() var size: Int = MemoryLayout<kinfo_proc>.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