首页
社区
课程
招聘
[原创]某道乐跑 APP 梆梆加固脱壳与反 Frida 检测绕过分析
发表于: 1天前 566

[原创]某道乐跑 APP 梆梆加固脱壳与反 Frida 检测绕过分析

1天前
566

声明:本次分析仅用于个人移动安全技术学习与研究,所有操作均在本地独立测试环境完成,严格遵守法律法规,严禁用于任何违规、非法用途。


首先使用反编译工具对乐跑APP进行代码dump操作,提取可读代码后,可直观观察到典型的APP加壳特征——原生业务代码被加密隐藏,无法直接读取和分析,初步判断该APP采用了主流商业加固方案。

 

可以看到很明显的加壳特征

为进一步确认加固类型,对APP安装包内的SO库文件进行深度检索,定位到负责加壳逻辑的核心SO文件。该SO文件除实现加壳加密功能外,还集成了反调试防护模块,通过特征码匹配与加固逻辑比对,最终确认该APP采用的是梆梆安全加固壳(梆梆加固)。


在确认加固类型后,尝试使用Frida工具直接附加乐跑APP进程,用于后续反调试绕过及脱壳操作。但操作过程中触发了梆梆加固的反调试拦截机制,Frida连接被直接阻断,无法正常执行调试脚本,无法获取APP内部运行逻辑,因此需先实现反调试机制的绕过


为绕过梆梆加固的基础反调试防护、获取完整可分析的程序逻辑,首先通过通用Frida脚本,成功解密并dump出梆梆加固壳的核心功能SO文件——libDexHelper.so。将该脱壳后的核心SO文件导入IDA Pro工具进行静态分析,重点检索与Frida检测、进程自杀、调试环境检测相关的代码逻辑,逐步梳理反调试层级并实现针对性绕过。


将脱壳后的 libDexHelper.so 导入 IDA Pro 进行静态分析,重点检索 Frida 检测、进程自杀、环境检测相关逻辑。

通过IDA动态调试与栈回溯分析,发现loc_31C64为梆梆加固壳检测到异常调试行为后,强制结束APP进程的自杀调用函数。只要使用Frida拦截该函数的调用,即可避免进程被终止,实现Frida的正常附加。拦截后测试确认,Frida可成功附加APP进程,不再被加固壳强制终止。

使用 Frida 直接拦截loc_31C64调用,即可实现正常附加,不会被进程终止。 

发现已经可以正常Frida附加不被结束 

为完整梳理其检测逻辑,通过打印调用栈分析,确定该壳主要检测以下特征:

校验/proc/self/task/tid/status内是否存在gum js loop、gdbus、gmain等 Frida 相关字符串,据此实现第一层绕过。 

对应的bypass

 

第二层检测为读取/proc/self/maps,识别memfd内存区域,若该区域以 ELF 魔术头开头,则判定为非法注入,对应完成该层绕过。 


 

对应bypass

 

继续向下逆向,程序进入 sub_57E64 函数,该函数主要用于读取系统属性 ro.build.user,当读取值等于 0x69746F6D796E6567LL 时,会直接跳过后续所有反调试检测逻辑,结合特征判断,该值为梆梆加固预留的工程机白名单标识。 

随后代码进入 sub_58e98 函数,该部分实现本地端口扫描检测,用于识别 Frida 本地调试端口。 

加固壳会主动扫描本机三个端口区间:27000 30000、24000 26999、20000 23999,进入端口检测子函数后,会对区间内每个端口依次尝试建立网络连接,若成功连通则判定存在调试风险。 

 

针对该检测,直接 Hook 端口判断函数,强制让函数永久返回 0,实现端口检测绕过 


将多层 Bypass 逻辑整合为完整 Frida 脚本并运行后,可稳定实现 Frida 附加,不再被加固壳拦截查杀。

(function () {
    console.log("[-] 正在开启模块蹲守模式...");

    const targetSoName = "libDexHelper.so";

    // 蹲守函数
    function hook_target(base) {
        console.log("[+] 成功拦截到目标模块,开始执行精准打击...");



        //第一层 bypass 绕过读取/proc/self/task/tid/status中的敏感字符
        const addr3 = base.add(0x391FC);
        Interceptor.attach(addr3, {
            onEnter: function () {
                var buf = this.context.x1;
                var c = buf.readU8();
                if ([0x67,0x6D,0x61,0x69,0x6E,0x64,0x62,0x75,0x73].includes(c)) {
                    buf.writeU8(0x78); // x
                }
            }
        });

        // 第二次bypass 绕过读取proc/self/maps中的检测memefd的内存是elf开头
        const loc_582E8_addr = base.add(0x582E8); // BL sub_2DC40 执行完的位置
        Interceptor.attach(loc_582E8_addr, {
            onEnter: function(args) {
                var v226_ptr = this.context.sp.add(0x9F0 - 0x4F0);
                var original = v226_ptr.readCString();
                if (original.includes("frida")) {
                    // 替换字符串:mem -> mam
                    var modified = original.replace("memfd", "mamfd");
                    v226_ptr.writeUtf8String(modified);
                }
                 else {
                   
                }

            }
        });

        // 第三次bypass 绕过本地连接frida的端口扫描
        Interceptor.attach(base.add(0x590FC), {
            onEnter: function() {
              
                var port_addr = this.context.sp.add(0x12);
                var port_net = port_addr.readU16();
                var real_port = ((port_net & 0xFF) << 8) | (port_net >> 8);
                if (real_port === 27042) {
                    port_addr.writeU16(0xFFFF); 
                }
            }
        });
        

     

    

}
        
const android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
if (android_dlopen_ext) {
    Interceptor.attach(android_dlopen_ext, {
        onEnter: function (args) {
            this.path = args[0].readUtf8String();
        },
        onLeave: function (retval) {
            if (this.path && this.path.indexOf(targetSoName) !== -1) {
                console.log("[+] 监测到 dlopen 加载模块: " + this.path);
                var module = Process.findModuleByName(targetSoName);
                if (module) hook_target(module.base);
            }
        }
    });
}

})();

若仅用于快速调试、不想逐层绕过所有检测逻辑,可直接拦截加固壳的进程自杀函数,实现最简绕过,脚本如下:

   const loc_31C64_addr = base.add(0x31C64);
        Interceptor.replace(loc_31C64_addr, new NativeCallback(function (a, b, c) {
            console.info("loc_31C64_addr into")
            console.error("[!!!] 警告:壳尝试触发自杀逻辑!已拦截。");
            console.warn(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n"));
            return 0;
        }, 'void', ['pointer', 'pointer', 'pointer']));


除上述核心 Frida 注入检测外,该梆梆加固壳还内置大量辅助安全检测逻辑,均通过独立函数调用实现,本次暂不做深入逆向分析,主要包含:扫描 /data/local 目录识别 Xposed、Root、

LSPosed 等权限环境,检测各类主流模拟器运行环境,校验调试器附加、进程注入等逆向行为。 

 


 

完成 Frida 稳定附加后,正式开展脱壳工作。


在解密后的 SO 文件中全局搜索 OpenCommon 字符串,找到两处关键引用位置,顺着交叉引用定位到 ART 虚拟机加载 Dex 文件的核心函数: 

看到他调用

_ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE

直接使用 Frida 脚本 Hook 该 Dex 加载函数,在内存中 dump 出解密后的完整 Dex 文件。 

var sym = Module.findExportByName("libart.so", "_ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE");
Interceptor.attach(sym, {
    onEnter: function(args) {
        console.log("\n进入OpenCommon");
        for (var i = 0; i < 4; i++) {
            try {
                var maybeBase = args[i];
                var magic = Memory.readUtf8String(maybeBase, 4);
                if (magic === "dex\n") {
                 
                    var base = maybeBase;
                 
                    var size = args[i + 1].toInt32();
               
                    if (size <= 0) {
                        size = Memory.readU32(base.add(32));
                    }
                    var file = new File("/data/local/tmp/dump_" + base + "_" + size + ".dex", "wb");
                    file.write(Memory.readByteArray(base, size));
                    file.flush();
                    file.close();
                    break;
                }
            } catch (e) {
            }
        }
    }
});


最后使用 Jadx 反编译工具打开脱壳后的 Dex 文件,APP 全部 Java 业务代码完整还原,无加密遮挡、无加固干扰,逆向分析顺利完成。 

 


 



[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
免费 5
支持
分享
最新回复 (5)
雪    币: 3975
活跃值: (2093)
能力值: (RANK:100 )
在线值:
发帖
回帖
粉丝
2
分析过程不错。流程清晰,也输出了结果。希望文章输出的记录能更细致一些,过程详细一些。比如dump出梆梆加固壳的核心功能SO文件——libDexHelper.so。 是如何dump出来的,把过程记录一下。还有ida中是如何一步一步分析和定位到的准确的函数等。 
20小时前
0
雪    币: 1915
活跃值: (2262)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
学习一下新知识
19小时前
0
雪    币: 6455
活跃值: (6958)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享,学习下。
19小时前
0
雪    币: 2
活跃值: (8077)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
fyrlove 分析过程不错。流程清晰,也输出了结果。希望文章输出的记录能更细致一些,过程详细一些。比如dump出梆梆加固壳的核心功能SO文件——libDexHelper.so。 是如何dump出来的,把过程记录一下 ...
哈哈大佬说出了我的心声
15小时前
0
雪    币: 22
活跃值: (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
pexillove 哈哈大佬说出了我的心声
哈哈哈哈也说出了我的心声,教程还是非常不错的
2小时前
0
游客
登录 | 注册 方可回帖
返回