【Android逆向】脱壳项目 frida-dexdump 原理分析
1. 项目代码地址
https://github.com/hluwa/frida-dexdump
2. 核心逻辑为
def dump(self):
logger.info("[+] Searching...")
st = time.time()
ranges = self.agent.search_dex(enable_deep_search=self.enable_deep)
et = time.time()
logger.info("[*] Successful found {} dex, used {} time.".format(len(ranges), int(et - st)))
logger.info("[+] Starting dump to '{}'...".format(self.output))
idx = 1
for dex in ranges:
try:
bs = self.agent.memory_dump(dex['addr'], dex['size'])
md = md5(bs)
if md in self.mds:
continue
self.mds.add(md)
bs = fix_header(bs)
out_path = os.path.join(self.output, "classes{}.dex".format('%d' % idx if idx != 1 else ''))
with open(out_path, 'wb') as out:
out.write(bs)
logger.info("[+] DexMd5={}, SavePath={}, DexSize={}"
.format(md, out_path, hex(dex['size'])))
idx += 1
except Exception as e:
logger.exception("[-] {}: {}".format(e, dex))
logger.info("[*] All done...")
这里面有三个比较核心的方法 1 self.agent.search_dex 2 self.agent.memory_dump 3 fix_header, 分别对应着在内存中寻找dex,然后dump下来 ,最后修后dex的header
3. search_dex 分析
// /agent/src/search.ts
export function searchDex(deepSearch: boolean) {
const result: any = [];
Process.enumerateRanges('r--').forEach(function (range: RangeDetails) {
try {
Memory.scanSync(range.base, range.size, "64 65 78 0a 30 ?? ?? 00").forEach(function (match) {
......
}
});
if (deepSearch) {
Memory.scanSync(range.base, range.size, "70 00 00 00").forEach(function (match) {
const dex_base = match.address.sub(0x3C);
if (dex_base < range.base) {
return;
}
if (dex_base.readCString(4) != "dex\n" && verify(dex_base, range, true)) {
const 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
});
const max_size = range.size - dex_base.sub(range.base).toInt32();
if (max_size != real_dex_size) {
result.push({
"addr": dex_base,
"size": max_size
});
}
}
})
} else {
if (range.base.readCString(4) != "dex\n" && verify(range.base, range, true)) {
const real_dex_size = get_dex_real_size(range.base, range.base, range.base.add(range.size));
result.push({
"addr": range.base,
"size": real_dex_size
});
}
}
} catch (e) {
}
});
return result;
}
原理:
- Process.enumerateRanges('r--') 枚举可读的内存区块
2.1. 非深度搜索 :Memory.scanSync(range.base, range.size, "64 65 78 0a 30 ?? ?? 00"),这里就是在搜索 DEX.035. 但是很多加壳软件会修改掉这个DEX的标志(不影响虚拟机的加载,但会影响静态分析),这里就不展开解读了
2.2 深度搜索: Memory.scanSync(range.base, range.size, "70 00 00 00") , DEX 头的大小是 0x70 = 112,而紧挨着头的是string_ids区域,那么它的偏移必然是0x70,这个值是固定的,那么stringIdsOffset在DEX header里的值必然是"70 00 00 00",可以通过搜索它来缩小一波范围,成为怀疑对象 - 执行verify 来确认是不是dex
function verify(dexptr: NativePointer, range: RangeDetails, enable_verify_maps: boolean): boolean {
if (range != null) {
var range_end = range.base.add(range.size);
// verify header_size
if (dexptr.add(0x70) > range_end) {
return false;
}
if (enable_verify_maps) {
var maps_address = get_maps_address(dexptr, range.base, range_end);
if (!maps_address) {
return false;
}
var maps_end = get_maps_end(maps_address, range.base, range_end);
if (!maps_end) {
return false;
}
return verify_by_maps(dexptr, maps_address)
} else {
return dexptr.add(0x3C).readUInt() === 0x70;
}
}
return false;
}
其他的比较简单,核心是 verify_by_maps
function verify_by_maps(dexptr: NativePointer, mapsptr: NativePointer): boolean {
const maps_offset = dexptr.add(0x34).readUInt();
const maps_size = mapsptr.readUInt();
for (let i = 0; i < maps_size; i++) {
const item_type = mapsptr.add(4 + i * 0xC).readU16();
if (item_type === 4096) {
const map_offset = mapsptr.add(4 + i * 0xC + 8).readUInt();
if (maps_offset === map_offset) {
return true;
}
}
}
return false;
}
通过 map_off 找到 DEX 的 map_list, 通过解析它,并得到type为 TYPE_MAP_LIST(4096) 的item。理论上讲,这个条目里面的索引值应该要与 map_off 一致,那么通过校验这两个地方,就可以实现一个更加精确的验证方案。
这里涉及到Dex MapItem的数据结构
struct MapItem {
uint16_t type_;
uint16_t unused_;
uint32_t size_;
uint32_t offset_;
};
确认完毕后,开始获取dex的大小get_dex_real_size,(内存中的 DEX Header 并不只有 magic 可以抹掉,还有另一个运行时无关但对我们至关重要的字段:file_size,也就是文件的大小)
function get_dex_real_size(dexptr: NativePointer, range_base: NativePointer, range_end: NativePointer): Number {
const dex_size = dexptr.add(0x20).readUInt();
const maps_address = get_maps_address(dexptr, range_base, range_end);
if (!maps_address) {
return dex_size;
}
const maps_end = get_maps_end(maps_address, range_base, range_end);
if (!maps_end) {
return dex_size;
}
return maps_end.sub(dexptr).toInt32();
}
function get_maps_address(dexptr: NativePointer, range_base: NativePointer, range_end: NativePointer): NativePointer | null {
const maps_offset = dexptr.add(0x34).readUInt();
if (maps_offset === 0) {
return null;
}
const maps_address = dexptr.add(maps_offset);
if (maps_address < range_base || maps_address > range_end) {
return null;
}
return maps_address;
}
function get_maps_end(maps: NativePointer, range_base: NativePointer, range_end: NativePointer): NativePointer | null {
const maps_size = maps.readUInt();
if (maps_size < 2 || maps_size > 50) {
return null;
}
const maps_end = maps.add(maps_size * 0xC + 4);
if (maps_end < range_base || maps_end > range_end) {
return null;
}
return maps_end;
}
原理就是根据range的大小,和dex mapSize中的信息,确认出maps_end的地址,因为map就是dex的结尾,
获得了maps_end的地址,减去dexptr,即为整个dex的实际大小
searchDex最后 将数据写入到result返回
result.push({
"addr": dex_base,
"size": real_dex_size
});
4. 找到dex在内存中的位置了,就执行memory_dump 将数据dump出来
5. 然后修复dex头 fix_header
def fix_header(dex_bytes):
import struct
dex_size = len(dex_bytes)
if dex_bytes[:4] != b"dex\n":
dex_bytes = b"dex\n035\x00" + dex_bytes[8:]
if dex_size >= 0x24:
dex_bytes = dex_bytes[:0x20] + struct.Struct("<I").pack(dex_size) + dex_bytes[0x24:]
if dex_size >= 0x28:
dex_bytes = dex_bytes[:0x24] + struct.Struct("<I").pack(0x70) + dex_bytes[0x28:]
if dex_size >= 0x2C and dex_bytes[0x28:0x2C] not in [b'\x78\x56\x34\x12', b'\x12\x34\x56\x78']:
dex_bytes = dex_bytes[:0x28] + b'\x78\x56\x34\x12' + dex_bytes[0x2C:]
return dex_bytes
这里一共做了四件事
- 修复magic, dex.035。
- 修复filesize
- 修复stringIdsOffset
- 修复小端特征 "78 56 34 12"
【Android逆向】脱壳项目 frida-dexdump 原理分析的更多相关文章
- Android中Input型输入设备驱动原理分析(一)
转自:http://blog.csdn.net/eilianlau/article/details/6969361 话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反 ...
- Android中Input型输入设备驱动原理分析<一>
话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反正这个是没变的,在android的底层开发中对于Linux的基本驱动程序设计还是没变的,当然Android底层机制也 ...
- Android大图片裁剪终极解决方案 原理分析
约几个月前,我正为公司的APP在Android手机上实现拍照截图而烦恼不已. 上网搜索,确实有不少的例子,大多都是抄来抄去,而且水平多半处于demo的样子,可以用来讲解知识点,但是一碰到实际项目,就漏 ...
- Android控件TextView的实现原理分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8636153 在前面一个系列的文章中,我们以窗口 ...
- DexHunter的原理分析和使用说明(二)
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53715325 前面的博文<Android通用脱壳工具DexHunter的原理 ...
- Android视图SurfaceView的实现原理分析(示例,出错代码)
在Android系统中,有一种特殊的视图,称为SurfaceView,它拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面.由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独 ...
- drizzleDumper的原理分析和使用说明
https://blog.csdn.net/qq1084283172/article/details/53561622 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog. ...
- Xposed原理分析
目录 安卓系统启动 什么zygote? 安卓应用运行? Xposed介绍 Xposed构成 Xposed初始化大体工作流程 源码分析 初始化 app_main#main app_main#initia ...
- android脱壳之DexExtractor原理分析[zhuan]
http://www.cnblogs.com/jiaoxiake/p/6818786.html内容如下 导语: 上一篇我们分析android脱壳使用对dvmDexFileOpenPartial下断点的 ...
- android脱壳之DexExtractor原理分析
导语: 上一篇我们分析android脱壳使用对dvmDexFileOpenPartial下断点的原理,使用这种方法脱壳的有2个缺点: 1. 需要动态调试 2. 对抗反调试方案 为了提高工作效率, ...
随机推荐
- [转帖]Jmeter之JDBC Request使用方法(oracle)
https://zhuanlan.zhihu.com/p/121747788 JDBC Request: 这个sampler可以向数据库发送一个jdbc请求(sql语句),它经常需要和JDBC Con ...
- 部署于K8S集群上面应用性能影响点推测
前言 本人2017年第一次接触K8S. 中间断断续续学习K8S相关的内容. 但是最近一年,几乎没太有学习. 因为之前学习了四五年, 一直以为产品马上要用 结果一直被浇冷水. 去年开始学乖了. 不这么搞 ...
- Redis 菜鸟进阶
Redis 菜鸟进阶 背景 最近产品一直要优化性能,加强高可用. 有一个课题是Redis高可用与性能调优. 我这边其实获取到的内容很有限. 最近济南疫情严重,自己锁骨骨折. 然后通勤时间基本上都用来查 ...
- [译]深入了解现代web浏览器(三)
本文是根据Mariko Kosaka在谷歌开发者网站上的系列文章https://developer.chrome.com/blog/inside-browser-part3/ 翻译而来,共有四篇,该篇 ...
- SHA加密在实际应用中的优势与局限
SHA加密算法简介 SHA(Secure Hash Algorithm)加密算法是一种单向加密算法,常用于加密数据的完整性校验和加密签名.它是由美国国家安全局(NSA)设计并广泛应用于各种安全场景.S ...
- 【K哥爬虫普法】不要沾边!涉案 7k 合判 6 年!
我国目前并未出台专门针对网络爬虫技术的法律规范,但在司法实践中,相关判决已屡见不鲜,K 哥特设了"K哥爬虫普法"专栏,本栏目通过对真实案例的分析,旨在提高广大爬虫工程师的法律意识, ...
- 每日一道Java面试题:Java是值传递还是引用传递?
写在开头 Java是值传递还是引用传递?这个问题几乎100%的出现在了各大主流Java面试题中,知识点很小,但很考验面试者对于Java运行的理解,今晚趁着生产投产的空子,过来小聊一下. 实参与形参 所 ...
- 19.8 Boost Asio 异或加密传输
异或加密是一种对称加密算法,通常用于加密二进制数据.异或操作的本质是对两个二进制数字进行比较,如果它们相同则返回0,如果不同则返回1.异或加密使用一把密钥将明文与密文进行异或运算,从而产生密文.同时, ...
- SpringSecurity使用步骤
一.导入jar包(使用maven构建项目导入其坐标) <dependency> <groupId>org.springframework.security</groupI ...
- 网友感到担忧!iOS 17支持第三方应用商店:这下跟安卓没区别了
苹果此前官宣将于6月6日召开WWDC2023大会,按照往年的惯例,在这次大会上将会推出下一代iOS系统,也就是iOS 17.最近国外有关iOS 17的爆料中提到,迫于欧盟法案压力,iOS 17或将支持 ...