iOS逆向之Frida Hook繞過人臉識別
*本文不涉及任何漏洞,本文僅限技術研究與討論,嚴禁用於非法用途,否則產生的一切後果自行承擔。
0×00前言
解決問題的手段有很多種,也許存在優劣之分,這次選擇了其中一種不那麼好玩的方法——frida hook。
人臉識別校驗狀態存儲在服務端,因此即使通過該種方法繞過客戶端人臉識別,由於並未獲得合法session,所以並無任何實際危害,僅做爲IOS Hook學習思路。
0×01準備
越獄IOS(12.4.4)
app一個
登錄時存在人臉識別:
0×02 脫殼&安裝
CrackerXI+脫殼,ReProvision簽名一條龍,有疑問的可以參考上一篇文章。一毛一樣的app。
0×03 尋找關鍵函數
CrackerXI+脫殼後,二進制文件拉出來拖到ida中分析找到關鍵函數:
XXXXBaseViewController loginSuccessIsNeedBind:WithInfo:
void __cdecl -[XXXXAPPBaseViewController loginSuccessIsNeedBind:WithInfo:](XXXXAPPBaseViewController *self, SEL a2, bool a3, id a4) { BOOL v4; // w22 XXXXAPPBaseViewController *v5; // x21 __int64 v6; // x19 void *v7; // x0 void *v8; // x0 void *v9; // x23 void *v10; // x0 void *v11; // x20 XXXXAPPLoginHelper *v12; // x0 void *v13; // x23 __int64 v14; // x1 __int64 v15; // x1 __int64 v16; // x0 struct objc_object *v17; // x0 void *v18; // x0 void *v19; // x22 void *v20; // x0 void *v21; // x23 int v22; // w24 void *v23; // x0 void *v24; // x0 __int64 v25; // x22 void *v26; // x0 __int64 v27; // x23 const char *v28; // x1 void **v29; // [xsp+0h] [xbp-70h] __int64 v30; // [xsp+8h] [xbp-68h] __int64 (__fastcall *v31)(); // [xsp+10h] [xbp-60h] void *v32; // [xsp+18h] [xbp-58h] XXXXAPPBaseViewController *v33; // [xsp+20h] [xbp-50h] __int64 v34; // [xsp+28h] [xbp-48h] __int64 v35; // [xsp+30h] [xbp-40h] bool v36; // [xsp+38h] [xbp-38h] v4 = a3;//賦值v4 v5 = self; v6 = objc_retain(a4, a2); v7 = objc_msgSend(&OBJC_CLASS___UIApplication, "sharedApplication"); v8 = (void *)objc_retainAutoreleasedReturnValue(v7); v9 = v8; v10 = objc_msgSend(v8, "delegate"); v11 = (void *)objc_retainAutoreleasedReturnValue(v10); objc_release(v9); if ( (unsigned int)-[XXXXAPPBaseViewController needInputIDCardInfomation:](v5, "needInputIDCardInfomation:", v6) ) { +[PCUtil setObject:forKey:](&OBJC_CLASS___PCUtil, "setObject:forKey:", CFSTR("1"), CFSTR("maybeNeedBackLoginGuide")); v12 = +[XXXXAPPLoginHelper sharedInstance](&OBJC_CLASS___XXXXAPPLoginHelper, "sharedInstance"); v13 = (void *)objc_retainAutoreleasedReturnValue(v12); v29 = _NSConcreteStackBlock; v30 = 3254779904LL; v31 = sub_1000B05E4; v32 = &unk_10263F2D8; v33 = v5; v36 = v4; v34 = objc_retain(v11, v14); v35 = objc_retain(v6, v15); objc_msgSend( v13, "setCompleteGuideBlock:", &v29, _NSConcreteStackBlock, 3254779904LL, sub_1000B05E4, &unk_10263F2D8, v5); objc_release(v13); objc_release(v35); v16 = v34; LABEL_9: objc_release(v16); goto LABEL_10; } if ( v4 ) //判斷v4(bool)值,確定是否進入人臉識別 { v17 = +[PNCMBankGlobal sharedData](&OBJC_CLASS___PNCMBankGlobal, "sharedData"); v18 = (void *)objc_retainAutoreleasedReturnValue(v17); v19 = v18; v20 = objc_msgSend(v18, "bindType"); v21 = (void *)objc_retainAutoreleasedReturnValue(v20); v22 = (unsigned __int64)objc_msgSend(v21, "isEqualToString:", CFSTR("FACE")); objc_release(v21); objc_release(v19); v23 = objc_msgSend(v11, "rootVC"); v24 = (void *)objc_retainAutoreleasedReturnValue(v23); v25 = (__int64)v24; v26 = objc_msgSend(v24, "navigationController"); v27 = objc_retainAutoreleasedReturnValue(v26); if ( v22 ) v28 = "goToFaceCheckBindVC:info:"; else v28 = "goSmsOrUKBindInfoVC:info:"; objc_msgSend(v5, v28, v27, v6); objc_release(v27); v16 = v25; goto LABEL_9; } -[XXXXAPPBaseViewController AfterBindSuccess:isNeedBind:](v5, "AfterBindSuccess:isNeedBind:", v6, 0LL); LABEL_10: objc_release(v11); objc_release(v6); }
分析代碼發現:
void __cdecl -[XXXXAPPBaseViewController loginSuccessIsNeedBind:WithInfo:](XXXXAPPBaseViewController *self, SEL a2, bool a3, id a4) v4 = a3; //... if ( v4 ) { //... //人臉識別函數 //... }
其中a3是參數傳入,只要我們hook -[XXXXAPPBaseViewController loginSuccessIsNeedBind:WithInfo:]使其傳入bool參數a3爲永假,即可不進入人臉識別判斷。
手機中啓動frida-server(手機中的frida版本須和PC中的版本一致):
PC上啓動lxhToolHTTPDecrypt(ps:不使用該工具也可以,使用frida腳本能夠直接查找類名函數等,但這個工具圖形化界面比較直觀,也有一些其他功能)
訪問 http://127.0.0.1:8088 選擇Identifier
在Hook裏選擇類名
進入人臉識別時hook到該函數:
0×04 HOOK
編寫hook腳本hooking.js:
var search_class = ['XXXXXXBaseViewController']; var search_method = ['loginSuccessIsNeedBind:WithInfo:']; function search_methods(className) { var methods_found = []; var methods = ObjC.classes[className].$ownMethods; if (Array.isArray(search_method) && search_method.length) { //search_method not empty for (var j = 0; j < search_method.length; j++) { if (methods.join(' ').toLowerCase().includes(search_method[j].toLowerCase())) { for (var i = 0; i < methods.length; i++){ if (methods[i].toLowerCase().includes(search_method[j].toLowerCase())) { methods_found.push(methods[i]); } } } } } else { var methods = ObjC.classes[className].$ownMethods; for (var i = 0; i < methods.length; i++){ methods_found.push(methods[i]); } } return methods_found; } function search_classes(){ var classes_found = []; for (var className in ObjC.classes) { if (Array.isArray(search_class) && search_class.length) { for (var i = 0; i < search_class.length; i++) { if (className.toLowerCase().includes(search_class[i].toLowerCase())) { classes_found.push(className); } } } } return classes_found; } function print_arguments(args) { var n = 100; var last_arg = ''; for (var i = 2; i < n; ++i) { var arg = (new ObjC.Object(args[i])).toString(); if (arg == 'nil' || arg == last_arg) { break; } last_arg = arg; console.log('\t[-] arg' + i + ': ' + (new ObjC.Object(args[i])).toString()); } } if (ObjC.available) { console.log('\n[*] Starting Hooking'); var classes_found = search_classes(); for (var i = 0; i < classes_found.length; ++i) { var methods_found = 0; methods_found = search_methods(classes_found[i]); if (Object.keys(methods_found).length){ console.log(classes_found[i]); } for (var j = 0; j < methods_found.length; ++j) { var _className = "" + classes_found[i]; var _methodName = "" + methods_found[j]; var hooking = ObjC.classes[_className][_methodName]; console.log(' ' + methods_found[j]); Interceptor.attach(hooking.implementation, { onEnter: function (args) { this._className = ObjC.Object(args[0]).toString(); this._methodName = ObjC.selectorAsString(args[1]); console.log('Detected call to:'); console.log(' ' + this._className + ' --> ' + this._methodName); //print_arguments(args); }, onLeave: function(returnValues) { } }); } } console.log('\n[*] Starting Intercepting'); } else { console.log('Objective-C Runtime is not available!'); }
執行
frida -l hooking.js -U “appname”
從圖上可見hook成功
編寫onEnter Hook傳入參數修改成0×0:
onEnter: function (args) { this._className = ObjC.Object(args[0]).toString(); this._methodName = ObjC.selectorAsString(args[1]); console.log('Detected call to:'); console.log(' ' + this._className + ' --> ' + this._methodName); console.log('[-]原始值:'+ args[2]); args[2]=ptr("0x0"); console.log('[+]修改值:'+ args[2]); //print_arguments(args); },
運行Hook:
進入app輸入密碼登錄,已不存在人臉識別。
但登錄後由於人臉識別驗證在服務端,客戶端中並無數據,因此無實際危害。