软件脱壳

什么是 dex 文件

他是Android系统的可执行文件,包含应用程序的全部操作指令以及运行时数据。

由于dalvik是一种针对嵌入式设备而特殊设计的java虚拟机,所以dex文件与标准的class文件在结构设计上有着本质的区别

当java程序编译成class后,还需要使用dx工具将所有的class文件整合到一个dex文件,目的是其中各个类能够共享数据,在一定程度上降低了冗余,同时也是文件结构更加经凑,实验表明,dex文件是传统jar文件大小的50%左右

可以看见:
dex将原来class每个文件都有的共有信息合成一体,这样减少了class的冗余

数据结构

类型 含义
u1 unit8_t,1字节无符号数
u2 unit16_t,2字节无符号数
u4 unit32_t,4字节无符号数
u8 unit64_t,8字节无符号数
sleb128 有符号LEB128,可变长度1~5
uleb128 无符号LEB128,
uleb128p1 无符号LEB128值加1,

dex文件结构

首先从宏观上来说 dex 的文件结构很简单,实际上是由多个不同结构的数据体以首尾相接的方式拼接而成。如下图:

数据名称 解释
header dex文件头部,记录整个dex文件的相关属性
string_ids 字符串数据索引,记录了每个字符串在数据区的偏移量
type_ids 类似数据索引,记录了每个类型的字符串索引
proto_ids 原型数据索引,记录了方法声明的字符串,返回类型字符串,参数列表
field_ids 字段数据索引,记录了所属类,类型以及方法名
method_ids 类方法索引,记录方法所属类名,方法声明以及方法名等信息
class_defs 类定义数据索引,记录指定类各类信息,包括接口,超类,类数据偏移量
data 数据区,保存了各个类的真是数据
link_data 连接数据区

header

简单记录了dex文件的一些基本信息,以及大致的数据分布。长度固定为0x70,其中每一项信息所占用的内存空间也是固定的,好处是虚拟机在处理dex时不用考虑dex文件的多样性

字段名称 偏移值 长度 说明
magic 0x0 8 魔术字段,值为"dex 035"
checksum 0x8 4 校验码
signature 0xc 20 sha-1签名
file_size 0x20 4 dex文件总长度
header_size 0x24 4 文件头长度,009版本=0x5c,035版本=0x70
endian_tag 0x28 4 标示字节顺序的常量
link_size 0x2c 4 链接段的大小,如果为0就是静态链接
link_off 0x30 4 链接段的开始位置
map_off 0x34 4 map数据基址
string_ids_size 0x38 4 字符串列表中字符串个数
string_ids_off 0x3c 4 字符串列表基址
type_ids_size 0x40 4 类列表里的类型个数
type_ids_off 0x44 4 类列表基址
proto_ids_size 0x48 4 原型列表里面的原型个数
proto_ids_off 0x4c 4 原型列表基址
field_ids_size 0x50 4 字段个数
field_ids_off 0x54 4 字段列表基址
method_ids_size 0x58 4 方法个数
method_ids_off 0x5c 4 方法列表基址
class_defs_size 0x60 4 类定义标中类的个数
class_defs_off 0x64 4 类定义列表基址
data_size 0x68 4 数据段的大小,必须4k对齐
data_off 0x6c 4 数据段基址

magic

标识一个有效的dex文件,他的固定值为:64 65 78 0a 30 33 35 00,转换为字符串为dex.035.
在电子取证中也称“文件签名”

checksum

他是整个头部的校验和。它被用来校验头部是否损坏

signature

二次打包时的签名

file_size

记录包括dexHeader在内的整个dex文件大小,用来计算偏移和方便定位某区段(section),他也有诸如唯一的标识dex,因为他是dex文件中计算sha-1区段的一个组成部分

header_size

存放整个DexHeadeer结构体的长度,它也可用来计算下一个区段在文件中的起始位置,目前值为0x70


脱壳原理

什么是软件脱壳?

软件脱壳,顾名思义,就是对软件加壳的逆操作,把软件上存在的壳去掉

加固是如何运行起来的?

  1. -->APP启动
  2. -->壳dex先加载起来
  3. -->壳负责把源dex文件读出来
  4. -->壳把源dex文件解密
  5. -->把解密后的dex加载进内存 源dex运行起来

原理:

源dex文件最终会加载进内存
Hook加载Dex的函数,把Dex从内存中dump出来
Hook DexFile::OpenMemory()
DexFile::OpenMemory(const uint8_t* base,
size_t size,
const std::string& location,
uint32_t location_checksum,
MemMap* mem_map,//nullptr
const OatDexFile* oat_dex_file,
std::string* error_msg)


脱壳方案

方案一:Xposed + Fdex2

方案二:Frida 脱壳

1、启动手机内的 frida-server 并进行端口转发。27043 27042

2、导出 Android 的 /system/lib/libart.so 到本地。然后使用 IDA 查看 OpenMemory 对应的签名函数名。我这里对应的是 _ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_

import frida
import sys

package = 'com.iCitySuzhou.suzhou001'

def on_message(message, data):
    if message['type'] == 'send':
        print(f"[*] {message['payload']}")
    else:
        print(message)

# OpenMenory 在 libart.so 中,art 虚拟机(安卓5),davlink 虚拟机(安卓4)
# Hook OpenMemory 的导出方法
# 用 IDA 打开 libart.so ,查看 OpenMemory 的到处方法名
# OpenMemory 的第一个参数是 dex 文件在内存中的其实位置
# 根据 dex 文件格式,从其实位置开始第32个字节是该 dex 文件的大小
# 知道 dex 起始位置和整个文件大小,只需要把这段内存 dump 出来即可
# 使用于 安卓6 7 8 9

src = """
var openMemory_address = Module.findExportByName("libart.so", "_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_")

Interceptor.attach(openMemory_address, {
    onEnter: function(args){
    
        // dex 文件的起始位置
        var dex_begin_address = args[1]
        
        //dex 文件的前 8 个字节是 magic 字段
        // 打印 magic (会显示“dex 035”)三个字符,可以验证是否为 dex 文件
        console.log("magic:" + Memory.readUtf8String(dex_begin_address))
        
        // 把地址转换成整形,再加32
        //因为 dex 文件的第32个字节处存放的是 dex 文件的大小
        var address = parseInt(dex_begin_address, 16) + 0x20
        
        // 把 address 地址指向的内存值读出来,该值就是 dex 的文件大小
        // ptr(address)转换的原因是 frida 只接受 NativePointer 类型指针
        var dex_size = Memory.readInt(ptr(address))
        console.log("dex_size:" + dex_size)
        
        //frida 写入文件,把内存中的数据写到本地
        var timestamp = new Date().getTime();
        var file = new File("/data/data/%s/" + timestamp + ".dex", "wb")
        
        // Memory.readByteArray(begin, length)
        // 把内存里的数据读出来,从 begin 开始读,取 length 长度
        file.write(Memory.readByteArray(dex_begin_address, dex_size))
        file.flush()
        file.close()
        
        send("dex begin address: "+parseInt(dex_begin_address,16))
        send("dex file size:" +dex_size)
    },
    onLeave: function (retval) {
        if (retval.toInt32() > 0){
        }
    } 
});
"""%(package)

print(f"dex 导出目录为: /data/data/{package}")
device = frida.get_remote_device()
pid = device.spawn(package)
session = device.attach(pid)
script  =session.create_script(src)
script.on("message", on_message)
script.load()
device.resume(pid)
sys.stdin.read()

输出:

D:Pythonpython.exe E:/wx_h5/frida_dev.py
dex 导出目录为: /data/data/com.iCitySuzhou.suzhou001
magic:dex
035
dex_size:5993048
[*] dex begin address: 3760596376
[*] dex file size:5993048
magic:dex
035
dex_size:292
[*] dex begin address: 4111687680
[*] dex file size:292
magic:dex
035
dex_size:292
[*] dex begin address: 4098458380
[*] dex file size:292
magic:dex
035
dex_size:5542488
[*] dex begin address: 3761046936
[*] dex file size:5542488
magic:dex
035
dex_size:143416
[*] dex begin address: 3729728852
[*] dex file size:143416
magic:dex
035
dex_size:5432740
[*] dex begin address: 3697659272
[*] dex file size:5432740
magic:dex
035
dex_size:74656
[*] dex begin address: 3723183372
[*] dex file size:74656
magic:dex
035
dex_size:358828
[*] dex begin address: 3536693200
[*] dex file size:358828
magic:dex
035
dex_size:358828
[*] dex begin address: 3536693200
[*] dex file size:358828

到手机上看,脱壳成功

然后使用 adb 命令 pull 到本地,用 jadx 打开

需要将手机里的目录加上权限

chmod -R 777 目标文件夹
原文地址:https://www.cnblogs.com/kai-/p/13662201.html