逆向-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层中对应的函数