banner
约 1,200 字
4 分钟

记一次某银行app的逆向分析,过frida检测脱壳,登录加密参数分析

无标签

作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!


前言:本人也是android逆向新手,本文主要记录和回顾整理一些分析过程,因此并不能做到每处细节详解,主要为同样入门学习的朋友提供些思路和案例,高手大佬也还请多指教。


过frida反调试#

首先写个测试脚本然后frida spwan方式注入看看是否存在注入检测

纯文本
function hook_test() {
    Java.perform(function () {
        console.log("hook test called");
    });
}

可以看到app启动后弹出弹窗但是进程并没有杀死,应该是存在frida检测。

blog image
blog image

之前学习frida检测的案例时候,大多都是杀掉进程,这里有些不一样。


blog image
blog image

也尝试过通过pthread_create、clone等寻找检测点,但是没有成功待以后技术有所提升或有相关思路在进行分析吧。目前还是换个思路找弹窗的触发时机并hook拦截,经验证该方法可行,因为不杀掉进程不影响后续分析。


blog image
blog image


可以看到成功拦截弹窗了,hook_dialog.js脚本如下:

纯文本
Java.perform(function () {
    const Dialog = Java.use('android.app.Dialog');
    const Log = Java.use('android.util.Log');
    const Exception = Java.use('java.lang.Exception');

    Dialog.show.implementation = function () {
        const stack = Log.getStackTraceString(Exception.$new());

        if (stack.indexOf('injectmonitor') !== -1) {
            console.log('\n[!!!] Inject monitor dialog triggered!');
            console.log(stack);

            // 这里可以直接阻断
            return;
        }

        return this.show();
    };
});

抓包#

接下来就是抓包,有抓包检测。如图


blog image
blog image

这里需要绕过SSL pinning,成功后抓到包了。


blog image
blog image

脱壳#

然后打开jadx静态分析下,搜索PostData,因为加固的原因应该是搜索不到有用的信息的。


blog image
blog image

需要脱壳处理,首先整体脱下,我是通过hook ClassLinker:DefineClass。hook_define_class.js脚本

纯文本
function hook_define_class() {
    var module_name = "libart.so";
    var target_module = Process.findModuleByName(module_name);

    if (!target_module) {
        console.log("[-] 未找到 " + module_name);
        return;
    }


    // var dump_dir = "/sdcard/dex_dump";
    var dump_dir="/data/data/包名/cache"
    var symbols = target_module.enumerateSymbols();
    var target_addr = null;

    for (var i = 0; i < symbols.length; i++) {
        var name = symbols[i].name;
        if (name.indexOf("ClassLinker") !== -1 && name.indexOf("DefineClass") !== -1) {
            if (name.indexOf("Thread") !== -1 && name.indexOf("DexFile") !== -1) {
                target_addr = symbols[i].address;
                break;
            }
        }
    }

    if (!target_addr) {
        console.log("[-] 未能定位到 ClassLinker::DefineClass 函数");
        return;
    }

    console.log("[*] 成功hook DefineClass,准备本地 Dump...");
    var dumped_dex = {};

    Interceptor.attach(target_addr, {
        onEnter: function (args) {
            try {
                var className = Memory.readUtf8String(args[2]);
                var dex_file_ptr = args[5];

                if (dex_file_ptr.isNull()) return;

      
                var base = null;
                var size = 0;

                for (var offset = 1; offset <= 3; offset++) {
                    try {
                        var maybe_base = dex_file_ptr.add(offset * Process.pointerSize).readPointer();
                        var magic = Memory.readUtf8String(maybe_base, 4);
                        if (magic === "dex\n") {
                            base = maybe_base;
                            size = dex_file_ptr.add((offset + 1) * Process.pointerSize).readPointer().toInt32();
                            break;
                        }
                    } catch (e) {}
                }

                // 3. 执行本地写入
                if (base && size > 0) {
                    var base_str = base.toString();
                    if (!dumped_dex[base_str]) {
                        dumped_dex[base_str] = true;

                        // 构造文件名:以十六进制基址和大小命名
                        var file_path = dump_dir + "/" + base_str + "_" + size + ".dex";

                        console.log("\n[+] 发现新 DEX (来自类: " + className + ")");
                        console.log("    尝试保存至: " + file_path);

                        try {
                            // 写入手机本地
                            var file = new File(file_path, "wb");
                            file.write(base.readByteArray(size));
                            file.flush();
                            file.close();
                            console.log("    [✓] Dump 成功!大小: " + size + " 字节");
                        } catch (file_err) {
                            console.log("    [✗] 写入失败。原因可能为权限不足(Android 12 分区存储限制)");
                            console.log("        错误详情: " + file_err);

                        }
                    }
                }
            } catch (err) {}
        }
    });
}

hook_define_class();

一共20多个dex文件,然后mt修复下dex

blog image
blog image

再次jadx打开,基本就能看到包结构了,再搜索下参数PostData。


blog image
blog image

但是经过测试登录时候并没有走这里,而是makeRequestParam,这里函数应该是被抽取了。

blog image
blog image

后来又通过主动调用尝试脱下壳得到如下结果

blog image
blog image

最终可以自己据此动态分析或利用jadx-ai-mcp分析,找到加密逻辑。我这里最终复现看是标准SM4-CBC然后标准base64,并且是在java层的。

blog image
blog image

结尾:总的来说有很多待提升的地方,要学会波浪式前进多实战和复盘,此次分析也参考了看雪的大佬文章https://bbs.kanxue.com/thread-290187-5.htm

 

END