版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
前言
逆向目标是一个第三方 SDK,核心代码在 so 层,已知 so 有加壳。
调用入口是 *******************************************,会触发 libhexymsb.so 中的 token 和 time 检查方法,目标是绕过检查并成功调用 SDK。
Java 层代码逆向分析
通过 Frida hook 一下 目标类
/**
* Hook 指定类的所有方法(每个方法所有重载)
* @param {string} className - Java 类的完整名
*/
function hook_all_methods(className) {
Java.perform(function () {
var clazz = Java.use(className);
var methods = clazz.class.getDeclaredMethods(); // 反射获取所有声明的方法
var hooked = new Set(); // 用于避免重复 hook 相同方法名(因为多重载)
methods.forEach(function (m) {
var methodName = m.getName();
// 如果这个方法已经 Hook 过,就跳过
if (hooked.has(methodName)) return;
hooked.add(methodName);
try {
hook_method(className, methodName);
} catch (e) {
console.error("❌ Failed to hook " + methodName + ": " + e);
}
});
});
}
setImmediate(function () {
hook_all_methods("*******************************************");
});
// frida -H 127.0.0.1:1234 -F -l hook_class_methods.js
得到日志如下:
================= HOOK START =================
🎯 Class: *******************************************
🔧 Method: hdic
📥 Arguments:
[0]: com.superbock.App@48a2d8d
[1]: com.superbock.ui.StartupActivity
[2]: 1
📤 Return value: undefined
================== HOOK END ==================
调用的是一个 native 方法:public final native void hdic(Context context, String mainActivityClassName, int type);

JNI 函数地址追踪
so 中并没有找到对应的 native 方法,说明是动态注册的。

通过 jni_addr.py 脚本追踪一下目标类的 jni 函数地址
def main():
class_name = "*******************************************"
device = frida.get_device_manager().add_remote_device("127.0.0.1:1234")
pid = device.get_frontmost_application().pid
session: frida.core.Session = device.attach(pid)
script = session.create_script(read_frida_js_source("jni_addr.js"))
script.on('message', on_message)
script.load()
script.exports.getjnimethodaddr(class_name)
# 退出
session.detach()
具体参考:逆向 JNI 函数找不到入口?动态注册定位技巧全解析
得到日志如下,hdic 函数入口在 libhexymsb.so,偏移 0x37c3c 的位置
------------ [ #34 Native Method ] ------------
Method Name : public final native void *******************************************.hdic(android.content.Context,java.lang.String,int)
ArtMethod Ptr : 0x7d52b1bb38
Native Addr : 0x7c682c4c3c
Module Name : libhexymsb.so
Module Offset : 0x37c3c
Module Base : 0x7c6828d000
Module Size : 335872 bytes
Module Path : /data/app/com.demotestapp.test-9uO6vVLGXVJ71eZiJvIxYQ==/lib/arm64/libhexymsb.so
------------------------------------------------
so 反汇编代码分析
使用 IDA Pro 打开 libhexymsb.so 按 G 跳转到 0x37c3c ,再 F5 一下得到反汇编代码如下:

反汇编代码分析:
1、参数准备逻辑
v15[2] = a3;
v15[3] = a4;
v15[0] = 0;
v15[1] = a2;
v15[4] = a5;
把 a2~a5 填入参数数组 v15,供后续解释器读取。a1 是 JNIEnv*,a2 是 jboject,a3 是 Context,a4 是 String,a5 是 int
2、结构体和 flag 设置
v11 = 0;
v12 = 257;
v14 = 0;
v13 = 1;
这些字节构成一个紧凑的 flag 结构,代表不同标志配置,可能是传递给 vmInterpret 函数的执行参数(例如:执行类型、安全等级、是否调试模式等)。
3、准备参数指针
v6 = &unk_24202;
v7 = 32;
v8 = v15;
v9 = &v11;
v10 = 0;
v6 是一个指针,指向 .rodata 段中的某个地址,由于 IDA 并不知道这块内存是什么类型,所以叫 unk_,即“未知类型”。
.rodata + 0x24202 是一个只读区域的地址,它的第一个字节是 0xC7。
.rodata:0000000000024202 C7 unk_24202 DCB 0xC7 ; DATA XREF: sub_37C3C+20↓o
这些变量构成一个参数结构体,最终会被传入:
return vmInterpret(a1, &v6, &off_46C50);
-
a1: JNIEnv指针。
-
&v6: 应该是所有参数结构组合的起始指针。
-
off_46C50: 通常是某个回调表或解释器指令表。
off_46C50 保存的是 sub_438F4 函数地址
.data.rel.ro:0000000000046C50 F4 38 04 00 00 00 00 00 off_46C50 DCQ sub_438F4 ; DATA XREF: sub_33FF0+3C↑o
sub_438F4 函数地址反汇编代码如下:

VMP 壳分析
使用 Frida Hook 一下 sub_438F4 函数并打印堆栈、参数和返回值
const libName = "libhexymsb.so";
const offset = 0x438F4;
function hookFunction() {
let baseAddr = Module.findBaseAddress(libName);
if (!baseAddr) {
console.error(`❌ 找不到模块 ${libName}`);
return;
}
let funcAddr = baseAddr.add(offset);
console.log(`✅ Hooking ${libName} at offset 0x${offset.toString(16)} => ${funcAddr}`);
let callCount = 0;
Interceptor.attach(funcAddr, {
onEnter(args) {
this.callId = ++callCount;
this.jniEnv = args[0];
this.arg = args[1].toUInt32();
this.log = [];
this.log.push(`\n================== 🚀 Call #${this.callId} Start ==================`);
this.log.push(`🧵 ThreadId: ${Process.getCurrentThreadId()}`);
this.log.push(`🔹 JNIEnv*: ${this.jniEnv}`)
this.log.push(`🔹 unsigned int a2: ${this.arg}`);
// 可选打印调用栈
let backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress)
.join("\n ");
this

最低0.47元/天 解锁文章
1万+

被折叠的 条评论
为什么被折叠?



