逆向步骤
- 软件是否能正常运行,是否带有检测
- 抓包分析是否有需要逆向的加密字段
- 判断app怎么开发的,了解框架(判断是否加壳)
- 反编译观察代码
Android扫盲
apk基本结构
- assets
资源文件(图片、音频、数据库、网页、配置文件、dll、so等)
- res
资源文件(编译后的布局文件、程序图标)
- lib
各种平台下使用的对应的so文件
- META-INF
签名文件
- resources.arsc
资源加密(语言包)
- AndroidManifest.xml
清单文件(图标、界面、权限、代码执行入口)
- classes.dex
源代码
Android常见目录
- data/data
存放用户apk数据的目录,每个apk都有自己的目录,以包名命名就是在data/data/目录下,会产生一个跟Package一样的目录这是一个私有目录,app只能访问各自的目录,除非root权限
- data/app
用户安装的app存放在这个目录下
- data/local/tmp
临时目录,权限比较大
- system/app
存放系统自带的app
- system/lib、system/lib64
存放app用到so文件
- system/bin
存放she||命令
- system/framework
Android系统所用到框架,如一些jar文件,XposedBridge.jar
- sd卡目录不管手机有没有存储卡都会有这个目录,app操作sd卡目录需要申请权限
/sdcard->/storage/self/primary
/mnt/sdcard
/storage/emulated/0
build.gradle文档
AndroidManifest.xml清单
字符串文件存放
resources --> values --> strings.xml
xml文件中通过strings.xml中 @string/字符串id(name) 来使用字符串
代码中通过 R.string.字符串id(name)来使用字符串
public.xml中存放字符串对应的硬编码id
Linux下文件权限
权限
ls -l
lrw-r--r-- 1 root root 21 2009-01-01 08:00 sdcard -> /storage/self/primary
0位确定文件类型
- d:目录
-
l:软链接(相当于快捷方式)
ln -s 路径 软连接名 //创建软链接
- -:文件
-
c:设备文件,鼠标,键盘 /dev
-
b:块设备,比如硬盘 /dev
1-3位确定所有者拥有该文件的权限
4-6位确定所属组拥有该文件的权限
7-9位确定其他用户组拥有该文件的权限
权限表示方式
rwx可读可写可执行一代表没有权限
权限还可以用数字来表示r=4 w=2 x=1
目录和文件都是有权限的,操作目录和文件都需要有对应权限才能操作
adb命令
路径:D:\development\Android\sdk\platform-tools
链接魅族20🥰:adb connect 192.168.19.10:38641
adb version //查看版本
adb help //查看帮助
adb start-server //启动server
adb kill-server //停止server
adb devices //显示连接欸的设备列表
adb install xxx.apk //通过adb安装app
adb install -r xxx.apk //覆盖安装
adb uninstall 包名 //通过adb卸载app
adb push 电脑路径 手机路径 //推送电脑的文件到手机
adb pull 手机路径 电脑路径 //拉去手机的文件到电脑
adb pull 手机路径 //电脑所在路径
adb shell //进入到手机的Linux控制台
adb -s 设备名 shell //多设备时,指定设备
adb shell am force-stop packagename //强制停止应用
adb shel 1 pm clear (apk 包名) //清楚应用数据与缓存
adb shell pm list package //查询已安装包名列表
adb shell dumpsys window | findstr mCurrentFocus //获取当前正在运行的 activity (活动)
adb shell am monitor //查看运行的 app 包名
adb forward tcp: 27042 tcp: 27042 //端口转发
logcat使用
adb logcat -help //查看帮助
adb logcat //常规显示
adb logcat-c //清除日志
adb logcat-g //显示缓冲区大小
adb logcat -G 256M //修改缓冲区大小
adb logcat -v time //设置不同的显示格式
adb logcat -v color //带颜色的显示
adb logcat -s 过滤tag //根据tag过滤日志
ps -A | grep 进程名 //先获取进程pid(进入shell后)
adb logcat | findstr pid 根据pid过滤日志
r0capture使用
一个Hook抓包脚本,主要原理就是Hook了一些SSL相关的系统函数,直接抓取系统函数(在此目录下)
attach模式(附加模式)
python3 r0capture. py -U 包名 -v -p 流量包
spawn模式
python3 r0capture. py -U -f 包名 -v
[[密码学]]
本地随机生成对称加密的密钥A
然后用RSA算法加密A,得到密文B
密文B提交给服务器,服务器用私钥解密,得到明文A
后续通信过程,使用明文A来加密
对称加密算法
代表有AES
加密和解密使用相同的密钥
特点
加密速度快,可以加密大量数据
非对称加密算法
代表有RSA
加密和解密使用不同密钥
特点
存在密钥对,公钥 私钥
从公钥无法推导私钥
加密速度慢,加密数据有限
证书传输原理
HTTP协议
常规协议头
app界面控件的查看与分析
使用uiautomatorviewer.bat工具来判断是否为H5框架
D:\development\Android\androidtools\uiautomatorviewer.bat
点击截图功能查看控件
不是H5 (使用Webview控件加载网页) 的app则加密不在js里面
app禁止截屏权限
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
清楚权限
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
传统关键代码定位方法
通过控件绑定的事件代码,来一步步定位
控件id、setOnClickListener
人肉手工搜索字符串
搜索链接
搜索加密的参数名:当有多个可疑关键处,如何确定是哪一个?动态调试、Hook
搜索同一个数据包中,没有加密的参数名
一般这些R.xxxx 的内容都存在 resources. arsc. res. values. string 中
关键代码快速定位
跑一下自吐算法插件
Hook常用系统函数,如果app有调用就打印函数栈
在自制的沙盒中运行,打印app运行过程中的指令、函数调用关系等
FridaHook
作用:
可以用来判断app执行某个操作的时候,是否经过我们的怀疑的这个函数
可以用来修改被hook函数的运行逻辑
可以用来在运行过程中,获取被hook的函数传入的具体的参数和返回值
可以用来主动调用app中的某些函数
frida版本设置:
https://github.com/frida/frida/releases/tag/12.8.0
frida-server安卓端运行:
./data/local/tmp/frida-server
frida命令注入:
frida -UF -l demo.js
-U代表远程USB设备
-F代表附加到最前的这个app
-l后面指明需要加载的JS脚本
-o输出调试文件
frida转发端口:
通过usb连接真机不需要转发端口,通过ip连接需要转发,连接模拟器也需要转发
adb forward tcp : 27042 tcp : 27042
fridaHook普通方法和重载方法:
java层hook
//如果是java hook代码 都放到 perform 中
Java.perform(function (){
//里面写hook代码
var jsonRequest = Java.use("com.dodonew.online.http.JsonRequest"); //进行查找类
console.log("jsonRequest:",jsonRequest); //判断是否找到类
jsonRequest.paraMap.implementation = function (a){ //查找到的类使用对应hook方法,implementation实现匿名方法
console.log("params1:",a) //打印参数1
this.paraMap(a) //调用原有方法
//后续也可打印出返回值
}
//遇到重载函数要选定
jsonRequest.addRequestMap.overload('java.util.Map', 'int').implementation = function (a,b){ //查找到的类使用对应hook方法,implementation实现匿名方法
console.log("addRequestMap params:",a,b) //打印参数
console.log("addRequestMap params:",a.get("username")); //获取参数值
this.addRequestMap(a) //调用原有方法
//后续也可打印出返回值
}
var utils = Java.use("com.dodonew.online.util.Utils"); //进行查找类
utils.md5.implementation = function (a) { //查找到的类使用对应hook方法,implementation实现匿名方法
console.log("md5 params:", a) //打印参数
var retval = this.md5(a) //md5函数返回值
console.log("md5 retval:",retval)
return retval
}
var requestUtil = Java.use("com.dodonew.online.http.RequestUtil"); //进行查找类
requestUtil.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function (a,b,c) {
console.log("encodeDesMap params:", a) //打印参数
console.log("encodeDesMap key:", b) //打印参数
console.log("encodeDesMap iv:", c) //打印参数
var retval = this.encodeDesMap(a,b,c)
console.log("encodeDesMap retval:",retval)
return retval;
}
var base64 = Java.use("android.util.Base64")
var dESKeySpec = Java.use("javax.crypto.spec.DESKeySpec");
dESKeySpec.$init.overload('[B').implementation = function (a){ //构造函数hook
console.log("DESKeySpec params:",base64.encodeToString(a,0)) //通过android方法主动调用
this.$init(a)
}
})
主动调用的作用
- 可以用来测试Hook代码的正确性
- 调用加密函数观察算法输出结果特征
- 调用加密函数测试算法复现正确性
- 比较复杂的算法,需要借助主动调用实现算法转发
算法复现
嘟嘟牛sign算法:
// md5 params: equtype=ANDROID&loginImei=Android352746027467770&timeStamp=1714901267011&userPwd=a12345678&username=15968079477&key=sdlkjsdljf0j2fsjk
// md5 retval: 26d761fda910ba4fc5c975cb7c78f5c8
var CryptoJS = require("crypto-js"); //导入加密包
function getsign(user,pwd,time){
var data = "equtype=ANDROID&loginImei=Android352746027467770&timeStamp="+ time +"&userPwd="+ pwd +"&username="+ user +"&key=sdlkjsdljf0j2fsjk";
return CryptoJS.MD5(data).toString(); //进行md5加密
}
function getdata(user,pwd){
var time = new Date().getTime(); //获取时间戳
time = "1714908310243"; //测试时间戳(可删除)
var sign = getsign(user,pwd,time).toUpperCase(); //转大写
var _plainText = '{"equtype":"ANDROID","loginImei":"Android352746027467770","sign":"'+ sign +'","timeStamp":"'+ time +'","userPwd":"'+ pwd +'","username":"'+user+'"}'
// encodeDesMap key: 65102933
// encodeDesMap iv: 32028092
var keyMD5 = CryptoJS.MD5("65102933").toString(); //对key进行md5加密
var _iv = CryptoJS.enc.Utf8.parse("32028092") //对iv进行UTF8(一个字符解析成一个字节)解析(将字符串转换成字节数组)
var _key = CryptoJS.enc.Hex.parse(keyMD5) //对key进行HEX(两个字符解析成一个字节)解析(将字符串转换成字节数组)
return CryptoJS.DES.encrypt(_plainText,_key,{ //进行DES加密(数据,密钥,对象)
iv:_iv, //iv向量
mode:CryptoJS.mode.CBC, //指明模式
padding:CryptoJS.pad.Pkcs7 //填充方式
}).toString();
}
console.log(getdata("15968079477","a12345678"))
协议复现
可以进行批量登录,批量注册,批量获取数据
关键代码快速定位
### 打印函数栈信息
对Java.util.HashMap类进行hook
[!tip]
打印函数栈信息,自下而上,自外而内
Log.getstackTracestring(new Throwable())
frida主动调用打印栈信息函数
function showStacks(){ //封装打印堆栈函数
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
)
}
//如果是java hook代码 都放到 perform 中
Java.perform(function (){
var hashMap = Java.use("java.util.HashMap")
hashMap.put.implementation = function (a,b){
if (a.equals("username")){ //过滤特定键名来打印,匹配字符串是否相等,js里方法【indexof(键名)!=-1】
showStacks();
console.log("hashMap.put:",a,b);
} return this.put(a,b)
}
})
重学
切换 java 版本
scoop list # 安装列表
scoop reset openjdk8-redhat # java8
scoop reset openjdk11 # java11
Frida Hook
frida //hook
frida-ps //查看进程
frida-ps -Ua //列出运行的 app
frida-ps -Uai //列出所有 app
frida-kill -U 包名 //杀死进程
frida -H //指定端口
frida-trace //跟踪函数调用
[!note]
免检测
1. 启动
./Sherry_frida -l 0.0.0.0:6666
2. 端口转发
adb forward tcp:6666 tcp:6666
3. 启动方式
frida -H 127.0.0.1:6666 -f 包名 -l 文件名
被检测
Java.use ()
:该方法用于获取 Java 类对象,以便于在 JavaScript 中对其进行操作。例如,Java.use ('java. util. ArrayList') 会返回 ArrayList 类的代理对象,从而可以使用 Frida 来访问和修改该类的属性和方法。-
Java.perform ()
:该方法用于在 Frida 的 JavaScript 环境中执行代码块,这个代码块中可以包含对 Java 类的修改、Hook 方法的实现等操作。 -
implementation
属性:用于指定要 Hook 的方法的新实现,通过设置 implementation 属性,可以在方法执行前后添加自定义的逻辑。例如,targetMethod. implementation = function () { ... } 可以将 targetMethod 替换为自定义的实现。 -
this 和参数:在自定义的方法实现中,可以使用 this 关键字表示原始方法的调用和属性词词。此外,可以使用传入的参数对方法的输入进行修改或记录。
-
调用原始方法:在自定义的方法实现中,通过调用原始方法,可以确保原始方法的行为仍然被执行。例如,this.targetMethod.apply (this, arguments) 可以调用原始的 targetMethod 方法。
方式名称 | 方式说明 | CLI 启动方式 |
---|---|---|
spawn | 将启动 APP 的权利交由 Frida 来控制,不管 APP 是否启动,都会重新启动 APP。 | -参数指定包名 |
attach | 建立在目标 APP 已经启动的情况下,Frida 通过 ptrace 注入程序从而执行 Hook 的操作 | 不加-参数 |
frida -U -l myhook.js -f com.xxx.xxxx
frida -U -l myhook.js -n com.xxx.xxxx
[!note]
frida 运行过程中,执行
%resume
重新注入,执行%reload
来重新加载脚本;执行exit
结束脚本注)
重载函数常用类型
java 中的类型 | frida 里面的类型 |
---|---|
int | int |
float | float |
boolean | boolean |
string | java. lang. String |
char | [C |
byte | [B |
list | java. util. List |
HashMap | java. util. HashMap |
ArrayList | java. util. ArrayList |
JavaObject | java. lang. Object |
String[] | [Ljava. lang. String |
脚本编写
// 修改字段,同字段名前面加一个下划线
obj._name.value = "小七";
// ---------------------------------------------------
// 获取方法,针对类
var money = Java.use("com.Luoge.com.Money")
var methods = money.class.getDeclaredMethods()
console.log(methods)
for (var i=0;i<methods.Length;1++){
console.log("methods--->",methods[i].getName());
}
// 获取构造函数
var contructors = money.class.getDeclaredConstructors();
console.Log(contructors)
for(var jj=0;jj<contructors.length;jj++){
console.log("contructors--->"+contructors.toString())
}
// 获取字段属性
var field money.class.getDeclaredFields()
for(var s=0;s<field.length;s++){
var fd = field[s];
console.Log("field--->"+fd.toString())
}
// hook全部内部类
var classes money.class.getDeclaredclasses();
console.log(classes);
for (var h =0;h<classes.Length;h++){
var cla = classes[h];
console.Log("classes--->"+cla.tostring())
}
// 打印栈信息
function showstacks(){
Java.perform(function (){
console.Log(Java.use("android.util.Log").getstackTracestring(
Java.use("java.lang.Throwable").$new()
));
})
}
// hook控件
var btn_login_id = Java.use("com.dodonew.online.R$id").btn_login.value;
console.log(btn_login_id, '11111111111111');
var View = Java.use('android.view.View');
View.setOnClickListener.implementation = function(listener) {
console.log(this.getId(), "22222222222");
if (this.getId() === btn_login_id) {
showStacks();
console.log("view.id-->" + this.getId());
}
// 调用原始的setOnClickListener方法
return this.setOnClickListener(listener);
};
// hook hashmap
var hashMap = Java.use("java.util.HashMap");
hashMap.put.implementation = function(a,b){
console.log('输出-->', a, b);
// if (a == "username"){
// showStacks() // !!!
// }
return this.put(a,b);
};
// hook 用户输入
Java.perform(function () {
var TextUtils = Java.use("android.text.TextUtils");
TextUtils.isEmpty.implementation = function (aa){
console.log('TextUtils-->', aa);
return this.isEmpty(aa);
}
});
// hook json
var JSONObject = Java.use('org.json.JSONObject');
// Hook JSONObject.put() 方法
JSONObject.put.overload('java.lang.String', 'java.lang.Object').implementation = function(key, value) {
console.log('Hooked JSONObject.put()');
console.log('Key: ' + key.toString());
console.log('Value: ' + value.toString());
// 可在此处对参数进行修改或记录
// 调用原始的put()方法
return this.put(key, value);
};
// hook 排序
Java.perform(function() {
var Collections = Java.use('java.util.Collections');
// Hook sort() 方法
Collections.sort.overload("java.util.List").implementation = function(list) {
console.log('Hooked Collections.sort()');
console.log('List: ' + list.toString());
// 可在此处对参数进行修改或记录
// 使用 Java.cast 进行类型转换 将list转换成ArrayList类型
var res = Java.cast(list, Java.use("java.util.ArrayList"))
console.log('List list-->', res)
// 调用原始的 sort() 方法
return this.sort(list);
};
Collections.sort.overload("java.util.List", "java.util.Comparator").implementation = function(a,b) {
console.log('Hooked Collections.sort()');
showStacks()
var res = Java.cast(a, Java.use("java.util.ArrayList"))
console.log('Comparator list-->', res)
return this.sort(a,b);
};
});
// hook 请求头
Java.perform(function (){
var Builder = Java.use("okhttp3.Request$Builder");
Builder["addHeader"].implementation = function (str, str2) {
console.log("key: " + str)
console.log("val: " + str2)
var result = this["addHeader"](str, str2);
console.log("result: " + result);
return result;
};
});
- 基本框架
Java.perform(function() {
// 主要代码逻辑
});
- 常用 API:
// 获取类
var targetClass = Java.use("完整类名");
// HOOK 构造方法
targetClass.$init.implementation = function() {
// 自定义逻辑
return this.$init(); // 调用原方法
}
// HOOK 普通方法
targetClass.methodName.implementation = function(param1, param2) {
// 自定义逻辑
return this.methodName(param1, param2); // 调用原方法
}
// 重载方法处理
targetClass.methodName.overload('java.lang.String').implementation = function(str) {
// 处理特定参数类型的方法
}
// 主动调用
function demo8(){
var money = Java.use("com.luoge.com.Money");
//非静态字段的修改
Java.choose("com.luoge.com.Money", {
// 每找一次调用一次
// 找到后回调
onMatch: function(obj){
console.log(obj.getInfo());
},
// 找完输出
onComplete: function(){
console.log('内存中Money操作完毕');
}
});
}
// 主动调用
function demo6(){
// 新建对象调用
var res = Java.use("com.luoge.com.Money").$new("非非",18).getInfo();
console.log(res);
// 获取已有的对象调用 一般在rpc会经常使用
Java.choose("com.luoge.com.Money",{
onMatch:function (obj){
console.log("主动调用--》" + obj.getInfo());
},
onComplete:function (){
console.log("内存中搜索完毕")
}
})
}
// 内部类hook
function demo8(){
var InnerClass = Java.use("com.luoge.com.Money$innerClass");
console.log(InnerClass);
// hook内部类的构造函数($init)
InnerClass.$init.implementation = function (name,num){
console.log(name,num);
// 主动调用原始函数
this.$init(name,num);
console.log(this.outPrint());
return this.$init(name,num);
}
}
// hook 字符串转换
function get_String(){
var StringClass = Java.use('java.lang.String');
// Hook String 类的构造函数
StringClass.getBytes.overload().implementation = function () {
console.log('Original Value');
// 可在此处修改传入的字符串参数
var res = this.getBytes();
var newString = StringClass.$new(res)
// 输出修改后的值
console.log('Modified Value: ' + newString);
return res;
};
}
// hook StringBuilder定位字符串
function showStacks() {
Java.perform(function () {
console.log(Java.use("android.util.Log").getStackTraceString(
Java.use("java.lang.Throwable").$new()
));
});
}
Java.perform(function() {
// 获取 StringBuilder 类并定义需要 Hook 的方法名
var stringBuilderClass = Java.use("java.lang.StringBuilder");
stringBuilderClass.toString.implementation = function (){
var res = this.toString.apply(this,arguments)
if (res == "username=13535353535"){
showStacks()
console.log('tostring is called ',res)
}
return res
}
});
- 常用操作:
// 打印堆栈
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
// 创建数组
var ByteArray = Java.array('byte', [ 1, 2, 3, 4 ]);
// 字节数组转字符串
var result = Java.use("java.lang.String").$new(byteArray);
// 创建新对象
var obj = targetClass.$new();
- 启动方式:
# spawn 方式(重新启动应用)
frida -U -f 包名 -l script.js
# attach 方式(附加到运行中的应用)
frida -U 进程名/PID -l script.js
- 实用技巧:
// 遍历所有重载方法
targetClass.methodName.overloads.forEach(function(overload) {
overload.implementation = function() {
console.log("参数: ", arguments);
return overload.apply(this, arguments);
};
});
// 枚举类的所有方法
Java.enumerateLoadedClasses({
onMatch: function(className) {
console.log(className);
},
onComplete: function() {}
});
- 异常处理:
try {
// 可能出错的代码
} catch(e) {
console.log("错误信息: " + e);
console.log("堆栈: " + e.stack);
}
- 常用调试输出:
// 打印参数
console.log("参数1:", param1);
console.log("返回值:", retval);
console.log("this:", JSON.stringify(this));
// 格式化输出
console.log(`方法被调用: ${methodName}`);
- 内存操作:
// 读取内存
Memory.readByteArray(ptr, size);
Memory.readUtf8String(ptr);
// 写入内存
Memory.writeByteArray(ptr, array);
Memory.writeUtf8String(ptr, "text");
python 调用 frida
- Spawn 模式: 在目标设备上启动一个新的应用程序进程,并在该进程中运行 Frida 脚本。这种模式非常适用于对未安装的应用程序进行注入。
-
Attach 模式: 在目标设备上附加到已经运行的应用程序进程,并在该进程中运行 Frida 脚本。这种模式非常适用于对已经运行的应用程序进行调试和分析。
-
USB 模式: 将目标设备通过 USB 连接到计算机上,并使用 Frida 通过 USB 进行通信。这种模式比较适用于对物理上可访问的设备进行注入,但需要确保设备已经打开了 USB 调试模式。
-
Remote 模式: 在远程设备上运行 Frida,并使用 Frida 提供的远程 API 进行通信。这种模式适用于需要对无法直接访问的设备进行注入,例如云服务器、虚拟机等。
Spawn
import frida
import sys
# 通过Spawn模式启动一个新的应用程序进程,并在该进程中加载Frida脚本
device = frida.get_usb_device()
pid = device.spawn(["com.luoge.com"])
session = device.attach(pid)
with open("script.js") as f:
script = session.create_script(f.read())
script.load()
# 恢复应用程序的执行
device.resume(pid)
# 阻塞主线程,以保持脚本运行
sys.stdin.read()
Attach
import sys
import frida
# 通过Attach模式附加到已经运行的应用程序进程,并在该进程中加载Frida脚本
device = frida.get_usb_device()
# 获取目标应用程序的PID
pid = device.get_process("com.luoge.com").pid
# 附加到目标进程
session = device.attach(pid)
# 读取Frida脚本
js_cpde = '''
Java.perform(function () {
console.log(Java.use("android.util.Log").getStackTraceString(
Java.use("java.lang.Throwable").$new()
));
})
'''
# 创建Frida脚本并加载
script = session.create_script(js_cpde)
script.load()
# 阻塞主线程,以保持脚本运行
sys.stdin.read()
USB
import frida
# 通过USB连接将设备连接到计算机上,并在该设备上加载Frida脚本
# 或者使用 frida.get_remote_device
device = frida.get_usb_device()
# 包名或pid都行
session = device.attach(pid)
with open("script.js") as f:
script = session.create_script(f.read())
script.load()
sys.stdin.read() # 阻塞主线程,以保持脚本运行
Remote
import frida
# 在远程设备上启动Frida Server,并将其连接到计算机上
device = frida.get_device_manager().add_remote_device("192.168.0.1:1234")
pid = device.spawn(["com.example.target"])
session = device.attach(pid)
with open("script.js") as f:
script = session.create_script(f.read())
script.load()
device.resume(pid)
## 算法自吐
字符转换
Java.perform(function (){
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
var methods = ByteString.class.getDeclaredMethods();
for(var j = 0; j < methods.length; j++) {
var methodName = methods[j].getName();
console.log(methodName);
}
function toBase64(data){
console.log("[XL]base64-->",ByteString.of(data).base64());
}
function toHex(data){
console.log("[XL]hex-->",ByteString.of(data).hex());
}
function toUtf8(data){
console.log("[XL]utf8-->",ByteString.of(data).utf8());
}
// 测试样例
toUtf8([-27, -92, -113, -26, -76, -101])
toHex([-27, -92, -113, -26, -76, -101])
});
MD5
Java.perform(function (){
// 添加辅助函数定义
function bytesToString(bytes) {
if (!bytes) return '';
var result = [];
for(var i = 0; i < bytes.length; i++) {
result.push(('0' + (bytes[i] & 0xFF).toString(16)).slice(-2));
}
return result.join('');
}
function bytesToHex(bytes) {
return bytesToString(bytes);
}
function bytesToBase64(bytes) {
if (!bytes) return '';
var base64 = Java.use('android.util.Base64');
return base64.encodeToString(bytes, base64.NO_WRAP.value);
}
try {
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
var methods = ByteString.class.getDeclaredMethods();
for(var j = 0; j < methods.length; j++) {
var methodName = methods[j].getName();
console.log(methodName);
}
function toBase64(data){
console.log("[XL]base64-->", ByteString.of(data).base64());
}
function toHex(data){
console.log("[XL]hex-->", ByteString.of(data).hex());
}
function toUtf8(data){
console.log("[XL]utf8-->", ByteString.of(data).utf8());
}
console.log("=========hook MD5算法==============")
var MessageDigest = Java.use("java.security.MessageDigest")
// Hook getInstance方法
MessageDigest.getInstance.overload('java.lang.String').implementation = function (a){
console.log("[*]算法是-->" + a )
return this.getInstance(a)
}
// Hook update方法的几个重载
MessageDigest.update.overload('byte').implementation = function(a){
console.log(a)
return this.update(a)
}
MessageDigest.update.overload('java.nio.ByteBuffer').implementation = function(a){
return this.update(a)
}
// Hook update方法处理字节数组的重载
MessageDigest.update.overload('[B').implementation = function(a){
console.log("=====================================")
console.log("update--->" + bytesToString(a))
return this.update(a)
}
MessageDigest.digest.overload('[B').implementation = function (arg){
console.log("=====================================")
console.log("digest参数 -->" + bytesToString(arg))
var result = this.digest(arg)
console.log("digest结果 (hex) -->" + bytesToHex(result))
console.log("digest结果 (base64) -->" + bytesToBase64(result))
console.log("digest结果 (string) -->" + bytesToString(result))
return result
}
} catch(err) {
console.error(err);
}
})
对称加密算法
function main() {
Java.perform(function () {
// 处理对称算法
var Cipher = Java.use("javax.crypto.Cipher")
// 监控加密模式
Cipher.getInstance.overload('java.lang.String').implementation = function (a) {
var result = this.getInstance(a)
console.log("=======================================")
send("填充模式---》" + a + "《---")
return result
}
Cipher.getInstance.overload('java.lang.String', 'java.lang.String').implementation = function (a, b) {
var result = this.getInstance(a, b)
console.log("=======================================")
send("填充模式---》" + a + "《---" + "|" + b)
return result
}
// 监控密钥
var SecretKeySpec = Java.use("javax.crypto.spec.SecretKeySpec");
SecretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function (a, b) {
console.log("=============================================")
send("算法名:" + b + "|String密钥--》" + bytesToString(a) + "《---")
send("算法名:" + b + "|HEX密钥--》" + bytesToHex(a) + "《---")
send("算法名:" + b + "|Base64密钥--》" + bytesToBase64(a) + "《---")
return this.$init(a, b)
}
// 监控 IV 向量
var IvParameterSpec = Java.use("javax.crypto.spec.IvParameterSpec")
IvParameterSpec.$init.overload('[B').implementation = function (arg) {
console.log("=============================================")
send("|IV偏移量(String)--》" + bytesToString(arg) + "《---")
send("|IV偏移量(HEX)--》" + bytesToHex(arg) + "《---")
return this.$init(arg)
}
// 监控加密/解密过程
Cipher.update.overload('[B').implementation = function (arg) {
console.log("=============================================")
send("update入参 --》--》" + bytesToString(arg) + "《---")
var result = this.update(arg)
return result
}
Cipher.update.overload('[B', 'int', 'int').implementation = function (arg, b, c) {
console.log("=============================================")
send("update入参 --》" + bytesToString(arg) + "《---" + "|" + b + "|" + c)
var result = this.update(arg, b, c)
return result
}
Cipher.doFinal.overload().implementation = function () {
var result = this.doFinal()
send("doFinal加密结果(hex)--》" + bytesToHex(result))
send("doFinal加密结果(base64)--》" + bytesToBase64(result))
return result
}
Cipher.doFinal.overload('[B').implementation = function (arg) {
console.log("=============================================")
var alg = this.getAlgorithm();
console.log(alg)
send(alg + "-doFinal入参 --》" + bytesToString(arg) + "《---")
var result = this.doFinal(arg)
send(alg + "-digest结果(hex)--》" + bytesToHex(result))
send(alg + "-digest结果(base64)--》" + bytesToBase64(result))
return result
}
})
}
// 辅助函数
function bytesToHex(arr) {
var str = "";
for (var i = 0; i < arr.length; i++) {
var tmp = arr[i];
if (tmp < 0) {
tmp = (255 + tmp + 1).toString(16);
} else {
tmp = tmp.toString(16);
}
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}
function bytesToBase64(e) {
var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
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 bytesToString(arr) {
if (typeof arr === 'string') {
return arr;
}
var str = '',
_arr = arr;
for (var i = 0; i < _arr.length; i++) {
var one = _arr[i].toString(2),
v = one.match(/^1+?(?=0)/);
if (v && one.length == 8) {
var bytesLength = v[0].length;
var store = _arr[i].toString(2).slice(7 - bytesLength);
for (var st = 1; st < bytesLength; st++) {
store += _arr[st + i].toString(2).slice(2);
}
str += String.fromCharCode(parseInt(store, 2));
i += bytesLength - 1;
} else {
str += String.fromCharCode(_arr[i]);
}
}
return str;
}
setImmediate(main)
RSA
function main() {
Java.perform(function () {
// RSA 相关 hook
// 监控公钥加载
var X509EncodedKeySpec = Java.use("java.security.spec.X509EncodedKeySpec")
X509EncodedKeySpec.$init.overload('[B').implementation = function (a) {
var res = this.$init(a)
console.log("=============获取公钥文件=================")
send("RSA 公钥(Base64):" + bytesToBase64(a))
send("RSA 公钥(Hex):" + bytesToHex(a))
return res
}
// 监控私钥加载
var PKCS8EncodedKeySpec = Java.use("java.security.spec.PKCS8EncodedKeySpec")
PKCS8EncodedKeySpec.$init.overload('[B').implementation = function (a) {
var res = this.$init(a)
console.log("=============获取私钥文件=================")
send("RSA 私钥(Base64):" + bytesToBase64(a))
send("RSA 私钥(Hex):" + bytesToHex(a))
return res
}
// 监控密钥对生成
var KeyPairGenerator = Java.use("java.security.KeyPairGenerator")
KeyPairGenerator.getInstance.overload('java.lang.String').implementation = function (a) {
console.log("=============密钥对生成=================")
send("密钥算法:" + a)
return this.getInstance(a)
}
// 监控 RSA Cipher 操作
var Cipher = Java.use("javax.crypto.Cipher")
// 监控初始化模式
Cipher.getInstance.overload('java.lang.String').implementation = function (a) {
var result = this.getInstance(a)
if (a.indexOf("RSA") !== -1) {
console.log("=======================================")
send("RSA 模式---》" + a + "《---")
}
return result
}
// 监控加密/解密过程
Cipher.doFinal.overload('[B').implementation = function (arg) {
var alg = this.getAlgorithm();
if (alg.indexOf("RSA") !== -1) {
console.log("=============================================")
console.log(alg)
send(alg + "-doFinal入参 --》" + bytesToString(arg) + "《---")
send(alg + "-doFinal入参(hex)--》" + bytesToHex(arg) + "《---")
send(alg + "-doFinal入参(base64)--》" + bytesToBase64(arg) + "《---")
var result = this.doFinal(arg)
send(alg + "-结果(hex)--》" + bytesToHex(result))
send(alg + "-结果(base64)--》" + bytesToBase64(result))
return result
}
return this.doFinal(arg)
}
// 监控签名操作
var Signature = Java.use("java.security.Signature")
// 监控签名算法
Signature.getInstance.overload('java.lang.String').implementation = function (a) {
console.log("=============签名算法=================")
send("签名算法:" + a)
return this.getInstance(a)
}
// 监控签名数据
Signature.update.overload('[B').implementation = function (data) {
console.log("=============签名数据=================")
send("签名数据(String):" + bytesToString(data))
send("签名数据(Hex):" + bytesToHex(data))
send("签名数据(Base64):" + bytesToBase64(data))
return this.update(data)
}
// 监控签名结果
Signature.sign.overload().implementation = function () {
var result = this.sign()
console.log("=============签名结果=================")
send("签名结果(Hex):" + bytesToHex(result))
send("签名结果(Base64):" + bytesToBase64(result))
return result
}
// 监控验签过程
Signature.verify.overload('[B').implementation = function (sigBytes) {
console.log("=============验签过程=================")
send("待验证签名(Hex):" + bytesToHex(sigBytes))
send("待验证签名(Base64):" + bytesToBase64(sigBytes))
var result = this.verify(sigBytes)
send("验签结果:" + result)
return result
}
})
}
// 辅助函数
function bytesToHex(arr) {
var str = "";
for (var i = 0; i < arr.length; i++) {
var tmp = arr[i];
if (tmp < 0) {
tmp = (255 + tmp + 1).toString(16);
} else {
tmp = tmp.toString(16);
}
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}
function bytesToBase64(e) {
var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
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 bytesToString(arr) {
if (typeof arr === 'string') {
return arr;
}
var str = '',
_arr = arr;
for (var i = 0; i < _arr.length; i++) {
var one = _arr[i].toString(2),
v = one.match(/^1+?(?=0)/);
if (v && one.length == 8) {
var bytesLength = v[0].length;
var store = _arr[i].toString(2).slice(7 - bytesLength);
for (var st = 1; st < bytesLength; st++) {
store += _arr[st + i].toString(2).slice(2);
}
str += String.fromCharCode(parseInt(store, 2));
i += bytesLength - 1;
} else {
str += String.fromCharCode(_arr[i]);
}
}
return str;
}
setImmediate(main)
通用 hook 自吐算法
function main() {
Java.perform(function () {
console.log("==========通用加密 Hook 工具 启动==========")
// 存储加密上下文的全局对象
var cryptoContext = {
currentAlgorithm: "",
key: null,
iv: null,
mode: "",
padding: "",
input: null,
output: null
}
// ================ 监控密钥规格 ================
// 对称密钥监控
var SecretKeySpec = Java.use("javax.crypto.spec.SecretKeySpec")
SecretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function (keyBytes, algorithm) {
console.log("\n[+] 检测到对称加密操作")
console.log("[*] 算法: " + algorithm)
console.log("[*] 密钥信息:")
console.log(" - HEX: " + bytesToHex(keyBytes))
console.log(" - Base64: " + bytesToBase64(keyBytes))
console.log(" - UTF8: " + bytesToString(keyBytes))
cryptoContext.currentAlgorithm = algorithm
cryptoContext.key = keyBytes
return this.$init(keyBytes, algorithm)
}
// IV向量监控
var IvParameterSpec = Java.use("javax.crypto.spec.IvParameterSpec")
IvParameterSpec.$init.overload('[B').implementation = function (ivBytes) {
console.log("\n[+] 检测到 IV 向量")
console.log("[*] IV信息:")
console.log(" - HEX: " + bytesToHex(ivBytes))
console.log(" - Base64: " + bytesToBase64(ivBytes))
console.log(" - UTF8: " + bytesToString(ivBytes))
cryptoContext.iv = ivBytes
return this.$init(ivBytes)
}
// RSA公钥监控
var X509EncodedKeySpec = Java.use("java.security.spec.X509EncodedKeySpec")
X509EncodedKeySpec.$init.overload('[B').implementation = function (pubKeyBytes) {
console.log("\n[+] 检测到 RSA 公钥")
console.log("[*] 公钥信息:")
console.log(" - Base64: " + bytesToBase64(pubKeyBytes))
cryptoContext.currentAlgorithm = "RSA"
cryptoContext.key = pubKeyBytes
return this.$init(pubKeyBytes)
}
// RSA私钥监控
var PKCS8EncodedKeySpec = Java.use("java.security.spec.PKCS8EncodedKeySpec")
PKCS8EncodedKeySpec.$init.overload('[B').implementation = function (privKeyBytes) {
console.log("\n[+] 检测到 RSA 私钥")
console.log("[*] 私钥信息:")
console.log(" - Base64: " + bytesToBase64(privKeyBytes))
cryptoContext.currentAlgorithm = "RSA"
cryptoContext.key = privKeyBytes
return this.$init(privKeyBytes)
}
// ================ 监控加密过程 ================
var Cipher = Java.use("javax.crypto.Cipher")
// 监控加密模式初始化
Cipher.getInstance.overload('java.lang.String').implementation = function (transformation) {
console.log("\n[+] 加密模式初始化")
console.log("[*] 转换方式: " + transformation)
// 解析加密模式和填充
var parts = transformation.split('/')
if (parts.length > 1) {
cryptoContext.mode = parts[1]
cryptoContext.padding = parts[2]
}
return this.getInstance(transformation)
}
// 监控加密/解密数据
Cipher.doFinal.overload('[B').implementation = function (data) {
try {
console.log("\n[+] 加密/解密操作")
console.log("[*] 输入数据:")
// 保护性复制数据
var inputData = Java.array('byte', data);
console.log(" - HEX: " + bytesToHex(inputData))
console.log(" - Base64: " + bytesToBase64(inputData))
console.log(" - UTF8: " + bytesToString(inputData))
// 调用原始方法
var result = this.doFinal(data);
// 保护性复制结果
var outputData = Java.array('byte', result);
console.log("[*] 输出数据:")
console.log(" - HEX: " + bytesToHex(outputData))
console.log(" - Base64: " + bytesToBase64(outputData))
// 安全地存储上下文
if (cryptoContext) {
cryptoContext.input = inputData;
cryptoContext.output = outputData;
}
return result;
} catch (error) {
console.log("[!] Error in doFinal hook: " + error);
// 返回原始调用结果
return this.doFinal(data);
}
}
// ================ 监控消息摘要 ================
var MessageDigest = Java.use("java.security.MessageDigest")
MessageDigest.getInstance.overload('java.lang.String').implementation = function (algorithm) {
console.log("\n[+] 检测到哈希运算")
console.log("[*] 哈希算法: " + algorithm)
cryptoContext.currentAlgorithm = algorithm
return this.getInstance(algorithm)
}
MessageDigest.digest.overload('[B').implementation = function (input) {
console.log("[*] 哈希输入:")
console.log(" - UTF8: " + bytesToString(input))
var result = this.digest(input)
console.log("[*] 哈希结果:")
console.log(" - HEX: " + bytesToHex(result))
console.log(" - Base64: " + bytesToBase64(result))
// 输出重现代码
generateHashReplayCode(cryptoContext.currentAlgorithm, input, result)
return result
}
})
}
// 生成重现代码
function generateReplayCode(context) {
try {
console.log("\n[+] 重现代码:")
if (!context) return;
if (context.currentAlgorithm && context.currentAlgorithm.indexOf("RSA") !== -1) {
console.log("// RSA加密重现代码")
if (context.key) {
console.log("String publicKeyBase64 = \"" + bytesToBase64(context.key) + "\";")
}
if (context.input) {
console.log("byte[] input = Base64.decode(\"" + bytesToBase64(context.input) + "\", Base64.DEFAULT);")
}
console.log("// 请根据实际情况选择加密或解密操作")
} else {
console.log("// 对称加密重现代码")
if (context.key) {
console.log("String key = \"" + bytesToBase64(context.key) + "\"; // Base64编码的密钥")
}
if (context.iv) {
console.log("String iv = \"" + bytesToBase64(context.iv) + "\"; // Base64编码的IV")
}
if (context.input) {
console.log("String input = \"" + bytesToBase64(context.input) + "\"; // Base64编码的输入")
}
console.log("// 算法: " + context.currentAlgorithm +
(context.mode ? "/" + context.mode : "") +
(context.padding ? "/" + context.padding : ""))
}
} catch (error) {
console.log("[!] Error in generateReplayCode: " + error);
}
}
function generateHashReplayCode(algorithm, input, output) {
console.log("\n[+] 哈希重现代码:")
console.log("// " + algorithm + " 哈希计算")
console.log("String input = \"" + bytesToString(input) + "\";")
console.log("// 预期输出:")
console.log("// HEX: " + bytesToHex(output))
console.log("// Base64: " + bytesToBase64(output))
}
// 辅助函数
function bytesToHex(arr) {
var str = "";
for (var i = 0; i < arr.length; i++) {
var tmp = arr[i];
if (tmp < 0) {
tmp = (255 + tmp + 1).toString(16);
} else {
tmp = tmp.toString(16);
}
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}
function bytesToBase64(e) {
var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
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 bytesToString(arr) {
if (typeof arr === 'string') {
return arr;
}
var str = '',
_arr = arr;
for (var i = 0; i < _arr.length; i++) {
var one = _arr[i].toString(2),
v = one.match(/^1+?(?=0)/);
if (v && one.length == 8) {
var bytesLength = v[0].length;
var store = _arr[i].toString(2).slice(7 - bytesLength);
for (var st = 1; st < bytesLength; st++) {
store += _arr[st + i].toString(2).slice(2);
}
str += String.fromCharCode(parseInt(store, 2));
i += bytesLength - 1;
} else {
str += String.fromCharCode(_arr[i]);
}
}
return str;
}
setImmediate(main)
夏洛 hook
function bytesToHex(arr) {
var str = "";
for (var i = 0; i < arr.length; i++) {
var tmp = arr[i];
if (tmp < 0) {
tmp = (255 + tmp + 1).toString(16);
} else {
tmp = tmp.toString(16);
}
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}
function bytesToBase64(e) {
var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
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 bytesToString(arr) {
if (typeof arr === 'string') {
return arr;
}
var str = '',
_arr = arr;
for (var i = 0; i < _arr.length; i++) {
var one = _arr[i].toString(2),
v = one.match(/^1+?(?=0)/);
if (v && one.length == 8) {
var bytesLength = v[0].length;
var store = _arr[i].toString(2).slice(7 - bytesLength);
for (var st = 1; st < bytesLength; st++) {
store += _arr[st + i].toString(2).slice(2);
}
str += String.fromCharCode(parseInt(store, 2));
i += bytesLength - 1;
} else {
str += String.fromCharCode(_arr[i]);
}
}
return str;
}
function bytesToUtf8(arr) {
const utf8Bytes = new Uint8Array(arr);
const decoder = new TextDecoder('utf-8');
const decodedString = decoder.decode(utf8Bytes);
return decodedString
}
function main() {
Java.perform(function () {
console.log('作者-----》夏洛')
var MessageDigest = Java.use("java.security.MessageDigest")
MessageDigest.getInstance.overload('java.lang.String').implementation = function (a) {
console.log("[*]算法是--》" + a + "《---")
return this.getInstance(a)
}
MessageDigest.update.overload('byte').implementation = function (a) {
console.log(a)
return this.update(a)
}
MessageDigest.update.overload('java.nio.ByteBuffer').implementation = function (a) {
return this.update(a)
}
// 字节码 string.getBytes
MessageDigest.update.overload('[B').implementation = function (a) {
console.log("=============================================")
send("update算法传参---》" + bytesToString(a) + "《---")
return this.update(a)
}
MessageDigest.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {
console.log("=============================================")
send("update--->" + bytesToString(a) + "|" + b + "|" + c)
return this.update(a)
}
MessageDigest.digest.overload().implementation = function () {
console.log("=============================================")
var result = this.digest()
send("digest结果(hex)--》" + bytesToHex(result))
send("digest结果(base64)--》" + bytesToBase64(result))
send("digest结果(string)--》" + bytesToString(result))
return result;
}
MessageDigest.digest.overload('[B').implementation = function (arg) {
console.log("=============================================")
send("digest算法传参---》" + bytesToString(arg) + "《---")
var result = this.digest(arg)
send("digest结果(hex)--》" + bytesToHex(result))
send("digest结果(base64)--》" + bytesToBase64(result))
send("digest结果(string)--》" + bytesToString(result))
return result;
}
// 处理对称算法 对于算法还原 需要知道哪些信息?? 明文 key iv 模式
var Cipher = Java.use("javax.crypto.Cipher")
Cipher.getInstance.overload('java.lang.String').implementation = function (a) {
var result = this.getInstance(a)
console.log("=======================================")
send("填充模式---》" + a + "《---")
return result
}
Cipher.getInstance.overload('java.lang.String', 'java.lang.String').implementation = function (a, b) {
var result = this.getInstance(a, b)
console.log("=======================================")
send("填充模式---》" + a + "《---" + "|" + b)
return result
}
// var DESKeySpec = Java.use("javax.crypto.spec.DESKeySpec")
// DESKeySpec.$init.overload('[B').implementation = function (a){
// console.log("=============================================")
// send("密钥文件是---》" + bytesToString(a) + "《---")
// send("密钥文件是---》" + bytesToHex(a) + "《---")
// send("密钥文件是---》" + bytesToBase64(a) + "《---")
// return this.$init(a)
// }
var SecretKeySpec = Java.use("javax.crypto.spec.SecretKeySpec");
SecretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function (a, b) {
console.log("=============================================")
send("算法名:" + b + "|String密钥--》" + bytesToString(a) + "《---")
send("算法名:" + b + "|HEX密钥--》" + bytesToHex(a) + "《---")
send("算法名:" + b + "|Base64密钥--》" + bytesToBase64(a) + "《---")
return this.$init(a, b)
}
// 操作 iv 偏移量
var IvParameterSpec = Java.use("javax.crypto.spec.IvParameterSpec")
IvParameterSpec.$init.overload('[B').implementation = function (arg) {
console.log("=============================================")
send("|IV偏移量(String)--》" + bytesToString(arg) + "《---")
send("|IV偏移量(HEX)--》" + bytesToHex(arg) + "《---")
// send("|IV偏移量(Base64)--》" + bytesToBase64(arg) + "《---")
var result = this.$init(arg)
return result
}
Cipher.update.overload('[B').implementation = function (arg) {
console.log("=============================================")
send("update入参 --》--》" + bytesToString(arg) + "《---")
var result = this.update(arg)
return result
}
Cipher.update.overload('[B', 'int', 'int').implementation = function (arg, b, c) {
console.log("=============================================")
send("update入参 --》" + bytesToString(arg) + "《---" + "|" + b + "|" + c)
var result = this.update(arg, b, c)
return result
}
Cipher.doFinal.overload().implementation = function () {
var result = this.doFinal()
send("doFinal加密结果(hex)--》" + bytesToHex(result))
send("doFinal加密结果(base64)--》" + bytesToBase64(result))
return result
}
Cipher.doFinal.overload('[B').implementation = function (arg) {
console.log("=============================================")
var alg = this.getAlgorithm();
console.log(alg)
send(alg + "-doFinal入参 --》" + bytesToString(arg) + "《---")
var result = this.doFinal(arg)
send(alg + "-digest结果(hex)--》" + bytesToHex(result))
send(alg + "-digest结果(base64)--》" + bytesToBase64(result))
return result
}
var mac = Java.use("javax.crypto.Mac")
mac.update.overload('[B').implementation = function (arg) {
console.log("=============================================")
var alg = this.getAlgorithm();
console.log(alg)
send(alg + "-update入参 --》--》" + bytesToString(arg) + "《---")
var result = this.update(arg)
return result
}
mac.update.overload('[B', 'int', 'int').implementation = function (arg, b, c) {
console.log("=============================================")
send("MAC update入参 --》" + bytesToString(arg) + "《---" + "|" + b + "|" + c)
var result = this.update(arg, b, c)
return result
}
mac.doFinal.overload().implementation = function () {
console.log("=============================================")
var alg = this.getAlgorithm();
var result = this.doFinal()
send(alg + "-doFinal加密结果(hex)--》" + bytesToHex(result))
send(alg + "-doFinal加密结果(base64)--》" + bytesToBase64(result))
return result
}
mac.doFinal.overload('[B').implementation = function (arg) {
console.log("=============================================")
var alg = this.getAlgorithm();
send(alg + "-doFinal入参 --》" + bytesToString(arg) + "《---")
var result = this.doFinal(arg)
send(alg + "-digest结果(hex)--》" + bytesToHex(result))
send(alg + "-digest结果(base64)--》" + bytesToBase64(result))
return result
}
// RSA算法 密钥文件 加密之后的结果
// 尝试下base64
var b64 = Java.use("android.util.Base64")
b64.decode.overload('java.lang.String', 'int').implementation = function (a, b) {
send("b64接受入参--》" + a + "***|*" + b)
return this.decode(a, b)
}
var X509EncodedKeySpec = Java.use("java.security.spec.X509EncodedKeySpec")
X509EncodedKeySpec.$init.overload('[B').implementation = function (a) {
var res = this.$init(a)
console.log("=============获取公钥文件=================")
send("RSA 密钥:" + bytesToBase64(a))
return res
}
})
}
setImmediate(main)
RPC
console.log("Script loaded successfully ");
function callSecretFun() { //定义导出函数
Java.perform(function () { //找到隐藏函数并且调用
Java.choose("com.xxxx.demo02.MainActivity", {
// 主动调用开始
onMatch: function (instance) {
console.log("Found instance: " + instance);
console.log("Result of secret func: " + instance.secret());
},
// 执行完毕
onComplete: function () { }
});
});
}
rpc.exports = {
// 把callSecretFun函数导出为callsecretfunction符号,
// 导出名不可以有大写字母或者下划线
callsecretfunction: callSecretFun
};
python 调用
def get_sign(datas):
jscode = '''...'''
process = frida.get_usb_device().attach('com.taobao.idlefish')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
result = script.exports.sign(datas["data"], st)
# print("闲鱼x-sign结果:", result)
return result
脱壳
利用 frida 的搜索内存,通过匹配 DEX 文件的特征,例如 DEX 文件的文件头中的 magic-dex. 035 这个特征。frida-Dexdump 便是这种脱壳方法的代表作。
- 对于于完整的 dex, 采用暴力搜索 dex 035 即可找到。
- 而对于抹头的 dx, 通过匹配一些特征来找到,然后自动修复文件头。
frida-dexdump
脱壳要刷手机
https://github.com/hluwa/FRIDA-DEXDump
附件
frida-dexdump -FU
spwan 模式
frida-dexdump -U -f com.app.pkgname
查询类
findstr /s /i /m "com.uzmap.pkg.LauncherUI" *
GDA 4
objection
应用注入
objection -g 包名 explore
指定 ip 和端口链接
objection -N -h 192.168.0.0 -p 9999 -g packageName explore
spawn 启动
objection -g packageName explore --startup-command "android hooking watch class'com.xx.classname"
spawn 启动-打印参数,返回值,函数调用栈
objection -g packageName explore --startup-command "android hooking watch class_method 'com.classname.methedname' --dump-args --dump-return --dump-backtrace"
参数说明:
--dump-args: 显示参数
--dump-return: 显示返回值
--dump-backtrace: 显示堆栈
# 测试豆瓣签名信息注入
objection -g com.douban.frodo explore --startup-command "android hooking watch class_method com.douban.frodo.network.ApiSignatureHelper.a --dump-args --dump-return --dump-backtrace"
类相关
# 列出所有已加载的类
android hooking list classes
# 搜索特定类
android hooking search classes signature
# 查看类的方法
android hooking list class_methods com.douban.frodo.network.ApiSignatureHelper
# 监控特定方法
android hooking watch class_method com.douban.frodo.network.ApiSignatureHelper.a --dump-args --dump-return --dump-backtrace
命令
# === 基础命令 ===
! # 执行操作系统命令(宿主机上)
cd # 改变当前工作目录
pwd # 打印当前工作目录
ls # 列出当前工作目录下的文件
rm # 从设备上删除文件
exit # 退出
ping # ping agent
reconnect # 重新连接设备
# === Android 命令 ===
android
# --- 剪贴板 ---
clipboard
monitor # 监控剪贴板
# --- 堆操作 ---
heap
evaluate # 在 Java 类中执行 JavaScript 脚本
execute # 在 Java 类中执行方法
print # 打印堆信息
search
instances # 搜索类的实例
# --- Hook 相关 ---
hooking
generate
class # 生成类的通用 hook
simple # 生成类方法的简单 hook
get
current_activity # 获取当前前景 activity
list
activities # 已注册的 Activities
class_loaders # 已注册的 class loaders
class_methods # 类的可用方法
classes # 当前加载的所有类
receivers # 已注册的 BroadcastReceivers
services # 已注册的 Services
search
classes 关键字 # 搜索匹配的 Java 类
methods 关键字 # 搜索匹配的 Java 方法
set
return_value # 设置方法返回值(仅支持布尔值)
watch
class # 监控类的所有方法调用
class_method # 监控特定类方法的调用
# --- Intent 相关 ---
intent
launch_activity # 启动 Activity
launch_service # 启动 Service
# --- KeyStore 相关 ---
keystore
clear # 清除 KeyStore
list # 列出 KeyStore 条目
watch # 监控 KeyStore 使用
# --- 其他功能 ---
proxy
set # 设置应用代理
root
disable # 禁用 root 检测
simulate # 模拟 root 环境
sslpinning
disable # 禁用 SSL pinning
ui
FLAG_SECURE # 控制当前 Activity 的 FLAG_SECURE
screenshot # 当前 Activity 截图
# === 内存操作 ===
memory
dump
all 文件名 # 转储整个进程内存
from_base 起始地址 字节数 文件 # 转储指定范围内存
list
exports # 列出模块的导出
modules # 列出已加载的模块
search # 搜索内存模式
write # 写入原始字节到内存
# === 实用工具 ===
commands
clear # 清除命令历史
history # 列出命令历史
save # 保存命令历史到文件
evaluate # 执行 JavaScript
env # 打印环境信息
file
cat # 打印文件内容
download # 下载文件
upload # 上传文件
http
start # 启动 HTTP 服务器
status # 获取 HTTP 服务器状态
stop # 停止 HTTP 服务器
sqlite # SQLite 数据库操作
connect # 连接数据库文件
jobs
kill # 结束任务
list # 列出当前任务
import # 导入并运行 frida 脚本
memory
search <value> # 在进程内存中搜索包含指定值的内存位置
string <address> # 从指定内存地址开始,打印内存中的 ASCII 字符串
dump <address> <length> # 从指定的内存地址开始,以十六进制格式打印指定长度的内存内容
write <address> <value> # 将指定的值写入到指定的内存地址中
alloc <size> # 在进程内存中分配指定大小的内存块
dealloc <address> # 释放指定地址的内存块 **
# === 常用命令示例 ===
# 枚举内存中所有模块
memory list modules
# 将模块列表保存为 JSON 格式
memory list modules --json xx.log
# 列举指定 so 文件的导出方法
memory list exports libEGL_adreno.so
# 获取类示例 ID
search instances search instances com.xx.xx.class
# 禁用 SSL Pinning
android sslpinning disable
内存漫游
# === Android Hooking 常用命令 ===
# 枚举加载的 activities
android hooking list activities
# 列出内存中所有的类
android hooking list classes
# 在内存中所有已加载的类中搜索包含特定关键词的类
android hooking search classes key
# 列出类的所有方法
android hooking list class_methods 包名.类名
# hook类的所有方法
android hooking watch class 包名.类名
# hook方法的参数、返回值和调用栈
android hooking watch class_method 包名.类名.方法 --dump-args --dump-return --dump-backtrace
# 默认会Hook方法的所有重载
android hooking watch class_method 包名.类名.方法
# 如果只需hook其中一个重载函数(指定参数类型,多个参数用逗号分隔)
android hooking watch class_method 包名.类名.方法 "包名.类名.方法"
# 搜索堆中的实例
android heap search instances 包名.类名 --fresh
# 设置返回值(只支持bool类型)
android hooking set return_value com.xxx.xxx.methodName false
抓包
使用 keystore_dump.js
,把 ssl 证书 dump 下来,证书密码 hooker,快速刷 app,不要开抓包
frida -Uf com.mmzztt.app --no-pause -l keystore_dump.js
在https双向认证的情况下,dump客户端证书为p12. 存储位置:/data/user/0/{packageName}/client_keystore_{nowtime}.p12
代理端口,host:443
charles 配置 ssl 证书,密码 105105
reqable 配置 ssl 证书
爬虫携带证书
from requests_pkcs12 import Pkcs12Adapter
session.mount(url,Pkcs12Adapter(pkcs12_filename='./client_keystore__2023_10_26_14_46_47_73.p12',pkcs12_password="hooker"))
spdy 协议
hook url 判断是否包含协议
function showStacks() {
Java.perform(function () {
console.log("------------------------------------------------------------------\n");
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
console.log("------------------------------------------------------------------\n");
});
}
Java.perform(function () {
// Hook url 初始化类
const url = Java.use("java.net.URL");
url.$init.overload("java.lang.String").implementation = function (v1) {
//这里判断一下是否包含xxxxxxxx
// if (v1.indexOf("/client?") > 0) {
console.log(v1);
showStacks();
// }
return this.$init(v1);
}
})
判断开启关键词
hook 关闭
Java.perform(function () {
var SwitchConfig = Java.use('mtopsdk.mtop.global.SwitchConfig');
SwitchConfig.A.overload().implementation = function () {
return false;
}
});
r0capture
- Spawn 模式:
python3 r0capture.py -U -f 包名 -v
- Attach 模式,抓包内容保存成pcap文件供后续分析:
python3 r0capture.py -U 包名 -v -p iqiyi.pcap
- 客户端证书导出功能:默认开启;必须以Spawm模式运行;
- 导出后的证书位于/sdcard/Download/包名xxx.p12路径,导出多次,每一份均可用,密码默认为:r0ysue,推荐使用 keystore-explorer 打开查看证书。
-
特殊端口抓包
python r0capture.py -H 192.168.8.112:10002 com.sinovatech.unicom.ui
APKtools反编译
# 一、基础命令选项
# 1. 查看版本
apktool --version
# 2. 详细输出(必须作为第一个参数)
apktool -v
# 3. 静默输出(必须作为第一个参数)
apktool -q
# 4. 打印详细操作信息
apktool -advance
# 二、反编译(Decode)命令选项
# 基本格式: apktool d [选项] <apk文件路径>
# 1. 设置 API 等级
apktool d -api 21 test.apk
# 2. 不输出调试信息
apktool d -b test.apk
# 3. 强制覆盖已存在的文件
apktool d -f test.apk
# 4. 强制反编译 AndroidManifest.xml
apktool d --force-manifest test.apk
# 5. 跳过资源错误
apktool d --keep-broken-res test.apk
# 6. 保持原始文件格式(不可重新打包)
apktool d -m test.apk
# 7. 不处理未知资源文件
apktool d --no-assets test.apk
# 8. 指定输出路径
apktool d -f test.apk -o ./decode
# 9. 仅反编译主 dex 文件
apktool d --only-main-classes test.apk
# 10. 不反编译资源文件
apktool d -r test.apk
# 11. 不反编译代码
apktool d -s test.apk
# 三、回编(Build)命令选项
# 基本格式: apktool b [选项] <反编译文件夹路径>
# 1. 指定 aapt 路径
apktool b -a /path/to/aapt test/
# 2. 设置 API 等级
apktool b -api 10 test/
# 3. 复制原始 manifest 文件(即将弃用)
apktool b -c test/
# 4. 添加 debuggable 标记
apktool b -d test/
# 5. 强制覆盖已存在文件
apktool b -f test/
# 6. 禁止资源文件处理
apktool b -nc test/
# 7. 指定输出路径
apktool b test/ -o test_unsigned.apk
# 8. 使用 aapt2 打包
apktool b test/ --use-aapt2
通常文件名称资源在 res/values/strings.xml
生成 key
keytool -genkey -alias new.keystore -keyalg RSA -validity 20000 -keystore new.keystore
签名
jarsigner -verbose -keystore new.keystore -signedjar news.apk old.apk new.keystore
Comments NOTHING