安卓逆向学习篇---frida_server应用
frida的使用
环境搭建
配环境好麻烦好麻烦的!!!哎
为了方便学习,以下所有的东西都打包到了阿里云盘:
frida_server.txt https://www.aliyundrive.com/s/RwsGa937eh9
去010修改文件头为PK(50 4B 03 04)然后修改后缀为zip解压
注意版本!!一定要注意版本!
本人环境:
Python 3.8
xiaomi9 root android 12
Frida版本: 14.2.18
tips:另外好像有python的要求,我个人是建议使用python3.8(最好是3.8.6之前的版本)
虽然差别不大,但是本人亲身经历,3.8.6之后的python打包的exe程序就用uncompyle6反编译不出来了!
下面都是按照python8展开的环境配置
frida和frida-tools
pip install frida
pip install frida-tools
python3.8配置
直接安装python3.8.3傻瓜式安装就可以了,注意自动添加PATH环境变量就好
webstorm安装
用来写js代码,一个简单的js编辑器(其实没有也行,主要是可以省很多事
另外安装一下nodejs,这是运行js必不可少的
然后用webstorm新建一个项目,在项目的文件夹下载一下frida的文件
命令:npm i @types/frida-gum
配置好这些之后,就要下载好frida-server-14.2.18-android-arm64这个文件
把frida-server-14.2.18-android-arm64给push进去
然后运行就行,具体不多说啦!
frida在js代码上的使用
首先开启windows端的server
代码分析
然后就是用jadx看一下嘟嘟牛的反编译代码部分,通过搜索encrypt,可以看到有两个加密的类,
第一个类:addRequestMap
1 | public void addRequestMap(Map<String, String> addMap, int a) { |
第二个类:paraMap
1 | private void paraMap(Map<String, String> addMap) { |
这里就直接拷贝gpt的回答:很明显的des加密,另一个也差不多
- 当
useDes
为true
时,使用 DES 加密算法:- 首先,将当前时间戳转换为字符串,并将其放入
addMap
中的"timeStamp"
键。 - 如果存在登录用户(
DodonewOnlineApplication.loginUser
不为null
),将用户的 ID 放入addMap
中的"userId"
键。 - 然后,如果设备 ID 为空(
DodonewOnlineApplication.devId
为空),通过Utils.getDevId(DodonewOnlineApplication.getAppContext())
获取设备 ID,并将其放入addMap
中的"imei"
键。这里将设备 ID 加上前缀"Android"
。 - 使用
RequestUtil.paraMap()
方法对addMap
进行处理,生成一个包含签名的字符串code
,并将其放入addMap
中的"sign"
键。 - 使用
RequestUtil.encodeDesMap()
方法对code
进行 DES 加密,使用指定的密钥desKey
和初始向量desIV
,得到最终的加密结果encrypt
。
- 首先,将当前时间戳转换为字符串,并将其放入
- 当
useDes
为false
时,直接使用 Gson 库将addMap
对象转换为 JSON 字符串,并将其赋值给encrypt
。
但是我们不知道这个apk走的是哪个加密,所以我们现在就用钩子来勾一下函数,测试一下:
js代码部分:
1 | Java.perform(function () {}); |
以下是一个测试代码。就是去hook掉JsonRequest的类,然后输出检查一下
1 | Java.perform(function () { |
然后去cmd注入一下: frida -UF -l test1.js
可以看到,成功勾到了josnRequest的类,然后就试试其他:
1 | josnRequest.paraMap.implementation=function (){ |
这个函数的作用是根据提供的代码,josnRequest.paraMap.implementation
是对 paraMap
方法进行重写的操作。具体来说,它将 paraMap
方法的实现替换为一个空函数体。这意味着当调用 josnRequest
对象的 paraMap
方法时,将不执行原始的 paraMap
方法的实现,而是执行上述代码中定义的空函数体。
如果是要重新调用原函数的话,也可以用this.paraMap()进行调用,运行
接下来就注入测试一下:
1 | Java.perform(function () { |
好好好,正好自动运行了,发现没有输出我的参数,所以可以断定没有调用我的那个类
那就试试去钩一下另一个addRequest,但这个函数的参数有两个,就a,b
然后…..继续去注入一下,很好,寄,报错了!
重载函数
大概问题是因为重载函数的问题。重载函数就是同一个类的同一个函数,但是传入类和参数是不一样的,大概是有三个重载函数,
在后面加上就好,然后在手机端输入账号和密码,就可以在终端看到我们的参数:
成功!
然后就详细看一下这个函数的加密具体流程:
因为之前我们通过frida函数运行判断,找到了主要的加密的函数是:addRequestMap 函数,所以就集中分析一下这个函数
1 | String encrypt = RequestUtil.encodeDesMap(code, this.desKey, this.desIV); |
这是一个大的函数 RequestUtil.encodeDesMap(),然后传入了三个参数,分别是:code, this.desKey, this.desIV
然后这里面的第一次参数code,是上面的一个函数:
String code = RequestUtil.paraMap(addMap, Config.BASE_APPEND, “sign”);
code也是一个函数的值,这个函数有三个变量:addMap, Config.BASE_APPEND, “sign”
所以就先看看这个code是怎么来的,先看code的第一个调用函数:addMap
1 | public static String paraMap(Map<String, String> addMap, String append, String sign) { |
值得注意的一个点是这个函数并不是之前的com.dodonew.online.http.JsonRequest类下的函数,而是com.dodonew.online.http.RequestUtil
下的函数所以是不能直接用之前的js代码,只能重新去hook
这里再对这个函数进行一下分析:
首先对传入的参数addMap进行一个遍历然后在后面加点东西
其次再进行一个拼接,然后转化成string,再然后转化成md5加缪
得到字节数组,然后转化成16进制的编码…….
然后把这个数值放到sign里面
不算很难理解的流程。
如果是想得到md5加密的数值的话,直接去逆或者是算,比较麻烦,还是一样·,通过frida去把程序hook出去,就可以看到数值的
找到了函数,并且是在com.dodonew.online.util.Utils类下的,直接去写hook代码,这里传入的是string,返回的也是string
1 | var utils=Java.use("com.dodonew.online.util.Utils"); |
上面这个代码就不多解释了,就是一个简单的hook,然后输出,最后为了保证程序正常运行就加上了return’返回值
得到了输出:
账号是: 1111111111
密码是: 1111111111
md5的加密之前数值是: equtype=ANDROID&loginImei=Androidnull&timeStamp=1699850537421&userPwd=1111111111&username=1111111111&key=sdlkjsdljf0j2fsjk
md5加密之后的数值是: 8d030c8406caf188ad02388465b363c5
前面是固定格式,然后timeStamp应该是一个时间戳,然后账号密码,还有key的数值
至于这些字符串是怎么来的,大概就是上面分析的那个加密吧
说完了RequestUtil.paraMap,得到的code值,然后就看看他这个md5之后的数值是怎么继续进行des加密的
看 RequestUtil.encodeDesMap
1 | public static String encodeDesMap(String data, String desKey, String desIV) { |
这个加密传入了三个string的数据:(String data, String desKey, String desIV)
建议hook来查看具体的key和iv,因为他很有可能在运行过程中把数值发生改变
写一个hook代码查看一下:
1 | var RequestUtil=Java.use("com.dodonew.online.http.RequestUtil"); |
果然,报错了,又是重载函数
就找复合输入值的那个,加在后面就行
完整函数:
1 | var RequestUtil=Java.use("com.dodonew.online.http.RequestUtil"); |
看一下输出的结果:所以大体流程理解了
[MI 9 Transparent Edition::嘟嘟牛在线]-> 参数是: [object Object] 0
账号是: 11111111111
密码是: 22222222222
md5的加密之前数值是: equtype=ANDROID&loginImei=Androidnull&timeStamp=1699853155009&userPwd=22222222222&username=11111111111&key=sdlkjsdljf0j2fsjk
md5加密之后的数值是: b977d63aac03bc10091ab7677736e02b
md5加密之后的数值是: b977d63aac03bc10091ab7677736e02b
data: {“equtype”:”ANDROID”,”loginImei”:”Androidnull”,”sign”:”B977D63AAC03BC10091AB7677736E02B”,”timeStamp”:”1699853155009”,”userPwd”:”22222222222”,”username”:”11111111111”}
desKe: 65102933
desIV: 32028092
结果是: NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii/UNcdXYMk0EKKwKT83MJV1M
3opnnzgh/0WqmKhtngnsYDp8smOzTJTSIcT3TBxJsinUUebTJKy3t3FK69OEaP7CYlIt888QO6L+
lVJYenmhcM4b7sm+IOxxAiY/8COm/dQ4sEkbUL/Y7edhg6LEAuSK0xDAhHRE/bj6u7AB3OxP
fridaHOOK构造函数:
构造函数(Constructor)是面向对象编程中的一个特殊方法,用于创建和初始化类的实例(对象)。它通常在对象创建的过程中自动被调用,用于执行必要的初始化操作。
说白了,如果拿python举例子的话,比如在python中使用了 import base64 导入了这个功能,然后再去调用. base64.b64decode()函数就不需要自己去写的类了,所以在hook的时候,好像是点不进去的(个人理解,有错的话欢迎指出)
这样的话就要用到frida的功能–构造函数
比如:
1 | DESKeySpec(md.digest()); |
这个函数就是系统的构造函数了,当然也是可以hook的!用这个$init来代替本来的函数名(好像用原来的也可以)
1 | var desKeySpec=Java.use("javax.crypto.spec.DESKeySpec"); |
小问题,重载函数,选择一个参数的就行,经过调试之后,运行成功
但是他输出的object,这个问题困扰了我很久,额,现在知道了,原来是因为他…..是字节数组,无法输出!
主动调用
就是我们在写脚本的时候,主动调用Android里面的方法,大题和hook的方法差不多
因为字节数组无法输出,所以我们可以去主动调用Android的方法,把字节转化成base编码,具体的语法如下:
1 | var base64 =Java.use("android.util.Base64"); |
去主动调用base64的函数
然后在下面就可以直接调用;
1 | var base64 =Java.use("android.util.Base64"); |
然后就有正常的输出;
另外也可以调用其他的类的函数,在前面钩子的基础上,加上一个函数运行和参数
1 | requestUtil.encodeDesMap("hello","iamhaha","hahahaha"); |
总结:
代码:
1 | Java.perform(function () { |
代码输出:
[MI 9 Transparent Edition::嘟嘟牛在线]-> 参数是: [object Object] 0
账号是: 11112222333
密码是: woshimima
md5的加密之前数值是: equtype=ANDROID&loginImei=Androidnull&timeStamp=1699870290703&userPwd=woshimima&username=11112222333&key=sdlkjsdljf0j2fsjk
md5加密之后的数值是: f47d70a0d19de14449c66eada93469af
勾到了类: <class: com.dodonew.online.http.RequestUtil>
data: {“equtype”:”ANDROID”,”loginImei”:”Androidnull”,”sign”:”F47D70A0D19DE14449C66EADA93469AF”,”timeStamp”:”1699870290703”,”userPwd”:”woshimima”,”username”:”11112222333”}
deskey: 65102933
desiv: 32028092
构造函数的输入是: w9JtyoYll4K9kYZIH4kq5g==
加密结果是: NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii/UNcdXYMk0Egsgxk5jT/u+3
JC3j4TS00tIk3gtA2ZxdIEkTyrSpJ8z12bUOwEAlD8X77yMdklkBknFFsoRfdi9ycqZX5r4w+K8O
9PYfRF+QnU/3qxSyn/KieEJUy2EsfXxHbi7gOZuveR0o6Egkf7fS+mFcjPgTnGw09S0ylX+5
构造函数的输入是: w9JtyoYll4K9kYZIH4kq5g==