浏览此文档的过程中遇到任何问题,请关注公众号【挪动端 Android 和 iOS 开发技术分享】或加 QQ 群【812546729】
1. 指标
应用 frida stalker 剖析某营业厅的签名算法。
2. 操作环境
mac 零碎
frida-ios-dump:砸壳
Charles:抓包
已越狱 iOS 设施:脱壳及 frida 调试
IDA Pro:动态剖析
3. 流程
寻找切入点
在账号密码登录页,点击登录,通过 Charles 抓包获取到关键词为 loginAuthCipherAsymmertric,这也就是咱们的切入点:
剖析过程
应用 frida-ios-dump 的砸壳命令 dump.py com.wemomo.momoappdemo1 砸壳获取到 ipa 文件,再应用 IDA Pro 编译 ipa 文件,而后搜寻搜寻字符串 loginAuthCipherAsymmertric 失败,持续搜寻 userLoginNormal,失败。该利用对字符串都进行了混同。搜寻无果后,只能换个思路,尝试 hook NSMutableURLRequest 类。应用 frida-trace 的 frida-trace -UF -m “-[NSMutableURLRequest setHTTPBody:]” 命令跟踪该函数,js 代码如下:
{onEnter(log, args, state) {var arg2 = new ObjC.Object(args[2]) log(`-[NSMutableURLRequest setHTTPBody:${arg2.bytes().readUtf8String()}]`); }, onLeave(log, retval, state) {}}
点击登录后,获取到的日志如下:
-[NSMu```
tableURLRequest setHTTPBody:ndh=1&t=00%2F00%2F0000%2000%3A00%3A00%200%20-480&c.&v3=baoguang_event&deviceId=7B063D78-C5D8-45A6-8E3D-37B787DEB2CD&hitDate=2022-09-02%2023%3A01%3A52¤tPage=Denglujh-kuandaidenglushouji&v63=101-106&appVersion=4&c25=%E6%B6%88%E6%81%AF%2BSyjh-sytop-message-1_1%2B%28null%29%2B%28null%29&es_timestamp=1662130874519&v60=lo158b3b81&codeVersion=22033101&v65=WIFI&v61=disable&a.&action=trkAppBgAction&OSVersion=iOS%2012.5.5&DeviceName=iPhone7%2C2&RunMode=Application&AppID=CTPocket%209.4.0%20%284%29&CarrierName=%28null%29&Resolution=750x1334&TimeSinceLaunch=2536&.a&lunchtype=aut_lunch&.c&pev2=AMACTION%3AtrkAppBgAction&pageName=CTPocket%2F4&ce=UTF-8&aid=540B88F2280D4599-146C532D0B076319&pe=lnk_o&cp=foreground]-[NSMutableURLRequest setHTTPBody:{"content":{"fieldData":{"isChinatelecom":"0","phoneNum":"13245678901","authentication":"222222","accountType":"","deviceUid":"ddecc53e1fce414e9e89b6ea47e567e3","systemVersion":"12.5.5","loginAuthCipherAsymmertric":"o4Af5TvC5iV25FhTE9NIZJEiqHLWg+JkcCF4AGp727uhvFydBWvkCz8HauqTDpIoRhpfqLUMLN6Hk1ucBZOPYhCHwm4N\/4PuPsMWZTEbips+uL74ufgeLMci0nIZRmFsCsBrgvUVkebKcRo2yO0DZQ2jtnKe+cG78v6aOHl5ssk=","loginType":"4"},"attach":"iPhone"},"headerInfos":{"broadAccount":"","source":"120002","shopId":"20004","userLoginName":"13245678901","broadToken":"","code":"userLoginNormal","clientType":"#9.4.0#channel50#iPhone 6#","token":"","timestamp":"20220902230115","sourcePassword":"TiqmIZ"}}]-[NSMutableURLRequest setHTTPBody:ndh=1&t=00%2F00%2F0000%2000%3A00%3A00%200%20-480&c.&v3=hit_event&deviceId=7B063D78-C5D8-45A6-8E3D-37B787DEB2CD&hitDate=2022-09-02%2023%3A01%3A05&c20=%E5%8F%B3%E6%BB%91%E7%99%BB%E5%BD%95&appVersion=4&lastAction=Denglujh-denglu-mima-2_8%5E%E5%8F%B3%E6%BB%91%E7%99%BB%E5%BD%95&method=trkAppButtonClick%20-action%20...&v63=101-106¤tPage=Denglujh-kuandaidenglushouji&es_timestamp=1662130875056&c21=Denglujh-denglu-mima-2_8&v60=lo158b3b81&codeVersion=22033101&v65=WIFI&v61=disable&a.&action=trkAppButtonClick&OSVersion=iOS%2012.5.5&CarrierName=%28null%29&DeviceName=iPhone7%2C2&AppID=CTPocket%209.4.0%20%284%29&RunMode=Application&Resolution=750x1334&TimeSinceLaunch=2537&.a&lunchtype=aut_lunch&prePageAction=hit_event&.c&pev2=AMACTION%3AtrkAppButtonClick&pageName=CTPocket%2F4&ce=UTF-8&aid=540B88F2280D4599-146C532D0B076319&pe=lnk_o&cp=foreground]
搜寻登录的账号 13245678901 后,发现日志里有登录的 body 信息,在 setHTTPBody 的 js 代码里打印堆栈:
{onEnter(log, args, state) {var arg2 = new ObjC.Object(args[2]) log(`-[NSMutableURLRequest setHTTPBody:${arg2.bytes().readUtf8String()}]`); log('NSMutableURLRequest setHTTPBody called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\n') + '\n'); }, onLeave(log, retval, state) {}}
获取到的堆栈信息如下:
CTPocket!-[AFJSONRequestSerializer requestBySerializingRequest:withParameters:error:]0x10397ae50 CTPocket!-[AFHTTPRequestSerializer requestWithMethod:URLString:parameters:error:]0x10394edac CTPocket!-[AFHTTPSessionManager dataTaskWithHTTPMethod:URLString:parameters:headers:uploadProgress:downloadProgress:success:failure:]0x10394e378 CTPocket!-[AFHTTPSessionManager POST:parameters:headers:progress:success:failure:]0x101b8fae8 CTPocket!-[ESHttpSessionManager postWithHost:urlString:parameters:success:failure:]0x1015f91c8 CTPocket!-[ESNetworkingManager postWithURLString:parameters:modifiParamsBlock:success:failure:]0x100c14da4 CTPocket!-[ESLoginService loginWithPhoneNbr:type:code:isChinatelecom:slidingTime:percentage:success:failure:]0x101f85f58 CTPocket!-[ESBindLoginViewController phoneLoginWithPhoneNbr:type:code:slidingTime:percentage:isChinatelecom:isBind:failureBlock:]0x101f6a060 CTPocket!-[ESBindLoginViewController phoneLoginViewController:inputView:didSliderWithSlidingTime:Percentage:phoneNbr:passwd:loginType:isChinatelecom:isBind:failureBlock:]0x102ccb964 CTPocket!-[ESLoginViewController phoneLoginView:inputView:didSliderWithSlidingTime:Percentage:phoneNbr:passwd:loginType:isChinatelecom:isBind:]0x101261728 CTPocket!-[ESPhoneLoginView passwordInputView:sliderWithSlidingTime:Percentage:phoneNbr:passwd:]0x101e73d10 CTPocket!-[PasswordInputView commitBtnAction:]0x1e7091300 UIKitCore!-[UIApplication sendAction:to:from:forEvent:]0x10400a288 CTPocket!-[UIApplication(AutoTrack) sa_sendAction:to:from:forEvent:]0x1e6b3a424 UIKitCore!-[UIControl sendAction:to:forEvent:]0x1e6b3a744 UIKitCore!-[UIControl _sendActionsForEvents:withEvent:]
接下来咱们应用 frida-trace 工具,对以上调用栈进行一一跟踪并打印入参,最终确定 loginAuthCipherAsymmertric 参数在 [ESLoginService loginWithPhoneNbr:type:code:isChinatelecom:slidingTime:percentage:success:failure:] 办法里生成的,关上 IDA Pro 并找到该办法,代码如下:
__cdecl `
-ESLoginService loginWithPhoneNbr:type:code:isChinatelecom:slidingTime:percentage:success:failure:{id v10; // x19 id v11; // x20 id v12; // x25 id v13; // x21 ESLoginService v14; // x24 __int64 v15; // x1 __int64 v16; // x1 __int64 v17; // x1 __int64 v18; // x1 __int64 v19; // x1 __int64 v20; // x1 void v21; // x0 __int64 v22; // x25 void v23; // x0 unsigned int v24; // off signed int v25; // w8 v10 = a8; v11 = a7; v12 = a6; v13 = a5; v14 = self; objc_retain(a3, a2); objc_retain(a9, v15); objc_retain(a10, v16); objc_retain(v10, v17); objc_retain(v11, v18); objc_retain(v12, v19); objc_retain(v13, v20); v21 = objc_msgSend(&OBJC_CLASS___NSDate, “date”); v22 = objc_retainAutoreleasedReturnValue(v21); -ESLoginService setRequestDate:; objc_release(v22); v23 = (void )((__int64 (__fastcall )(void ))((char )off_105153068 + 92708870))(&OBJC_CLASS___NSDateFormatter); objc_msgSend(v23, “init”); v24 = __ldar((unsigned int )&dword_10577A424); if ((unsigned int)&dword_10577A424 ) v25 = 7; else v25 = 34; JUMPOUT(__CS__, (char )(&off_105153070 + v25) – dword_1051531F0[v25]);}
傻眼了了吧。JUMPOUT,也就是常常逆向会遇到的跳表,须要手动复原。在这,咱们应用 frida stalker 来跟踪该函数。ts 代码如下:12345678910111213141516171819202122232425262728293031323334353637383940var addr = 0x0000000029FD08 // loginWithPhoneNbr 函数的起始地址 var mainModule = Process.enumerateModules()[0];console.log(JSON.stringify(mainModule));var mainName: string = mainModule.name;var baseAddr = Module.findBaseAddress(mainName)!;Interceptor.attach(baseAddr.add(addr), {onEnter: function(args) {console.log(addr.toString(16), “= loginWithPhoneNbr onEnter =”); var tid = Process.getCurrentThreadId(); Stalker.follow(tid, { events: { call: true, // CALL instructions: yes please ret: false, // RET instructions exec: false, // all instructions: not recommended as it’s block: false, // block executed: coarse execution trace compile: false // block compiled: useful for coverage}, transform: (iterator: StalkerArm64Iterator) => {let instruction = iterator.next(); const startAddress = instruction!.address; var isAppCode = startAddress.compare(baseAddr.add(addr)) >= 0 && startAddress.compare(baseAddr.add(addr).add(10000)) === -1; do {if (isAppCode) {if (instruction!.mnemonic === “bl”) {iterator.putCallout((ctx) => {var arm64Context = ctx as Arm64CpuContext; console.log(“bl x0 = ” + new ObjC.Object(arm64Context.x0)) console.log(“bl x1 = ” + arm64Context.x1.readCString()) }); } } iterator.keep();} while ((instruction = iterator.next()) !== null); } }) }, onLeave: function(retval) {console.log(“retval:”, new ObjC.Object(retval)) console.log(addr.toString(16), “= loginWithPhoneNbr onLeave =”); }}); 获取到的要害日志如下:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115229fd08 = loginWithPhoneNbr onEnter =bl x0 = Utilsbl x1 = createRSAStringWithPhone:authentication:timestamp:slidingTime:percentage:bl x0 = s21NZBfNx8ndPADDLDJCxCUOdWo45EL8jNr6TnHcwKwfnahCFpLGjXkZkhbGj2CZODR3LVjabCRw6Li3SLcvLtA6g0meKbUTgQzANtn9y2ttg9Svj9flxj9k18Ju5EBjloSbdd4ee4o0rHgn9W0iSSDs4zwKsqm+rOxYDZFHgWI=bl x1 = autoreleasebl x0 = 0.000000bl x1 = nullbl x0 = 0bl x1 = nullbl x0 = NSMutableDictionarybl x1 = dictionarybl x0 = {}bl x1 = autoreleasebl x0 = {}bl x1 = setObject:forKey:bl x0 = {accountType = “”;}bl x1 = setObjectOrNil:forKey:bl x0 = 222222bl x1 = copyWithZone:bl x0 = ESGlobalFactorybl x1 = sharedInstancebl x0 = <ESGlobalFactory: 0x282e568b0>bl x1 = sharedInstancebl x0 = <ESGlobalFactory: 0x282e568b0>bl x1 = deviceInfobl x0 = <ESDeviceInfo: 0x28225e120>bl x1 = deviceInfobl x0 = <ESDeviceInfo: 0x28225e120>bl x1 = uuidForDevicebl x0 = ddecc53e1fce414e9e89b6ea47e567e3bl x1 = autoreleasebl x0 = {accountType = “”; authentication = 222222;}bl x1 = setObjectOrNil:forKey:bl x0 = ddecc53e1fce414e9e89b6ea47e567e3bl x1 = copyWithZone:bl x0 = <ESDeviceInfo: 0x28225e120>bl x1 = releasebl x0 = <ESGlobalFactory: 0x282e568b0>bl x1 = releasebl x0 = {accountType = “”; authentication = 222222; deviceUid = ddecc53e1fce414e9e89b6ea47e567e3;}bl x1 = setObjectOrNil:forKey:bl x0 = 0bl x1 = copyWithZone:bl x0 = NSStringbl x1 = stringWithFormat:bl x0 = 4bl x1 = autoreleasebl x0 = {accountType = “”; authentication = 222222; deviceUid = ddecc53e1fce414e9e89b6ea47e567e3; isChinatelecom = 0;}bl x1 = setObjectOrNil:forKey:bl x0 = 4bl x1 = copyWithZone:bl x0 = {accountType = “”; authentication = 222222; deviceUid = ddecc53e1fce414e9e89b6ea47e567e3; isChinatelecom = 0; loginType = 4;}bl x1 = setObjectOrNil:forKey:bl x0 = UIDevicebl x1 = currentDevicebl x0 = <UIDevice: 0x2820598e0>bl x1 = currentDevicebl x0 = <UIDevice: 0x2820598e0>bl x1 = systemVersionbl x0 = 12.5.5bl x1 = X7Lyw9oGPgMDQbl x0 = {accountType = “”; authentication = 222222; deviceUid = ddecc53e1fce414e9e89b6ea47e567e3; isChinatelecom = 0; loginType = 4; phoneNum = 13245678901;}bl x1 = setObjectOrNil:forKey:bl x0 = 12.5.5bl x1 = 0��ו�bl x0 = <UIDevice: 0x2820598e0>bl x1 = 0��ו�bl x0 = {accountType = “”; authentication = 222222; deviceUid = ddecc53e1fce414e9e89b6ea47e567e3; isChinatelecom = 0; loginType = 4; phoneNum = 13245678901; systemVersion = “12.5.5”;}bl x1 = setObjectOrNil:forKey:bl x0 = ESDataAccessFactorybl x1 = sharedInstancebl x0 = <ESDataAccessFactory: 0x280472460>bl x1 = sharedInstancebl x0 = <ESDataAccessFactory: 0x280472460>bl x1 = highFrequencyNetworkingManagerbl x0 = <ESNetworkingManager: 0x282e9cc90>bl x1 = highFrequencyNetworkingManagerbl x0 = <__NSStackBlock__: 0x16f4f0c28>bl x1 = highFrequencyNetworkingManagerbl x0 = <__NSStackBlock__: 0x16f4f0c88>bl x1 = retainbl x0 = 20220905003215bl x1 = retainbl x0 = 13245678901bl x1 = nullbl x0 = <ESNetworkingManager: 0x282e9cc90>bl x1 = postWithURLString:parameters:modifiParamsBlock:success:failure:bl x0 = {content = { attach = iPhone; fieldData = { accountType = “”; authentication = 222222; deviceUid = ddecc53e1fce414e9e89b6ea47e567e3; isChinatelecom = 0; loginAuthCipherAsymmertric = “s21NZBfNx8ndPADDLDJCxCUOdWo45EL8jNr6TnHcwKwfnahCFpLGjXkZkhbGj2CZODR3LVjabCRw6Li3SLcvLtA6g0meKbUTgQzANtn9y2ttg9Svj9flxj9k18Ju5EBjloSbdd4ee4o0rHgn9W0iSSDs4zwKsqm+rOxYDZFHgWI=”; loginType = 4; phoneNum = 13245678901; systemVersion = “12.5.5”;}; }; headerInfos = {broadAccount = “”; broadToken = “”; clientType = “#9.4.0#channel50#iPhone 6#”; code = userLoginNormal; shopId = 20004; source = 120002; sourcePassword = TiqmIZ; timestamp = 20220905003218; token = “”; userLoginName = “”;};}bl x1 = a���后果通过日志,咱们能够发现 loginAuthCipherAsymmertric 参数是应用 Utils 类的 createRSAStringWithPhone:authentication:timestamp:slidingTime:percentage: 办法生成的,伪代码如下:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879id __cdecl +Utils createRSAStringWithPhone:authentication:timestamp:slidingTime:percentage:{v7 = a7; v8 = a5; v9 = a4; v10 = a3; v11 = self; v12 = objc_retain(a6, a2); v14 = objc_retain(v7, v13); v16 = (void )objc_retain(v8, v15); v18 = objc_retain(v9, v17); v20 = objc_retain(v10, v19); v21 = +ESGlobalFactory sharedInstance; v22 = (void )objc_retainAutoreleasedReturnValue(v21); v23 = v22; v24 = objc_msgSend(v22, “deviceInfo”); v25 = (void )objc_retainAutoreleasedReturnValue(v24); v26 = v25; v27 = objc_msgSend(v25, “uuidForDevice”); v28 = objc_retainAutoreleasedReturnValue(v27); v29 = +Utils substring:ToIndex:; v30 = objc_retainAutoreleasedReturnValue(v29); v31 = +Utils deviceName; v32 = objc_retainAutoreleasedReturnValue(v31); v33 = v32; v34 = +Utils substring:ToIndex:; v35 = objc_retainAutoreleasedReturnValue(v34); v36 = objc_msgSend(&OBJC_CLASS___UIDevice, “currentDevice”); v37 = (void )objc_retainAutoreleasedReturnValue(v36); v38 = v37; v39 = objc_msgSend(v37, “systemVersion”); v40 = objc_retainAutoreleasedReturnValue(v39); objc_release(v38); v41 = +Utils substring:ToIndex:; v42 = objc_retainAutoreleasedReturnValue(v41); objc_release(v40); v43 = (void )objc_alloc(&OBJC_CLASS___NSDateFormatter); v44 = objc_msgSend(v43, “init”); v45 = v44; v46 = v44; objc_msgSend(v44, “setDateFormat:”, CFSTR(“yyyyMMddHHmmss”)); v47 = (void )objc_alloc(&OBJC_CLASS___NSLocale); v48 = objc_msgSend(v47, “initWithLocaleIdentifier:”, CFSTR(“en_US”)); objc_msgSend(v45, “setLocale:”, v48); v49 = objc_msgSend(v16, “stringByReplacingOccurrencesOfString:withString:”, CFSTR(“:”), &stru_10480C358); v50 = objc_retainAutoreleasedReturnValue(v49); objc_release(v16); v51 = +Utils substring:ToIndex:; v52 = objc_retainAutoreleasedReturnValue(v51); v53 = v52; v54 = v52; v55 = objc_msgSend(v11, “substring:ToIndex:”, v12, 4LL); v56 = objc_retainAutoreleasedReturnValue(v55); v57 = objc_msgSend(v11, “substring:ToIndex:”, v14, 2LL); v58 = objc_retainAutoreleasedReturnValue(v57); v59 = objc_msgSend(v11, “substring:ToIndex:”, v18, 6LL); v60 = objc_retainAutoreleasedReturnValue(v59); v61 = objc_msgSend(v11, “substring:ToIndex:”, v20, 11LL); v62 = objc_retainAutoreleasedReturnValue(v61); v63 = objc_msgSend(&OBJC_CLASS___NSString, “stringWithFormat:”, CFSTR(“%@%@%@%@%@%@%@%@”), v35, v42, v30, v62, v53, v60, v56, v58); v64 = objc_retainAutoreleasedReturnValue(v63); v65 = +Utils loginRsaKey2; v66 = objc_retainAutoreleasedReturnValue(v65); v67 = v66; v68 = +RSAEncryptor encryptString:publicKey:; v69 = objc_retainAutoreleasedReturnValue(v68); return (id)objc_autoreleaseReturnValue(v69);}这就是生成 loginAuthCipherAsymmertric 的最终函数。End 浏览此文档的过程中遇到任何问题,请关注公众号【挪动端 Android 和 iOS 开发技术分享】或加 QQ 群【812546729】