前言
本次分析的鼎鼎大名的frida-dexdump,这个脚本已经发布pypi包,可以通过pip安装也可以在github下载脚本通过python RPC调用使用,它与前两个脱壳脚本有着本质的区别,前面是通过hook系统函数找到dexfile对象进行dump,而dexdump是在内存搜索dex文件特征后判断该dex是否是一个真实的dex文件,然后保存到文件。
【frida-dexdump地址】
原理分析
本次分析展示的代码是笔者根据frida-dexdump作者的源码照抄下来,稍有改动的代码
frida-dexdump执行流程,让读者大致有个轮廓
- 扫描进程内存maps文件,读取所有可读内存段
- 通Frida提供的API匹配可读内存段的dex文件特征(dex0??)
- 修复dex文件头
- 保存到文件
全部代码展示
自己手写的代码注释很全,每个函数都有注释,并不是所有的函数都有用,下文会讲解重点函数,辅助的函数看注释就好。
/**
* Author: samle
* CreateTime: 2024/09/04
*
*
*
* myfrida_dexdump执行流程
* 1.内存搜索dex文件特征,保存到result中
* 2.保存到文件
* .
*
*/
//取map_off偏移后验证该位置是否在dex文件内存区间中,是的话返回,否则返回null
function get_maps_address(dexptr,range_base,range_end){
var maps_offset = dexptr.add(0x34).readUInt();
if(maps_offset === 0 ){
return null;
}
var maps_address = dexptr.add(maps_offset);
if(maps_address < range_base || maps_address > range_end){
return null;
}
return maps_address;
}
//验证了maps_end的两个情况不存在,就是有效地址
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);
//这里有个疑问,地址是向高处生长的,应该是maps_end > base || maps_end < end 吧
//返回空就没问题
if(maps_end < range_base || maps_end > range_end){
return null;
}
return maps_end;
}
/*
map结构
map_offset是map_list的文件偏移,通常在dex文件末尾
map_list有两个成员,map_item有四个成员
maplist
uint size
list map_item[size]
map_item
ushort type
ushort unused
uint size
uint offset
如果要想知道整个map_list的长度,用size + map_item[size]就可以了
4 + 12 * size
enum <ushort> TYPE_CODES {
TYPE_HEADER_ITEM = 0x0000,
TYPE_STRING_ID_ITEM = 0x0001,
TYPE_TYPE_ID_ITEM = 0x0002,
TYPE_PROTO_ID_ITEM = 0x0003,
TYPE_FIELD_ID_ITEM = 0x0004,
TYPE_METHOD_ID_ITEM = 0x0005,
TYPE_CLASS_DEF_ITEM = 0x0006,
TYPE_MAP_LIST = 0x1000,
TYPE_TYPE_LIST = 0x1001,
TYPE_ANNOTATION_SET_REF_LIST = 0x1002,
TYPE_ANNOTATION_SET_ITEM = 0x1003,
TYPE_CLASS_DATA_ITEM = 0x2000,
TYPE_CODE_ITEM = 0x2001,
TYPE_STRING_DATA_ITEM = 0x2002,
TYPE_DEBUG_INFO_ITEM = 0x2003,
TYPE_ANNOTATION_ITEM = 0x2004,
TYPE_ENCODED_ARRAY_ITEM = 0x2005,
TYPE_ANNOTATIONS_DIRECTORY_ITEM = 0x2006
};
*/
//获取dex头的map_off偏移和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){
//取到map_item[size].offset和dex文件头的map_off比较,如果两个一样就表示没修改过
var map_offset = mapsptr.add( 4 + i * 0xc + 8 ).readUInt();
//严格比较,比较值类型,值
if(maps_offset === map_offset){
return true;
}
}
}
return false;
}
//验证dex 0x3c的位置是不是0x70,这个位置固定是0x70,验证map_off表的真实位置和dexheader中是否一致
function verify(dexptr,range ,enable_verify_maps){
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(dexptr,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;
}
//通过maps_list的尾部-dex_base求dex的真实长度。可信度极高
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();
}
//验证字符串off,类型off,原型off,字段off,方法off的位置是否在文件头后面,dex尾部前面
function verify_ids_off(dexptr,dex_size){
var string_ids_off = dexptr.add(0x3c).readUInt();
var type_ids_off = dexptr.add(0x44).readUInt();
var proto_ids_off = dexptr.add(0x4c).readUInt();
var field_ids_off = dexptr.add(0x54).readUInt();
var method_ids_off = dexptr.add(0x5c).readUInt();
return string_ids_off < dex_size && string_ids_off >= 0x70 && type_ids_off < dex_size && type_ids_off >= 0x70 && proto_ids_off < dex_size && proto_ids_off >= 0x70 && field_ids_off < dex_size && field_ids_off >= 0x70 && method_ids_off < dex_size && method_ids_off >= 0x70
}
var pattern = "64 65 78 0a 30 ?? ?? 00";
var DeepPattern = "70 00 00 00";
function search_dex(deepSearch){
// console.log("deepSearch value is ",deepSearch.toString())
var result = []
Process.enumerateRanges("r--").forEach(function(range){
try{
// console.log("search_dex begin...")
//遍历进程的maps文件的可读内存段
/*
if(range.file.path.indexOf("testdex") != -1){
console.log("rang.base = ",range.base,range.size,range.file.path);
}
*/
Memory.scanSync(range.base,range.size,pattern).forEach(function(match){
//if(range.file.path.indexOf("testdex") != -1){
//}
//console.log("rang.base = ",range.base,range.size,range.file.path);
//console.log("base",range.base,range.file.path);
//排除系统自带dex文件-通过字符串排除
if( range.file && range.file.path &&
( range.file.path.startsWith("/data/dalvik-cache/") || range.file.path.startsWith("/system/") || range.file.path.startsWith("/apex/com") ) ){
return;
}
/*
range.base 输出的是vdex 内存页开始
match.address 输出的dex 匹配特征后的开头
console.log("range",hexdump(range.base),{
offset:0,
length:0x70,
header:true,
ansi:false})
console.log("match",hexdump(match.address),{
offset:0,
length:0x70,
header:true,
ansi:false})
range.base match.address 0x7af96d7000 5586944 0x7af96d7030 8
*/
//console.log("range.base match.address",range.base, range.size,match.address,match.size)
//console.log("rang.base = ",range.base,range.size,range.file.path);
//验证dex文件的合法性,获取真实长度
//console.log("验证dex文件的合法性,获取真实长度,进入前");
//var dex_size1 = match.address.add(0x20).readUInt();
//console.log("验证dex文件的合法性,获取真实长度 ",dex_size1);
if(verify(match.address,range,false)){
//console.log("验证dex文件的合法性,获取真实长度,进入后");
var dex_size = get_dex_real_size(match.address,range.base,range.base.add(range.size));
//数组操作
result.push({
"addr": match.address,
"size": dex_size
});
//console.log("当前 result length = ",result.length)
//console.log("result addr size ",result[0].addr,result[0].size)
//减去vdex的头
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
})
}
//console.log("当前 result length = ",result.length)
}
})
//如果开启了深度搜索
if(deepSearch){
Memory.scanSync(range.base,range.size,DeepPattern).forEach(function(match){
var dex_base = match.address.sub(0x3c);
//匹配dex文件头长度0x70,反推dex的基址,如果基址不在这个内存页,则不是一个dex文件
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;
}
//console.log("进入深度搜索 begin dex_base",ptr(dex_base).readCString(16));
//验证dex文件特征没问题就加入result数组
result.push({
"addr" : dex_base ,
"size" : real_dex_size
})
// avail_off = dex_base.sub(range.base).toInt32() == dexheader - range.base = 匹配内存页的首地址 - 匹配0x70特征反推出dexheader并验证dex文件合法性的的首地址
//max_size == 匹配页的大小减去上面的的结果
/*
range.base
#################
# avail_off #
# #
#################
# # range.size
# #
# max_size #
# #
# #
# #
# #
#################
range.end
*/
var max_size = range.size - dex_base.sub(range.base).toInt32();
//理论上max_size应该和real_dex_size一致,不一致说明dex长度没取到位
if(max_size != real_dex_size){
result.push({
"addr" : dex_base ,
"size" : max_size
})
}
//console.log("深度搜索结束,result length is", result.length)
}
})
}//如果没有开启深度搜索
else{
//处理dexheader magic被抹掉的情况,这是range是分页的首地址,不能完全确定magic被抹掉的情况,因为页开始的地方有可能是vdex的头
if(range.base.readCString(4) != "dex\n" && verify(range.base,range,true)){
console.log("magic被抹掉的情况出现了")
var 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
})
}
//增加vdex后移到dex头,而dex头被抹掉的情况
var Vdex2Dex_base = range.base.add(0x30);
if(Vdex2Dex_base.readCString(4) != "dex\n" && verify(Vdex2Dex_base,range,true) ){
console.log("dex magic被抹掉的情况出现了")
var real_dex_size = get_dex_real_size(Vdex2Dex_base, range.base, range.base.add(range.size))
result.push({
"addr" : Vdex2Dex_base ,
"size" : real_dex_size
})
}
// console.log("没有开启深度搜索 end")
}
} catch(e){
//console.log("异常---",e.message,e.stack)
}
})
//console.log("search_dex end...")
//console.error("当前 result length = ",result.length)
return result;
}
//修改内存页权限,没有使用
function setReadPermission(base, size) {
var end = base.add(size);
Process.enumerateRanges("---").forEach(function (range) {
var range_end = range.base.add(range.size);
if (range.base < base || range_end > end) {
return;
}
if (!range.protection.startsWith("r")) {
console.log("Set read permission for memory range: " + base + "-" + range_end);
Memory.protect(range.base, range.size, "r" + range.protection.substr(1, 2));
}
});
}
//没有使用
function memorydump(address, size) {
var ptr = new NativePointer(address);
setReadPermission(ptr, size);
return ptr.readByteArray(size);
}
function fix_header(dex_base, size){
/*
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
*/
var magic = [0x64,0x65,0x78,0x0a,0x30,0x33,0x35,0x00]
if(dex_base.readCString(4) != "dex\n"){
Memory.writeByteArray(magic,dex_base);
}
}
function main(){
var dexsz =[]
dexsz = search_dex(false)
console.error("当前 dexsz length = ",dexsz.length)
dexsz.forEach(function(value,index,array){
//dump dex
//var dex_bytes = memorydump(value.addr,value.size);
//不修复文件头了,暂时不清楚内存属性的问题
// dex_bytes_file = fix_header(dex_bytes,value.size);
//fix header
//写文件到手机
//写入前确认app拥有读写sd的权限
var file = new File("/sdcard/Download/samle/" + value.size + ".dex","wb");
file.write(Memory.readByteArray(value.addr,value.size))
file.flush();
file.close();
console.log("写入dex 文件大小 :0x" ,value.size.toString(16));
//console.log(e.stack)
})
}
setImmediate(main)
扫描进程内存maps文件,读取所有可读内存段
这一步借助的是Frida提供的进程相关的API
/*Process.enumerateRanges(protection|specifier) :枚举满足给定保护的内存范围,该保护以字符串形式表示:rwx,其中rw-表示“至少可读写”。返回一个包含以下属性的对象数组:
base :基址作为 NativePointer
size :大小(字节)
protection :保护字符串
file :(可用时)文件映射详细信息作为对象,包含:
path :完整的文件系统路径作为字符串
offset :磁盘上映射文件的偏移量,以字节为单位
size :磁盘上映射文件的大小,以字节为单位
*/
Process.enumerateRanges("r--").forEach(function(range){}
foreach是数组遍历,enumerateRangers返回的是一个数组。延申几个Process的api
- Process.enumerateThreads() :枚举所有线程,返回包含以下属性的对象数组
id:操作系统特定ID
state:指定 running 、 stopped 、 waiting 、 uninterruptible 或 halted 的字符串
context:对于ia32/x64/arm,使用pc和sp为NativePointer对象,分别指定EIP/RIP/ pc和ESP/RSP/ sp。其他特定于处理器的键也可用,例如eax、rax、r0、x0等。
- Process.findModuleByAddress(address),
Process.getModuleByAddress(address),
Process.findModuleByName(name),
Process.getModuleByName(name):
查找与指定的地址或名称匹配的模块,返回值是一个地址或名称。如果找不到模块,find-开头的函数返回 null,而 get-开头的函数抛出异常。
- Process.enumerateModules() 枚举当前所有已加载的 so 模块,并且返回 Module对象 的 数组
- Process.findRangeByAddress(address)
- getRangeByAddress(address)
查找包含地址范围的详细信息。返回一个对象。
- Process.enumerateMallocRanges() :就像 enumerateRanges() 一样,但是用于系统单个内存分配的堆。
- Process.setExceptionHandler(callback) :安装一个进程范围的异常处理回调,让它有机会在宿主进程本身之前处理本机异常。
通Frida提供的API匹配可读内存段的dex文件特征(dex0??)
主要是通过Frida提供的内存匹配特征的API在可读内存中搜索指定特征,由于搜索结果会匹配到许多系统dex文件,所以通过去调用系统相关的dex文件,保留剩下的。
这个可能会遇到imeout was reached 错误,关于错误请看frida作者的解释
Memory.scanSync(range.base,range.size,pattern).forEach(function(match){
//排除系统自带dex文件-通过字符串排除
if( range.file && range.file.path &&
( range.file.path.startsWith("/data/dalvik-cache/") || range.file.path.startsWith("/system/") || range.file.path.startsWith("/apex/com") ) ){
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
})
}
}
})
深度搜索
Memory.scanSync(range.base,range.size,DeepPattern).forEach(function(match){
var dex_base = match.address.sub(0x3c);
//匹配dex文件头长度0x70,反推dex的基址,如果基址不在这个内存页,则不是一个dex文件
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;
}
//console.log("进入深度搜索 begin dex_base",ptr(dex_base).readCString(16));
//验证dex文件特征没问题就加入result数组
result.push({
"addr" : dex_base ,
"size" : real_dex_size
})
// avail_off = dex_base.sub(range.base).toInt32() == dexheader - range.base = 匹配内存页的首地址 - 匹配0x70特征反推出dexheader并验证dex文件合法性的的首地址
//max_size == 匹配页的大小减去上面的的结果
/*
range.base
#################
# avail_off #
# #
#################
# # range.size
# #
# max_size #
# #
# #
# #
# #
#################
range.end
*/
var max_size = range.size - dex_base.sub(range.base).toInt32();
//理论上max_size应该和real_dex_size一致,不一致说明dex长度没取到位
if(max_size != real_dex_size){
result.push({
"addr" : dex_base ,
"size" : max_size
})
}
}
})
普通匹配是通过"64 65 78 0a 30 ?? ?? 00",而深度搜索是通过dexheader的头部的大小匹配的,这一点很有有意思,如果dexheader没有被篡改过,那这个值固定是0x70。那么在内存中扫描0x70的小端存储【"70 00 00 00"】,然后反推dexheader在判断这个是不是一个有效dex文件(dex-magic和verify函数验证)。这一手不可不谓巧妙。
修复dex文件头
原作者修复文件头是通过python完成的,因为原脚本是RPC调用的。我这个脚本没有实现修复文件头,看下作者的文件头修复
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
fix_header一共修复了文件头的四个属性
1.dex magic 【破环dump出来的dex文件】
2.dex size 【破环dump出来的dex文件】
3.dexheader size 【破环dump出来的dex文件】
4.字节序,【破环dump出来的dex文件】
保存到文件
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
保存到脚本目录的包名下,以classes1.dex依次递增。
与原作者的区别
排除系统函数
我在做排除系统目录下dex文件时,比原作者多排除以/apax/com开头文件,这个路径加载都是android运行时的文件
//排除系统自带dex文件-通过字符串排除
if( range.file && range.file.path &&
( range.file.path.startsWith("/data/dalvik-cache/") || range.file.path.startsWith("/system/") || range.file.path.startsWith("/apex/com") ) ){
return;
}
处理被抹掉magic的情况
//增加vdex后移到dex头,而dex头被抹掉的情况
var Vdex2Dex_base = range.base.add(0x30);
if(Vdex2Dex_base.readCString(4) != "dex\n" && verify(Vdex2Dex_base,range,true) ){
console.log("dex magic被抹掉的情况出现了")
var real_dex_size = get_dex_real_size(Vdex2Dex_base, range.base, range.base.add(range.size))
result.push({
"addr" : Vdex2Dex_base ,
"size" : real_dex_size
})
}
> range
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7af96d7000 76 64 65 78 30 32 31 00 30 30 32 00 01 00 00 00 vdex021.002.....
7af96d7010 0f ad 00 00 00 00 00 00 00 00 00 00 59 4f e9 71 ............YO.q
7af96d7020 9c 89 54 00 00 00 00 00 00 00 00 00 00 00 00 00 ..T.............
7af96d7030 64 65 78 0a 30 33 37 00 21 36 a6 d9 eb 59 7d 26 dex.037.!6...Y}&
7af96d7040 b7 b1 4a 6b f3 33 71 05 60 70 e5 d2 a7 e7 1b 70 ..Jk.3q.`p.....p
7af96d7050 98 89 54 00 70 00 00 00 78 56 34 12 00 00 00 00 ..T.p...xV4.....
7af96d7060 00 00 00 00 c8 88 54 00 73 b4 00 00 70 00 00 00 ......T.s...p...
7af96d7070 18 12 00 00 3c d2 02 00 28 1c 00 00 9c 1a 03 00 ....<...(.......
7af96d7080 94 6d 00 00 7c 6c 04 00 bc 90 00 00 1c d9 07 00 .m..|l..........
7af96d7090 19 0e 00 00 fc 5e 0c 00 7c 67 46 00 1c 22 0e 00 .....^..|gF.."..
7af96d70a0 b2 f7 36 00 b4 f7 36 00 ed f7 36 00 35 f8 36 00 ..6...6...6.5.6.
7af96d70b0 a8 f8 36 00 db f8 36 00 1a f9 36 00 1d f9 36 00 ..6...6...6...6.
7af96d70c0 25 f9 36 00 33 f9 36 00 3d f9 36 00 53 f9 36 00 %.6.3.6.=.6.S.6.
7af96d70d0 5a f9 36 00 6f f9 36 00 8c f9 36 00 bd f9 36 00 Z.6.o.6...6...6.
7af96d70e0 d0 f9 36 00 d5 f9 36 00 e9 f9 36 00 fc f9 36 00 ..6...6...6...6.
7af96d70f0 0c fa 36 00 2a fa 36 00 3b fa 36 00 4c fa 36 00 ..6.*.6.;.6.L.6. [object Object]
match
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7af96d7030 64 65 78 0a 30 33 37 00 21 36 a6 d9 eb 59 7d 26 dex.037.!6...Y}&
7af96d7040 b7 b1 4a 6b f3 33 71 05 60 70 e5 d2 a7 e7 1b 70 ..Jk.3q.`p.....p
7af96d7050 98 89 54 00 70 00 00 00 78 56 34 12 00 00 00 00 ..T.p...xV4.....
7af96d7060 00 00 00 00 c8 88 54 00 73 b4 00 00 70 00 00 00 ......T.s...p...
7af96d7070 18 12 00 00 3c d2 02 00 28 1c 00 00 9c 1a 03 00 ....<...(.......
7af96d7080 94 6d 00 00 7c 6c 04 00 bc 90 00 00 1c d9 07 00 .m..|l..........
7af96d7090 19 0e 00 00 fc 5e 0c 00 7c 67 46 00 1c 22 0e 00 .....^..|gF.."..
7af96d70a0 b2 f7 36 00 b4 f7 36 00 ed f7 36 00 35 f8 36 00 ..6...6...6.5.6.
7af96d70b0 a8 f8 36 00 db f8 36 00 1a f9 36 00 1d f9 36 00 ..6...6...6...6.
7af96d70c0 25 f9 36 00 33 f9 36 00 3d f9 36 00 53 f9 36 00 %.6.3.6.=.6.S.6.
7af96d70d0 5a f9 36 00 6f f9 36 00 8c f9 36 00 bd f9 36 00 Z.6.o.6...6...6.
7af96d70e0 d0 f9 36 00 d5 f9 36 00 e9 f9 36 00 fc f9 36 00 ..6...6...6...6.
7af96d70f0 0c fa 36 00 2a fa 36 00 3b fa 36 00 4c fa 36 00 ..6.*.6.;.6.L.6.
7af96d7100 67 fa 36 00 73 fa 36 00 78 fa 36 00 7b fa 36 00 g.6.s.6.x.6.{.6.
7af96d7110 7f fa 36 00 92 fa 36 00 96 fa 36 00 9b fa 36 00 ..6...6...6...6.
7af96d7120 a1 fa 36 00 a8 fa 36 00 b1 fa 36 00 c5 fa 36 00 ..6...6...6...6. [object Object]
我在输出日志是发现range匹配到的是vdex头,match匹配的dex头,如果出现magic被抹掉的情况,原作者的判断会出问题
> if(range.base.readCString(4) != "dex\n" && verify(range.base,range,true)){ }
verify的第一个参数就不是dex_base了,而是vdex_base。所以我在我的脚本这里兼容了一下这种情况。
将vdex向后移动0x30个字节到达dex_base开始验证dex文件的有效性。
总结
至此,分析了通过hook系统函数和通过内存暴力搜索dex特征的脚本,对脱壳的理解,使用frida上又进步了一点。有想学习frida脱壳脚本的也和我一样。手写我分析的脱壳原理一二三的脚本之后应该会对脱壳理解的更深,可以自己修改开源的这些脚本来解决特殊的apk加固场景。