加固
经过查壳,没有发现加固
抓包分析
打开 app ,进入登录界面
随便输入账号密码,点击登录,提示版本过低,虽然提示这个但是并不妨碍我们抓包
打开小黄鸟,重新登录一下,可以看到下面的结果包
请求提交的参数如下
除了 username 之外这里还对 password 加密了
目前未知的参数有 udid、_sign、pwd 这三个参数
加密参数定位与分析
使用 jadx 直接打开 apk
udid
直接搜索关键字 udid
搜索关键词类似的结果很多
可以全部都打开看看
基本都类似下面的代码
而且可以看到,所有生成 udid 的方法都使用了 encode3Des 加密了这么一大串的值getIMEI(context) + "|" + System.nanoTime() + "|" + SPUtils.getDeviceId()
找到离加密最近的地方才有机会看到真相
所以直接 hook encode3Des这个加密的方法,先拿到加密的 str 和结果看看
function main(){ Java.perform(function () { var EncryptUtil = Java.use("com.xx.xx.xx.SecurityUtil"); EncryptUtil.encode3Des.implementation = function (arg1, arg2) { console.log("参数 str2 ==> " + arg2); var result = this.encode3Des(arg1,arg2); console.log("result ==> " + result); return result; } }) } setImmediate(main)
然后使用命令运行 hook 脚本
frida -U -F xx.xx.xx-l hook.js
hook 结果如下
多次尝试下,
参数 2 的第一部分是getIMEI
,中间部分是动态时间戳,第三部分是getDeviceId
只有中间部分是动态的
接下来可以看看 encode3Des 的加密部分了
这里可以看到加密使用的是 CBC 加密模式,然后这里的 iv 就在 class 的上方,也是一个固定值
看过之前文章的朋友应该知道,现在就缺少 key,我们就可以直接复现 udid 的加密了
这里的 key 由String desKey = AHAPIHelper.getDesKey(context);
生成
跟进去可以看到加密由 native方法生成
直接 hook 一下看看
function main(){ Java.perform(function () { var EncryptUtil = Java.use("com.xxx.xxx.xxx.xxx"); EncryptUtil.encode3Des.implementation = function (arg1, arg2) { console.log("参数 arg2 ==> " + arg2); var result = this.encode3Des(arg1,arg2); console.log("result ==> " + result); return result; } var deskey = Java.use('com.xxx.xxx'); deskey.getDesKey.implementation=function (arg1){ var result = this.getDesKey(arg1); console.log("deskey result ==> " + result); return result; } var jni_check = Java.use("com.xxx.xxx.xxx.xxx"); jni_check.checkSignimplementation = function (arg1) { var result = this.encode3Des(arg1); console.log("jni_check result ==> " + result); return result; } }) } setImmediate(main)
然后再运行一下,打印结果如下
注意这里 jni_check 想要 hook 上记得使用 spawn 模式,上面的两个方法可以使用 attach 模式直接hook
以上就获取到了udid加密的 key iv mod 和加密的内容和结果,可以直接使用 Python 复现一下
import pyDes import base64 def decrypt_response(data): secretkey = "这里key"[:-8] iv = "appapich" des_obj = pyDes.triple_des(key=secretkey, IV=iv, padmode=pyDes.PAD_PKCS5, mode=pyDes.CBC) print(base64.b64encode(des_obj.encrypt(data)).decode()) if __name__ == '__main__': decrypt_response("351615080737802|16936902758945|332220")
输出结果如下
和 hook 的结果一样,这样就完成了 udid 的生成
pwd
pwd 的结果是一串 32 位的字符串
同样的搜索一下,可以搜索 pwd 得到的结果有很多
但是搜索"pwd"
结果如下,而且所在位置也是请求登录的逻辑中,所以大胆猜测就是这里了
可以看到这里使用 encodeMD5
我输入的结果是12345678,通过抓包可以知道,pwd加密好的结果是25d55ad283aa400af464c76d713c07ad
直接扔到加密站里测试一下,看看结果是不是标准的 MD5 ,是的话连分析都免了
和标准的 MD5 加密结果相同
_sign
同样的搜索一下关键字
找到下面这段逻辑,是生成请求参数的逻辑
这里的 toSign 方法继续追进去
用到了上面 pwd 相同的 encodeMD5 逻辑
不过这里就需要 hook 一下 encodeMD5看一下入参
function main(){ Java.perform(function () { var EncryptUtil = Java.use("com.xxx.xxx.xxx.xxx"); EncryptUtil.encode3Des.implementation = function (arg1, arg2) { console.log("参数 arg2 ==> " + arg2); var result = this.encode3Des(arg1,arg2); console.log("result ==> " + result); return result; } EncryptUtil.encodeMD5.implementation = function (arg1) { console.log("encodeMD5 参数 arg1 ==> " + arg1); var result = this.encodeMD5(arg1); console.log("result ==> " + result); return result; } var deskey = Java.use('com.xxx.xxx.xxx'); deskey.getDesKey.implementation=function (arg1){ var result = this.getDesKey(arg1); console.log("deskey result ==> " + result); return result; } }) } setImmediate(main)
hook 结果如下
对比一下抓包,大致就清楚了,这里的hash 内容就是请求提交的参数,做这个 sign 就是防止改包的
到这里我们就完成了这个 app 请求包中所有加密参数的分析
我们试着用 Python 复现一下请求流程
Python 实现请求全流程
结果和抓包是一样的
这次研究的样本是网上找的旧版样本,之后有机会试试新版的
End.