前言

最近在尝试逆向方向相关的探索,针对某款运动APP的登录协议进行了剖析,此文记录一下剖析进程与成果,仅供学习研讨,运用的东西较少,内容也比较简略,新手项,大佬请越过。针对暗码登录模块进行剖析,随便输入一个手机号与暗码,后续运用抓包东西剖析,针对登录协议的几个字段从学习角度仍是值得看下完成逻辑的。

抓包

  1. 抓包运用 Charles,请自行装置并装备证书
  2. 抓取登陆接口,点击暗码登陆。运用假账密测试抓包,能够抓包成功
    某运动APP的登录协议分析

Sign剖析

首要能看到请求头里边有sign字段,针对该字段进行剖析:

sign: b61df9a8bce7a8641c5ca986b55670e633a7ab29

全体长度为40,常用的MD5长度为32,第一反应不太像,可是也有可能md5今后再拼接其它字段,sha1散列函数的长度是40,正好符合。那咱们就逐个验证,先看下是否有MD5的痕迹,直接写脚本frida试着跑下。 脚本内容比较清晰,针对MD5的Init、Update、Final分别hook打印看下输入与输出,下面给到关键代码:

 // hook CC_MD5
 // unsigned char * CC_MD5(const void *data, CC_LONG len, unsigned char *md);
 Interceptor.attach(Module.findExportByName("libcommonCrypto.dylib",g_funcName), {
   onEnter:function(args) {
     console.log(g_funcName+" begin");
     varlen=args[1].toInt32();
     console.log("input:");
     dumpBytes(args[0],len);
     this.md=args[2];
   },
   onLeave:function(retval) {
     console.log(g_funcName+" return value");
     dumpBytes(this.md,g_funcRetvalLength);
​
     console.log(g_funcName+' called from:\n'+
       Thread.backtrace(this.context,Backtracer.ACCURATE)
       .map(DebugSymbol.fromAddress).join('\n')+'\n');
   }
 });
​
 // hook CC_MD5_Update
 // int CC_MD5_Update(CC_MD5_CTX *c, const void *data, CC_LONG len);
 Interceptor.attach(Module.findExportByName("libcommonCrypto.dylib",g_updateFuncName), {
   onEnter:function(args) {
     console.log(g_updateFuncName+" begin");
     varlen=args[2].toInt32();
     console.log("input:");
     dumpBytes(args[1],len);
   },
   onLeave:function(retval) {
     console.log(g_updateFuncName+' called from:\n'+
       Thread.backtrace(this.context,Backtracer.ACCURATE)
       .map(DebugSymbol.fromAddress).join('\n')+'\n');
   }
 });
​
 // hook CC_MD5_Final
 // int CC_MD5_Final(unsigned char *md, CC_MD5_CTX *c);
 Interceptor.attach(Module.findExportByName("libcommonCrypto.dylib",g_finalFuncName), {
   onEnter:function(args) {
     //console.log(func.name + " begin");
     finalArgs_md=args[0];
   },
   onLeave:function(retval) {
     console.log(g_finalFuncName+" return value");
     dumpBytes(finalArgs_md,g_funcRetvalLength);
​
     console.log(g_finalFuncName+' called from:\n'+
       Thread.backtrace(this.context,Backtracer.ACCURATE)
       .map(DebugSymbol.fromAddress).join('\n')+'\n');
   }
 });

很走运,在打印中显着看到了sign相关的内容打印,可是缺少sign的后边一部分,那就清晰sign值的构成为32(md5)+8,先看下md5的数据构造进程。

b61df9a8bce7a8641c5ca986b55670e6 33a7ab29

某运动APP的登录协议分析
通过打印能够清晰的看到,sign的MD5由三部分数据组成,分别为:bodyData+Url+Str,body数据也可从Charles获取到。

  • {“body”:”5gJEXtLqe3tzRsP8a/bSwehe0ta3zQx6wG7K74sOeXQ6Auz1NI1bg68wNLmj1e5Xl7CIwWelukC445W7HXxJY6nQ0v0SUg1tVyWS5L8E2oaCgoSeC6ypFNXV2xVm8hHV”}
  • /account/v4/login/password
  • V1QiLCJhbGciOiJIUzI1NiJ9
    某运动APP的登录协议分析
    到这里有一个疑问,数据的第三部分:V1QiLCJhbGciOiJIUzI1NiJ9,该值是固定的字符串仍是每次都改动的?猜测应该是固定的字符串,作为MD5的Salt值来运用,咱们再次请求验证一下。
    某运动APP的登录协议分析
    新的sign值为:131329a5af4ecb025fb5088615d5e5c526dbd1a3,通过脚本打印的数据能承认第三部分为固定字符串。 MD5({“body”:”12BcOSg50nLxdbt++r7liZpeyWAVpmihTy8Zu8BmpA6a1hqdevS5PPYwnbtpjN05xgeyReSihh9idyfriR6qx1Fbo8AA0k8HQt6gJ3spWITI21GhLTzh9PDUkgjCtrEK”}/account/v4/login/passwordV1QiLCJhbGciOiJIUzI1NiJ9)
    某运动APP的登录协议分析

Sign尾部剖析

接下来咱们针对Sign的尾部数据进行剖析,单纯盲猜或许挂frida脚本现已解决不了问题了,咱们用IDA看下详细的完成逻辑,当然上面的MD5剖析也能够直接从IDA反编译入手,通过查找sign关键字进行定位,只是我习惯先钩一下脚本,万一直接命中就不用费时刻去剖析了…

通过MD5的脚本打印,咱们也能看到相关的函数调用栈,这对于咱们快速定位也提供了很大的方便。咱们直接查找 [KEPPostSecuritySign kep_signWithURL: body:] 办法,能够看到显着的字符串拼接的痕迹,IDA仍是比较智能的,现已辨认出了MD5的salt值。

某运动APP的登录协议分析
通过剖析,定位到[NSString kep_networkStringOffsetSecurity]函数,在内部进行了字符串的处理,在循环里边进行了各种判别以及移位操作,不嫌麻烦的话能够剖析一下逻辑,重写一下处理流程。
某运动APP的登录协议分析
我这边处理比较暴力,发现kep_networkStringOffsetSecurity是NSString的Catetory,那就直接调用验证一下吧,运用frida挂载今后,找到NSString类,调用办法传入md5之后的值,然后就会发现通过该函数,奇特的sign值就给到了。
某运动APP的登录协议分析

x-ads剖析

剖析完sign今后,观察到还有一个x-ads的字段,按照惯例,先用脚本试着钩一下,常常采用的加密大致便是DES、AES或RC4这些算法。

某运动APP的登录协议分析
针对 AES128、DES、3DES、CAST、RC4、RC2、Blowfish等加密算法进行hook,脚本的关键代码如下:

varhandlers={
 CCCrypt: {
   onEnter:function(args) {
     varoperation=CCOperation[args[0].toInt32()];
     varalg=CCAlgorithm[args[1].toInt32()].name;
     this.options=CCoptions[args[2].toInt32()];
     varkeyBytes=args[3];
     varkeyLength=args[4].toInt32();
     varivBuffer=args[5];
     varinBuffer=args[6];
     this.inLength=args[7].toInt32();
     this.outBuffer=args[8];
     varoutLength=args[9].toInt32();
     this.outCountPtr=args[10];
     if(this.inLength<MIN_LENGTH||this.inLength>MAX_LENGTH){
    return;
     }
     if(operation==="kCCEncrypt") {
       this.operation="encrypt"
       console.log("***************** encrypt begin **********************");
     }else{
       this.operation="decrypt"
       console.log("***************** decrypt begin **********************");
     }
     console.log("CCCrypt("+
       "operation: "+this.operation+", "+
       "CCAlgorithm: "+alg+", "+
       "CCOptions: "+this.options+", "+
       "keyBytes: "+keyBytes+", "+
       "keyLength: "+keyLength+", "+
       "ivBuffer: "+ivBuffer+", "+
       "inBuffer: "+inBuffer+", "+
       "inLength: "+this.inLength+", "+
       "outBuffer: "+this.outBuffer+", "+
       "outLength: "+outLength+", "+
       "outCountPtr: "+this.outCountPtr+")"
     );
​
     //console.log("Key: utf-8 string:" + ptr(keyBytes).readUtf8String())
     //console.log("Key: utf-16 string:" + ptr(keyBytes).readUtf16String())
     console.log("key: ");
     dumpBytes(keyBytes,keyLength);
​
     console.log("IV: ");
     // ECB模式不需要iv,所以iv是null
     dumpBytes(ivBuffer,keyLength);
​
     varisOutput=true;
     if(!SHOW_PLAIN_AND_CIPHER&&this.operation=="decrypt") {
     isOutput=false;
     }
​
     if(isOutput){
    // Show the buffers here if this an encryption operation
     console.log("In buffer:");
     dumpBytes(inBuffer,this.inLength);
     }
     
   },
   onLeave:function(retVal) {
  // 长度过长和长度太短的都不要输出
     if(this.inLength<MIN_LENGTH||this.inLength>MAX_LENGTH){
    return;
     }
     varisOutput=true;
     if(!SHOW_PLAIN_AND_CIPHER&&this.operation=="encrypt") {
     isOutput=false;
     }
     if(isOutput) {
     // Show the buffers here if this a decryption operation
     console.log("Out buffer:");
     dumpBytes(this.outBuffer,Memory.readUInt(this.outCountPtr));
     }
     // 输出调用仓库,会辨认类名函数名,十分好用
     console.log('CCCrypt called from:\n'+
       Thread.backtrace(this.context,Backtracer.ACCURATE)
       .map(DebugSymbol.fromAddress).join('\n')+'\n');
   }
 },
};
​
​
if(ObjC.available) {
 console.log("frida attach");
 for(varfuncinhandlers) {
console.log("hook "+func);
   Interceptor.attach(Module.findExportByName("libcommonCrypto.dylib",func),handlers[func]);
 }
}else{
 console.log("Objective-C Runtime is not available!");
}

查看脚本的输出日志,直接命中了AES128的加密算法,而且输出的Base64数据彻底匹配,只能说命运爆棚。

某运动APP的登录协议分析
拿到对应的key跟iv,尝试解密看下也是没问题的。x-ads剖析结束,都不用反编译看代码:)
某运动APP的登录协议分析

Body的剖析

最后看下sign值的组成部分,body数据是怎么计算的,抱着试试的主意,直接用x-ads剖析得到的算法以及对应的key、iv进行解密:

{ “body”: “5gJEXtLqe3tzRsP8a/bSwXDiK0VslZZZyOEj1jBDBhtYTGGdWltuIjLbzwZ2OxMcb3mFX7bJtgH3WlqGET5W34P4dTEIDhLH6FkT3HSLaDnEXYHvEl9IZRQKf19wMG/t” }

某运动APP的登录协议分析
这次说不上什么命运爆棚了…只能说开发者比较懒或许安全意识有点差了,运用了AES-CBC模式,iv都不改动一下的…

总结

这次剖析全体来看,没什么技术含量,大部分都是脚本直接解决了,从成果来看,也是运用的常规的加密、签名算法,这也从侧面给咱们安全开发提个醒,是不是能够有策略性的改动一下,比如咱们拿MD5来看下都能够做哪些改动。

opensource.apple.com/source/ppp/…

首要针对MD5Init,咱们能够改动它的初始化数据:

voidMD5Init(mdContext)
MD5_CTX*mdContext;
{
mdContext->i[0]=mdContext->i[1]=(UINT4)0;
​
/* Load magic initialization constants.
*/
mdContext->buf[0]=(UINT4)0x67452301;
mdContext->buf[1]=(UINT4)0xefcdab89;
mdContext->buf[2]=(UINT4)0x98badcfe;
mdContext->buf[3]=(UINT4)0x10325476;
}

其次针对Transform咱们也能够改动其间的某几个数据:

staticvoidTransform(buf,in)
UINT4*buf;
UINT4*in;
{
UINT4a=buf[0],b=buf[1],c=buf[2],d=buf[3];
​
/* Round 1 */
#define S11 7
#define S12 12
#define S13 17
#define S14 22
FF(a,b,c,d,in[0],S11,UL(3614090360));/* 1 */
FF(d,a,b,c,in[1],S12,UL(3905402710));/* 2 */
FF(c,d,a,b,in[2],S13,UL(606105819));/* 3 */
FF(b,c,d,a,in[3],S14,UL(3250441966));/* 4 */
FF(a,b,c,d,in[4],S11,UL(4118548399));/* 5 */
FF(d,a,b,c,in[5],S12,UL(1200080426));/* 6 */
FF(c,d,a,b,in[6],S13,UL(2821735955));/* 7 */
FF(b,c,d,a,in[7],S14,UL(4249261313));/* 8 */
FF(a,b,c,d,in[8],S11,UL(1770035416));/* 9 */
FF(d,a,b,c,in[9],S12,UL(2336552879));/* 10 */
FF(c,d,a,b,in[10],S13,UL(4294925233));/* 11 */
FF(b,c,d,a,in[11],S14,UL(2304563134));/* 12 */
FF(a,b,c,d,in[12],S11,UL(1804603682));/* 13 */
FF(d,a,b,c,in[13],S12,UL(4254626195));/* 14 */
FF(c,d,a,b,in[14],S13,UL(2792965006));/* 15 */
FF(b,c,d,a,in[15],S14,UL(1236535329));/* 16 *//* Round 2 */
#define S21 5
#define S22 9
#define S23 14
#define S24 20
GG(a,b,c,d,in[1],S21,UL(4129170786));/* 17 */
GG(d,a,b,c,in[6],S22,UL(3225465664));/* 18 */

...

简略的变形今后,即使脚天性hook到对应的函数,可是想直接脱机调用成果仍是不能够的,此刻就要不得不进行反编译剖析或许动态调试,此刻合作代码混杂、VMP等静态防护手段,再加上反调试等安全手段,对于进犯的门槛也相应的提高。