Android 逆向

Sherry 发布于 2025-03-27 30 次阅读 文章 最后更新于 2025-03-27 11254 字


逆向步骤

  • 软件是否能正常运行,是否带有检测
  • 抓包分析是否有需要逆向的加密字段
  • 判断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 文件名

被检测

  1. Java.use ():该方法用于获取 Java 类对象,以便于在 JavaScript 中对其进行操作。例如,Java.use ('java. util. ArrayList') 会返回 ArrayList 类的代理对象,从而可以使用 Frida 来访问和修改该类的属性和方法。

  2. Java.perform ():该方法用于在 Frida 的 JavaScript 环境中执行代码块,这个代码块中可以包含对 Java 类的修改、Hook 方法的实现等操作。

  3. implementation 属性:用于指定要 Hook 的方法的新实现,通过设置 implementation 属性,可以在方法执行前后添加自定义的逻辑。例如,targetMethod. implementation = function () { ... } 可以将 targetMethod 替换为自定义的实现。

  4. this 和参数:在自定义的方法实现中,可以使用 this 关键字表示原始方法的调用和属性词词。此外,可以使用传入的参数对方法的输入进行修改或记录。

  5. 调用原始方法:在自定义的方法实现中,通过调用原始方法,可以确保原始方法的行为仍然被执行。例如,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;
    };
});

  1. 基本框架
Java.perform(function() {
    // 主要代码逻辑
});
  1. 常用 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
    }
});
  1. 常用操作:
// 打印堆栈
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();
  1. 启动方式:
# spawn 方式(重新启动应用)
frida -U -f 包名 -l script.js

# attach 方式(附加到运行中的应用)
frida -U 进程名/PID -l script.js
  1. 实用技巧:
// 遍历所有重载方法
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() {}
});
  1. 异常处理:
try {
    // 可能出错的代码
} catch(e) {
    console.log("错误信息: " + e);
    console.log("堆栈: " + e.stack);
}
  1. 常用调试输出:
// 打印参数
console.log("参数1:", param1);
console.log("返回值:", retval);
console.log("this:", JSON.stringify(this));

// 格式化输出
console.log(`方法被调用: ${methodName}`);
  1. 内存操作:
// 读取内存
Memory.readByteArray(ptr, size);
Memory.readUtf8String(ptr);

// 写入内存
Memory.writeByteArray(ptr, array);
Memory.writeUtf8String(ptr, "text");

python 调用 frida

  1. Spawn 模式: 在目标设备上启动一个新的应用程序进程,并在该进程中运行 Frida 脚本。这种模式非常适用于对未安装的应用程序进行注入。

  2. Attach 模式: 在目标设备上附加到已经运行的应用程序进程,并在该进程中运行 Frida 脚本。这种模式非常适用于对已经运行的应用程序进行调试和分析。

  3. USB 模式: 将目标设备通过 USB 连接到计算机上,并使用 Frida 通过 USB 进行通信。这种模式比较适用于对物理上可访问的设备进行注入,但需要确保设备已经打开了 USB 调试模式。

  4. 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