frida脱壳
1. 原理
apk加壳的原理
壳apk的dex文件读取源apk的 classes.dex 文件,加密后写入新的dex文件,组成新的apk
加壳apk的加载过程
新apk被点击, 壳apk会把源classes.dex文件读取出来,解密后, 把源classes.dex给加载起来
frida脱壳的思路
在壳apk把源classes.dex文件读取出来解密之后, 将源classes.dex文件内容从内存中dump出来, 写入自定义dex文件中
2. 工具
1. python 3.6.1
安装frida模块
pip install frida==12.8.0
2. 安卓设备导入frida-server 版本12.8.0 与模块版本相对应
3. ida 用来查看hook方法的导出方法名
3. 需要用到的frida模块及方法
1. Module 模块 (操作内存地址)
案例: hello.so ——函数——> printhello()
findExportByName(moduleName|null, exportName)
moduleName: lib名字 (so文件名称)
exportName: 函数名称
返回exportName的地址指针
用法: var pointer = Module.findExportByName('hello.so', "printhello")
2. Interceptor模块
attach(target, callback) (不会修改函数逻辑)
target: 指针地址 pointer (方法地址)
callback: 回调函数
onEnter: hook住一个函数之后会进入onEnter, 主要用于打印 如: 加密前的变量值
onLeave: 当被hook的函数执行结束之后会进入onLeave, 用于打印加密之后的秘文
3. Memory 模块
a. 把内存里的值转成字符串
Memory.readUtf8String()
b. 把内存里的值转换成整型
Memory.readInt()
c. 以begin为起始位置,从内存中读length长度的数据出来 返回ArrayBuffer类型
Memory.readByteArray(begin, length)
begin: 起始位置
length: 读取的长度(十进制)
4. File 模块 写文件流程
new File(filepath, mode)
write(data)
flush()
close()
用法:
file = new File("yuanrenxue.dex", "wb")
//data 是字符串或者 arrayBuffer // readByteArray() 返回的arrayBuffer
file.write(data)
file.flush()
file.close()
4. 需要用到的js方法
1. 把十进制地址转换成NativePointer类型 frida里操作内存地址需要NativePointer类型
ptr()
2. 把其它进制转换成10进制
parseInt(num, radix)
num: 需要转化的数值
radix: 进制数(2, 16...)
5. dex文件结构
dex文件在内存中的大致规格("每一行都表示一个内存单元"):
8个字节 magic ----> dex 035 (固定)
4个字节 校验位
20个字节 签名
4个字节 dex文件大小
数据
预留位
说明:
1. 假设源apk的dex文件在内存中的起始地址为 begin_addr
2. 则 begin_addr + 32 之后,改地址中存放的就是该dex文件的大小
3. 已知dex文件的起始位置 以及 dex文件的大小,则使用frida中的Memory.readByteArray就可以将其从内存中读取出来
6. hook 方法
安卓5,6,7,8,9, 使用art虚拟机
源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)
该方法的位置System/Lib/libart.so文件中
注意:
1. hook前需要使用ida打开该so文件,查看OpenMemory的导出方法名,该方法名为最终的hook目标
2. 第一个参数为源classes.dex文件的起始位置
3. 安卓其他版本:
安卓4: ,默认使用davlink虚拟机 libdvm.so dexFileParse 函数
安卓5, 6, 7, 8, 9: libart.so OpenMemory 或 OpenCommon 函数
安卓10及以后: libdexfile.so ArtDexFileLoader 函数
7. 案例 脱壳特来电
import sys
import frida
# 特来电app,运行起来之后使用frida-ps 查看app包名
package = 'com.qdtevc.teld.app'
jsCode = """
// 获取OpenMemory在so文件中的地址
var openMemory_address = Module.findExportByName('libart.so', '_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_');
send(openMemory_address);
Interceptor.attach(openMemory_address, {
onEnter: function(args){
// openMemory 方法的第一个参数为源apk在内存中的起始位置
var dex_begin_address = args[1];
send('dex_begin_address: ' + dex_begin_address);
// dex 文件的前8个字节为magic字段,查看dex的格式说明(可使用dex的起始位置地址进行查看)
// 打印magic会打印"dex 035"三个字符可以验证是否为dex文件
send('magic: ' + Memory.readUtf8String(dex_begin_address));
// 起始地址的32个字节之后, 表示dex文件的大小
// 所以若要拿到dex文件的大小,需要将起始位置地址(16进制)转化成十进制之后+32
var dex_size_address = parseInt(dex_begin_address, 16) + 32;
// 将dex_size_address十进制地址转化成frida可以识别的NativePointer类型
// 将改地址指向的值转化成十进制就是dex文件的大小
var dex_size = Memory.readInt(ptr(dex_size_address));
send('dex size: ' + dex_size);
// 已知dex文件的起始位置/文件大小
// 读取并写入
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();
},
onLeave: function(retval){
}
});
"""%(package)
def on_message(message, data):
if message["type"] == "send":
print(u"[*] {0}".format(message["payload"]))
else:
print(message)
device = frida.get_remote_device()
pid = device.spawn(package)
process = device.attach(pid)
script = process.create_script(jsCode)
script.on("message", on_message)
print("【*】start")
script.load()
device.resume(pid) # 表示app重启
sys.stdin.read()
说明:除了jsCode 其他为frida hook的固定写法
8. 执行方式
1. 手机usb链接电脑
2. 启动设备中的frida-server,并在cmd中做端口转发
./frida-server
adb forward tcp:27043 tcp:27043
adb forward tcp:27042 tcp:27042
3. 启动需要脱壳的app
4. 在cmd 中 frida-ps -U 查看指定app的包名
5. 将包名写入代码中的package
6. 在pycharm中运行frida代码
7. 手机上的app会自动重启,最终脱壳后的dex文件会被保存在/data/data/包名/时间戳.dex
8. 使用 adb pull 安卓地址 本地地址 将dex文件从安卓设备中pull到本地 脱壳完成