逆向-Frida扫描
😘5ea1
#本地层
直接扫本地全局变量
let pairs = [];
function hex(num) {
return [
num & 0xFF,
(num >> 8) & 0xFF,
(num >> 16) & 0xFF,
(num >> 24) & 0xFF
].map(b => b.toString(16).padStart(2, '0')).join(' ');
}
function first_scan(data) {
pairs = [];
const module = Process.getModuleByName("libezscan.so");
const base = module.base;
const size = module.size;
const pattern = hex(data);
for (const pair of Memory.scanSync(base, size, pattern)) {
pairs.push(pair);
console.log(`Found at ${pair.address}: ${Memory.readU32(pair.address)}`);
}
if (pairs.length === 0) {
console.log("No matches found.");
} else {
console.log(`Total matches: ${pairs.length}`);
}
}
function read() {
if (pairs.length === 0) {
console.log("pairs 未初始化或为空。请先调用 first_scan()");
return;
}
for (const r of pairs) {
try {
const val = Memory.readU32(r.address);
console.log(`地址 ${r.address}: ${val}`);
} catch (e) {
console.log(`地址 ${r.address} 读取失败: ${e.message}`);
}
}
}
function change(addrStr, newVal) {
for (const r of pairs) {
if (r.address.toString() === addrStr) {
try {
Memory.writeU32(r.address, newVal);
console.log(`地址 ${r.address} 已修改为: ${newVal}`);
} catch (e) {
console.log(`地址 ${r.address} 修改失败: ${e.message}`);
}
return;
}
}
console.log("未找到目标地址。");
}
原理:直接把要搜索是数据转换成16进制字节,然后将这个字节作为一个模式在内存中进行匹配,内存中直接读取要检索的本地库,然后直接在这个本地库的整个内存中检索;
缺点:如果要检索的变量是某个函数中的一个局部变量,那么这个变量实际上是在栈上的,此时这个变量不能被检索到;全局变量可以被检索到
但是由于此处本地库中返回到java层的行为是直接创建随机数并返回的,所以修改这个全局变量的时候,事实上返回的行为已经结束了,所以不能做到修改的操作
##hook本地函数
function module_export() {
var modules = Module.enumerateExports("libezscan.so");
for (var i = 0; i < modules.length; i++) {
var symbol = DebugSymbol.fromAddress(modules[i].address);
console.log(`函数名: ${symbol.name} + @ ${modules[i].address}`);
if(symbol.name.includes("native_get_random")) {
Interceptor.attach(modules[i].address, {
onEnter: function(args) {
console.log("native_get_random called");
},
onLeave: function(retval) {
console.log(`native_get_random returned: ${retval}`);
retval.replace(525); // 替换为你想要的值
}
});
}
}
}
原理:首先使用enumerateExports来获取到要hook的本地库,然后在本地库中注册的符号表中检索你要hook的函数,如果检索到了,直接获取模块的地址进行hook;由于是直接修改返回值,所以可以成功修改
#java层
##扫Java层变量
function scan_java() {
Java.perform(() => {
const targets = [];
Java.enumerateLoadedClasses({
onMatch: function (className) {
if (className.startsWith("com.seal")) {
targets.push(className);
}
},
onComplete: function () {
console.log(`共发现 ${targets.length} 个类,开始分析字段...`);
processClasses(targets);
}
});
function processClasses(classNames) {
classNames.forEach(className => {
try {
const clazz = Java.use(className);
console.log(`[Class] ${className}`);
const fields = clazz.class.getDeclaredFields();
fields.forEach(field => {
try {
field.setAccessible(true);
const isStatic = (field.getModifiers() & 8) !== 0;
if (isStatic) {
const val = field.get(null);
console.log(` ↳ ${field.getName()} (static) = ${val}`);
} else {
console.log(` ↳ ${field.getName()} (instance) = [需 Java.choose 实例访问]`);
}
} catch (e) {
console.log(` ↳ 字段读取失败: ${e}`);
}
});
} catch (e) {
console.log(`[!] 处理 ${className} 失败: ${e}`);
}
});
console.log("✅ 所有类字段分析完毕");
}
});
}
原理:首先借助enumerateLoadedClass函数获取当前java层加载的所有类,然后从这些类中检索出包名正确的类(在此处检索条件为com.seal开头),检索到了以后,使用use方法加载class,再用getDeclaredFields方法获取到每个class中的全部的field;由于static状态下的field和其他状态下的field获取值的方法不一样,所以此处需要使用getModifiers方法来获取到field对应的一个标志位,通过标志位的计算判别这个field是不是static,如果是,则可以直接get数值;如果不是,则需要遍历当前的class中的每个field,重新创建实例,然后再按照实例依次获取实例中的数值
hookjava层函数
function hook_java_static() {
Java.perform(function () {
var MainActivity = Java.use("com.seal.ezscan.MainActivity");
var current = MainActivity.number.value;
console.log("当前 number 值是:", current);
MainActivity.number.value = 100;
});
}
直接通过包名和类名hook java层中对应的函数