こんにちは。技術部エンジニアの松永です。
昔、iOSアプリケーションのセキュリティ診断を頑張っていた頃に作ったツールを紹介します。
全ての Objective-C のメソッド呼び出しをトレースするものです。
使ってみる
設定のitracer
から、トレース対象アプリを選択します。
今回はiOS標準のメモアプリにします。
メモアプリを起動し、適当にメモを保存します。
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呼び出しのレシーバー、セレクタをトレースすることができました。
MobileSubstrate(Cydia Substrate)の仕組みを使ってGUIアプリケーションのフックに対応し、セレクタの引数解析機能を実装したのが itracer
です!
実装
おおまかに以下の処理を行います。
objc_msgSend
系の関数をフック- レシーバー、セレクタを取得
method_getTypeEncoding
というObjectiv-Cのランタイム関数を使ってメソッドの型情報(引数の定義)を取得- レジスタ、スタックから引数を取得
- 型情報に従って引数をダンプ
引数がクラスの場合はインスタンス内のデータが欲しいので、NSString など頻出するクラスをいくつかピックアップし、構造を gdb で解析してパースするようにしました。
実戦投入
満を持して実戦投入した itracer ですが、色々と問題がありました。
- 動作が不安定
- チューニングするまでトレース対象アプリの起動に至らないことがほとんど
- ログが多すぎる
- メモアプリにメモ残しただけで 8.9M、43万行
- 通常のアプリであれば Introspy で事足りることが多い
- 本気のアプリは機微な情報を Objective-C で扱ってくれない
- IDA + gdbで対応
そしてそのまま64bit、Swiftの時代に突入し、itracer が日の目を見ることはありませんでした。。。
おわりに
Githubで公開しました。
どこかで誰かの役に立つことを祈るばかりです。
https://github.com/CyberDefenseInstitute/itracer