好久没有更新技术类文章了,不过其实我也有慢慢在写几篇文章,然而它们依旧躺在草稿箱。刚好群里讨论写个Pica客户端,于是我就来分析下Pica的接口吧。
拆开来一看,竟然没有混淆……build.gradle改改也没多大成本吧,虽然给我省事就是了。
抓包得知,Pica的接口使用signature头以校验。所以首先要找出signature的计算方法。由于,Pica采用了okhttp3,所以signature的计算逻辑八成会写在interceptor里。搜索addInterceptor,果然只有一处,出现在com.picacomic.fregata.networks.RestClient的构造器里。
// 局部变量名称与部分逻辑有所调整 Request request = chain.request(); String nonce = UUID.randomUUID().toString().replace("-", ""); String path = request.url().toString().replace("https://picaapi.picacomic.com/", ""); String timeStamp = System.currentTimeMillis() / 1000L + PreferenceHelper.getTimeDifference(var1) + ""; String signature = MyApplication.getInstance().getStringCon(new String[]{"https://picaapi.picacomic.com/", path, timeStamp, nonce, request.method(), "C69BAF41DA5ABD1FFEDC6D2FEA56B", RestClient.version, RestClient.buildVersion});
于是顺藤摸瓜,看com.picacomic.fregata.MyApplication的getStringCon方法。
public String getStringCon(String[] strs) { if (this.generateSignature == null) { this.generateSignature = new GenerateSignature(); } String rawParams = ""; for (String str : strs) { rawParams += str + ", "; } PrintLog.PrintErrorLog(TAG, "RAW parameters = " + rawParams); String concatParam = this.getStringConFromNative(strs); PrintLog.PrintErrorLog(TAG, "CONCAT parameters = " + concatParam); String concatKey = this.getStringSigFromNative(); PrintLog.PrintErrorLog(TAG, "CONCAT KEY = " + concatKey); return this.generateSignature.getSignature(concatParam, concatKey); }
getStringConFromNative和getStringSigFromNative两个方法都是native的,那暂且放一遍,先把Java层的getSignature看完。不过逻辑其实也不难猜到,就是转小写后HmacSHA256。所以重点还应该是在native层。不过有点挺有趣的,getStringSigFromNative很好理解,但getStringConFromNative怎么看都像是拼接字符串。在这上面做文章还是没怎么见过,有意思。
lib名是libJniTest,很优秀。丢进IDA,很顺利。找到函数,很顺利,毕竟静态注册。F5,依旧很顺利。嘛,看到lib名也应该能想到的。首先分析Java_com_picacomic_fregata_MyApplication_getStringConFromNative,依旧是当初分析B站App的套路,改类型、重命名,然后分析逻辑。其实也什么好分析,首先从数组中取出对应下标的字符串然后GetStringUTFChars,然后就是主要的拼接逻辑。
拼接很直接,就是这个用于判断的repack_chk_and_genKey10(原名genKey10)需要分析一哈。通过分析,发现这个函数是用来校验apk签名的。
然而校验失败返回false后,getStringConFromNative依旧可以拼接,而且会变更拼接的方式。至于为什么这么做,笔者暂且被蒙在吉他里。
接下来分析Java_com_picacomic_fregata_MyApplication_getStringSigFromNative。
……
心凉半截。看看汇编。
没办法,按照esp的偏移慢慢算咯。当然傻傻的一个个字符改也确实没效率,所以简单处理一下数据,写个py jio本。
char_dic = {0x9: 110, 0x2D: 107, 0x37: 97, 0x3B: 107, 0x40: 114, 0x46: 114, 0xC: 83, 0x14: 85, 0x19: 76, 0x1B: 82, 0x27: 67, 0x28: 69, 0x2C: 75, 0x33: 90, 0x45: 67, 0xD: 57, 0x16: 56, 0x1F: 57, 0x21: 52, 0x23: 51, 0x2F: 55, 0x38: 53, 0x42: 49} int_dic = {0x17: "zf", 0x29: "sl", 0x39: "zk", 0x1D: "PM", 0x31: "BY", 0x35: "BA", 0x0F: "lG", 0x11: "ts", 0x3C: "RB", 0x3E: "L7"} if __name__ == "__main__": offset = 0x8 org_str = "~*}$#,$-/").=$)/",,#/-.'%(;$[,|@/&(#/"~%*!-?*/"-:*!!*,$/"%.&'*|%/*,*" ls = list(org_str) for pos, ch in char_dic.items(): ls[pos - offset] = chr(ch) for pos, ch in int_dic.items(): ls[pos - offset] = ch[0] ls[pos - offset + 1] = ch[1] print("".join(ls))
这放飞自我的编码风格……嘛,总之算是跑出了结果。另外,校验失败同样会返回另一个key。
简单汇总一下signature的计算方法:
其中nonce生成的java实现为UUID.randomUUID().toString().replace(“-“, “”);,常见的随机串生成方式。实现时生成任意32长随机字符串即可。
总体感觉就是有安全意识但是做的很不够。不过讲字符串拼接的逻辑放在native层很有趣,而且native层的处理较B站早期的实现也更完善。改天测试下另一种signature有啥不同。