分析环境:
这里我选择用fiddler,nexus5,Android6.0,由于手机架构和apk版本支持问题,本次分析了一个之前的版本,不是最新的(本次分析只是用于学习,没有其他目的,如果分析过程中有什么问题,希望大佬指出)
第一步还是先抓包
发送验证码的时候的数据包:
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 }
|
输入完验证码,点击登录后的数据包:
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里面的就是要下断点了)
然后一个个的都下断点,看他段在哪里,最终定位到这个方法中:
如果不确定的话可以一步步的跟着看看动态分析出来的数据和fiddler抓到的数据包的内容是不是一样的:
这是mid的值:
和之前抓包抓到的一样,所以这里就是关键代码了:
然后就要分析各个字段是怎么加密的了:
(这里我吐槽一句,这个jadx有很多函数都反编译不出来,也不知道为啥)
这两个东西就是从本地获得的:
appid和客户端版本
然后分析那种比较好分析的字段,因为说那种什么sign字段啦,p字段了,就是这样的最后的校验字段,一般都是需要之前的一些字段作为参数,然后在进行加密的,
咱们这里先看一下时间戳字段:
就是系统当前时间/1000:
然后后面就是mid字段了,这个加密还是有点意思的:
(这里插一句,这个app不是一次分析完的,所以分析过程中的数据有的都是新输入的)
你看这个mid字段,他的加密过程就出现在了上面,所以想动态分析的话,又得重新搞一遍,这里先简单的静态的看一下吧:就是从这里开始我开始用上了gda分析:
咱们先看一下gadx分析出来的KGCommonApplication.d()这个函数:
最终定位到这里:(他说反编译不出来)
然后就要上gda了:
接下来就要看那个d函数了:
继续跟进去:
返回一个传递上下文的字段,这个参数在接下来的函数的分析中会调用
然后继续看外层的br.j()函数:
参数p0是刚刚传入的那个字段,在接下来的这个k函数中会用到这个参数
然后继续看br.k()函数:
就是用这个contest字段来调用deviceid()函数
这个函数就是如果devicesid不为空,就返回deviceid
然后分析外层的那个bq.j()函数:
这里先解释一下这个对象:BigInteger
这个对象就是在java中能方便调用add()函数和pow()函数
然后那个az.a()函数就是MD5加密函数:
这是我修复之后的代码:
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字段了(这个看着就挺重要的,这一堆参数):
一个个的看!
先看这个参数i,就一个参数哈哈,先从简单的开始分析:
他是在这里定义的
然后看看这个函数KGCommonApplication.d():
还是返回那个contest联系上下文的字段,估计和刚刚分析出来的一样
然后看他的外层函数c.a()函数:
正如所料,刚刚分析出来的那个字段就是p0字段,p0字段就是调用上下文的contest字段,用来调用getPackageManager()函数的,所以这个c.a()函数就是返回apk的包名
然后看看i1参数:
根据上面的定义就是返回时间戳
接下来分析这个c.a(l, c.a().b(a.jD)参数
先看看l参数:
这个是a.jC:
就是一个配置文件的字段
这个是b()函数:
所以b(a.jC)就是返回刚刚那个字段
还是一层层的看,在看内层函数,先看a,jD
还是配置文件的字段
然后看一下b()函数:
所以b(a.jD)就是返回刚刚那个参数
然后在看一下最外层的c.a()函数:
String.valueOf(long l)这个函数的作用就是将long变量l转换成字符串
接下来看看每一个参数是什么:
这个是我修复之后a()函数的代码:
第二个参数不好改,也是配置字段的参数
所以这个函数就是将各个字段拼接起来转成小写,然后进行md5()加密
到此为止key字段就分析完了
最后就剩下一个p字段了(其实这个才是最关键的字段,相当于sign字段):
c.a()b(a.jE))这个函数和之前的一样,还是返回配置文件的字段
e.a(jSONObject.toString()这个函数的参数用到了之前的jsonobject中的参数,所以向上回溯看看:
第一个是aes是这个字段:
第二个uid字段的函数跟进去分析:
所以就要分析这个函数了:
(这里动态调试一下看看吧):
定位到关键代码位置,下断点:
然后发现直接从if分支里面跳出来了:
所以i的取值就要符合if分支:
这里我又动态挂起了一次,发现了b.a()函数的返回值是这样的:
这个是b.b()方法的返回值,接下来就要看看是if中的哪个分支成立了,看看下面这个if分支怎么跳才是关键:
直接就跳向了结束
然后我取消了全部的断点,运行程序:
登录成功了,说明之前的分析是没有问题的
经过上面的动态调试和if语句的逻辑判断可以推理出他这个(i = b.a().b(8, 0))判断的返回条件一定为真,然后这样整个的if判断语句的返回值为假:
所以跳向了最后的return i;
那么接下来的操作就是分析这个函数了 b.a().b(8, 0):
先看这个b(8, 0):
这是修复之后的代码:
这个是b.a()函数:
返回b.a
这样uid字段就分析完了,接下来分析token字段:
看看a.e()方法:
直接动态看看怎么走吧,其实我觉着还是直接return应该是(因为他不可能为空啊):
和我想象中的一样,直接return
取消断点之后,程序可以正常运行:
所以 String str = b.a().b(13, "")这个就是要分析的了:
跟进分析:
就是返回p1
其实我们也可以看看都是什么原因跳向的错误分支:
根据意思就是让你重新输入用户名了
到此为止jSONObject中的三个对象已经分析完了
然后就要分析最外层的e.a()函数了:
先看这个函数 uoe.a(p1):
进行rsa加密最后在进行去除空格的操作
好了,发往服务器的所有数据包都分析完啦:
写写最近学习的总结吧:
最近一直在学习加固脱壳,一直没能实战,总是感觉自己看懂文章,自己分析就分析不出来,经过今天的协议分析我发现了一个道理,不能光看,得真正的练,然后不要上来就学习一些高级分析工具,例如frida,unicorn,unidbg之类的工具,还是要打好基础,学习逆向分析的思路,动态分析去,一步步的跟才是关键,这样脚踏实地的搞才能弄清程序到底怎么执行的,才能体验到逆向的乐趣
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2022-10-2 14:43
被以和爲貴编辑
,原因: