好久没有更新技术类文章了,不过其实我也有慢慢在写几篇文章,然而它们依旧躺在草稿箱。刚好群里讨论写个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+请求方法+"C69BAF41DA5ABD1FFEDC6D2FEA56B"
- 转小写
- 计算其
HmacSHA256
,密钥为"~n}$S9$lGts=U)8zfL/R.PM9;4[3|@/CEsl~Kk!7?BYZ:BAa5zkkRBL7r|1/*Cr"
其中nonce生成的java实现为UUID.randomUUID().toString().replace("-", "");
,常见的随机串生成方式。实现时生成任意32长随机字符串即可。
总体感觉就是有安全意识但是做的很不够。不过讲字符串拼接的逻辑放在native层很有趣,而且native层的处理较B站早期的实现也更完善。改天测试下另一种signature有啥不同。
哦,强制更新啊。
新密钥:
"~d}$Q7$eIni=V)9\\RK/P.RM4;9[7|@/CA}b~OW!3?EV`:<>M7pddUBL5n|0/*Cn"
Signature后面加的C69BAF…这一串也在请求头里面啊。(顺便你站SEO不行啊,技术文上写的已经被Google索引了)
是指C69BAF41DA5ABD1FFEDC6D2FEA56B嘛?是的,这是Appkey所以要加入请求头的。
另外本站SEO其实约等于没做啦,尤其是Google,基本处于弃疗状态w
秘钥9后面多了一个\
并没有,那个是转义字符。
~d}$Q7$eIni=V)9\\RK/P.RM4;9[7|@/CA}b~OW!3?EV`:<>M7pddUBL5n|0/*Cn
密钥是这个吗?
是的,注意\\是转义字符。
getStringConFromNative函数主要是做了什么,我没看懂。方便为明文加密做个示例吗?
URL: https://picaapi.picacomic.com/init?platform=android h2
time: 1568095535
nonce: f52361e4e0ec456dbc35c1bd0827b780
Method: GET
app-version: 2.2.1.0.1.2
app-build-version: 43
如果请求参数若是如上,用于HmacSHA256加密的明文是?请先生教我。
这个文章中已经说明了“拼接请求路径(不包含/)+当前时间戳+nonce+请求方法+‘C69BAF41DA5ABD1FFEDC6D2FEA56B’”。
拼接结果是:init?platform=android1568095535f52361e4e0ec456dbc35c1bd0827b780GETC69BAF41DA5ABD1FFEDC6D2FEA56B
能否讲解下计算密钥时是如何从汇编写成python脚本里的数组的?为什么26234对应zf?或者有对应的资料能推荐下吗?
其实这里都是没必要的工作……只是IDA的F5抽风了而已,可以通过改首地址变量的类型为数组解决。
26234是__int16 2字节,当时用的高低两字节转字符转写的,其实根本是没必要,更改类型IDA都可以帮你做。
相关资料可以看比如《IDA Pro 权威指南》。
请问下最新的密钥还是这个么 请问是怎么破解的呢 我这里 使用旧版本api https://picaapi.picacomic.com/ 都会提示我 更新 然而新版本的 https://uatapi.wakamoment.ga/ 我用的https://uatapi.wakamoment.ga/keywords 这个网址 出来的结果一直都是 {
"code": 200,
"message": "success"
}
最近没空拆……有空看看吧
另外更新的话,需要在请求头传入正确的版本号。
我是抓包的ios的 发现请求的网站不一样希望大佬有时间研究下
有机会看下,不过好像原接口是可以用的
好的请问可以告诉我怎么反编译出来的加密密钥么 我按照你的算法写的 把网址提交的换成了ios的地址却发现一只返回200success 并无其他数据 初步判断ios算法和android不一样
大佬 我研究了下发现 ios的 随机字符串是大写 我尝试了 算法修改为大写但是无用 所以估计密钥也是不对的 如果需要 安装包 我可以提供给您
ios逆向我也不会啦……
不能使用安卓接口么?
他好像有强制更新了
其实我想学习一下如何逆向 安卓的跟着你的步骤来学我看到心凉半截哪里我就看不懂了吗在我眼里就是字节和word 我想知道如何算出来的密码希望大佬能教给我下我下个版本可以自己弄嘿嘿
今天 安卓的更新了 密钥不变其他参数变了 app-channel 由 1 变为 2 app-version 由 2.2.1.0.1.2 变为 2.2.1.2.3.3 app-build-version 由 43 变为 44
感谢提醒。最近相当忙,实在是没有办法看……
刚才解了一下2.2.1.2.3.4版本的,密钥没有更新,就改了app-version和app-build-version
感谢提醒~
app-channel和你登录时候选的那个有关,不是签名时候必须的东西。签名时候传入的参数依次是:’https://picaapi.picacomic.com/’,域名后面的地址(没有开始的斜杠),时间戳,随机数nonce,
,请求方法(GET/POST),api-key(C69BAF41DA5ABD1FFEDC6D2FEA56B),app-version,app-build-version
那看起来接口校验完全没有变化
我用python登录的时候post出去,结果返回{‘code’: 500, ‘error’: "Cannot read property ‘toLowerCase’ of undefined", ‘message’: ‘–‘, ‘detail’: ‘:(‘}
而且我手机登录抓包,验证写的是对的,但就是不知道为什么返回这个东西,就算signature是空的也会返回success,只是没有token,大佬能告诉一下原因吗?
从你的返回结果推测,应该是你传入数据缺失了。具体的还是要看你的payload,可以脱敏后和我邮件交流下,目前来看只能猜是数据缺了。
请问你用的是什么工具,我用的AndroidKiller v1.3.1,只有汇编代码,没有java代码。方便的话,能给我一个你的工具链接吗?另外又更新了,"app-version": "2.2.1.3.3.4","app-build-version": "45"。
可以先转jar(dex2jar)再使用jar的逆向工具(Fern Flower, CFR, etc),也可以直接使用Jadx。
此外,由于我最近没什么精力看,似乎已经落下很多个版本了。而且在签名方式大改前应该也不会更新这篇博客了,感谢提供参数。