読者です 読者をやめる 読者になる 読者になる

DARK MATTER

CDI Engineer's Technical Blog

iOS Reverse Engineering Tool開発 - Objective-C tracer


こんにちは。技術部エンジニアの松永です。
昔、iOSアプリケーションのセキュリティ診断を頑張っていた頃に作ったツールを紹介します。

全ての Objective-C のメソッド呼び出しをトレースするものです。

使ってみる

設定のitracerから、トレース対象アプリを選択します。

f:id:yuji-matsunaga:20161026162949p:plain:w320

今回はiOS標準のメモアプリにします。

f:id:yuji-matsunaga:20161026163002p:plain:w320

メモアプリを起動し、適当にメモを保存します。

f:id:yuji-matsunaga:20161026163010p:plain:w320


iOS端末へsshログインし、トレーサーのログを確認してみます。
書き込んだ文字列が、アプリケーションで処理される様子が確認できました。

iPhone5:~ root# ls -lh /tmp/itracer_com.apple.mobilenotes.log 
-rw-r--r-- 1 mobile wheel 8.9M Oct 26 16:28 /tmp/itracer_com.apple.mobilenotes.log

iPhone5:~ root# grep -C 50 -m 1 P@ssw0rd /tmp/itracer_com.apple.mobilenotes.log 

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>", "

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>"?"

[NSSQLBindVariable initWithValue:sqlType:attributeDescription:]
	<__NSDate>
	(uint)8
	<NSAttributeDescription>

[NSSQLBindVariable setIndex:]
	(uint)16

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>", "

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>"?"

[NSSQLBindVariable initWithValue:sqlType:attributeDescription:]
	nil
	(uint)6
	<NSAttributeDescription>

[NSSQLBindVariable setIndex:]
	(uint)17

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>", "

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>"?"

[NSSQLBindVariable initWithValue:sqlType:attributeDescription:]
	<__NSCFString>"l�"
	(uint)6
	<NSAttributeDescription>

[NSSQLBindVariable setIndex:]
	(uint)18

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>", "

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>"?"

[NSSQLBindVariable initWithValue:sqlType:attributeDescription:]
	<__NSCFString>"P@ssw0rd"
	(uint)6
	<NSAttributeDescription>

[NSSQLBindVariable setIndex:]
	(uint)19

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>", "

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>"?"

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>")"

[NSSQLiteAdapter newInsertStatementWithRow:]
	<NSSQLRow>

[__NSPlaceholderArray initWithCapacity:]
	(uint)0

[__NSPlaceholderArray initWithCapacity:]
	(uint)0

[__NSPlaceholderDictionary initWithCapacity:]
	(uint)0

[__NSPlaceholderDictionary initWithCapacity:]
	(uint)0

[_NSSQLGenerator appendSQL:]
	<__NSCFString>"��	"

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>"("

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>"Z_PK"

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>", "

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>"Z_ENT"

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>", "

[_NSSQLGenerator appendSQL:]
	<__NSCFConstantString>"Z_OPT"

ツール誕生の背景

iOSアプリケーションの解析は、Androidと比べると難易度が高く時間がかかるものでした。
その主な原因はプログラミング言語の違い(Objective-CとJava)によるものです。

Objective-C バイナリの解析は時間がかかる

ファイルの保護属性を変更する簡単なコンソールアプリケーションを例に見てみましょう。

int main ( int argc, const char * argv[] ){
  if( argc < 3 ) {
    printf( "usage: setFileAttr file protection\n" );
    return 0;
  }
  NSString *filePath =  [NSString stringWithCString: argv[ 1 ] encoding:NSUTF8StringEncoding];
  NSString *protection = [NSString stringWithCString: argv[ 2 ] encoding:NSUTF8StringEncoding];
  NSDictionary* fileAttrs = [NSDictionary dictionaryWithObject:protection forKey:NSFileProtectionKey];
  [[NSFileManager defaultManager] setAttributes:fileAttrs ofItemAtPath:filePath error:nil];
  return 0;
}

IDAを使って逆アセンブリした状態です。

__text:00002E38 ; int __cdecl main(int argc, const char **argv, const char **envp)
__text:00002E38                 EXPORT _main
__text:00002E38 _main                                   ; CODE XREF: start+30p
__text:00002E38                                         ; DATA XREF: __nl_symbol_ptr:_main_ptro ...
__text:00002E38
__text:00002E38 var_64          = -0x64
__text:00002E38 var_60          = -0x60
__text:00002E38 var_5C          = -0x5C
__text:00002E38 var_58          = -0x58
__text:00002E38 var_54          = -0x54
__text:00002E38 var_50          = -0x50
__text:00002E38 var_4C          = -0x4C
__text:00002E38 var_48          = -0x48
__text:00002E38 var_44          = -0x44
__text:00002E38 var_40          = -0x40
__text:00002E38 var_3C          = -0x3C
__text:00002E38 var_38          = -0x38
__text:00002E38 var_34          = -0x34
__text:00002E38 var_30          = -0x30
__text:00002E38 var_2C          = -0x2C
__text:00002E38 var_28          = -0x28
__text:00002E38 var_24          = -0x24
__text:00002E38 var_20          = -0x20
__text:00002E38 var_1C          = -0x1C
__text:00002E38 var_18          = -0x18
__text:00002E38 var_14          = -0x14
__text:00002E38 var_10          = -0x10
__text:00002E38 var_C           = -0xC
__text:00002E38
__text:00002E38                 PUSH            {R7,LR}
__text:00002E3A                 MOV             R7, SP
__text:00002E3C
__text:00002E3C loc_2E3C                                ; DATA XREF: __text:off_2E34o
__text:00002E3C                 SUB             SP, SP, #0x5C
__text:00002E3E                 MOVS            R2, #2
__text:00002E44                 STR             R0, [SP,#0x64+var_C]
__text:00002E46                 STR             R1, [SP,#0x64+var_10]
__text:00002E48                 LDR             R0, [SP,#0x64+var_C]
__text:00002E4A                 CMP             R0, R2
__text:00002E4C                 BLE             loc_2E50
__text:00002E4E                 B               loc_2E6E
__text:00002E50 ; ---------------------------------------------------------------------------
__text:00002E50
__text:00002E50 loc_2E50                                ; CODE XREF: _main+14j
__text:00002E50                 MOVS            R0, #0
__text:00002E56                 STR             R0, [SP,#0x64+var_3C]
__text:00002E58                 MOV             R0, #(aUsageSetfileat - 0x2E64) ; "usage: setFileAttr file protection"
__text:00002E60                 ADD             R0, PC  ; "usage: setFileAttr file protection"
__text:00002E62                 BLX             _puts
__text:00002E66                 LDR             R1, [SP,#0x64+var_3C]
__text:00002E68                 STR             R1, [SP,#0x64+var_2C]
__text:00002E6A                 STR             R0, [SP,#0x64+var_40]
__text:00002E6C                 B               loc_2F36
__text:00002E6E ; ---------------------------------------------------------------------------
__text:00002E6E
__text:00002E6E loc_2E6E                                ; CODE XREF: _main+16j
__text:00002E6E                 MOVS            R0, #0
__text:00002E74                 MOV             R1, #(off_304C - 0x2E80)
__text:00002E7C                 ADD             R1, PC ; off_304C
__text:00002E7E                 LDR             R2, [R1] ; _OBJC_CLASS_$_NSString
__text:00002E80                 STR             R2, [SP,#0x64+var_28]
__text:00002E82                 MOV             R3, #(paStringwithcstr - 0x2E8E)
__text:00002E8A                 ADD             R3, PC ; paStringwithcstr
__text:00002E8C                 STR             R1, [SP,#0x64+var_44]
__text:00002E8E                 LDR             R1, [R3] ; "stringWithCString:encoding:"
__text:00002E90                 LDR.W           R12, [SP,#0x64+var_10]
__text:00002E94                 MOV.W           LR, #4
__text:00002E98                 STR             R2, [SP,#0x64+var_48]
__text:00002E9A                 LDR.W           R2, [R12,#4]
__text:00002E9E                 STR             R0, [SP,#0x64+var_4C]
__text:00002EA0                 LDR             R0, [SP,#0x64+var_48]
__text:00002EA2                 STR             R3, [SP,#0x64+var_50]
__text:00002EA4                 MOV             R3, LR
__text:00002EA6                 STR.W           LR, [SP,#0x64+var_54]
__text:00002EAA                 BLX             _objc_msgSend
__text:00002EAE                 STR             R0, [SP,#0x64+var_30]
__text:00002EB0                 LDR             R0, [SP,#0x64+var_44]
__text:00002EB2                 LDR             R1, [R0]
__text:00002EB4                 STR             R1, [SP,#0x64+var_24]
__text:00002EB6                 LDR             R2, [SP,#0x64+var_50]
__text:00002EB8                 STR             R1, [SP,#0x64+var_58]
__text:00002EBA                 LDR             R1, [R2]
__text:00002EBC                 LDR             R3, [SP,#0x64+var_10]
__text:00002EBE                 LDR             R2, [R3,#8]
__text:00002EC0                 LDR             R0, [SP,#0x64+var_58]
__text:00002EC2                 LDR             R3, [SP,#0x64+var_54]
__text:00002EC4                 BLX             _objc_msgSend
__text:00002EC8                 STR             R0, [SP,#0x64+var_34]
__text:00002ECA                 MOV             R0, #(off_3050 - 0x2ED6)
__text:00002ED2                 ADD             R0, PC ; off_3050
__text:00002ED4                 LDR             R0, [R0] ; _OBJC_CLASS_$_NSDictionary
__text:00002ED6                 STR             R0, [SP,#0x64+var_20]
__text:00002ED8                 MOV             R0, #(paDictionarywith - 0x2EE4)
__text:00002EE0                 ADD             R0, PC ; paDictionarywith
__text:00002EE2                 LDR             R1, [R0] ; "dictionaryWithObject:forKey:"
__text:00002EE4                 LDR             R2, [SP,#0x64+var_34]
__text:00002EE6                 MOV             R0, #(_NSFileProtectionKey_ptr - 0x2EF2)
__text:00002EEE                 ADD             R0, PC ; _NSFileProtectionKey_ptr
__text:00002EF0                 LDR             R0, [R0] ; _NSFileProtectionKey
__text:00002EF2                 LDR             R0, [R0]
__text:00002EF4                 STR             R0, [SP,#0x64+var_5C]
__text:00002EF6                 LDR             R0, [SP,#0x64+var_20]
__text:00002EF8                 LDR             R3, [SP,#0x64+var_5C]
__text:00002EFA                 BLX             _objc_msgSend
__text:00002EFE                 STR             R0, [SP,#0x64+var_38]
__text:00002F00                 LDR             R0, =(off_3054 - 0x2F06)
__text:00002F02                 ADD             R0, PC ; off_3054
__text:00002F04                 LDR             R0, [R0] ; _OBJC_CLASS_$_NSFileManager
__text:00002F06                 STR             R0, [SP,#0x64+var_1C]
__text:00002F08                 LDR             R0, =(paDefaultmanager - 0x2F0E)
__text:00002F0A                 ADD             R0, PC ; paDefaultmanager
__text:00002F0C                 LDR             R1, [R0] ; "defaultManager"
__text:00002F0E                 LDR             R0, [SP,#0x64+var_1C]
__text:00002F10                 BLX             _objc_msgSend
__text:00002F14                 STR             R0, [SP,#0x64+var_18]
__text:00002F16                 LDR             R0, =(paSetattributesO - 0x2F1C)
__text:00002F18                 ADD             R0, PC ; paSetattributesO
__text:00002F1A                 LDR             R1, [R0] ; "setAttributes:ofItemAtPath:error:"
__text:00002F1C                 LDR             R0, [SP,#0x64+var_18]
__text:00002F1E                 LDR             R2, [SP,#0x64+var_38]
__text:00002F20                 LDR             R3, [SP,#0x64+var_30]
__text:00002F22                 MOV.W           R12, #0
__text:00002F26                 MOV             LR, SP
__text:00002F28                 STR.W           R12, [LR,#0x64+var_64]
__text:00002F2C                 BLX             _objc_msgSend
__text:00002F30                 LDR             R1, [SP,#0x64+var_4C]
__text:00002F32                 STR             R1, [SP,#0x64+var_2C]
__text:00002F34                 STR             R0, [SP,#0x64+var_60]
__text:00002F36
__text:00002F36 loc_2F36                                ; CODE XREF: _main+34j
__text:00002F36                 LDR             R0, [SP,#0x64+var_2C]
__text:00002F38                 STR             R0, [SP,#0x64+var_14]
__text:00002F3A                 LDR             R0, [SP,#0x64+var_14]
__text:00002F3C                 ADD             SP, SP, #0x5C
__text:00002F3E                 POP             {R7,PC}
__text:00002F3E ; End of function _main
__text:00002F3E

同じくIDAを使って、単純にデコンパイル(擬似コード生成)したものです。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  void *v3; // ST34_4@2
  void *v4; // ST30_4@2
  void *v5; // ST2C_4@2
  void *v6; // ST4C_4@2
  int v8; // [sp+38h] [bp-2Ch]@2
  const char **v9; // [sp+54h] [bp-10h]@1

  v9 = argv;
  if ( argc <= 2 )
  {
    v8 = 0;
    puts("usage: setFileAttr file protection");
  }
  else
  {
    v3 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithCString:encoding:", argv[1], 4);
    v4 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithCString:encoding:", v9[2], 4);
    v5 = objc_msgSend(&OBJC_CLASS___NSDictionary, "dictionaryWithObject:forKey:", v4, NSFileProtectionKey);
    v6 = objc_msgSend(&OBJC_CLASS___NSFileManager, "defaultManager");
    v8 = 0;
    objc_msgSend(v6, "setAttributes:ofItemAtPath:error:", v5, v3, 0);
  }
  return v8;
}

例が単純すぎて元のプログラムとの差異が少ないですが、雰囲気はつかめるかと思います。
Objective-Cの呼び出しは、基本的に objc_msgSend という関数によって行われ、関数の引数としてレシーバー(インスタンス)、セレクタ(メソッド)、セレクタの引数が与えられます。

規模が大きなアプリケーションになると、クラスの継承などによってセレクタ文字列の重複が多くなり、またレシーバーが具体的にどのクラスなのか解析するのが難しくなっていきます。
(現在は改善している可能性がありますが)IDAのデコンパイル結果が間違っていることも多く、アセンブリの確認も欠かせません。

脆弱そうな処理や機微な情報を扱いそうな処理が見つかっても、実際にその処理がどういう条件で呼び出されるのか(または呼び出される可能性が無いのか)突き止めるのに時間を要する場面が度々ありました。

惜しいツールはあった

objc-tracerというツールがあり、コンソールアプリケーションのObjectie-C呼び出しのレシーバー、セレクタをトレースすることができました。
github.com

MobileSubstrate(Cydia Substrate)の仕組みを使ってGUIアプリケーションのフックに対応し、セレクタの引数解析機能を実装したのが itracer です!

実装

おおまかに以下の処理を行います。

  1. objc_msgSend 系の関数をフック
  2. レシーバー、セレクタを取得
  3. method_getTypeEncoding というObjectiv-Cのランタイム関数を使ってメソッドの型情報(引数の定義)を取得
  4. レジスタ、スタックから引数を取得
  5. 型情報に従って引数をダンプ

引数がクラスの場合はインスタンス内のデータが欲しいので、NSString など頻出するクラスをいくつかピックアップし、構造を gdb で解析してパースするようにしました。

実戦投入

満を持して実戦投入した itracer ですが、色々と問題がありました。

  • 動作が不安定
    • チューニングするまでトレース対象アプリの起動に至らないことがほとんど
  • ログが多すぎる
    • メモアプリにメモ残しただけで 8.9M、43万行
  • 通常のアプリであれば Introspy で事足りることが多い
  • 本気のアプリは機微な情報を Objective-C で扱ってくれない
    • IDA + gdbで対応

そしてそのまま64bit、Swiftの時代に突入し、itracer が日の目を見ることはありませんでした。。。

おわりに

Githubで公開しました。
どこかで誰かの役に立つことを祈るばかりです。
https://github.com/CyberDefenseInstitute/itracer


株式会社サイバーディフェンス研究所 / Cyber Defense Institute Inc.