上記のツールをマスターしたら、プラグインの開発を開始できます。まず、プラグインが実装する必要があるニーズについて考え、関連するビューとコントローラーを見つける必要があります。最初のステップは、クラスダンプを使用してシェル化された APP を分析することです。クラスダンプは、Mach-O ファイル内のシンボル テーブルに基づいてすべてのクラス名とメソッド宣言を分析できます。
~ » ./class-dump -H -o /header/path WeChat
ヘッダー ファイルをエクスポートした後、これらのヘッダー ファイルを XCode プロジェクトにプルして、以降の検索を容易にします。
ヘッダー ファイルを取得したら、それを関連するインターフェイスのコントローラー クラスにロックする必要があります。 OpenSSH を使用してジェイルブレイクされた iPhone に Mac で接続し、Cycript を使用してプロセスを挿入し、メソッド[[UIApp keyWindow] recursiveDescription]
呼び出して現在のビューの階層を取得し、ビューのアドレスを取得して、 [#0x2b2b2b00 nextResponder]
を繰り返し呼び出します。 [#0x2b2b2b00 nextResponder]
メソッドを実行すると、最終的にインターフェイス コントローラー クラスが取得されます。
この時点で、通常、リバース分析を続行するには 2 つのエントリ ポイントがあります。
インターフェイス上のビューのタッチ イベントによってフック操作をトリガーしたい場合、アクションを見つけるには通常 2 つの方法があります。
[button allTargets]
呼び出してターゲットのアドレスを取得し、 [button actionsForTarget: Targets forControlEvent: [button allControlEvents]]
対応するアクション メソッドを取得します。コントローラーのデータ ソースは、プラグインの書き込み中に使用できます。データ ソースは、コントローラー クラスのメンバー変数リストを分析するか、一部のビュー (TableView など) のデータ ソース プロキシ メソッドを分析することによって取得できます。データ ソースを使用すると、コントローラーに関するより豊富な情報を取得できます。
単純なプラグインでは、Cycript のみを使用してターゲットをロックしてフックできる場合がありますが、多くの場合、ターゲット関数には複数のパラメーターがあり、内部実装の詳細を知りたい場合はロジックが比較的複雑です。静的解析アーティファクト IDA を使用する ---- Objective-C で書かれたコードをアセンブリ コードに逆コンパイルでき、関数の実装詳細が一目でわかります。逆アセンブリの結果を分析する場合、Objective-C のメッセージ パッシングの特性により、一般的なメッセージ送信では、実際にはobjc_msgSend
関数が呼び出されます。 objc_msgSend
の各パラメータの意味と ARM アーキテクチャの呼び出し規則を覚えておくだけで、アセンブリ コードから関数呼び出しを正常に復元できます。呼び出し規則では、関数の最初の 4 つのパラメータが R0 ~ R3 汎用レジスタを使用して渡され、さらに多くのパラメータがスタックにプッシュされ、戻り値が R0 レジスタに格納されます。 [aObject aMessage: arg1];
はobjc_msgSend(aObject, aMessage, arg1);
R0 にはメッセージの受信者アドレスが格納され、R1 にはセレクターが格納され、R2 には最初のパラメーターのアドレスが格納されます。その他のパラメータのメッセージ形式は次のとおりです。
objc_msgSend(R0, R1, R2, R3, *SP, *(SP + sizeOfLastArg), …)
上記の形式により、特定の関数のロジックを段階的に解析できます。これを、特定のアセンブリ ステートメントのアドレスでの LLDB の動的なシングル ステップ デバッグとブレークポイント追跡デバッグによって補完すると、関数実装の詳細を理解するのに役立ちます。
Hook ターゲット関数には多くのソリューションがありますが、原則としてそれらはすべて Objective-C の動的特性に基づいており、メソッド スウィズリングを使用して元の実装を置き換えます。今回はCaptainHookライブラリの使い方を詳しく紹介します。このライブラリは、Cydia Substrate のMSHookMessageEx()
に基づいて実装されています。この関数の宣言は次のとおりです。
void MSHookMessageEx(Class _class, SEL message, IMP hook, IMP *old);
iOSOpenDev がインストールされたら、CaptainHook Tweak テンプレートを使用してプロジェクトを作成できます。 CaptainHook ライブラリには、フック関数を記述するための新しい構文が導入されています。まず、 CHConstructor()
で Hook する関数が配置されているクラス ( CHLoadLateClass(UIView)
など) をロードします。次にフックする関数CHHook(argNumber, className, arg1, arg2)
を登録します。 CHConstructor
のマクロ定義は次のとおりです。
#define CHConcat(a, b) CHConcat_(a, b)
#define CHConstructor static __attribute__((constructor)) void CHConcat(CHConstructor, __LINE__)()
__attribute__((constructor))
の後の内容は、dylib のロード時 (通常はプログラムの開始時) に実行されることが保証されています。同様に、他のシンボルもマクロ定義を通じて導入されます。
ここでは、CaptainHook を使用してフック関数を宣言し、コードに直接実装する方法を紹介します。
CHDeclareClass ( BXViewController );
CHOptimizedMethod ( 0 , self , void , BXViewController , viewDidLoad ) {
CHSuper ( 0 , BXViewController , viewDidLoad );
/* HERE TO WRITE YOUR CODE */
}
CHDeclareMethod0 ( void , BXViewController , addFriends ) {
/* HERE TO WRITE YOUR CODE */
}
書き込みが完了したら、手元のiPhoneに接続してコンパイルを行い、アーキテクチャに応じたダイナミックライブラリが生成されることを確認します。
yololib ツールを使用して、コンパイルされた dylib ファイルを Mach-O 実行可能ファイルのロード コマンド リストに挿入します。
~ » ./yololib [binary] [dylib file]
Mach-O ファイルの構造は主に 3 つの部分で構成されます。フロントエンド部分は、Mach-O のプラットフォーム タイプ、ファイル タイプ、LoadCommand の数、およびその他の情報を保存するヘッダー構造です。この部分を解析することで、ヘッダーがロード コマンド部分になります。仮想ファイル内のその場所は、メモリ内レイアウトで決定できます。 yololib ツールは、dylib をロードするように「Load Commands」セクションの情報を変更します。具体的な実装プロセスは次のとおりです。
ロード コマンド情報が変更されると、対応するヘッダー構造内の ncmds と sizeofcmds の両方が変更されるため、最初にヘッダーを変更する必要があります。
// 取出 Header
fseek(newFile, top, SEEK_SET);
struct mach_header mach;
fread(&mach, sizeof(struct mach_header), 1, newFile);
NSData* data = [DYLIB_PATH dataUsingEncoding:NSUTF8StringEncoding];
// 计算 dylib 的大小
uint32_t dylib_size = (uint32_t)[data length] + sizeof(struct dylib_command);
dylib_size += sizeof(long) - (dylib_size % sizeof(long));
// 修改 cmds 和 sizeofcmds
mach.ncmds += 1;
uint32_t sizeofcmds = mach.sizeofcmds;
mach.sizeofcmds += dylib_size;
// 写回修改后的 Header
fseek(newFile, -sizeof(struct mach_header), SEEK_CUR);
fwrite(&mach, sizeof(struct mach_header), 1, newFile);
次に、Load Commands セクションを変更し、dylib ロード情報を追加します。
fseek(newFile, sizeofcmds, SEEK_CUR);
// 创建一个 dylib 类型的 command
struct dylib_command dyld;
fread(&dyld, sizeof(struct dylib_command), 1, newFile);
// 修改 dyld 结构体数据
dyld.cmd = LC_LOAD_DYLIB;
dyld.cmdsize = dylib_size;
dyld.dylib.compatibility_version = DYLIB_COMPATIBILITY_VERSION;
dyld.dylib.current_version = DYLIB_CURRENT_VER;
dyld.dylib.timestamp = 2;
dyld.dylib.name.offset = sizeof(struct dylib_command);
// 写回修改
fseek(newFile, -sizeof(struct dylib_command), SEEK_CUR);
fwrite(&dyld, sizeof(struct dylib_command), 1, newFile);
dylib に書き込まれた最後のデータ。
fwrite([data bytes], [data length], 1, newFile);
codesign
コマンドを使用して、生成されたダイナミック ライブラリと APP 内のすべての実行可能ファイル (プラグイン フォルダー内の APP 拡張機能を含む) に再署名します。 xcrun -sdk iphoneos PackageApplication -v
コマンドを使用して、ダイナミック ライブラリとすべてのファイルをパッケージ化します。の全過程がわかります。エンタープライズ証明書がある場合、署名されパッケージ化されたアプリケーションは、その証明書を信頼する脱獄されていない iPhone にインストールできます。漕ぎ手スター!