71、Android逆向(七):加固脱壳(360加固、腾讯加固、梆梆加固)

各位好,今天我们来聊聊Android加固脱壳。说实话,这话题在逆向圈里一直挺热的。我早年刚接触这块时,也踩过不少坑。你想想看,一个APK扔进Jadx,结果啥也看不到,就一个壳的入口点——那种感觉,嗯,挺让人抓狂的。

但别急。脱壳这事儿,说白了就是一层窗户纸。捅破了,里面其实就那么几招。今天我就把360加固、腾讯加固、梆梆加固这三家的脱壳思路,掰开了讲给你听。

加固壳的本质是什么?

加固壳的核心逻辑,其实就三步:

  • 隐藏Dex:把原始的classes.dex加密或压缩,藏到so文件或资源文件里。
  • 动态加载:壳程序在运行时,通过自定义ClassLoader把解密后的Dex加载进内存。
  • HOOK防护:反调试、反内存dump、检测Xposed/Frida等。

我在项目中遇到过最离谱的一次,是某个加固壳把Dex拆成了几百个小块,散落在so的不同节区里。当时我盯着IDA的Hex视图看了整整一下午……后来发现,其实只要找到它解密后的内存基址,一锅端就行。

核心思路:无论壳怎么藏,最终都要在内存中还原出完整的Dex。我们只要在它解密后、加载前,把内存中的Dex dump出来即可。

一、360加固脱壳

360加固算是国内用得比较多的。它的特点是:壳的so层做了大量混淆,反调试也做得挺狠。

脱壳步骤

  1. 准备环境:一台已root的Android设备或模拟器,装上Frida。
  2. 找到壳的入口:用Jadx打开加固后的APK,你会发现主Activity被替换成了壳的入口,比如com.stub.StubApp
  3. Hook关键函数:360加固在加载Dex时,会调用DexClassLoaderInMemoryDexClassLoader。我们用Frida Hook住loadDexopenDexFile,就能拿到Dex的基址和大小。

我个人习惯用下面这个Frida脚本,简单粗暴:

// 360脱壳脚本片段
function dumpDex(addr, size) {
    var file = new File("/sdcard/dump_" + addr + ".dex", "wb");
    file.write(ptr(addr).readByteArray(size));
    file.flush();
    file.close();
    console.log("[+] Dumped dex at " + addr);
}

Interceptor.attach(Module.findExportByName("libart.so", "OpenDexFilesFromOat"), {
    onEnter: function(args) {
        console.log("[*] OpenDexFilesFromOat called");
        this.dexAddr = args[0];
    },
    onLeave: function(retval) {
        if (this.dexAddr != null) {
            // 这里需要根据实际情况计算大小
            dumpDex(this.dexAddr, 0x400000);
        }
    }
});

小技巧:如果Hook OpenDexFilesFromOat没反应,可以试试Hook DexFile::DexFile构造函数。不同Android版本,函数名略有差异。

二、腾讯加固脱壳

腾讯加固(乐固)的套路不太一样。它喜欢把Dex藏在so的.init_array段里,或者用自定义的加载器。我印象最深的是,它会在内存中把Dex分成多个chunk,然后拼接起来。

脱壳步骤

  1. 定位so加载点:腾讯加固的so文件通常叫libshell.solibtup.so。用IDA打开,找到JNI_OnLoad函数。
  2. Hook内存分配:腾讯加固在解密Dex时,会频繁调用mallocmmap。我们可以Hook这些函数,记录所有大块内存的分配。
  3. 特征匹配:Dex文件头是dex\n035(0x6465780A 0x30333500)。在内存中搜索这个魔数,就能找到Dex。

我曾经在调试一个腾讯加固的样本时,发现它把Dex藏在了匿名内存映射里。用cat /proc/pid/maps一看,一堆[anon:dalvik-classes]。嗯,就是它了。

// 搜索内存中的Dex魔数
function searchDex() {
    Process.enumerateRanges('rw-').forEach(function(range) {
        try {
            var data = Memory.readByteArray(range.base, range.size);
            // 这里用简单的字符串搜索,实际建议用KMP算法
            if (data.indexOf('dex\n035') !== -1) {
                console.log('[+] Found dex at ' + range.base);
                // 计算偏移并dump
            }
        } catch (e) {}
    });
}

注意:腾讯加固有反Frida检测。如果Frida被检测到,进程会直接退出。建议先用frida -n 包名 --no-pause启动,或者用Frida的Gadget模式。

三、梆梆加固脱壳

梆梆加固,我个人觉得是这三家里最“硬”的。它的VMP(虚拟机保护)做得相当到位,会把Dex的指令翻译成自定义的字节码,然后在自己的虚拟机里解释执行。

脱壳思路

梆梆加固的脱壳,不能只靠dump Dex了。因为即使你dump出来了,里面的指令也是被VMP化过的,Jadx根本看不懂。怎么办?

  • 方法一:Trace指令:用Frida Hook住梆梆虚拟机的解释器,记录每条指令的执行轨迹。然后根据轨迹还原原始逻辑。
  • 方法二:Unidbg模拟:把so文件拉到Unidbg里模拟执行,在模拟过程中dump解密后的Dex。这个方法比较干净,不会触发反调试。

我记得有一次,客户给了一个梆梆加固的样本,我折腾了两天没搞定。后来发现,它其实在某个so的JNI函数里,偷偷把Dex解密到了栈上。我Hook了那个JNI函数,在返回前把栈上的数据读出来——成了。

// 梆梆脱壳:Hook JNI函数
var nativeFunc = Module.findExportByName("libbangcle.so", "Java_com_example_MainActivity_nativeInit");
Interceptor.attach(nativeFunc, {
    onLeave: function(retval) {
        // 在栈上搜索Dex头
        var stackPtr = this.context.sp;
        for (var i = 0; i < 0x1000; i += 4) {
            var val = Memory.readU32(stackPtr.add(i));
            if (val == 0x6465780A) { // 'dex\n'
                console.log('[+] Found dex header on stack at ' + stackPtr.add(i));
                // dump逻辑
                break;
            }
        }
    }
});

三种加固的对比

加固类型 核心难点 推荐脱壳方法 反调试强度
360加固 so层混淆、多进程防护 Hook Dex加载函数 中等
腾讯加固 Dex分块、匿名内存映射 内存搜索魔数 较高
梆梆加固 VMP指令虚拟化 Unidbg模拟 + JNI Hook

通用脱壳流程

不管什么壳,我建议你按这个流程来:

  1. 静态分析:先看APK的AndroidManifest.xml,找到入口Activity和Native库。
  2. 动态调试:用Frida或Xposed,Hook关键函数,观察行为。
  3. 内存dump:在Dex解密后的第一时间,把内存中的Dex dump出来。
  4. 修复:dump出来的Dex可能不完整,需要用010 Editor或自定义脚本修复。

避坑指南:我曾经在dump一个360加固的Dex时,发现dump出来的文件只有几百字节。后来才意识到,壳在加载Dex时用了分块加载,我只dump到了第一个块。解决办法是:Hook住所有内存分配,等所有块都加载完再dump。

知识体系图

下面这张图,是我总结的加固脱壳核心逻辑。你看一眼,心里就有数了。

加固脱壳核心逻辑 加固APK 壳入口(StubApp) 360:Hook Dex加载 腾讯:内存搜索魔数 梆梆:Unidbg模拟 Dump 内存中的完整Dex

说白了,脱壳就是一场“猫鼠游戏”。壳在变,我们的工具也在变。但核心思路不变:找到它解密后的那个瞬间,把数据拿下来

好了,今天就聊到这儿。希望这些实战经验,能帮你少走些弯路。


公众号:蓝海资料掘金营,微信deep3321