吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2378|回复: 16
上一主题 下一主题
收起左侧

[Android 脱壳] frida脱壳脚本原理分析一(frida_dump)

  [复制链接]
跳转到指定楼层
楼主
快乐的小跳蛙 发表于 2024-9-3 13:51 回帖奖励

前言

目前通过frida脚本对android进行脱壳获取dex文件是目前的主流手段。本次对frida_dump脱壳脚本原理分析是为了加深脱壳脚本如何工作的。知己知彼,百战不殆!

脚本分析

整体分析
frida_dump一共有5个函数,自上至下分别是
1. get_self_process_name
2. mkdir
3. chmod
4. dump_dex
5. hook_dlopen
其中关于脱壳的核心函数是dump_dex。
核心函数分析
    function dump_dex() {
    var libart = Process.findModuleByName("libart.so");
    var addr_DefineClass = null;
    var symbols = libart.enumerateSymbols();
    for (var index = 0; index < symbols.length; index++) {
        var symbol = symbols[index];
        var symbol_name = symbol.name;
        //这个DefineClass的函数签名是Android9的
        //_ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE
        if (symbol_name.indexOf("ClassLinker") >= 0 &&
            symbol_name.indexOf("DefineClass") >= 0 &&
            symbol_name.indexOf("Thread") >= 0 &&
            symbol_name.indexOf("DexFile") >= 0) {
            console.log(symbol_name, symbol.address);
            addr_DefineClass = symbol.address;
        }
    }
    var dex_maps = {};
    var dex_count = 1;

    console.log("[DefineClass:]", addr_DefineClass);
    if (addr_DefineClass) {
        Interceptor.attach(addr_DefineClass, {
            onEnter: function(args) {
                var dex_file = args[5];
                //ptr(dex_file).add(Process.pointerSize) is "const uint8_t* const begin_;"
                //ptr(dex_file).add(Process.pointerSize + Process.pointerSize) is "const size_t size_;"
                var base = ptr(dex_file).add(Process.pointerSize).readPointer();
                var size = ptr(dex_file).add(Process.pointerSize + Process.pointerSize).readUInt();

                if (dex_maps[base] == undefined) {
                    dex_maps[base] = size;
                    var magic = ptr(base).readCString();
                    if (magic.indexOf("dex") == 0) {

                        var process_name = get_self_process_name();
                        if (process_name != "-1") {
                            var dex_dir_path = "/data/data/" + process_name + "/files/dump_dex_" + process_name;
                            mkdir(dex_dir_path);
                            var dex_path = dex_dir_path + "/class" + (dex_count == 1 ? "" : dex_count) + ".dex";
                            console.log("[find dex]:", dex_path);
                            var fd = new File(dex_path, "wb");
                            if (fd && fd != null) {
                                dex_count++;
                                var dex_buffer = ptr(base).readByteArray(size);
                                fd.write(dex_buffer);
                                fd.flush();
                                fd.close();
                                console.log("[dump dex]:", dex_path);

                            }
                        }
                    }
                }
            },
            onLeave: function(retval) {}
        });
    }
}

从函数第一行开始分析可知,首先获取libart.so的模块基址,然后通过frida提供的api(enumerateSymbols)来遍历模块的导出函数。
从里面得到DefineClass的函数地址,从导出名称看出libart.so在编译导出函数时没有按照C风格也是extern “C”对函数名称进行修饰。
导致导出函数名称不规则,通过对DefineClass函数名称中的特征字符来来过滤DefineClass的函数地址。
获取到DefineClass函数地址后,用frida提供的Interceptor来附加在函数地址上,(通过frida提供的注释看,Interceptor附加采用的是inline hook的方式)
为什么要hook DefineClass呢?因为DefinClass的第6个参数(第一个参数是this指针)是dexFile对象的引用。

    //DefineClass函数声明,是一个native函数
mirror::Class* ClassLinker::DefineClass(Thread* self,
                                        const char* descriptor,
                                        size_t hash,
                                        Handle<mirror::ClassLoader> class_loader,
                                        const DexFile& dex_file,
                                        const DexFile::ClassDef& dex_class_def);

    DexFile::DexFile(const uint8_t* base,             //dex文件基址
                                                     size_t size,                             //  dex文件长度
                            const uint8_t* data_begin,
                            size_t data_size,
                            const std::string& location,
                            uint32_t location_checksum,
                            const OatDexFile* oat_dex_file,
                            std::unique_ptr<DexFileContainer> container,
                            bool is_compact_dex)

dexFile是dex文件加载到内存后的对象。该对象是包含了dex在内存中的基址和长度。有了这些信息就可以把dex文件dump出来了
脚本会将DefineClass加载的每一个dex文件dump到当前包名的files目录下。并按照顺序对dex文件命名"class1 class2"。

myfridadump.js

myfridadump.js是我对照dump_dex.js自己实现的dump dex的脚本,其核心一样是在DefineClass hook拿到dexFile后dump dex文件到手机上。
区别是删除了原脚本的无用函数hook_dlopen,增加了更多的注释,帮助理解,本来想通过chrome的devtools对frida的js脚本进行单步调试,
这是会对脚本执行过程的理解更清晰,无奈折腾半天附加不上js脚本后放弃,采用日志输出的方式来看执行流程。
【myfridadump.js github地址】

总结

通过这次对frida_dump的脚本进行分析,对脱壳脚本执行更加清晰,增加了对frida脚本编写的理解

免费评分

参与人数 2威望 +1 吾爱币 +21 热心值 +2 收起 理由
正己 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
clarkyu + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
weishuirenjia 发表于 2024-9-3 14:06
这个可以脱最新的数字壳不?能不能还原被抽的代码?
3#
 楼主| 快乐的小跳蛙 发表于 2024-9-3 15:07 |楼主
weishuirenjia 发表于 2024-9-3 14:06
这个可以脱最新的数字壳不?能不能还原被抽的代码?

没试过数字壳,不能还原代码抽取
4#
whitegold 发表于 2024-9-3 15:24
5#
clabobo 发表于 2024-9-3 17:02
学到了 大佬
6#
asdasxzca 发表于 2024-9-3 17:15
牛逼,学习学习
7#
cmsttasd 发表于 2024-9-3 17:54
大佬,会山姆app脱壳吗?
8#
wsadwhh 发表于 2024-9-3 18:46
这真的是学到东西了
9#
debug_cat 发表于 2024-9-3 19:19
感谢分享,现在测试过那个厂子的可呢
10#
男神冷熙啊 发表于 2024-9-3 23:13
学到了 大佬
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 09:15

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表