一种可能可行的修改源码dump dex的方案
本来想通过https://blog.csdn.net/qq1084283172/article/details/78003184这篇文章来dump出dex,后来失败了。
可以知道Dalvik模式的设备,可以从DvmDex.cpp的dvmDexFileOpenPartial方法将dex dump出来。但是有一个问题:系统那么多apk都会走这个方法,如何确定执行当前方法的应用是我想要的应用?
我想到了一种办法:在dvmDexFileOpenPartial获取调用当前方法的进程id和包名,但是发现有很大困难,而且
调用当前方法的进程也并不是apk对应的进程,所以此方法不可行。
后来监听日志(adb logcat > logcat.txt会监听到所有日志)发现“Running dexopt on 包名",紧接着就会执行dvmDexFileOpenPartial方法。源码搜索"Running dexopt on“(参照我的另外一篇博客https://blog.51cto.com/4259297/1955867),找到日志输出的文件位置:PackageManagerService.java
所以我诞生一种思路:
在PackageManagerService.java的"Running dexopt on"将包名记录起来,然后在dvmDexFileOpenPartial获取包名,如果是想要的包名就将dex写入到文件。
这种方案就涉及到PackageManagerService.java和DvmDex.cpp之间的一个通讯的问题了,我尝试了2种方案,都遇到了困难。
-
通过JNI
既然涉及到C和Java文件的交互,自然会想到JNI技术。我的设想是这样的:在PackageManagerService.java使用一个静态常量,记住包名。然后DvmDex.cpp调用PackageManagerService.java的一个方法获取包名。
但是PackageManagerService.java属于“frameworks/base/services/java”模块,有一个对应的Android.mk文件。而DvmDex.cpp位于“dalvik/vm”模块,有一个对应的Android.mk文件。使用过Eclipse应用JNI的都知道,通过JNI关联的C和Java属于同一个工程,有着相同的Android.mk文件。
所以上面的PackageManagerService.java和DvmDex.cpp之间的通讯并非简单的JNI技术即可解决。 -
文件共享
选择对“/data/local/tmp”目录创建一个文件来记录包名,为什么使用这个目录?https://blog.csdn.net/weixin_30787531/article/details/95132119
PackageManagerService.java源码(4.4)修改
源码目录:frameworks/base/services/java/com/android/server/pm
最小编译目录:frameworks/base/services/java
修改目的:记住安装的应用的包名,为后面dump dex作准备。
...
Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName);
boolean test = write("/data/local/tmp/test.txt",pkg.applicationInfo.packageName);
Log.e(TAG,"write result:" + test);
//CZAdd
/**if("com.chinalwb.are.demo".equals(pkg.applicationInfo.packageName)){
killerAppName = pkg.applicationInfo.packageName;
}else{
killerAppName = null;
}*/
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg));
pkg.mDidDexOpt = true;
performed = true;
...
/*
*写文件
*/
private boolean write(String fileName, String fileContent) {
boolean result = false;
File f = new File(fileName);
File parentFile = f.getParentFile();
if(!parentFile.exists()){
parentFile.mkdirs();
}
if(!f.exists()){
try {
f.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
FileOutputStream fs = new FileOutputStream(f,false);
byte[] b = fileContent.getBytes();
fs.write(b);
fs.flush();
fs.close();
result = true;
} catch (Exception e) {
Log.e(TAG,"write failure:" + e.getMessage());
}
return result;
}
调试发现framework属于systemServer进程,对于“/data/local/tmp/”目录,没有写的权限。受高人指点,发现“/data/system"目录可以写文件。
boolean test = write("/data/system/tmp/test.txt",pkg.applicationInfo.packageName);
Log.e(TAG,"write result:" + test);
DvmDex.cpp源码(4.4)修改
源码目录:dalvik/vm
最小编译目录:dalvik/vm
修改目的:读dump的配置文件(保存用户想要dump的包名),读当前安装的应用的包名,然后判断两个包名相同就去dump dex。
int findTargetPName(){
ALOGE("try to find the target pname");
FILE *fp = NULL;
fp = fopen("/data/system/tmp/want_dump_pname.txt", "r");
if (fp==NULL){
// fclose(fp); fp==NULL不需要close。
fp = NULL;
ALOGE("find the target pname failure");
return -1;
}else{
// 读取脱壳apk的内存dex文件dump后的文件名称字符串
fgets(dexDumpName, 128, fp);
if( dexDumpName[strlen(dexDumpName) -1 ] == '\a'){
dexDumpName[strlen(dexDumpName)-1] = 0; //有回车符才要减1
}else{
dexDumpName[strlen(dexDumpName)] = 0;
}
fclose(fp);
fp = NULL;
ALOGE("find the target pname:%s,len=%d",dexDumpName,strlen(dexDumpName));
return 0;
}
}
int readCurrentInstalledPName(){
ALOGE("try to read current installed pname");
FILE *fp = NULL;
fp = fopen("/data/system/tmp/test.txt", "r");
if (fp==NULL){
// fclose(fp); fp==NULL不需要close。
fp = NULL;
ALOGE("read current installed pname failure");
return -1;
}else{
// 读取当前安装的APK的包名
fgets(currentInstalledPName, 128, fp);
if( currentInstalledPName[strlen(currentInstalledPName) -1 ] == '\a'){
currentInstalledPName[strlen(currentInstalledPName)-1] = 0; //有回车符才要减1
}else{
currentInstalledPName[strlen(currentInstalledPName)] = 0;
}
fclose(fp);
fp = NULL;
ALOGE("read current installed pname:%s,len=%d",currentInstalledPName,strlen(currentInstalledPName));
return 0;
}
}
int dumpDexFile(const void* addr, int len){
FILE *fp = NULL;
fp = fopen("/data/system/tmp/dump.dex", "wb+");
if (fp==NULL){
ALOGE("open dex output file failure");
return -1;
}else{
fwrite(addr,sizeof(char),len,fp);
fclose(fp);
fp = NULL;
ALOGE("dump dex success,dex file path:%s","/data/system/tmp/dump.dex");
return 0;
}
}
int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
{
//CZAdd-----------------------------------------------------------------start
// 读取当前安装的应用的包名。
int findTargetPNameResult = findTargetPName();
if(findTargetPNameResult >= 0){
int findInstalledPNameResult = readCurrentInstalledPName();
if(findInstalledPNameResult >= 0){
if (strcmp(dexDumpName, currentInstalledPName) == 0){
ALOGE("the target pname is same as the current installed pname,prepare to dump");
dumpDexFile(addr,len);
}else{
ALOGE("the target pname is diffrent with the current installed pname,findTargetPNameResult:%s,findInstalledPNameResult:%s",dexDumpName,currentInstalledPName);
}
}
}
//CZAdd-----------------------------------------------------------------end
...
}
findTargetPName():自定义方法,获取用户想要dump的应用的包名
readCurrentInstalledPName():自定义方法,读取当前最近安装的应用的包名,就是上面修改PackageManagerService.java保存的包名。
dumpDexFile():自定义方法,dump出相应的应用的dex文件。
dvmDexFileOpenPartial():系统源码的方法
开始dump
系统源码修改烧到系统后,就可以开始按照下面的步骤来dump了。
1.编辑并推送用户dump配置文件
编辑文件
want_dump_pname.txt
image.png
注意:不要带空格或者回车符。
推送文件
adb push 你电脑绝对路径/want_dump_pname.txt /data/system/tmp
如果没有tmp路径,则自己创建,如果创建没有权限,chmod 777 相应的目录或者文件。
2.安装要dump的apk
在framework创建的test.txt文件,由于权限的问题,在dalvik里无法读取。有两种解决办法:
1.在test.txt文件创建后,可以chomd 777这个文件,然后再安装apk.
2.framework在创建test.txt的时候, 用shell脚本创建(Runtime.execute),给其足够的权限。(没有验证)
3.pull dex文件
adb pull /data/system/tmp/dump.dex
可能会提示” remote Permission denied“,chmod 777这个文件所在的目录即可解决。
4.打开dex文件
用jadx-gui打开dex文件
image.png
遗憾的是仍然是360的壳。
不过不要灰心,只要找到真正的内部dex加载的源码位置,就能dump成功。