Frida07 - dexdump核心源码分析

2023-12-22 17:28

本文主要是介绍Frida07 - dexdump核心源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

项目地址

https://github.com/hluwa/frida-dexdump

代码解析

项目中的核心函数是 searchDex:

function searchDex(deepSearch) {var result = [];Process.enumerateRanges('r--').forEach(function (range) {try {....} catch (e) {}});return result;
}

里面用了一个新的API,Process.enumerateRanges,我们看一下API介绍:

enumerates memory ranges satisfying protection given as a string of the form: rwx, where rw- means “must be at least readable and writable”.

使用这个API可以在进程中搜索所有可读的内存段,我们可以直接传递 ‘r—’ 的形式,也可以传递一个对象:{protection: '---', coalesce: true } ,coalesce 的值表示是否需要合并相同权限的内存段,默认是 false。

这个函数会返回一个数组对象,里面的元素有如下属性:

  1. base:基地址,NativePointer,可以理解为C里面的指针。

  2. size:内存块大小,in bytes

  3. protection:保护属性,string

  4. file:(如果有的话)内存映射文件:

    1. path,文件路径,string

    2. offset,文件内偏移,in bytes

    3. size,文件大小,in bytes

继续看源码:

Memory.scanSync(range.base, range.size, "64 65 78 0a 30 ?? ?? 00").forEach(function (match) {if (range.file && range.file.path && (range.file.path.startsWith("/data/dalvik-cache/") || range.file.path.startsWith("/system/"))) {return;}if (verify(match.address, range, false)) {var dex_size = get_dex_real_size(match.address, range.base, range.base.add(range.size));result.push({"addr": match.address,"size": dex_size});var max_size = range.size - match.address.sub(range.base).toInt32();if (deepSearch && max_size != dex_size) {result.push({"addr": match.address,"size": max_size});}}
});

又用到了一个新的API,Memory.scanSync,看看文档介绍:

scan memory for occurrences of pattern in the memory range given by address and size.

就是按照 pattern 给定的模式来搜索指定范围的内存是否又匹配的。

64 65 78 0a 30 ?? ?? 00

表示搜索的模式是 以 64 65 78 0a 30 字节开头的,中间两个字节不关心,后面跟着一个 00 的8个字节,如果有满足的则触发回调。

为啥要搜索这几个字节呢?是因为这几个字节是 dex 的文件魔数。可以看下官方文档介绍:

https://source.android.com/docs/core/runtime/dex-format?hl=zh-cn

作者设置的比较宽泛,中间的两个字节表示的是 dex 的版本号,会搜索所有版本号的 dex。

文档介绍 pattern 还有一个 r2-style 的写法,但是搜了一下没看太明白,就不说了。

回调会传递一个对象,里面的属性有:

  1. onMatch: function(address, size): 扫描到一个内存块,起始地址是address,大小size的内存块,返回字符串 stop 表示停止扫描

  2. onError: function(reason): 扫描内存的时候出现内存访问异常的时候回调

  3. onComplete: function(): 内存扫描完毕的时候调用

再回到源码:

if (range.file && range.file.path && (range.file.path.startsWith("/data/dalvik-cache/") || range.file.path.startsWith("/system/"))) {return;
}

系统app的dex,我们不需要。

if (verify(match.address, range, false)) {var dex_size = get_dex_real_size(match.address, range.base, range.base.add(range.size));result.push({"addr": match.address,"size": dex_size});var max_size = range.size - match.address.sub(range.base).toInt32();if (deepSearch && max_size != dex_size) {result.push({"addr": match.address,"size": max_size});}
}

verify 函数是对 dex 进行校验,主要是根据自己对 dex 文件的熟悉程度来做校验。

比如 dex 文件应该至少有 0x70 个字节,因为这是 dex 文件头的大小。

比如,0x3c位置的字节必定是 0x70,因为文件头后面跟着的就是字符串。

作者还开了一个深度验证,利用maps,其实原理很简单,我们使用010editor打开一个dex:

文件头里面有一个 map_off 字段,它的值是 map_list 段在dex文件内的偏移。

我们再看 map_list 段:

这里也储存了自身的一个偏移,那么根据这两个东西,就可以认为这个是dex文件。

具体代码如下:

function verify_by_maps(dexptr, mapsptr) {var maps_offset = dexptr.add(0x34).readUInt();var maps_size = mapsptr.readUInt();for (var i = 0; i < maps_size; i++) {var item_type = mapsptr.add(4 + i * 0xC).readU16();if (item_type === 4096) {var map_offset = mapsptr.add(4 + i * 0xC + 8).readUInt();if (maps_offset === map_offset) {return true;}}}return false;
}

然后再计算 map_list 结束的位置:

function get_maps_end(maps, range_base, range_end) {var maps_size = maps.readUInt();if (maps_size < 2 || maps_size > 50) {return null;}var maps_end = maps.add(maps_size * 0xC + 4);if (maps_end < range_base || maps_end > range_end) {return null;}return maps_end;
}

最后通过减掉起始地址,就可以得到真正的文件大小了:

function get_dex_real_size(dexptr, range_base, range_end) {var dex_size = dexptr.add(0x20).readUInt();var maps_address = get_maps_address(dexptr, range_base, range_end);if (!maps_address) {return dex_size;}var maps_end = get_maps_end(maps_address, range_base, range_end);if (!maps_end) {return dex_size;}return maps_end.sub(dexptr).toInt32();
}

如果开了深度搜索,匹配方式又有不同:

Memory.scanSync(range.base, range.size, "70 00 00 00").forEach(function (match) {var dex_base = match.address.sub(0x3C);if (dex_base < range.base) {return;}if (dex_base.readCString(4) != "dex\n" && verify(dex_base, range, true)) {var real_dex_size = get_dex_real_size(dex_base, range.base, range.base.add(range.size));if (!verify_ids_off(dex_base, real_dex_size)) {return;}result.push({"addr": dex_base,"size": real_dex_size});var max_size = range.size - dex_base.sub(range.base).toInt32();if (max_size != real_dex_size) {result.push({"addr": dex_base,"size": max_size});}}
});

70 00 00 00 是dex文件头里面字符串的偏移段。这是因为有些加固厂商会修改 dex 的魔数,所以作者选择了这种匹配方式。

可以看到,逆向的重心,除了api用的熟之外,还需要对app本身的相关知识要有足够的了解才行。

这篇关于Frida07 - dexdump核心源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/524819

相关文章

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Redis中的AOF原理及分析

《Redis中的AOF原理及分析》Redis的AOF通过记录所有写操作命令实现持久化,支持always/everysec/no三种同步策略,重写机制优化文件体积,与RDB结合可平衡数据安全与恢复效率... 目录开篇:从日记本到AOF一、AOF的基本执行流程1. 命令执行与记录2. AOF重写机制二、AOF的

MyBatis Plus大数据量查询慢原因分析及解决

《MyBatisPlus大数据量查询慢原因分析及解决》大数据量查询慢常因全表扫描、分页不当、索引缺失、内存占用高及ORM开销,优化措施包括分页查询、流式读取、SQL优化、批处理、多数据源、结果集二次... 目录大数据量查询慢的常见原因优化方案高级方案配置调优监控与诊断总结大数据量查询慢的常见原因MyBAT

分析 Java Stream 的 peek使用实践与副作用处理方案

《分析JavaStream的peek使用实践与副作用处理方案》StreamAPI的peek操作是中间操作,用于观察元素但不终止流,其副作用风险包括线程安全、顺序混乱及性能问题,合理使用场景有限... 目录一、peek 操作的本质:有状态的中间操作二、副作用的定义与风险场景1. 并行流下的线程安全问题2. 顺

MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决

《MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决》MyBatis默认开启一级缓存,同一事务中循环调用查询方法时会重复使用缓存数据,导致获取的序列主键值均为1,... 目录问题原因解决办法如果是存储过程总结问题myBATis有如下代码获取序列作为主键IdMappe

Python异常处理之避免try-except滥用的3个核心原则

《Python异常处理之避免try-except滥用的3个核心原则》在Python开发中,异常处理是保证程序健壮性的关键机制,本文结合真实案例与Python核心机制,提炼出避免异常滥用的三大原则,有需... 目录一、精准打击:只捕获可预见的异常类型1.1 通用异常捕获的陷阱1.2 精准捕获的实践方案1.3

深入浅出Java中的Happens-Before核心规则

《深入浅出Java中的Happens-Before核心规则》本文解析Java内存模型中的Happens-Before原则,解释其定义、核心规则及实际应用,帮助理解多线程可见性与有序性问题,掌握并发编程... 目录前言一、Happens-Before是什么?为什么需要它?1.1 从一个问题说起1.2 Haht

Java中最全最基础的IO流概述和简介案例分析

《Java中最全最基础的IO流概述和简介案例分析》JavaIO流用于程序与外部设备的数据交互,分为字节流(InputStream/OutputStream)和字符流(Reader/Writer),处理... 目录IO流简介IO是什么应用场景IO流的分类流的超类类型字节文件流应用简介核心API文件输出流应用文

java 恺撒加密/解密实现原理(附带源码)

《java恺撒加密/解密实现原理(附带源码)》本文介绍Java实现恺撒加密与解密,通过固定位移量对字母进行循环替换,保留大小写及非字母字符,由于其实现简单、易于理解,恺撒加密常被用作学习加密算法的入... 目录Java 恺撒加密/解密实现1. 项目背景与介绍2. 相关知识2.1 恺撒加密算法原理2.2 Ja