之前我们把壳从Java层转到了Native层,但是我们依旧是通过调用Java层的loadDex()
方法来加载Dex文件
jclass clazz_DexFile = env->FindClass("dalvik/system/DexFile");
if (clazz_DexFile != nullptr) {
LOGI("---> Find Class dalvik.system.DexFile");
}
jmethodID methodID_loadDex = env->GetStaticMethodID(clazz_DexFile,
"loadDex",
"(Ljava/lang/String;Ljava/lang/String;I)Ldalvik/system/DexFile;");
if (methodID_loadDex != nullptr) {
LOGI("---> Get methodID_loadDex");
}
auto jobj_dexFile = env->CallStaticObjectMethod(clazz_DexFile,
methodID_loadDex,
jstr_cachefilePath,
jstr_cachefileOpt,
false);
从loadDex()
函数开始
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}
进入,其返回的是一个DexFile对象
static public DexFile loadDex(String sourcePathName, String outputPathName,
int flags) throws IOException {
return new DexFile(sourcePathName, outputPathName, flags);
}
其构造函数,通过openDexFile()
函数进行Dex文件的加载,返回一个mCookie
,这个会加入一个全局Table,暂且不说
private DexFile(String sourceName, String outputName, int flags) throws IOException {
if (outputName != null) {
try {
String parent = new File(outputName).getParent();
if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
throw new IllegalArgumentException("Optimized data directory " + parent
+ " is not owned by the current user. Shared storage cannot protect"
+ " your application from code injection attacks.");
}
} catch (ErrnoException ignored) {
// assume we'll fail with a more contextual error later
}
}
mCookie = openDexFile(sourceName, outputName, flags);
mFileName = sourceName;
guard.open("close");
}
openDexFile()
函数调用了一个Native函数openDexFileNative()
private static int openDexFile(String sourceName, String outputName,
int flags) throws IOException {
return openDexFileNative(new File(sourceName).getCanonicalPath(),
(outputName == null) ? null : new File(outputName).getCanonicalPath(),
flags);
}
openDexFileNative()
函数定义
native private static int openDexFileNative(String sourceName, String outputName,
int flags) throws IOException;
该函数在Native层使用动态注册的方式注册,函数注册表相应的Item如下
{ "openDexFileNative", "(Ljava/lang/String;Ljava/lang/String;I)I",
Dalvik_dalvik_system_DexFile_openDexFileNative },
终于找到关键的地方了
static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,
JValue* pResult)
{
StringObject* sourceNameObj = (StringObject*) args[0];
StringObject* outputNameObj = (StringObject*) args[1];
DexOrJar* pDexOrJar = NULL;
JarFile* pJarFile;
RawDexFile* pRawDexFile;
char* sourceName;
char* outputName;
if (sourceNameObj == NULL) {
dvmThrowNullPointerException("sourceName == null");
RETURN_VOID();
}
sourceName = dvmCreateCstrFromString(sourceNameObj);
if (outputNameObj != NULL)
outputName = dvmCreateCstrFromString(outputNameObj);
else
outputName = NULL;
if (dvmClassPathContains(gDvm.bootClassPath, sourceName)) {
ALOGW("Refusing to reopen boot DEX '%s'", sourceName);
dvmThrowIOException(
"Re-opening BOOTCLASSPATH DEX files is not allowed");
free(sourceName);
free(outputName);
RETURN_VOID();
}
if (hasDexExtension(sourceName)
&& dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
ALOGV("Opening DEX file '%s' (DEX)", sourceName);
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = true;
pDexOrJar->pRawDexFile = pRawDexFile;
pDexOrJar->pDexMemory = NULL;
} else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
ALOGV("Opening DEX file '%s' (Jar)", sourceName);
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = false;
pDexOrJar->pJarFile = pJarFile;
pDexOrJar->pDexMemory = NULL;
} else {
ALOGV("Unable to open DEX file '%s'", sourceName);
dvmThrowIOException("unable to open DEX file");
}
if (pDexOrJar != NULL) {
pDexOrJar->fileName = sourceName;
addToDexFileTable(pDexOrJar);
} else {
free(sourceName);
}
free(outputName);
RETURN_PTR(pDexOrJar);
}
我们要摆脱通过调用Java层函数进行Dex文件加载这种方式,必须是手动实现loadDex()
到Dalvik_dalvik_system_DexFile_openDexFileNative()
之间所有调用过程,不一定要完整模拟出来,但是关键的参数什么的要到位,所以要研究libdvm.so,毕竟这些函数都编译进去了,我们使用dlopen()
和dlsym()
来进行Native层函数的调用
由于这个还是要对应手机上的libdvm.so,我这里使用的是模拟器,从模拟器中导出
E:\>adb pull /system/lib/libdvm.so E:\OJ
/system/lib/libdvm.so: 1 file pulled. 1.1 MB/s (2629626 bytes in 2.368s)
记住这个方法是动态注册的,所以使用IDA反编译,找到Dalvik_dalvik_system_DexFile_openDexFileNative()
这个很明显是C++编译的,因为C++里有重载,所以函数前后会加上前缀和后缀,C语言就不存在这种问题了
我们看到Dalvik_dalvik_system_DexFile_openDexFileNative
变成了_ZL46Dalvik_dalvik_system_DexFile_openDexFileNativePKjP6JValue
不过这样毕竟不是很靠谱,每个手机都不一样那还了得,所以我们使用一种通用的方法
还记得之前我们说这个函数是使用动态注册吗?
那么它必然有一个函数表,并且这个函数表的名字是不会变的,通过查看源码,确定是dvm_dalvik_system_DexFile
于是使用dlopen()
和dlsym()
进行定位并循环遍历,对比名字及签名,并获取其函数指针
auto libdvm = dlopen("libdvm.so", RTLD_NOW);
auto dvm_dalvik_system_DexFile = (JNINativeMethod *) dlsym(libdvm, "dvm_dalvik_system_DexFile");
void (*fnOpenDexFileNative)(const u4* args, JValue* pResult) = nullptr;
for (auto p = dvm_dalvik_system_DexFile; p->fnPtr != nullptr; p++) {
if (strcmp(p->name, "openDexFileNative") == 0
&& strcmp(p->signature, "(Ljava/lang/String;Ljava/lang/String;I)I") == 0) {
fnOpenDexFileNative = (void (*)(const u4 *, JValue *)) p->fnPtr;
break;
}
}
if (fnOpenDexFileNative != nullptr) {
LOGI("Found fnOpenDexFileNative");
}
跑起来看效果
03-07 12:28:02.172 2270-2270/com.wnagzihxa1n.protectapk I/wnagzihxa1n: Found fnOpenDexFileNative
函数指针获取到了,接着来观察参数并尝试构造,第一个参数是u4*
的指针,第二个参数是JValue*
类型的指针
static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,
JValue* pResult)
继续看,args
一共有两个值,第一个是待加载的Dex文件路径,第二个是优化后的ODex文件路径
StringObject* sourceNameObj = (StringObject*) args[0];
StringObject* outputNameObj = (StringObject*) args[1];
但是我们观察到这两个参数转换成了StringObject*
类型,其定义如下
struct StringObject : Object {
u4 instanceData[1];
int length() const;
int utfLength() const;
ArrayObject* array() const;
const u2* chars() const;
};
而且幸运的是,我们发现了一个函数,可以帮助我们直接将char*
字符串转换成StringObject*
StringObject* dvmCreateStringFromCstr(const char* utf8Str) {
assert(utf8Str != NULL);
return dvmCreateStringFromCstrAndLength(utf8Str, dvmUtf8Len(utf8Str));
}
使用IDA找到这个函数的定义为_Z23dvmCreateStringFromCstrPKc
所以继续写代码来调用,其中需要强制转换类型
if (fnOpenDexFileNative != nullptr) {
LOGI("Found fnOpenDexFileNative");
auto fndvmCreateStringFromCstr = (void* (*)(const char* utf8Str)) dlsym(libdvm, "_Z23dvmCreateStringFromCstrPKc");
u4 args[2];
args[0] = static_cast<u4>(reinterpret_cast<uintptr_t>(fndvmCreateStringFromCstr(cachefilePath.c_str())));
args[1] = static_cast<u4>(reinterpret_cast<uintptr_t>(fndvmCreateStringFromCstr(cachefileOpt.c_str())));
JValue result;
fnOpenDexFileNative(args, &result);
}
这样就可以跑起来了
但是这仅仅是跑起来,我们接着要取出返回值并进行后续的调用,此处如果大家没有使用Dalvik虚拟机源码包的话,可以手动的一个个结构体添加,把不必要的注释去掉之后代码如下
#include <jni.h>
#include "Common.h"
#include "DexFile.h"
#ifndef PROTECTAPK_MANDROID_H
#define PROTECTAPK_MANDROID_H
#endif //PROTECTAPK_MANDROID_H
typedef struct MemMapping {
void* addr; /* start of data */
size_t length; /* length of data */
void* baseAddr; /* page-aligned base address */
size_t baseLength; /* length of mapping */
} MemMapping;
struct DvmDex {
DexFile* pDexFile;
const DexHeader* pHeader;
struct StringObject** pResStrings;
struct ClassObject** pResClasses;
struct Method** pResMethods;
struct Field** pResFields;
struct AtomicCache* pInterfaceCache;
bool isMappedReadOnly;
MemMapping memMap;
jobject dex_object;
pthread_mutex_t modLock;
};
struct RawDexFile {
char* cacheFileName;
DvmDex* pDvmDex;
};
typedef struct ZipHashEntry {
const char* name;
unsigned short nameLen;
} ZipHashEntry;
typedef struct ZipArchive {
int mFd;
MemMapping mMap;
int mNumEntries;
int mHashTableSize;
ZipHashEntry* mHashTable;
} ZipArchive;
struct JarFile {
ZipArchive archive;
char* cacheFileName;
DvmDex* pDvmDex;
};
struct DexOrJar {
char* fileName;
bool isDex;
bool okayToFree;
RawDexFile* pRawDexFile;
JarFile* pJarFile;
u1* pDexMemory; // malloc()ed memory, if any
};
我们获取返回的pDexOrJar指针
DexOrJar* pDexOrJar = nullptr;
if (fnOpenDexFileNative != nullptr) {
LOGI("Found fnOpenDexFileNative");
auto fndvmCreateStringFromCstr = (void* (*)(const char* utf8Str)) dlsym(libdvm, "_Z23dvmCreateStringFromCstrPKc");
u4 args[2];
args[0] = static_cast<u4>(reinterpret_cast<uintptr_t>(fndvmCreateStringFromCstr(cachefilePath.c_str())));
args[1] = static_cast<u4>(reinterpret_cast<uintptr_t>(fndvmCreateStringFromCstr(cachefileOpt.c_str())));
JValue result;
fnOpenDexFileNative(args, &result);
pDexOrJar = (DexOrJar*) result.l;
}
根据之前的分析,有一个地方是需要我们手动调用进行恢复的,就是下面这个mCookie
mCookie = openDexFile(sourceName, outputName, flags);
进行修改,把mCookie替换回去,这个值就是返回的pDexOrJar
jclass clazz_dalvik_system_DexFile = env->FindClass("dalvik/system/DexFile");
jobject jobj_dexFile = env->AllocObject(clazz_dalvik_system_DexFile);
jfieldID fieldID_mCookie = env->GetFieldID(clazz_dalvik_system_DexFile, "mCookie", "I");
env->SetIntField(jobj_dexFile, fieldID_mCookie, static_cast<jint>(reinterpret_cast<uintptr_t>(pDexOrJar)));
拿到mCookie
后,再加到dexElements
里就行了
跑起来的效果