Frida实战:3种OLLVM字符串加密的破解姿势与自动化脚本分享

Frida实战:3种OLLVM字符串加密的破解姿势与自动化脚本分享

在Android应用安全研究领域,OLLVM(Obfuscator-LLVM)的字符串加密混淆一直是逆向工程师需要面对的常见挑战。当你打开一个被混淆的so文件,发现所有关键字符串都变成了难以理解的byte_xxxx数据块时,那种感觉就像是在黑暗中摸索。但别担心,今天我将分享三种实战中遇到的OLLVM字符串加密场景,以及如何用Frida这把瑞士军刀来优雅地解决它们。

我遇到过不少开发者,他们面对OLLVM字符串混淆时往往感到无从下手。有的尝试手动分析每个解密函数,结果在IDA的伪代码海洋中迷失方向;有的试图静态解密,却发现解密逻辑分散在多个地方;还有的甚至想放弃,直接跳过字符串分析。实际上,只要掌握了正确的方法,这些加密字符串都能被轻松还原。本文将带你深入三种典型场景,从简单的.datadiv特征函数到复杂的运行时解密,提供可直接复用的Frida脚本模板和IDA分析技巧。

1. 基础场景:.datadiv特征函数的快速定位与解密

第一种情况是最经典的OLLVM字符串加密模式,也是新手最容易上手处理的类型。当你用IDA打开so文件,在导出表中看到一堆datadiv_decode开头的函数名时,恭喜你,遇到了最"友好"的混淆版本。

1.1 识别特征与静态分析

在IDA的Exports窗口中按Name排序,你会看到类似这样的函数名:

.datadiv_decode8846988481537047047
.datadiv_decode1234567890123456789

这些函数就是OLLVM自动生成的字符串解密函数。每个加密字符串在内存中都以加密形式存储,使用时通过对应的datadiv_decode函数进行解密。静态分析时,你看到的代码可能是这样的:

// 典型的datadiv_decode函数结构
void __fastcall datadiv_decode_xxxx(char *encrypted_str) {
    for (int i = 0; i < strlen(encrypted_str); i++) {
        encrypted_str[i] ^= 0xD2;  // 简单的异或加密
    }
}

注意:实际的解密算法可能比简单的异或复杂,但基本原理相同——在内存中修改加密数据,使其变为可读字符串。

1.2 Frida动态Hook方案

既然解密发生在运行时,我们可以用Frida在内存中直接捕获解密后的字符串。关键是要找到正确的Hook时机和位置。

Hook时机判断.init_array段是这类解密最常见的发生位置。在Android的so加载过程中,.init_array中的函数会在库初始化时自动执行。我们可以通过以下方式验证:

// 检查.init_array中的解密函数
function analyze_init_array() {
    var module = Process.findModuleByName("libtarget.so");
    if (!module) return;
    
    // 获取.init_array段
    var init_array = module.enumerateSections().filter(function(section) {
        return section.name === ".init_array";
    });
    
    if (init_array.length > 0) {
        var init_array_addr = init_array[0].address;
        var init_array_size = init_array[0].size;
        
        console.log(`[+] .init_array found at ${init_array_addr}, size: ${init_array_size}`);
        
        // 遍历.init_array中的函数指针
        for (var i = 0; i < init_array_size / Process.pointerSize; i++) {
            var func_ptr = init_array_addr.add(i * Process.pointerSize).readPointer();
            if (!func_ptr.isNull()) {
                console.log(`  Function ${i}: ${func_ptr}`);
                // 可以进一步分析这些函数
            }
        }
    }
}

通用解密Hook脚本:针对.datadiv_decode类型的解密,我常用的脚本模板如下:

function hook_datadiv_decrypt() {
    var target_module = "libtarget.so";
    var module_base = Module.findBaseAddress(target_module);
    
    if (!module_base) {
        console.log(`[-] Module ${target_module} not loaded yet`);
        // 等待模块加载
        Interceptor.attach(Module.findExportByName(null, "dlopen"), {
            onEnter: function(args) {
                var path = ptr(args[0]).readCString();
                if (path && path.indexOf(target_module) !== -1) {
                    console.log(`[+] ${target_module} loaded, hooking...`);
                    setTimeout(hook_datadiv_decrypt, 100);
                }
            }
        });
        return;
    }
    
    console.log(`[+] Module base: ${module_base}`);
    
    // 方法1:Hook所有.datadiv_decode函数
    var exports = Module.enumerateExportsSync(target_module);
    var decode_funcs = exports.filter(function(exp) {
        return exp.name.indexOf("datadiv_decode") === 0;
    });
    
    console.log(`[+] Found ${decode_funcs.length} datadiv_decode functions`);
    
    decode_funcs.forEach(function(func) {
        Interceptor.attach(func.address, {
            onEnter: function(args) {
                this.encrypted_str = args[0];
                console.log(`[+] datadiv_decode called: ${func.name}`);
                console.log(`    Encrypted string ptr: ${this.encrypted_str}`);
            },
            onLeave: function(retval) {
                // 解密完成后,字符串已经修改为明文
                var decrypted = ptr(this.encrypted_str).readCString();
                console.log(`    Decrypted: ${decrypted}`);
                
                // 可选:将解密结果保存到全局变量供后续使用
                if (!global.decrypted_strings) {
                    global.decrypted_strings = [];
                }
                global.decrypted_strings.push({
                    address: this.encrypted_str,
                    value: decrypted,
                    function: func.name
                });
            }
        });
    });
    
    // 方法2:直接扫描内存中的加密字符串区域
    // 通常加密字符串集中在.data或.rodata段
    var data_sections = Module.enumerateSectionsSync(target_module)
        .filter(function(section) {
            return section.name === ".data" || section.name === ".rodata";
        });
    
    data_sections.forEach(function(section) {
        console.log(`[+] Scanning ${section.name} at ${section.address} (size: ${section.size})`);
        // 这里可以添加内存扫描逻辑,识别加密字符串模式
    });
}

1.3 实战技巧与注意事项

在实际操作中,我发现有几个细节需要特别注意:

字符串长度识别:加密字符串通常以空字符结尾,但有时解密函数会接收长度参数。你需要观察调用约定:

// 如果解密函数有长度参数
Interceptor.attach(decode_func.address, {
    onEnter: function(args) {
        this.str_ptr = args[0];
        this.str_len = args[1].toInt32();
        console.log(`Decrypting ${this.str_len} bytes at ${this.str_ptr}`);
    },
    onLeave: function(retval) {
        // 读取指定长度的字符串
        var decrypted = ptr(this.str_ptr).readByteArray(this.str_len);
        console.log(hexdump(decrypted));
    }
});

多线程环境处理:如果解密函数可能在多线程环境下调用,需要确保Hook代码是线程安全的:

var string_cache = {};

Interceptor.attach(decode_func.address, {
    onEnter: function(args) {
        this.tid = Process.getCurrentThreadId();
        this.str_ptr = args[0];
        this.timestamp = Date.now();
    },
    onLeave: function(retval) {
        var key = `${this.str_ptr}-${this.tid}-${this.timestamp}`;
        if (!string_cache[key]) {
            var decrypted = ptr(this.str_ptr).readCString();
            string_cache[key] = decrypted;
            console.log(`[Thread ${this.tid}] Decrypted: ${decrypted}`);
        }
    }
});

性能考虑:频繁的Hook可能会影响应用性能。对于性能敏感的场景,可以考虑批量处理:

// 批量解密模式
var pending_decrypts = [];

function batch_decrypt_handler() {
    if (pending_decrypts.length > 0) {
        var batch = pending_decrypts.splice(0, 10); // 每次处理10个
        batch.forEach(function(item) {
            var decrypted = ptr(item.address).readCString();
            console.log(`Batch decrypted: ${decrypted}`);
        });
    }
}

// 设置定时器,每100ms处理一批
setInterval(batch_decrypt_handler, 100);

2. 进阶场景:init_array中的隐式解密函数

第二种情况稍微复杂一些——在64位应用中,你可能在导出表中找不到明显的datadiv_decode函数。这时候,.init_array段就成了我们的突破口。

2.1 识别无特征解密

当你在IDA中搜索不到明显的解密函数时,第一步应该是检查.init_array。按下Ctrl+S打开段视图,找到.init_array段:

段名 地址范围 大小 说明
.init_array 0x0000F000-0x0000F018 24字节 初始化函数数组
.fini_array 0x0000F018-0x0000F020 8字节 终止函数数组

双击.init_array段,你会看到一系列函数指针。这些函数在so加载时自动执行,字符串解密逻辑通常就隐藏在这里。

2.2 动态分析与Hook策略

对于这种情况,我们需要更精细的动态分析。以下是我常用的分析脚本:

function analyze_init_array_decrypt() {
    var target_so = "libtarget.so";
    var module = Process.findModuleByName(target_so);
    
    if (!module) {
        console.log(`[-] ${target_so} not loaded`);
        return;
    }
    
    // 查找.init_array
    var init_array_section = null;
    var sections = module.enumerateSections();
    
    for (var i = 0; i < sections.length; i++) {
        var section = sections[i];
        if (section.name.indexOf(".init_array") !== -1) {
            init_array_section = section;
            break;
        }
    }
    
    if (!init_array_section) {
        console.log("[-] .init_array section not found");
        return;
    }
    
    console.log(`[+] .init_array found at ${init_array_section.address}, size: ${init_array_section.size}`);
    
    // Hook .init_array中的所有函数
    var pointer_size = Process.pointerSize;
    var count = init_array_section.size / pointer_size;
    
    for (var i = 0; i < count; i++) {
        var func_ptr = init_array_section.address.add(i * pointer_size).readPointer();
        
        if (!func_ptr.isNull()) {
            console.log(`[+] Hook init function ${i} at ${func_ptr}`);
            
            Interceptor.attach(func_ptr, {
                onEnter: function(args) {
                    this.func_index = i;
                    console.log(`[+] init_array[${this.func_index}] called`);
                    
                    // 记录寄存器状态,有助于分析解密逻辑
                    if (Process.arch === 'arm64') {
                        this.reg_x0 = this.context.x0;
                        this.reg_x1 = this.context.x1;
                        // 可以记录更多寄存器
                    }
                },
                onLeave: function(retval) {
                    // 函数执行后,检查常见字符串区域是否被修改
                    check_string_regions();
                }
            });
        }
    }
}

// 监控常见字符串区域的变化
var monitored_regions = [];

function setup_string_monitoring() {
    var module = Process.findModuleByName("libtarget.so");
    
    // 通常字符串存储在.data、.rodata、.data.rel.ro等段
    var string_sections = [".data", ".rodata", ".data.rel.ro", ".rodata.str"];
    
    string_sections.forEach(function(section_name) {
        var section = module.enumerateSections().find(function(s) {
            return s.name === section_name;
        });
        
        if (section) {
            // 创建内存访问监控
            MemoryAccessMonitor.enable({
                base: section.address,
                size: section.size
            }, {
                onAccess: function(details) {
                    // 监控写入操作
                    if (details.operation === 'write') {
                        cons
内容概要:本文提出一种基于融合鱼鹰搜索行为柯西变异策略的改进麻雀优化算法(OCSSA),用于优化变分模态分解(VMD)的关键参数(如模态分量数K和惩罚因子α),以实现对滚动轴承振动信号的高效自适应分解,有效抑制模态混叠问题。经过OCSSA优化的VMD对原始信号进行预处理后,将分解得到的本征模态函数(IMF)重构为时频特征矩阵,作为卷积神经网络(CNN)的输入,以自动提取深层次的空间特征;随后,双向长短期记忆网络(BiLSTM)进一步挖掘特征序列中的前后向时序依赖关系,最终实现高精度的故障分类识别。该OCSSA-VMD-CNN-BiLSTM模型在西储大学公开轴承数据集上进行了充分验证,结果表明其在复杂噪声环境下对轴承不同故障类型程度的诊断准确率显著优于传统方法,充分体现了智能优化算法深度学习相结合在故障诊断领域的优越性能。; 适合人群:具备信号处理、机器学习及智能优化算法基础知识,从事机械装备状态监测、故障诊断、工业大数据分析等相关领域的科研人员、工程技术人员及高校研究生。; 使用场景及目标:①解决传统VMD参数依赖经验设定导致信号分解效果不稳定的问题;②提升强背景噪声和工况变化下滚动轴承早期微弱故障的检测灵敏度分类准确率;③为智能制造和工业互联网背景下的关键设备智能运维预测性维护提供一套可复现、高性能的技术解决方案。; 阅读建议:此资源以Matlab代码实现为核心,建议读者深入研读算法代码,重点理解OCSSA的寻优机制、VMD参数自适应选择过程以及CNN-BiLSTM的网络构建细节,通过复现完整实验流程,掌握从信号预处理、特征提取到智能分类的全流程关键技术,并尝试在自有数据集上进行迁移应用性能对比。
源码链接: https://pan.quark.cn/s/a4b39357ea24 接口测试框架(基于json格式、http请求,python3,不兼容python2.x版本) 注:现在基于Excel文件管理测试用例基本实现,) 备注:大家在运行的时候,如果参数不需要key,只需要字典,可以在ddt_case.py和case.py改造parame,注释掉现在的parem,启用新的即可 依赖用例支持用例执行,在testCase的ddt_case.py有实现,逻辑在代码中有写,参数的格式{"name":"$case1=data"}即代表name的值是case1的data字段,简单的实现。 依赖用例是简单的实现,具体在业务上面还有很多复杂的要处理,知识实现了,部分的思路。 (目前在部分window上会出现FileNotFoundError [Errno 2] No such file or directory,这个bug是路径过长,解决方案为吧log日志放在当前目录,或者修改动态生成的文件的名字,给了第一种方式,测试日志放在当前目录) qq交流群:194704520 Alt text 使用的库 requests,绝大部分是基于Python原有的库进行的,这样简单方便, 使用脚本参数分离等思想,尽可能降低代码的耦合度。 如果你不配置钉钉机器人,注释到机器人相关的代码 首先我们来看下我们的目录 Alt text ### 1.Case文件夹用来存放我们的测试用例相关的, test_case用来存储我们的测试数据,Excel管理测试用例,yaml文件管理测试用例,后续要把yaml管理测试用例的也封装出来。 Interface对测试接口相关的封装,包括requests库,发送...
内容概要:本文档围绕“配电网两阶段鲁棒故障恢复研究”展开,提供了完整的Matlab代码实现方案,属于高水平期刊论文的复现资料。研究针对配电网在发生故障后的恢复问题,提出了一种两阶段鲁棒优化方法,有效应对系统中诸如负荷波动、分布式电源出力不确定性等多重不确定因素。第一阶段进行预决策,包括网络重构、关键设备投切等操作;第二阶段则根据实际发生的故障场景进行动态调整恢复控制,确保系统在故障后仍能安全、稳定、可靠运行。该资源不仅包含可运行的Matlab代码,还隶属于一个涵盖电力系统优化、智能算法、路径规划、机器学习等多个技术方向的综合性科研服务体系。; 适合人群:具备电力系统分析基础、优化理论知识及Matlab编程能力的研究生、科研人员和工程技术人员,特别适用于从事智能电网、配电自动化、故障恢复策略、鲁棒优化等领域研究的专业人士。; 使用场景及目标:① 学习并复现顶刊关于配电网故障恢复的先进优化模型;② 掌握两阶段鲁棒优化在电力系统中的建模思路、求解流程技术细节;③ 利用所提供的Matlab代码进行算法验证、仿真测试,并在此基础上开展扩展性科研工作,如改进模型、引入新约束或应用于其他系统。; 阅读建议:建议结合经典电力系统优化鲁棒调度相关文献,深入理解两阶段鲁棒优化的数学建模原理物理背景,通过实际运行和调试代码,观察不同参数设置对优化结果的影响,进而掌握算法的核心机制。同时可参考文档中提及的其他相关研究主题,拓展研究视野,推动科研创新。
打开链接下载源码: https://pan.quark.cn/s/2f24438f641d 海康机器人工业相机软件MVS用户手册 本文档作为海康机器人工业相机客户端MVS的操作指南,致力于引导用户正确地应用和设置海康机器人工业相机客户端MVS。文档中包含了产品的概述、环境设定、菜单说明、操作步骤等方面的内容。 1. 重要声明 海康机器人对本手册所拥有的全部权利予以保留,任何单位或个人在未获得书面许可的情况下,均不得以任何形式进行摘录、复制、翻译或修改本手册的任何部分。 2. 产品介绍 海康机器人工业相机客户端MVS是一款工业相机软件,其目的是提供高水准的图像采集和处理功能。该软件兼容多种工业相机型号,能够适应不同工业自动化场景的需求。 3. 符号约定 在本手册中,采用以下符号约定: *加粗*表示重要提示 _斜体*表示术语解释 [ ]代表选项或菜单项 4. 运行环境 海康机器人工业相机客户端MVS支持多种操作系统,涵盖Windows、Linux等系统。用户必须确保计算机的配置满足最低系统标准,以便软件能够顺利运行。 5. 主要特性 海康机器人工业相机客户端MVS具备以下核心特性: * 高品质的图像采集和处理 * 支持多种工业相机型号 * 灵活的图像处理方法 * 强大的图像分析及处理能力 6. 环境配置 在应用海康机器人 industrial相机客户端MVS之前,必须完成环境配置。环境配置包括网口相机环境设定、U3V相机环境设定以及Camera Link相机环境设定等。 7. 菜单介绍 海康机器人工业相机客户端MVS提供了多种菜单选项,如文件菜单、编辑菜单、查看菜单等。用户可以根据实际需求选择不同的菜单选项,从而更高效地使用本软件。 8....
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值