本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80956614
作者dstmath在看雪论坛公布一个Android的art模式下基于frida框架的脱壳脚本,详情见文章《基于frida的脱壳工具》的介绍,作者dstmath公布的frida脚本代码见github地址:https://github.com/dstmath/frida-unpack,脱壳原理比较简单,和我前面的博客《ART模式下基于Xposed Hook开发脱壳工具》中提到的作者的脱壳的思路是一致,这个脱壳思路也比较常见,在看雪论坛有多位楼主提到了这个基于Android的art模式下的OpenMemory拦截进行内存dump的脱壳思路,其实很多Xposed框架的Hook思路都可以移植到基于Frida框架的Hook上,顺便提一句,作者dstmath的简书博客写的不错。
具体参考的其他的文章:
《乐固libshella 2.10.1分析笔记》作者的帖子写的很不错。
《Android第二代加固(support 4.4-8.1)》
《修改源码dump某加固保的dex》
《legu 2.10.7.1 脱壳简明过程》
下面简要的说下,作者dstmath提供的frida脱壳脚本需要注意的几个点:
由于随着Android系统版本的升级,Art模式下libart.so库中OpenMemory函数的传入参数会有所不同,因此libart.so库文件中OpenMemory函数在编译时的名称粉粹也会不同,不同版本的Android系统的libart.so库文件导出函数OpenMemory的系统符号也会有差异,可用通过adb pull导出相应版本的Android系统的libart.so库文件,用IDA打开该libart.so库文件查看导出表中OpenMemory函数的导出系统符号或者使用 nm 等能查看ELF文件的导出系统符号的工具,查看libart.so库文件中OpenMemory函数的导出系统符号,相应的修改frida-unpack中的frida脱壳脚本文件frida_unpack.py和OpenMemory.js中OpenMemory函数的导出系统符号。
frida的命令行帮助:
>frida -h
Usage: frida [options] target
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-f FILE, --file=FILE spawn FILE
-n NAME, --attach-name=NAME
attach to NAME
-p PID, --attach-pid=PID
attach to PID
--debug enable the Node.js compatible script debugger
--enable-jit enable JIT
-l SCRIPT, --load=SCRIPT
load SCRIPT
-c CODESHARE_URI, --codeshare=CODESHARE_URI
load CODESHARE_URI
-e CODE, --eval=CODE evaluate CODE
-q quiet mode (no prompt) and quit after -l and -e
--no-pause automatically start main thread after startup
-o LOGFILE, --output=LOGFILE
output to log file
1.直接执行frida命令加载脱壳脚本OpenMemory.js文件进行Hook OpenMemory函数的操作 ,内存dump dex文件。
frida -U -f $1 -l $2 --no-pause
# $1--为被脱壳的Android应用的包名
# $2--为被frida加载的脚本文件OpenMemory.js
被frida加载的脚本文件OpenMemory.js的代码如下(代码中使用的被脱壳的Android应用包名需要做相应的修改):
'use strict';
/**
* 此脚本在以下环境测试通过
* android os: 7.1.2 32bit (64位可能要改OpenMemory的签名)
* legu: libshella-2.8.so
* 360:libjiagu.so
*/
Interceptor.attach(Module.findExportByName("libart.so", "_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_"), {
onEnter: function (args) {
//dex起始位置
var begin = args[1]
//打印magic
console.log("magic : " + Memory.readUtf8String(begin))
//dex fileSize 地址
var address = parseInt(begin,16) + 0x20
//dex 大小
var dex_size = Memory.readInt(ptr(address))
console.log("dex_size :" + dex_size)
//dump dex 到/data/data/pkg/目录下
var file = new File("/data/data/xxx.xxx.xxx/" + dex_size + ".dex", "wb")
file.write(Memory.readByteArray(begin, dex_size))
file.flush()
file.close()
},
onLeave: function (retval) {
if (retval.toInt32() > 0) {
/* do something */
}
}
});
2.使用frida框架的远程控制端(客户端)提供的python语言的控制接口调用frida程序执行Hook OpenMemory函数的操作 ,内存dump dex文件,python的脱壳脚本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()
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) {
var begin = args[1]
console.log("magic : " + Memory.readUtf8String(begin))
var address = parseInt(begin,16) + 0x20
var dex_size = Memory.readInt(ptr(address))
console.log("dex_size :" + dex_size)
var file = new File("/data/data/%s/" + dex_size + ".dex", "wb")
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()
附上《修改源码dump某加固保的dex》这篇帖子的内容做个备份,本来打算在这篇博客中学习一下art模式下dex文件的加载流程,想想还是后面再研究吧,想看该帖子的内容建议去作者的原链接处看,谢谢原作者。
const DexFile* DexFile::OpenMemory(const byte* base,
size_t size,
const std::string& location,
uint32_t location_checksum,
MemMap* mem_map,
const OatFile* oat_file,
std::string* error_msg) {
CHECK_ALIGNED(base, 4); // various dex file structures must be word aligned
std::unique_ptr dex_file(
new DexFile(base, size, location, location_checksum, mem_map, oat_file));
if (!dex_file->Init(error_msg)) {
return nullptr;
} else {
__android_log_print(ANDROID_LOG_DEBUG,"chason 's DexFile::OpenMemory",
"size is:%zu,location is:%s", size, location.c_str());
if (!strcmp(location.c_str(),"/data/data/com.autohome.mycar/.jiagu/classes.dex"))
{
int fd = open("/data/data/com.autohome.mycar/classes.dex",O_CREAT|O_EXCL|O_WRONLY,S_IRWXU);
__android_log_print(ANDROID_LOG_DEBUG,"chason's copy is starting!","hello");
if (fd>0)
write(fd,base,size);
else
__android_log_print(ANDROID_LOG_DEBUG,"chason's copy is failed!","codeis:%d",fd);
close(fd);
}
return dex_file.release();
}
}