【frida逆向开发】frida脱壳原理及脱壳工具使用


一、知识补充

1、加固后的APP运行流程

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

2、脱壳原理

加固后的APP启动后最终源dex文件会被加载到内存中执行,此时的dex文件是加固程序解密后的dex,也就是app的源码。

Android脱壳的目的是从内存中dump下解密的应用dex文件,为了实现这个目的我们需要知道dex文件在内存中dex地址与dex文件大小。Android系统的libart.so库文件中提供了一个导出的OpenMemory函数用来加载dex文件。

在这里插入图片描述
这个函数的第一个参数指向了内存中的dex文件,如果我们可以Hook 这个函数,就可以得到dex文件加载进内存时的起始地址,再根据dex文件格式计算得到文件头保存的dex文件的长度fileSize。

了解dex文件格式:https://www.jianshu.com/p/f7f0a712ddfe

dex起始位置:OpenMemory第一个参数

8个字节    magin  ---> dex 035
4个字节    校验位
20个字节   签名
-----32个字节开始------
4个字节    dex文件大小

可以获得信息:

  • dex起始位置 (OpenMemory第一个参数)
  • dex文件大小 (dex起始位置 + 32字节)

这样我们就可以将内存中的数据读取出来写入本地

// 把内存里的数据读出来,从begin(dex在内存中的起始位置)开始读,取length长度(dex_size文件的大小)
file.write(Memory.readByteArray(begin, dex_size))
// 将这段读取出来的数据写入本地
var file = new File("/data/data/%s/" + dex_size + ".dex", "wb")

二、操作流程演示

在编写Frida脚本之前我们需要在libart.so文件中找到OpenMemory的导出函数名,这个函数名根据android版本或者架构的不同会有些许差异

这个导出函数名是什么呢?

相当于so文件中的方法对外部提供的接口,在Java代码中想要调用so文件中的OpenMemory方法的调用形式为:

javaCode.OpenMemory的导出函数名

OpenMemory导出函数名与so文件中的OpenMemory方法存在映射关系

java代码中想要调用so库中的OpenMemory方法就以它的导出函数名调用

根据应用运行过程中使用了64位的libart.so还是32位来导出不同的librat.so

adb pull /system/lib/libart.so C:\Users\v_mcsong\Desktop
adb pull /system/lib64/libart.so C:\Users\v_mcsong\Desktop

使用32/64位IDA打开librat.so文件查看OpenMemory的导出方法名,不同安卓版本导出方法名可能不同。

在这里插入图片描述
即调用_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_就是在调用so库中的OpenMemory

脱壳脚本:

参考开源项目:https://github.com/dstmath/frida-unpack/blob/master/frida_unpack.py

#-*- coding:utf-8 -*-
# coding=utf-8
import frida
import sys

def on_message(message, data):
    base = message['payload']['base']
    size = int(message['payload']['size'])
    print(hex(base),size)
    # print session
    # dex_bytes = session.read_bytes(base, size)
    # f = open("1.dex","wb")
    # f.write(dex_bytes)
    # f.close()

### libart.so
# 9.0 arm 需要拦截 _ZN3art13DexFileLoader10OpenCommonEPKhjS2_jRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE
# 7.0 arm:_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_

# android 10: libdexfile.so 
# #_ZN3art13DexFileLoader10OpenCommonEPKhjS2_jRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE

# 获取包名
package = sys.argv[1]
print("dex 导出目录为: /data/data/%s"%(package))
device = frida.get_usb_device()
pid = device.spawn(package)
session = device.attach(pid)
src = """
Interceptor.attach(Module.findExportByName("libart.so", "_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_"), {
    onEnter: function (args) {
      	//dex文件的起始位置
        var begin = args[1]
        
        //dex文件的前8个字节是magic字段 看dex的文件格式说明
        //打印magic(会显示 "dex 035") 三个字符 可以验证是否为dex文件 
        console.log("magic : " + Memory.readUtf8String(begin))
     	
     	//把地址转换成整型 再加32 
        //因为dex文件的第32个字节处存放的是 dex文件的大小
        var address = parseInt(begin,16) + 0x20
        
		//把address地址指向的内存值读出来 该值就是dex的文件大小
        //ptr(address)转换的原因是 frida只接受 NativePointer类型指针
        var dex_size = Memory.readInt(ptr(address))
        console.log("dex_size :" + dex_size)
      
      	//frida写文件 把内存中的数据 写到本地
        var file = new File("/data/data/%s/" + dex_size + ".dex", "wb")

		//Memory.readByteArray(begin, length)
        //把内存里的数据读出来,从begin开始读,取length长度(dex_size文件的大小)
        file.write(Memory.readByteArray(begin, dex_size))
        file.flush()
        file.close()
        var send_data = {}
        send_data.base = parseInt(begin,16)
        send_data.size = dex_size
        send(send_data)
    },
    onLeave: function (retval) {
        if (retval.toInt32() > 0) {
        }
    }
});
"""%(package)

script = session.create_script(src)

script.on("message" , on_message)

script.load()
device.resume(pid)
sys.stdin.read()

在这里插入图片描述
在这里插入图片描述
使用jadx逐个打开查看哪个dex文件中是app的源码。

参考文章:https://blog.51cto.com/yeshaochen/2496524

猜你喜欢

转载自blog.csdn.net/weixin_44032232/article/details/109676945
今日推荐