1. 前言

过年对我来说和平常没什么区别,该干什么干什么。

之前没接触过 frida 这个工具,前几天用了一些时间学习了一下,相比于 xposed hook 框架,frida 相对于调试方面真的很方便。现在网上也有一些 frida 通杀脚本(也有叫自吐算法脚本的),但是一般都是在 iv向量构造,key 构造分别进行 hook ,这样就导致 最后输出结果不是一个整体,加密和解密的数据,iv向量,key,输出不在同一块。我也不想从网上拿来就用(总感觉自己写一遍用起来才舒服,毕竟这个不算太复杂,还能熟悉一下 frida),所以我想制作一个输出以上信息在同一块算法通杀脚本,后面也用C++ Qt写了一个软件用来查看记录的数据。

2. 什么是hook?

这个问题让我想起我在大学期间,当时我用 Linux mint 系统,linux 系统上没有 QQ,所以我用 deepin-wine封装的QQ软件。但是使用过程中我发现有一个bug,就是不能打开接收到文件或文件夹,我当时猜测这个问题是 mint 没有 对应的文件管理器导致的,因为我用的是mint, 而软件使用的系统是在 deepin 上使用的,所以我在 mint系统上建立了一个与 deepin系统上的文件管理器同名的命令脚本,然后这个命令脚本去调用 mint 本地文件管理器去打开 对应的文件夹或文件,这样问题就解决了。

当年文章

这个原理就类似 hook,只不过我没有拦截消息的传递(因为压根就没有接收消息的命令)。通俗来讲就是 拦截住消息传递,然后再去处理这个消息。当时我解决 deepin-wine QQ上这个bug,感觉自己这个操作太秀了,现在想想不过是当时自己了解的东西太少,是一种无知的体现。

3. 通杀算法(自吐算法)脚本原理

安卓上调用 AES 加密解密, MD5 摘要算法时,都是需要调用基础的一个类,所以只要 hook 这个基础类,那么无论在什么时候什么地方调用到 算法,都会执行到基础类。除非是app 里面自己实现的加密算法,那就只能分析 app 内部的代码了。

例如一个AES加密的调用示例:

  1. public static byte[] aes_enc(byte[] bytesContent, String key) throws Exception
  2. {
  3. byte[] raw = key.getBytes("utf-8");
  4. SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
  5. Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
  6. cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
  7. byte[] enc = cipher.doFinal(bytesContent);
  8. return enc;
  9. }

可以看到这里有一个关键类 Cipher ,只需 hook doFinal()这个函数,就可以获得密文和明文,而秘钥 可以通过 hook SecretKeySpec() 这个类构造函数来获得。

这些类的实现都在 jce.jar 中实现,在 JDK中有。

Cipher 类:

SecretKeySpec

4. 分析

在 hook 这些类的构造函数或普通函数的时候,遇到了很多重载,下面简单理出这些重载的调用关系。

Cipher 类

  1. // getInstance 函数
  2. // 2.overload('java.lang.String', 'java.lang.String') -> 3
  3. // 1.overload('java.lang.String') |
  4. // 3.overload('java.lang.String', 'java.security.Provider') |
  5. // init 函数
  6. // 1.overload('int', 'java.security.Key') -> 4
  7. // 2.overload('int', 'java.security.cert.Certificate') -> 6
  8. // 3.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters') -> 7
  9. // 5.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec') ->8
  10. // 6.overload('int', 'java.security.cert.Certificate', 'java.security.SecureRandom') |
  11. // 4.overload('int', 'java.security.Key', 'java.security.SecureRandom') |
  12. // 7.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom') |
  13. // 8.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom') |
  14. // doFinal 函数
  15. // 1.overload() |
  16. // 2.overload('[B') |
  17. // 3.overload('java.nio.ByteBuffer', 'java.nio.ByteBuffer') |
  18. // 4.overload('[B', 'int') |
  19. // 5.overload('[B', 'int', 'int') |
  20. // 6.overload('[B', 'int', 'int', '[B') |
  21. // 7.overload('[B', 'int', 'int', '[B', 'int') |

这里的数字表示重载函数的编号条目, -> 表示调用,| 表示不调用其它重载函数了。通过这种写法,可以清晰的看出 重载函数之间的调用关系。

所以为什么需要理清重载关系? 因为这样可以知道 hook 哪些 函数是必要的。如果我们不知道重载函数之间调用的关系,直接hook 一个函数的所有重载:

  1. var cipher = Java.use("javax.crypto.Cipher");
  2. // 加密类型
  3. // 2.overload('java.lang.String', 'java.lang.String') -> 3
  4. // 1.overload('java.lang.String') |
  5. // 3.overload('java.lang.String', 'java.security.Provider') |
  6. for (let index = 0; index < cipher.getInstance.overloads.length; index++) {
  7. cipher.getInstance.overloads[index].implementation = function () {
  8. console.log("类型:" + JSON.stringify(arguments[0]));
  9. console.log(JSON.stringify(this));
  10. return this.getInstance.apply(this, arguments);
  11. }
  12. }

这样,你可能会发现,输出 类型: 两次, 因为 1,2,3 函数都被 hook了,app 可能只是调用了 2 ,而 2 本身有调用了 3。所以就会输出两次,所以针对这个 getInstance 函数,只需要 hook 1,3 重载函数就可以实现对所有调用的监听。

5. 深度分析

上述内容,可以实现对调用函数参数的监听,并且减少了 不必要 函数的 hook。但是还是不能实现 输出数据 在一整块地方,加密类型输出 和 明文,密文 可能是分散输出的。而要实现我 前言 中所述 的功能,就必须通过 hook 一个函数来实现,在一个函数内 获取 秘钥iv 向量明文密文模式加密or解密,这些信息,然后输出,其实这种肯定是要利用对象本身来传递的,就是查看对象属性上的绑定。


模式 : 这里所说的模式,指的是 "AES/ECB/PKCS5Padding" 字符串,所以从 getInstance 函数开始分析,当然这里只需要分析 肯定会被调用到的函数重载,也就是结尾带|的函数:

可以看到,getInstance 这个函数就是返回了一个 cipher 类的实例化对象,并且这个字符串传递过去,所以进一步跟踪分析其构造函数:

可以看到这几个构造函数,将 paramString 参数都专递给了 this.transformation,那么这个 模式 就可以通过 this来获取到了


加密or解密: 加密或解密,是 init 函数的第一个参数,所以这里重 init 函数开始分析参数的传递状态,同样也只需要分析肯定能被调用到的函数就可以:

这4个重载函数都是 paramInt函数都是 传递给 this.opmode


IV向量, 这个有点难找,具体过程不多说了,可以通过 this.spi.engineGetIV() 或者 this.getIV() 获得,其实this.getIV() 也是调用 this.spi.engineGetIV()获得的。


密文和明文:因为我要hook 一个函数,所以我想hook的就是最后的 doFinal 函数,这样就可以获取到 密文和明文了,然后再通过 this ,获取到上述所说的 模式加密或解密iv向量


秘钥: 只有这个参数是我没有通过 this 获取到,所以我 hook 了init 函数,其第二个参数就是秘钥。


6. 问题及解决

因为除了秘钥我都可以 通过 this 来获取到,所以秘钥获得后我在 JS 用一个全局变量来保存,然后在我hook 的 doFinal 函数 中进行输出,但是这里就遇到一个问题,多个线程可能同时进行加密解密,key 可能不是当时 对象 doFinal 函数使用的 key,那么这里我需要对实例化对象的唯一ID 与 key 进行一个绑定,然后 doFinal 函数里通过对象唯一ID 来获取key ,进行输出。

这里的解决方案是我制作了一个字典(python 叫字典,js叫啥我忘了),其中键为对象的唯一ID,值为 秘钥。这就能保证其对应关系的准确性了。

7. python 调用及数据保存

hookCalc.js

  1. var allKeys = {};
  2. Java.perform(function () {
  3. var cipher = Java.use("javax.crypto.Cipher");
  4. for (let index = 0; index < cipher.init.overloads.length; index++) {
  5. cipher.init.overloads[index].implementation = function () {
  6. allKeys[this.toString()] = arguments[1].getEncoded();
  7. this.init.apply(this, arguments);
  8. }
  9. }
  10. for (let index = 0; index < cipher.doFinal.overloads.length; index++) {
  11. cipher.doFinal.overloads[index].implementation = function () {
  12. var dict = {};
  13. dict["EorD"] = this.opmode.value; //模式 加密解密
  14. dict["method"] = this.transformation.value; //加密类型
  15. var iv = this.spi.value.engineGetIV();
  16. if (iv){
  17. dict["iv"] = iv;
  18. }else{
  19. dict["iv"] = "";
  20. }
  21. if (allKeys[this.toString()]){
  22. dict["password"] = allKeys[this.toString()]
  23. }else{
  24. dict["password"] = "";
  25. }
  26. var retVal = this.doFinal.apply(this, arguments);
  27. dict["receData"] = "";
  28. dict["resData"] = "";
  29. if (arguments.length >= 1 && arguments[0].$className != "java.nio.ByteBuffer") {
  30. dict['receData'] = arguments[0];
  31. dict["resData"] = retVal;
  32. }
  33. send(dict);
  34. return retVal;
  35. }
  36. }
  37. })

main.py

  1. import frida
  2. import sys
  3. import sqlite3
  4. import hashlib
  5. index = 0
  6. db = "me.db"
  7. def md5(data):
  8. hl = hashlib.md5()
  9. hl.update(data)
  10. return hl.hexdigest()
  11. def createDB():
  12. sql = '''
  13. CREATE TABLE IF NOT EXISTS "record" (
  14. "id" TEXT NOT NULL,
  15. "method" INTEGER,
  16. "EorD" TEXT,
  17. "password" BLOB,
  18. "iv" BLOB,
  19. "receData" BLOB,
  20. "resData" BLOB,
  21. PRIMARY KEY("id")
  22. );
  23. '''
  24. conn = sqlite3.connect(db)
  25. cursor = conn.cursor()
  26. cursor.execute(sql)
  27. conn.commit()
  28. conn.close()
  29. def message(message,arg2):
  30. try:
  31. global index
  32. conn = sqlite3.connect(db)
  33. cursor = conn.cursor()
  34. if message['type'] == "send":
  35. data = message['payload']
  36. method = data["method"]
  37. EorD = data["EorD"]
  38. password = bytes([i if i>=0 else 256+i for i in data["password"]])
  39. iv = bytes([i if i>=0 else 256+i for i in data["iv"]])
  40. receData = bytes([i if i>=0 else 256+i for i in data["receData"]])
  41. resData = bytes([i if i>=0 else 256+i for i in data["resData"]])
  42. id_md5 = md5((method+str(EorD)).encode()+password+iv+receData+resData)
  43. sql = "insert into record(id,method,EorD,password,iv,receData,resData) values (?,?,?,?,?,?,?)"
  44. cursor.execute(sql,(id_md5,method,EorD,sqlite3.Binary(password),sqlite3.Binary(iv),sqlite3.Binary(receData),sqlite3.Binary(resData)))
  45. conn.commit()
  46. print(index)
  47. index += 1
  48. except Exception as e:
  49. print(e)
  50. pass
  51. with open("hookCalc.js",encoding='utf8') as f:
  52. js = f.read()
  53. process = frida.get_remote_device().attach("APP名字,非包名")
  54. script = process.create_script(js)
  55. script.on("message",message)
  56. script.load()
  57. createDB()
  58. print(process)
  59. sys.stdin.read()

数据不便于输出预览,因为量大且不可输出(二进制数据输出乱码),JS 虽然有 转换编码及数据格式的函数,但是我还是习惯用 python 来处理。我将其保存到一个 sqlite3 数据库,这里我做了md5,保证数据保存的唯一性,然后我用 C++ QT 写个简单软件预览这些数据。

C++预览数据软件

软件读取同级目录的 me.db 数据库。

其它

脚本主要针对 AES 算法,也应该可以捕获的RSA,至于md5可以自行拓展,因为主要就是 AES算法 参数较多,会有输出会这一块那一块的问题,md5 根本不会有这样问题。

软件及脚本

写一个frida通杀脚本的更多相关文章

  1. r0capture安卓应用层通杀脚本-使用文档

    本文所有教程及源码.软件仅为技术研究.不涉及计算机信息系统功能的删除.修改.增加.干扰,更不会影响计算机信息系统的正常运行.不得将代码用于非法用途,如侵立删! r0capture安卓应用层通杀脚本-使 ...

  2. 用PHP写一个最简单的解释器Part4(写一个最简单的脚本语言)

    好吧!我承认我想标题党了.大家对解释器的吸引,绝对没有自己动手写一个脚本语言更有吸引力.不过如果看到标题过来的,可能也是 我承认,之前收藏的减肥视频,我都是这样对待他们的. 不过我还是相信很多程序猿o ...

  3. 用python写一个自动化盲注脚本

    前言 当我们进行SQL注入攻击时,当发现无法进行union注入或者报错等注入,那么,就需要考虑盲注了,当我们进行盲注时,需要通过页面的反馈(布尔盲注)或者相应时间(时间盲注),来一个字符一个字符的进行 ...

  4. 用if写一个备份mysql的脚本

    #!/bin/bash # 备份数据库 BAK_DIR=/data/backup/`date +%Y%m%d` MYSQLDB=dexin MYSQLUSER=root MYSQLPW=123456 ...

  5. 用node.js写一个jenkins发版脚本

    背景 每次到网页里手动发版有点烦,写个脚本来提高开发效率. CFG 在 jenkins 设置里获取 API TOKEN. 把 host 和账号密码拼接起来就可以通过鉴权. const token = ...

  6. 自动化运维:(3)写一个简单的Shell脚本(案例)

    一.需求 1.test.sh 脚本执行时候需要添加参数才能执行 参数和功能详情如下: 参数 执行效果 start 启动中... stop 关闭中... restart 重启中... * 脚本帮助信息. ...

  7. python写一个防御DDos的脚本(请安好环境否则无法实验)

    起因: 居然有ddos脚本,怎么可以没防御ddos的脚本! 开始: 1.请执行 install.py安装好DDos-defalte,会在root目录下多出这个文件夹 代码: 2.然后执行fyddos. ...

  8. Python写一个京东抢券脚本

    最近看到京东图书每天有优惠券发放,满200减100,诱惑还是蛮大的.反正自己抢不到,想着写个脚本试试. 几个关键步骤 获取优惠券的url 直接审查元素 获取cookie 通过本地代理,比如BurpSu ...

  9. 用shell写一个简单DHCP配置脚本

    轩轩写的这个小脚本,主要是可以进行对dhcp服务的安装.简单配置.开启.关闭/查看状态等情况 使用呢非常简单,按照步骤进行准确的设置就可以啦 #!/bin/bashyum -y install dhc ...

随机推荐

  1. Netty 学习(一):服务端启动 & 客户端启动

    Netty 学习(一):服务端启动 & 客户端启动 作者: Grey 原文地址: 博客园:Netty 学习(一):服务端启动 & 客户端启动 CSDN:Netty 学习(一):服务端启 ...

  2. python 模块、原始字符串

    模块 三种方法: import from 模块 import 成员,成员 from 模块 import * *代表所有的成员 隐藏成员: 模块中以下划线_开头的属性 隐藏成员不会被from 模块 im ...

  3. Kubernetes 存储系统 Storage 介绍:PV,PVC,SC

    要求:先了解数据docker容器中数据卷的挂载等知识 参考网址: https://www.cnblogs.com/sanduzxcvbnm/p/13176938.html https://www.cn ...

  4. 使用docker-compose方式安装redash

    转载自:https://anjia0532.github.io/2019/07/08/redash/ ## 安装必要工具 apt install -y pwgen python-pip pip ins ...

  5. git-flow模型

    git-flow 是在 git branch 和 git tag 基础上封装出来的代码分支管理模型,把实际开发模拟称 master develop feature release hotfix sup ...

  6. flutter系列之:深入理解布局的基础constraints

    目录 简介 Tight和loose constraints 理解constraints的原则 总结 简介 我们在flutter中使用layout的时候需要经常对组件进行一些大小的限制,这种限制就叫做c ...

  7. Vue中使用Switch开关用来控制商品的上架与下架情况、同时根据数据库商品的状态反应到前台、前台修改商品状态保存到数据库

    一般后台对商品的信息管理.包含商品的上架与下架.为了提高用户的体验.将商品上下架的操作做成开关的形式.同时后台数据库中保存的商品状态能够根据开关状态改变. 1.效果展示 这种效果:== 当开关是开启状 ...

  8. Vue学习之--------Scoped样式(2022/8/1)

    1.场景 一个页面开发团队进行页面的开发设计.无可避免的会发生样式选择器命名的重复(id的重复.class的重复等).这样间接导致的后果就是.自己的页面样式好好的.在整合一起的时候.可能就会发生样式的 ...

  9. java中实现File文件的重命名(renameTo)、将文件移动到其他目录下、文件的复制(copy)、目录和文件的组合(更加灵活方便)

    欢迎加入刚建立的社区:http://t.csdn.cn/Q52km 加入社区的好处: 1.专栏更加明确.便于学习 2.覆盖的知识点更多.便于发散学习 3.大家共同学习进步 3.不定时的发现金红包(不多 ...

  10. v-for和router-link的共同使用

    1. 错误例子 <div style="color: red" v-for="item in pressionList" :key="item. ...