【iOS逆向】某运营商签名算法分析

一、目标

分析某运营商App的x-lemon-sign签名

二、工具

  • mac系统
  • frida-ios-dump:砸壳
  • 已越狱iOS设备:脱壳及frida调试
  • IDA Pro:静态分析
  • Charles:抓包工具

三、步骤

1.寻找切入点

抓包获取到登录接口的信息如下:

image-20221009150216046

2.x-lemon-sign还原

该值长度32位,字母包含a-f,我们先用命令frida-trace -UF -i CC_MD5跟踪CC_MD5函数:

Js代码如下:

{
  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);
  }
}

点击登录按钮后,获取到的日志如下:

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:]函数,关键代码为:

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函数发现了关键信息:

__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:]函数的关键代码如下:

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:]函数的关键代码如下:

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函数为加密算法,查看该代码:

__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类型:

image-20221008180608573

修改后,该函数的代码如下:

__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函数:

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最后跟的字符串,查看该变量的交叉引用:

image-20221009101323033

image-20221009101505531

__block_object_dispose函数的作用是释放匿名函数里使用的变量(关于ios block介绍可自行百度),接下来我们就依次去查找[MWHttpManager createHttpHeaderWithUrl:data:header:query:httpType:]函数里的sub函数,最终在sub_1007B7D48函数发现关键信息:

image-20221009132016135

使用命令frida-trace -UF -a xxxxx\!0x7B7D48 -i CC_MD5跟踪sub_1007B7D48函数,并打印三个参入。获取到日志如下:

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:]函数代码如下:

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生成:

{"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

IMG_4048

posted @ 2022-10-09 16:34  移动端小陈  阅读(236)  评论(0编辑  收藏  举报