-
-
【iOS逆向】某运营商签名算法分析
-
发表于: 2022-10-9 15:11 18312
-
一、目标
分析某运营商App的x-lemon-sign签名
二、工具
- mac系统
- frida-ios-dump:砸壳
- 已越狱iOS设备:脱壳及frida调试
- IDA Pro:静态分析
- Charles:抓包工具
三、步骤
1.寻找切入点
抓包获取到登录接口的信息如下:
2.x-lemon-sign还原
该值长度32位,字母包含a-f,我们先用命令frida-trace -UF -i CC_MD5
跟踪CC_MD5函数:
Js代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | { onEnter(log, args, state) { this.args0 = args[ 0 ]; this.args2 = args[ 2 ]; this.backtrace = 'CC_MD5 called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE) . map (DebugSymbol.fromAddress).join( '\n' ) + '\n' ; }, onLeave(log, retval, state) { var ByteArray = Memory.readByteArray(this.args2, 16 ); var uint8Array = new Uint8Array(ByteArray); var str = ""; for (var i = 0 ; i < uint8Array.length; i + + ) { var hextemp = (uint8Array[i].toString( 16 )) if (hextemp.length = = 1 ){ hextemp = "0" + hextemp } str + = hextemp; } log(`CC_MD5(${this.args0.readUtf8String()})`); log(`CC_MD5() = ${ str } = `); log(this.backtrace); } } |
点击登录按钮后,获取到的日志如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | CC_MD5({ "osVer" : "12.5.5" , "rootFlag" : "1" , "userAgent" : "iPhone6" , "mac" : "de09248b51f16be942ea3ab38e97s83d" , "mobileNo" : "1323580xxxx" , "random" : "6" , "deviceId" : "de09248b51f16be942ea3ab38e97s83d" , "source" : "10000" , "wifiName" :" "," platform ":" 4 "," appName ":" xx "," password ":" 5FB5F1A57BF168ACE85A589BC82AE6E8CB60C6553D05E9DFFD3BFC80E99AF991D3B4274CA8129FC1268E9740FFFDDCAB2E30246C39ECD7481DC7101FAB251FFFB616584634C932A6E66BC5C45880421F7D8A819E2E55E64F776030BAE871B5777314082FA89A253BCC1042DB72E75F5891F73E377729C182C4A06934CAAD0FDC "," wifiBssid ":" "," deviceIdToken ":" "} 79pMh802Q89c04KV ) CC_MD5() = b58e0c0802bc0eba1f0e4f1295a3817f = CC_MD5 called from : 0x102881c24 xxxxx! - [NSData(BWTRideCodeSDKExt) md5String] 0x102883318 xxxxx! - [NSString(BWTRideCodeSDKExt) md5String] 0x10137782c xxxxx! - [MWHttpManager createHttpHeaderWithUrl:data:header:query:httpType:] 0x101374b04 xxxxx! - [MWHttpManager doRequestV8Inner:data:header: type :completion:error:] 0x10137353c xxxxx! - [MWHttpManager postV8:data:header:completion:error:] 0x100d21078 xxxxx! - [HBAFMember loginWithPhonePwd:pwd:completion:error:] 0x100cfde4c xxxxx! + [HBLoginModelHelper loginWithType:passOrToken:mobile:passedDict:completion:error:beforeException:] 0x101864404 xxxxx! 0xca4404 ( 0x100ca4404 ) 0x100ebd344 xxxxx! + [HBBaseParamsTool getPublicKeyAsync:error:] 0x1018641f4 xxxxx! - [HBLoginVC executeLogin:] 0x101863d54 xxxxx! 0xca3d54 ( 0x100ca3d54 ) 0x101337b9c xxxxx! 0x777b9c ( 0x100777b9c ) 0x198f8ca38 libdispatch.dylib!_dispatch_call_block_and_release 0x198f8d7d4 libdispatch.dylib!_dispatch_client_callout 0x198f3b008 libdispatch.dylib!_dispatch_main_queue_callback_4CF$VARIANT$mp 0x1994e0b20 CoreFoundation!__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ |
根据调用栈,使用IDA Pro查看[MWHttpManager createHttpHeaderWithUrl:data:header:query:httpType:]函数,关键代码为:
1 2 3 4 5 6 7 8 | v71 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:" , CFSTR( "%@%@" ), v123, v136[ 5 ]); v113 = (void * )objc_retainAutoreleasedReturnValue(v71); v72 = v152; v73 = objc_msgSend(v113, "md5String" ); v74 = (void * )objc_retainAutoreleasedReturnValue(v73); v75 = objc_msgSend(v74, "lowercaseString" ); v76 = objc_retainAutoreleasedReturnValue(v75); objc_msgSend(v72, "setObject:forKey:" ); |
其中v123是我们的请求体信息,在这主要关注password参数,根据调用栈,往上一层层的trace,最终在sub_101864404函数发现了关键信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | __int64 __fastcall sub_101864404(__int64 a1, __int64 a2) { v2 = (_QWORD * )a1; v29 = a1; v28 = 0LL ; objc_storeStrong(&v28, a2); v27 = v2; v3 = + [MWUtils encryptLoginPwd:pkvalue:](&OBJC_CLASS___MWUtils, "encryptLoginPwd:pkvalue:" , v2[ 4 ], v28); v4 = objc_retainAutoreleasedReturnValue(v3); v26 = v4; v5 = v4; + [HBLoginModelHelper loginWithType:passOrToken:mobile:passedDict:completion:error:beforeException:]( &OBJC_CLASS___HBLoginModelHelper, "loginWithType:passOrToken:mobile:passedDict:completion:error:beforeException:" , 1LL , v5, v6, 0LL , &v20, &v14, &v8); return objc_storeStrong(&v28, 0LL ); } |
继续查看[MWUtils encryptLoginPwd:pkvalue:]函数,pkvalue的值在[HBBaseParamsTool getPublicKeyAsync:error:]获取的(在CC_MD5的日志里有看到,也可以直接查看[HBLoginVC executeLogin:]函数),[MWUtils encryptLoginPwd:pkvalue:]函数的关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | id __cdecl + [MWUtils encryptLoginPwd:pkvalue:](MWUtils_meta * self , SEL a2, id a3, id a4) { v4 = a4; v14 = self ; v13 = a2; v12 = 0LL ; objc_storeStrong(&v12, a3); v11 = 0LL ; objc_storeStrong(&v11, v4); v5 = + [MWUtils toPKCSLoginPwd:](&OBJC_CLASS___MWUtils, "toPKCSLoginPwd:" , v12, v4); v6 = objc_retainAutoreleasedReturnValue(v5); v10 = v6; v7 = + [MWUtils doRSAPublicEncrypt:pkvalue:](&OBJC_CLASS___MWUtils, "doRSAPublicEncrypt:pkvalue:" , v6, v11); v8 = objc_retainAutoreleasedReturnValue(v7); return ( id )objc_autoreleaseReturnValue(v8); } |
[MWUtils toPKCSLoginPwd:]函数处理密码的长度,查看 [MWUtils doRSAPublicEncrypt:pkvalue:]函数的关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | id __cdecl + [MWUtils doRSAPublicEncrypt:pkvalue:](MWUtils_meta * self , SEL a2, id a3, id a4) { v4 = a4; v20 = self ; v19 = a2; v18 = 0LL ; objc_storeStrong(&v18, a3); v17 = 0LL ; objc_storeStrong(&v17, v4); v15 = + [MWUtils createRsaPublicKey:](&OBJC_CLASS___MWUtils, "createRsaPublicKey:" , v17); if ( v15 ) { v14 = (unsigned __int64)objc_msgSend(v18, "length" ); v13 = sub_10125D5E0(v15); v16 = malloc(v13 + 1 ); __memset_chk(v16, 0LL , v13 + 1 , - 1LL ); v5 = (void * )objc_retainAutorelease(v18); v6 = objc_msgSend(v5, "bytes" ); v11 = sub_10125D608(v14, v6, v16, v15, 3LL ); if ( v11 > = 0 ) { sub_101258C18(v15); v7 = objc_msgSend(&OBJC_CLASS___NSData, "dataWithBytes:length:" , v16, v13); v10 = objc_retainAutoreleasedReturnValue(v7); if ( v16 ) { free(v16); v16 = 0LL ; } v8 = + [MWUtils bytesToHex:](&OBJC_CLASS___MWUtils, "bytesToHex:" , v10); v21 = objc_retainAutoreleasedReturnValue(v8); v12 = 1 ; objc_storeStrong(&v10, 0LL ); } else { sub_101258C18(v15); v21 = objc_retain(&stru_102883E78); v12 = 1 ; } } else { v21 = objc_retain(&stru_102883E78); v12 = 1 ; } return ( id )objc_autoreleaseReturnValue(v21); } |
根据代码可知sub_10125D608函数为加密算法,查看该代码:
1 2 3 4 | __int64 __fastcall sub_10125D608(__int64 a1, __int64 a2, __int64 a3, __int64 a4) { return ( * (__int64 ( * * )(void))( * (_QWORD * )(a4 + 16 ) + 8LL ))(); } |
根据伪代码,可看出该该函数基于a4参数进行offset然后再调用。查看调用该函数时,a4的入参类型为rsa_st。修改a4类型:
修改后,该函数的代码如下:
1 2 3 4 | __int64 __fastcall sub_10125D608(__int64 a1, __int64 a2, __int64 a3, rsa_st * a4) { return ((__int64 (__fastcall * )(__int64, __int64, __int64))a4 - >var2 - >rsa_pub_enc)(a1, a2, a3); } |
双击rsa_pub_enc函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 00000000 rsa_meth_st struc ; (sizeof = 0x70 , align = 0x8 , copyof_10259) 00000000 name DCQ ? ; offset 00000008 rsa_pub_enc DCQ ? ; offset 00000010 rsa_pub_dec DCQ ? ; offset 00000018 rsa_priv_enc DCQ ? ; offset 00000020 rsa_priv_dec DCQ ? ; offset 00000028 rsa_mod_exp DCQ ? ; offset 00000030 bn_mod_exp DCQ ? ; offset 00000038 init DCQ ? ; offset 00000040 finish DCQ ? ; offset 00000048 flags DCD ? 0000004C DCB ? ; undefined 0000004D DCB ? ; undefined 0000004E DCB ? ; undefined 0000004F DCB ? ; undefined 00000050 app_data DCQ ? ; offset 00000058 rsa_sign DCQ ? ; offset 00000060 rsa_verify DCQ ? ; offset 00000068 rsa_keygen DCQ ? ; offset 00000070 rsa_meth_st ends |
结合之前的createRsaPublicKey,rsa_st,rsa_pub_enc,可看出,该加密为rsa,在google搜索rsa_st rsa_st rsa_pub_enc关键字,可知道该rsa是使用了第三方库openssl库。
回到[MWHttpManager createHttpHeaderWithUrl:data:header:query:httpType:]函数,生成x-lemon-sign最后跟的字符串,查看该变量的交叉引用:
__block_object_dispose函数的作用是释放匿名函数里使用的变量(关于ios block介绍可自行百度),接下来我们就依次去查找[MWHttpManager createHttpHeaderWithUrl:data:header:query:httpType:]函数里的sub函数,最终在sub_1007B7D48函数发现关键信息:
使用命令frida-trace -UF -a xxxxx\!0x7B7D48 -i CC_MD5
跟踪sub_1007B7D48函数,并打印三个参入。获取到日志如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | sub_7b7d48() <__NSStackBlock__: 0x16ea61278 > - - - f01A4K8f83V4C838 - - - R970ot8KI22K09cY CC_MD5 called from : 0x1017f25b8 xxxxx! + [HBHandlePrivateKey filterUrl:completion:] 0x101b52f5c xxxxx! - [MWHttpManager createHttpHeaderWithUrl:data:header:query:httpType:] 0x101b50b04 xxxxx! - [MWHttpManager doRequestV8Inner:data:header: type :completion:error:] 0x101b4f53c xxxxx! - [MWHttpManager postV8:data:header:completion:error:] 0x1014fd078 xxxxx! - [HBAFMember loginWithPhonePwd:pwd:completion:error:] 0x1014d9e4c xxxxx! + [HBLoginModelHelper loginWithType:passOrToken:mobile:passedDict:completion:error:beforeException:] 0x102040404 xxxxx! 0xca4404 ( 0x100ca4404 ) 0x101699344 xxxxx! + [HBBaseParamsTool getPublicKeyAsync:error:] 0x1020401f4 xxxxx! - [HBLoginVC executeLogin:] 0x10203fd54 xxxxx! 0xca3d54 ( 0x100ca3d54 ) 0x101b13b9c xxxxx! 0x777b9c ( 0x100777b9c ) 0x198f8ca38 libdispatch.dylib!_dispatch_call_block_and_release 0x198f8d7d4 libdispatch.dylib!_dispatch_client_callout 0x198f3b008 libdispatch.dylib!_dispatch_main_queue_callback_4CF$VARIANT$mp 0x1994e0b20 CoreFoundation!__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ 0x1994dba58 CoreFoundation!__CFRunLoopRun CC_MD5({ "osVer" : "12.5.5" , "rootFlag" : "1" , "userAgent" : "iPhone6" , "mac" : "de09248b51f16be942ea3ab38e97s83d" , "mobileNo" : "1323580xxxx" , "random" : "236" , "deviceId" : "de09248b51f16be942ea3ab38e97s83d" , "source" : "10000" , "wifiName" :" "," platform ":" 4 "," appName ":" xx "," password ":" A82D83A921CE4812F0D17A32B6DA6AFCC47E9A6AF3FA60E141C2D37C3473047E97645F4FF461C8C382560EE84E9D9B62EF77F50DBFE7A0D45836AC75CB821F6B95C3A1C3ED53836EB0736E85E9A7C39282F2D22D8EB5766B69F147B1A4C0EA31953C775FD96A45C857D0E2BC0994DBA4E9B737CCD0FC1E3336BBE2E7096D82A2 "," wifiBssid ":" "," deviceIdToken ":" "}f01A4K8f83V4C838) |
可以看到第二个参数,正是我们需要的,根据调用栈,我们查看[HBHandlePrivateKey filterUrl:completion:]函数代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | void __cdecl + [HBHandlePrivateKey filterUrl:completion:](HBHandlePrivateKey_meta * self , SEL a2, id a3, id a4) { if ( (unsigned __int64)objc_msgSend(v35, "containsString:" , CFSTR( "security/login/password/dynamicKey" )) & 1 ) { v5 = + [HBMocamSetting shareInstance](&OBJC_CLASS___HBMocamSetting, "shareInstance" ); v6 = (void * )objc_retainAutoreleasedReturnValue(v5); v7 = (unsigned __int64)objc_msgSend(v6, "notUseUserID" ); objc_release(v6); if ( v7 & 1 ) { v8 = + [MWUserProfile getMsspBatchSessionKey](&OBJC_CLASS___MWUserProfile, "getMsspBatchSessionKey" ); v33 = objc_retainAutoreleasedReturnValue(v8); v9 = + [MWUserProfile getMsspBatchSessionKeyVectore](&OBJC_CLASS___MWUserProfile, "getMsspBatchSessionKeyVectore" ); v32 = objc_retainAutoreleasedReturnValue(v9); ( * (void ( * * )(void))(v34 + 16 ))(); objc_storeStrong(&v32, 0LL ); objc_storeStrong(&v33, 0LL ); } } } |
[MWUserProfile getMsspBatchSessionKey]函数则是获取到该值,至于该传从哪获取的,可继续跟踪saveMsspBatchSessionKey:函数
总结
x-lemon-sign由以下信息md5生成:
1 | { "osVer" : "12.5.5" , "rootFlag" : "1" , "userAgent" : "iPhone6" , "mac" : "de09248b51f16be942ea3ab38e97s83d" , "mobileNo" : "1323580xxxx" , "random" : "236" , "deviceId" : "de09248b51f16be942ea3ab38e97s83d" , "source" : "10000" , "wifiName" :" "," platform ":" 4 "," appName ":" xx "," password ":" A82D83A921CE4812F0D17A32B6DA6AFCC47E9A6AF3FA60E141C2D37C3473047E97645F4FF461C8C382560EE84E9D9B62EF77F50DBFE7A0D45836AC75CB821F6B95C3A1C3ED53836EB0736E85E9A7C39282F2D22D8EB5766B69F147B1A4C0EA31953C775FD96A45C857D0E2BC0994DBA4E9B737CCD0FC1E3336BBE2E7096D82A2 "," wifiBssid ":" "," deviceIdToken ":" "}f01A4K8f83V4C838 |
其中的关键信息password为openssl的rsa加密,f01A4K8f83V4C838为sessionKey,从[MWUserProfile getMsspBatchSessionKey]函数获取。
提示:阅读此文档的过程中遇到任何问题,请关注公众号【
移动端Android和iOS开发技术分享
】或加QQ群【812546729
】
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!