分析环境:
这里我选择用fiddler,nexus5,Android6.0,由于手机架构和apk版本支持问题,本次分析了一个之前的版本,不是最新的(本次分析只是用于学习,没有其他目的,如果分析过程中有什么问题,希望大佬指出)
第一步还是先抓包
发送验证码的时候的数据包:
![图片描述](upload/attach/202209/939330_2822ZSCW3QF86XE.png)
1 2 3 4 5 6 7 8 9 10 | POST http: / / login.user.kugou.com / v3 / send_mobile_code HTTP / 1.1
User - Agent: Android601 - AndroidPhone - 8493 - 5 - 0 - User - wifi
Accept - Encoding: gzip, deflate
KG - USER - AGENT: AndroidPhone - 8493 - kugoumusic - 107
Content - Length: 224
Content - Type : text / plain; charset = ISO - 8859 - 1
Host: login.user.kugou.com
Connection: Keep - Alive
{ "mid" : "227653997421887160845682422178209025909" , "clientver" : 8493 , "key" : "2b49db7aef22cc6000174bed3c6da5f1" , "clienttime" : 1664197312 , "mobile" : "13905377927" , "uuid" : "dca10490278242e4a7c49adb40f78957" , "appid" : 1005 , "businessid" : 5 }
|
输入完验证码,点击登录后的数据包:
![图片描述](upload/attach/202209/939330_K3QN9B4CPNJP2CB.png)
1 2 3 4 5 6 7 8 9 10 | POST http: / / login.user.kugou.com / v1 / login_by_verifycode HTTP / 1.1
User - Agent: Android601 - AndroidPhone - 8493 - 5 - 0 - User - wifi
Accept - Encoding: gzip, deflate
KG - USER - AGENT: AndroidPhone - 8493 - kugoumusic - 107
Content - Length: 472
Content - Type : text / plain; charset = utf - 8
Host: login.user.kugou.com
Connection: Keep - Alive
{ "mid" : "227653997421887160845682422178209025909" , "clientver" : 8493 , "p" : "6D644A1E1975D792B5F45E89D1539F0D2EBE131FB87D9955A04238DB5C8E39FB71724252E1CA83E4F6459B8CAA96F2216F2AA05DB3A3F9BAE6185E900114FD02567B368605DEB87AADDA10D70F12E006C6FF1C4C2FBBE5217323A1DCBFA3BC8A2A335954ED743C0AF1FFECA7AA98E65132008FCED0E681A0BDF8FBE94299FD1F" , "key" : "86925039a0ad5961e42e42c71db08f8e" , "clienttime" : 1664197335 , "mobile" : "13905377927" , "uuid" : "dca10490278242e4a7c49adb40f78957" , "appid" : 1005 }
|
通过这两个数据包我们可以发现,有些数据是写死的:
例如appid和mobile,uuid这样的字段就不用主要看来
这里我们大致看一下,比较重要的字段就是mid,p,key
然后大致翻译一下各个字段的含义,方便后续的分析:
mid,p,key这三个应该就是校验字段
clienttime这个应该是时间戳字段
appid和mobile,uuid这样的字段就是和手机型号相关的字段了
然后我这里采用的是搜索出来那种比较少的,然后看起来比较像的字段我这里找到的是这两个部位,然后下断点:(基本上看见什么put到hashmap里面的就是要下断点了)
![图片描述](upload/attach/202209/939330_QKXWESYNESDER8P.png)
![图片描述](upload/attach/202209/939330_GTM65YHG3WWVVCJ.png)
然后一个个的都下断点,看他段在哪里,最终定位到这个方法中:
![图片描述](upload/attach/202209/939330_XJZGX4DP7EZWE2B.png)
![图片描述](upload/attach/202209/939330_XYPVUMRYGK6KGM8.png)
如果不确定的话可以一步步的跟着看看动态分析出来的数据和fiddler抓到的数据包的内容是不是一样的:
这是mid的值:
![图片描述](upload/attach/202209/939330_E3UMZP4T448FTJM.png)
和之前抓包抓到的一样,所以这里就是关键代码了:
然后就要分析各个字段是怎么加密的了:
(这里我吐槽一句,这个jadx有很多函数都反编译不出来,也不知道为啥)
这两个东西就是从本地获得的:
appid和客户端版本
![图片描述](upload/attach/202209/939330_85P9Q73T6C77FMW.png)
然后分析那种比较好分析的字段,因为说那种什么sign字段啦,p字段了,就是这样的最后的校验字段,一般都是需要之前的一些字段作为参数,然后在进行加密的,
咱们这里先看一下时间戳字段:
![图片描述](upload/attach/202209/939330_MEKRE3RXEQR6HK7.png)
就是系统当前时间/1000:
![图片描述](upload/attach/202209/939330_V64FNAVTA9KWZ3Q.png)
然后后面就是mid字段了,这个加密还是有点意思的:
(这里插一句,这个app不是一次分析完的,所以分析过程中的数据有的都是新输入的)
![图片描述](upload/attach/202209/939330_BASYVM66SKZ3XPJ.png)
你看这个mid字段,他的加密过程就出现在了上面,所以想动态分析的话,又得重新搞一遍,这里先简单的静态的看一下吧:就是从这里开始我开始用上了gda分析:
咱们先看一下gadx分析出来的KGCommonApplication.d()这个函数:
最终定位到这里:(他说反编译不出来)
![图片描述](upload/attach/202209/939330_B9YBC63NQ76TD6S.png)
然后就要上gda了:
![图片描述](upload/attach/202209/939330_V7BTJN5UJJ2KTCU.png)
接下来就要看那个d函数了:
![图片描述](upload/attach/202209/939330_QDUYKVQ4CRNY22A.png)
继续跟进去:
![图片描述](upload/attach/202209/939330_CBQPNYZTFZQS8G6.png)
返回一个传递上下文的字段,这个参数在接下来的函数的分析中会调用
然后继续看外层的br.j()函数:
![图片描述](upload/attach/202209/939330_UKZ6RHUY6QS2QUZ.png)
参数p0是刚刚传入的那个字段,在接下来的这个k函数中会用到这个参数
然后继续看br.k()函数:
![图片描述](upload/attach/202209/939330_RWCZYGRCVD2WEX9.png)
就是用这个contest字段来调用deviceid()函数
这个函数就是如果devicesid不为空,就返回deviceid
然后分析外层的那个bq.j()函数:
![图片描述](upload/attach/202209/939330_9FZEXZ5F39U4KGE.png)
这里先解释一下这个对象:BigInteger
这个对象就是在java中能方便调用add()函数和pow()函数
然后那个az.a()函数就是MD5加密函数:
![图片描述](upload/attach/202209/939330_XUNF9A75S4G8CU2.png)
这是我修复之后的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public static String j(String device_id){
String result;
try {
BigInteger num1 = new BigInteger( "0" );
BigInteger num2 = new BigInteger( "16" );
/ / 定义两个大整数对象
String str = new az().md5(device_id);
/ / 将deviceid字段进行md5加密
int len = md5_device_id.length();
/ / 得到加密之后的字符串的长度
for ( int i1 = 0 ; i1 < len ; i1 + + ) {
num1 = num1.add(new BigInteger(new StringBuilder() + "" + md5_device_id.charAt(i1), 16 ).multiply(num2. pow ((( len - 1 ) - i1))));
}
/ / 进行字符串的拼接,在 for 循环中num2的 pow ()乘方函数,然后 - i,在和前面的字段相乘
result = num1.toString();
/ / 转换成字符串类型的字符串
}catch(java.lang.Exception e0){
result = "0" ;
}
return result;
}
|
然后这个mid字段就分析完了(jda真好用yyds)
接下来就要看这个key字段了(这个看着就挺重要的,这一堆参数):
![图片描述](upload/attach/202209/939330_9KXUTKEUQHV4UT9.png)
一个个的看!
先看这个参数i,就一个参数哈哈,先从简单的开始分析:
![图片描述](upload/attach/202209/939330_SN6X7E5HRUXUQ9E.png)
他是在这里定义的
然后看看这个函数KGCommonApplication.d():
![图片描述](upload/attach/202209/939330_Q7UJ9VNJ4RXDQ5D.png)
![图片描述](upload/attach/202209/939330_62RJV6FCRTEZ77N.png)
还是返回那个contest联系上下文的字段,估计和刚刚分析出来的一样
然后看他的外层函数c.a()函数:
![图片描述](upload/attach/202209/939330_Y9G6YB8TCDS69KC.png)
正如所料,刚刚分析出来的那个字段就是p0字段,p0字段就是调用上下文的contest字段,用来调用getPackageManager()函数的,所以这个c.a()函数就是返回apk的包名
然后看看i1参数:
![图片描述](upload/attach/202209/939330_HFS22GP2AKGXRXR.png)
根据上面的定义就是返回时间戳
接下来分析这个c.a(l, c.a().b(a.jD)参数
先看看l参数:
![图片描述](upload/attach/202209/939330_SMB9MTWUBA6H7N2.png)
这个是a.jC:
![图片描述](upload/attach/202209/939330_QV6AGUXJ8PQUB79.png)
就是一个配置文件的字段
这个是b()函数:
![图片描述](upload/attach/202209/939330_HMC3DPPUAP72JEA.png)
所以b(a.jC)就是返回刚刚那个字段
还是一层层的看,在看内层函数,先看a,jD
![图片描述](upload/attach/202209/939330_Y284W23V3NMZ5B4.png)
还是配置文件的字段
然后看一下b()函数:
![图片描述](upload/attach/202209/939330_A9QMR2N3D7V6VDS.png)
所以b(a.jD)就是返回刚刚那个参数
然后在看一下最外层的c.a()函数:
![图片描述](upload/attach/202209/939330_47BPDHVFAFCK8S8.png)
String.valueOf(long l)这个函数的作用就是将long变量l转换成字符串
接下来看看每一个参数是什么:
这个是我修复之后a()函数的代码:
![图片描述](upload/attach/202209/939330_WXJDAPFCWJRU8AR.png)
第二个参数不好改,也是配置字段的参数
所以这个函数就是将各个字段拼接起来转成小写,然后进行md5()加密
到此为止key字段就分析完了
最后就剩下一个p字段了(其实这个才是最关键的字段,相当于sign字段):
![图片描述](upload/attach/202209/939330_W4YAJE3KYNQZN5Y.png)
c.a()b(a.jE))这个函数和之前的一样,还是返回配置文件的字段
e.a(jSONObject.toString()这个函数的参数用到了之前的jsonobject中的参数,所以向上回溯看看:
![图片描述](upload/attach/202209/939330_UK34ZFZ5GCT28KG.png)
第一个是aes是这个字段:
![图片描述](upload/attach/202209/939330_3JYRDGG4P6XDPN6.png)
第二个uid字段的函数跟进去分析:
![图片描述](upload/attach/202209/939330_TY5G69RHQ26VQTV.png)
所以就要分析这个函数了:
![图片描述](upload/attach/202209/939330_Q9W2XMCGM98K638.png)
(这里动态调试一下看看吧):
定位到关键代码位置,下断点:
![图片描述](upload/attach/202209/939330_RRZB6TUT8ZYTF8P.png)
然后发现直接从if分支里面跳出来了:
![图片描述](upload/attach/202209/939330_ZT33ZM6DRTD299K.png)
所以i的取值就要符合if分支:
这里我又动态挂起了一次,发现了b.a()函数的返回值是这样的:
![图片描述](upload/attach/202209/939330_72CR8V7QS5HWUNW.png)
这个是b.b()方法的返回值,接下来就要看看是if中的哪个分支成立了,看看下面这个if分支怎么跳才是关键:
![图片描述](upload/attach/202209/939330_QAPCVRQ6JF9AQVB.png)
直接就跳向了结束
然后我取消了全部的断点,运行程序:
![图片描述](upload/attach/202209/939330_2KD37HASSF5NEBZ.png)
登录成功了,说明之前的分析是没有问题的
经过上面的动态调试和if语句的逻辑判断可以推理出他这个(i = b.a().b(8, 0))判断的返回条件一定为真,然后这样整个的if判断语句的返回值为假:
![图片描述](upload/attach/202209/939330_AE5EP5QB52M5JBA.png)
所以跳向了最后的return i;
那么接下来的操作就是分析这个函数了 b.a().b(8, 0):
先看这个b(8, 0):
这是修复之后的代码:
![图片描述](upload/attach/202209/939330_MUXPB59N37KAY26.png)
这个是b.a()函数:
![图片描述](upload/attach/202209/939330_GT2F37TTZTJU54E.png)
返回b.a
这样uid字段就分析完了,接下来分析token字段:
![图片描述](upload/attach/202209/939330_VYZD5SAZ6VHTHXG.png)
看看a.e()方法:
![图片描述](upload/attach/202209/939330_5TSC4CRYD57QQTZ.png)
直接动态看看怎么走吧,其实我觉着还是直接return应该是(因为他不可能为空啊):
![图片描述](upload/attach/202209/939330_T4VSM5ZJKH6YPDD.png)
和我想象中的一样,直接return
![图片描述](upload/attach/202209/939330_K5GJZEQHNBNDCJQ.png)
取消断点之后,程序可以正常运行:
![图片描述](upload/attach/202209/939330_UFD4VWVAMYYU9MB.png)
所以 String str = b.a().b(13, "")这个就是要分析的了:
跟进分析:
![图片描述](upload/attach/202209/939330_XVERREBJXSGABSX.png)
就是返回p1
其实我们也可以看看都是什么原因跳向的错误分支:
![图片描述](upload/attach/202209/939330_MS96YJCD4HJ2PVZ.png)
根据意思就是让你重新输入用户名了
到此为止jSONObject中的三个对象已经分析完了
然后就要分析最外层的e.a()函数了:
![图片描述](upload/attach/202209/939330_DWEN954VDJHE5U6.png)
先看这个函数 uoe.a(p1):
![图片描述](upload/attach/202209/939330_TR5HJTWKUTZE9XF.png)
进行rsa加密最后在进行去除空格的操作
![图片描述](upload/attach/202209/939330_PYWDZZUE7W2RPSE.png)
好了,发往服务器的所有数据包都分析完啦:
![图片描述](upload/attach/202209/939330_AM2GDZW8QJEN9X7.png)
![图片描述](upload/attach/202209/939330_ZW8GSW33H2GFY27.png)
写写最近学习的总结吧:
最近一直在学习加固脱壳,一直没能实战,总是感觉自己看懂文章,自己分析就分析不出来,经过今天的协议分析我发现了一个道理,不能光看,得真正的练,然后不要上来就学习一些高级分析工具,例如frida,unicorn,unidbg之类的工具,还是要打好基础,学习逆向分析的思路,动态分析去,一步步的跟才是关键,这样脚踏实地的搞才能弄清程序到底怎么执行的,才能体验到逆向的乐趣
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法
最后于 2022-10-2 14:43
被以和爲貴编辑
,原因: