前言:
♛ frida hook android Android 逆向神器
前几天在学习 Android 逆向的时候发现了一个神器:通过 frida hook 我们可以 “劫持” 一些函数 为我们所用, 今天就和大家上手一个 加密函数的劫持 让打印出:
- 加密秘钥
- 密文
效果如下:可以很直观的看出 加密算法以及秘钥
准备工作:
安装运行环境:(因为我这里都装过来 你们自己对照着来)
- python 环境
C:\Users\Administrator>python --version Python 3.10.6
- frida
C:\Users\Administrator>pip install frida Requirement already satisfied: frida in c:\users\administrator\appdata\local\programs\python\python310\lib\site-packages (16.1.3) Requirement already satisfied: typing-extensions in c:\users\administrator\appdata\local\programs\python\python310\lib\site-packages (from frida) (4.7.1)[notice] A new release of pip available: 22.2.1 -> 23.2.1 [notice] To update, run: python.exe -m pip install --upgrade pipC:\Users\Administrator>frida usage: frida [options] target frida: error: target must be specified
- firda-tools
C:\Users\Administrator>pip install frida-tools Requirement already satisfied: frida-tools in c:\users\administrator\appdata\local\programs\python\python310\lib\site-packages (12.2.1) Requirement already satisfied: prompt-toolkit<4.0.0,>=2.0.0 in c:\users\administrator\appdata\local\programs\python\python310\lib\site-packages (from frida-tools) (3.0.39) Requirement already satisfied: colorama<1.0.0,>=0.2.7 in c:\users\administrator\appdata\local\programs\python\python310\lib\site-packages (from frida-tools) (0.4.6) Requirement already satisfied: pygments<3.0.0,>=2.0.2 in c:\users\administrator\appdata\local\programs\python\python310\lib\site-packages (from frida-tools) (2.15.1) Requirement already satisfied: frida<17.0.0,>=16.0.9 in c:\users\administrator\appdata\local\programs\python\python310\lib\site-packages (from frida-tools) (16.1.3) Requirement already satisfied: typing-extensions in c:\users\administrator\appdata\local\programs\python\python310\lib\site-packages (from frida<17.0.0,>=16.0.9->frida-tools) (4.7.1) Requirement already satisfied: wcwidth in c:\users\administrator\appdata\local\programs\python\python310\lib\site-packages (from prompt-toolkit<4.0.0,>=2.0.0->frida-tools) (0.2.6)[notice] A new release of pip available: 22.2.1 -> 23.2.1 [notice] To update, run: python.exe -m pip install --upgrade pip
- 下载一个 frida server Releases · frida/frida · GitHub
下载地址: https://github.com/frida/frida/releases
因为 我用的是模拟器所有下载这个版本,可以使用 命令查看对于的类型:
C:\Users\Administrator>adb shell getprop ro.product.cpu.abi x86_64
- adb
C:\Users\Administrator>adb devices List of devices attached emulator-5558 device
实操上手:
⒈建立注入环境:运行 frida server (不要关闭)
把刚刚下载的 解压发送到 手机 /data/local/ 目录下, 我这里重命名为 f86(应为一些加壳软件会监测是否使用 frifa)
执行 f86, 记得要给权限!chmod 777 f86
C:\Users\Administrator>adb shell star2qltechn:/ $ su star2qltechn:/ # cd /data/lcoal /system/bin/sh: cd: /data/lcoal: No such file or directory 2|star2qltechn:/ # cd /data/local star2qltechn:/data/local # ls cfg-zeiag f86 file-cache temp-zeiag tmp traces star2qltechn:/data/local # chmod 777 f86 star2qltechn:/data/local # su star2qltechn:/data/local # ./f86
2 建立端口转发 一共两个端口 (不要关闭)
- adb forward tcp:27043 tcp:27043
- adb forward tcp:27042 tcp:27042
C:\Users\Administrator>adb forward tcp:27043 tcp:27043 27043C:\Users\Administrator>adb forward tcp:27042 tcp:27042 27042
3 运行我们的 hook.js 代码 (不要关闭)
frida -U -F -l C:\Users\Administrator\Desktop\py\hook.js
- -U 指的的我们的 USB设备
- -F 指的是我们最手机 最上层运行的APP
- -l 指定我们脚本路径
运行这App的时候 跑起来我们的脚本
4 奇迹就发生了
hook.js 脚本
var N_ENCRYPT_MODE = 1 var N_DECRYPT_MODE = 2function showStacks() {var Exception = Java.use("java.lang.Exception");var ins = Exception.$new("Exception");var straces = ins.getStackTrace();if (undefined == straces || null == straces) {return;}console.log("============================= Stack strat=======================");console.log("");for (var i = 0; i < straces.length; i++) {var str = " " + straces[i].toString();console.log(str);}console.log("");console.log("============================= Stack end=======================\r\n");Exception.$dispose(); }//工具相关函数 var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',base64DecodeChars = new Array((-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), 62, (-1), (-1), (-1), 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, (-1), (-1), (-1), (-1), (-1), (-1), (-1), 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, (-1), (-1), (-1), (-1), (-1), (-1), 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, (-1), (-1), (-1), (-1), (-1));function stringToBase64(e) {var r, a, c, h, o, t;for (c = e.length, a = 0, r = ''; a < c;) {if (h = 255 & e.charCodeAt(a++), a == c) {r += base64EncodeChars.charAt(h >> 2),r += base64EncodeChars.charAt((3 & h) << 4),r += '==';break}if (o = e.charCodeAt(a++), a == c) {r += base64EncodeChars.charAt(h >> 2),r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),r += base64EncodeChars.charAt((15 & o) << 2),r += '=';break}t = e.charCodeAt(a++),r += base64EncodeChars.charAt(h >> 2),r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),r += base64EncodeChars.charAt(63 & t)}return r } function base64ToString(e) {var r, a, c, h, o, t, d;for (t = e.length, o = 0, d = ''; o < t;) {dor = base64DecodeChars[255 & e.charCodeAt(o++)];while (o < t && r == -1);if (r == -1)break;doa = base64DecodeChars[255 & e.charCodeAt(o++)];while (o < t && a == -1);if (a == -1)break;d += String.fromCharCode(r << 2 | (48 & a) >> 4);do {if (c = 255 & e.charCodeAt(o++), 61 == c)return d;c = base64DecodeChars[c]} while (o < t && c == -1);if (c == -1)break;d += String.fromCharCode((15 & a) << 4 | (60 & c) >> 2);do {if (h = 255 & e.charCodeAt(o++), 61 == h)return d;h = base64DecodeChars[h]} while (o < t && h == -1);if (h == -1)break;d += String.fromCharCode((3 & c) << 6 | h)}return d } function hexToBase64(str) {return base64Encode(String.fromCharCode.apply(null, str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" "))); } function base64ToHex(str) {for (var i = 0, bin = base64Decode(str.replace(/[ \r\n]+$/, "")), hex = []; i < bin.length; ++i) {var tmp = bin.charCodeAt(i).toString(16);if (tmp.length === 1)tmp = "0" + tmp;hex[hex.length] = tmp;}return hex.join(""); } function hexToBytes(str) {var pos = 0;var len = str.length;if (len % 2 != 0) {return null;}len /= 2;var hexA = new Array();for (var i = 0; i < len; i++) {var s = str.substr(pos, 2);var v = parseInt(s, 16);hexA.push(v);pos += 2;}return hexA; } function bytesToHex(arr) {var str = '';var k, j;for (var i = 0; i < arr.length; i++) {k = arr[i];j = k;if (k < 0) {j = k + 256;}if (j < 16) {str += "0";}str += j.toString(16);}return str; } function stringToHex(str) {var val = "";for (var i = 0; i < str.length; i++) {if (val == "")val = str.charCodeAt(i).toString(16);elseval += str.charCodeAt(i).toString(16);}return val } function stringToBytes(str) {var ch, st, re = [];for (var i = 0; i < str.length; i++) {ch = str.charCodeAt(i);st = [];do {st.push(ch & 0xFF);ch = ch >> 8;}while (ch);re = re.concat(st.reverse());}return re; } //将byte[]转成String的方法 function bytesToString(arr) {var str = '';arr = new Uint8Array(arr);for (var i in arr) {str += String.fromCharCode(arr[i]);}return str; } function bytesToBase64(e) {var r, a, c, h, o, t;for (c = e.length, a = 0, r = ''; a < c;) {if (h = 255 & e[a++], a == c) {r += base64EncodeChars.charAt(h >> 2),r += base64EncodeChars.charAt((3 & h) << 4),r += '==';break}if (o = e[a++], a == c) {r += base64EncodeChars.charAt(h >> 2),r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),r += base64EncodeChars.charAt((15 & o) << 2),r += '=';break}t = e[a++],r += base64EncodeChars.charAt(h >> 2),r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),r += base64EncodeChars.charAt(63 & t)}return r } function base64ToBytes(e) {var r, a, c, h, o, t, d;for (t = e.length, o = 0, d = []; o < t;) {dor = base64DecodeChars[255 & e.charCodeAt(o++)];while (o < t && r == -1);if (r == -1)break;doa = base64DecodeChars[255 & e.charCodeAt(o++)];while (o < t && a == -1);if (a == -1)break;d.push(r << 2 | (48 & a) >> 4);do {if (c = 255 & e.charCodeAt(o++), 61 == c)return d;c = base64DecodeChars[c]} while (o < t && c == -1);if (c == -1)break;d.push((15 & a) << 4 | (60 & c) >> 2);do {if (h = 255 & e.charCodeAt(o++), 61 == h)return d;h = base64DecodeChars[h]} while (o < t && h == -1);if (h == -1)break;d.push((3 & c) << 6 | h)}return d } //stringToBase64 stringToHex stringToBytes //base64ToString base64ToHex base64ToBytes // hexToBase64 hexToBytes // bytesToBase64 bytesToHex bytesToStringJava.perform(function () {var secretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');secretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function (a, b) {showStacks();var result = this.$init(a, b);console.log("======================================");console.log("算法名:" + b + "|str密钥:" + bytesToString(a));console.log("算法名:" + b + "|Hex密钥:" + bytesToHex(a));return result;}var DESKeySpec = Java.use('javax.crypto.spec.DESKeySpec');DESKeySpec.$init.overload('[B').implementation = function (a) {showStacks();var result = this.$init(a);console.log("======================================");var bytes_key_des = this.getKey();console.log("des密钥 |str " + bytesToString(bytes_key_des));console.log("des密钥 |hex " + bytesToHex(bytes_key_des));return result;}DESKeySpec.$init.overload('[B', 'int').implementation = function (a, b) {showStacks();var result = this.$init(a, b);console.log("======================================");var bytes_key_des = this.getKey();console.log("des密钥 |str " + bytesToString(bytes_key_des));console.log("des密钥 |hex " + bytesToHex(bytes_key_des));return result;}var mac = Java.use('javax.crypto.Mac');mac.getInstance.overload('java.lang.String').implementation = function (a) {showStacks();var result = this.getInstance(a);console.log("======================================");console.log("算法名:" + a);return result;}mac.update.overload('[B').implementation = function (a) {//showStacks();this.update(a);console.log("======================================");console.log("update:" + bytesToString(a))}mac.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {//showStacks();this.update(a, b, c)console.log("======================================");console.log("update:" + bytesToString(a) + "|" + b + "|" + c);}mac.doFinal.overload().implementation = function () {//showStacks();var result = this.doFinal();console.log("======================================");console.log("doFinal结果: |str :" + bytesToString(result));console.log("doFinal结果: |hex :" + bytesToHex(result));console.log("doFinal结果: |base64 :" + bytesToBase64(result));return result;}mac.doFinal.overload('[B').implementation = function (a) {//showStacks();var result = this.doFinal(a);console.log("======================================");console.log("doFinal参数: |str :" + bytesToString(a));console.log("doFinal结果: |str :" + bytesToString(result));console.log("doFinal结果: |hex :" + bytesToHex(result));console.log("doFinal结果: |base64 :" + bytesToBase64(result));return result;}var md = Java.use('java.security.MessageDigest');md.getInstance.overload('java.lang.String', 'java.lang.String').implementation = function (a, b) {//showStacks();console.log("======================================");console.log("算法名:" + a);return this.getInstance(a, b);}md.getInstance.overload('java.lang.String').implementation = function (a) {//showStacks();console.log("======================================");console.log("算法名:" + a);return this.getInstance(a);}md.update.overload('[B').implementation = function (a) {//showStacks();console.log("======================================");console.log("update:" + bytesToString(a))return this.update(a);}md.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {//showStacks();console.log("======================================");console.log("update:" + bytesToString(a) + "|" + b + "|" + c);return this.update(a, b, c);}md.digest.overload().implementation = function () {//showStacks();console.log("======================================");var result = this.digest();console.log("digest结果:" + bytesToHex(result));console.log("digest结果:" + bytesToBase64(result));return result;}md.digest.overload('[B').implementation = function (a) {//showStacks();console.log("======================================");console.log("digest参数:" + bytesToString(a));var result = this.digest(a);console.log("digest结果:" + bytesToHex(result));console.log("digest结果:" + bytesToBase64(result));return result;}var ivParameterSpec = Java.use('javax.crypto.spec.IvParameterSpec');ivParameterSpec.$init.overload('[B').implementation = function (a) {//showStacks();var result = this.$init(a);console.log("======================================");console.log("iv向量: |str:" + bytesToString(a));console.log("iv向量: |hex:" + bytesToHex(a));return result;}var cipher = Java.use('javax.crypto.Cipher');cipher.getInstance.overload('java.lang.String').implementation = function (a) {//showStacks();var result = this.getInstance(a);console.log("======================================");console.log("模式填充:" + a);return result;}cipher.init.overload('int', 'java.security.Key').implementation = function (a, b) {//showStacks();var result = this.init(a, b);console.log("======================================");if (N_ENCRYPT_MODE == a){console.log("init | 加密模式"); }else if(N_DECRYPT_MODE == a){console.log("init | 解密模式"); }var bytes_key = b.getEncoded();console.log("init key:" + "|str密钥:" + bytesToString(bytes_key));console.log("init key:" + "|Hex密钥:" + bytesToHex(bytes_key));return result;}cipher.init.overload('int', 'java.security.cert.Certificate').implementation = function (a, b) {//showStacks();var result = this.init(a, b);console.log("======================================");if (N_ENCRYPT_MODE == a){console.log("init | 加密模式"); }else if(N_DECRYPT_MODE == a){console.log("init | 解密模式"); }return result;}cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (a, b, c) {//showStacks();var result = this.init(a, b, c);console.log("======================================");if (N_ENCRYPT_MODE == a){console.log("init | 加密模式"); }else if(N_DECRYPT_MODE == a){console.log("init | 解密模式"); }var bytes_key = b.getEncoded();console.log("init key:" + "|str密钥:" + bytesToString(bytes_key));console.log("init key:" + "|Hex密钥:" + bytesToHex(bytes_key));return result;}cipher.init.overload('int', 'java.security.cert.Certificate', 'java.security.SecureRandom').implementation = function (a, b, c) {//showStacks();var result = this.init(a, b, c);if (N_ENCRYPT_MODE == a){console.log("init | 加密模式"); }else if(N_DECRYPT_MODE == a){console.log("init | 解密模式"); }return result;}cipher.init.overload('int', 'java.security.Key', 'java.security.SecureRandom').implementation = function (a, b, c) {//showStacks();var result = this.init(a, b, c);if (N_ENCRYPT_MODE == a){console.log("init | 加密模式"); }else if(N_DECRYPT_MODE == a){console.log("init | 解密模式"); }var bytes_key = b.getEncoded();console.log("init key:" + "|str密钥:" + bytesToString(bytes_key));console.log("init key:" + "|Hex密钥:" + bytesToHex(bytes_key));return result;}cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters').implementation = function (a, b, c) {//showStacks();var result = this.init(a, b, c);if (N_ENCRYPT_MODE == a){console.log("init | 加密模式"); }else if(N_DECRYPT_MODE == a){console.log("init | 解密模式"); }var bytes_key = b.getEncoded();console.log("init key:" + "|str密钥:" + bytesToString(bytes_key));console.log("init key:" + "|Hex密钥:" + bytesToHex(bytes_key));return result;}cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom').implementation = function (a, b, c, d) {//showStacks();var result = this.init(a, b, c, d);if (N_ENCRYPT_MODE == a){console.log("init | 加密模式"); }else if(N_DECRYPT_MODE == a){console.log("init | 解密模式"); }var bytes_key = b.getEncoded();console.log("init key:" + "|str密钥:" + bytesToString(bytes_key));console.log("init key:" + "|Hex密钥:" + bytesToHex(bytes_key));return result;}cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom').implementation = function (a, b, c, d) {//showStacks();var result = this.update(a, b, c, d);if (N_ENCRYPT_MODE == a){console.log("init | 加密模式"); }else if(N_DECRYPT_MODE == a){console.log("init | 解密模式"); }var bytes_key = b.getEncoded();console.log("init key:" + "|str密钥:" + bytesToString(bytes_key));console.log("init key:" + "|Hex密钥:" + bytesToHex(bytes_key));return result;}cipher.update.overload('[B').implementation = function (a) {//showStacks();var result = this.update(a);console.log("======================================");console.log("update:" + bytesToString(a));return result;}cipher.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {//showStacks();var result = this.update(a, b, c);console.log("======================================");console.log("update:" + bytesToString(a) + "|" + b + "|" + c);return result;}cipher.doFinal.overload().implementation = function () {//showStacks();var result = this.doFinal();console.log("======================================");console.log("doFinal结果: |str :" + bytesToString(result));console.log("doFinal结果: |hex :" + bytesToHex(result));console.log("doFinal结果: |base64 :" + bytesToBase64(result));return result;}cipher.doFinal.overload('[B').implementation = function (a) {//showStacks();var result = this.doFinal(a);console.log("======================================");console.log("doFinal参数: |str :" + bytesToString(a));console.log("doFinal结果: |str :" + bytesToString(result));console.log("doFinal结果: |hex :" + bytesToHex(result));console.log("doFinal结果: |base64 :" + bytesToBase64(result));return result;}var x509EncodedKeySpec = Java.use('java.security.spec.X509EncodedKeySpec');x509EncodedKeySpec.$init.overload('[B').implementation = function (a) {//showStacks();var result = this.$init(a);console.log("======================================");console.log("RSA密钥:" + bytesToBase64(a));return result;}var rSAPublicKeySpec = Java.use('java.security.spec.RSAPublicKeySpec');rSAPublicKeySpec.$init.overload('java.math.BigInteger', 'java.math.BigInteger').implementation = function (a, b) {//showStacks();var result = this.$init(a, b);console.log("======================================");//console.log("RSA密钥:" + bytesToBase64(a));console.log("RSA密钥N:" + a.toString(16));console.log("RSA密钥E:" + b.toString(16));return result;}var KeyPairGenerator = Java.use('java.security.KeyPairGenerator');KeyPairGenerator.generateKeyPair.implementation = function (){//showStacks();var result = this.generateKeyPair();console.log("======================================");var str_private = result.getPrivate().getEncoded();var str_public = result.getPublic().getEncoded();console.log("公钥 |hex" + bytesToHex(str_public));console.log("私钥 |hex" + bytesToHex(str_private));return result;}KeyPairGenerator.genKeyPair.implementation = function (){//showStacks();var result = this.genKeyPair();console.log("======================================");var str_private = result.getPrivate().getEncoded();var str_public = result.getPublic().getEncoded();console.log("公钥 |hex" + bytesToHex(str_public));console.log("私钥 |hex" + bytesToHex(str_private));return result;} });
总结:
使用 frida hook 的原理, 是直接劫持java加密函数, 不需要去逆向代码直接, 效率很高!
对于使用 java 原生开发的应用 效率很高! 但是相对于 flutter 开发的应用效果就比较差了
后面抽空研究研究 fiida 在 flutter 身上的新姿势