目录:
逆向加壳apk的典型方法
我们知道,逆向加壳apk的时候,可以对libdvm.so
中的int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
函数打断点,然后IDA
中使用下面的代码根据addr
和len
将内存中的dex
文件dump
到文件中。
// dump memory dex to file
static main(void)
{
auto fp, begin, end, dexbyte;
fp = fopen("C:\\dump.dex", "wb");
begin = r0;
end = r0 + r1;
for ( dexbyte = begin; dexbyte < end; dexbyte ++ )
fputc(Byte(dexbyte), fp);
}
dvmDexFileOpenPartial
函数的原型为
int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
addr
,加载的DEX文件在内存中的基址.(也就是DEX.035)len
,加载的DEX文件的文件长度,ppDvmDex
,DEX 文件转成DvmDex结构,里面包含Dex文件的类,字段,方法,字符串信息。dalvik操作Dex文件的对象这是结构结构体
流程分析
Android系统启动时会先启动init进程,init进程会启动zygote进程,该进程会为每个要启动的App进程孵化出一个Davlik虚拟机实例。Zygote 进程还会启动optdex进程,这个进程将要运行App的DEX文件映射到内存中,然后校验,优化,转成虚拟机能够操作的Dex对象DvmDex.
首先来看一下dexopt
对应的入口main()
函数:
dalvik/dexopt/OptMain.cpp
/*
* Main entry point. Decide where to go.
*/
int main(int argc, char* const argv[])
{
set_process_name("dexopt");
setvbuf(stdout, NULL, _IONBF, 0);
if (argc > 1) {
if (strcmp(argv[1], "--zip") == 0)
return fromZip(argc, argv);
else if (strcmp(argv[1], "--dex") == 0) // 重点看dex
return fromDex(argc, argv);
else if (strcmp(argv[1], "--preopt") == 0)
return preopt(argc, argv);
}
...
return 1;
}
跟进函数fromDex函数:
{
int result = -1;
char* bootClassPath = NULL;
int fd, flags, vmBuildVersion;
long offset, length;
const char* debugFileName;
DexClassVerifyMode verifyMode;
DexOptimizerMode dexOptMode;
...
if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode, flags) != 0) {
ALOGE("VM init failed");
goto bail;
}
...
/* do the optimization */
if (!dvmContinueOptimization(fd, offset, length, debugFileName,
modWhen, crc, (flags & DEXOPT_IS_BOOTSTRAP) != 0))
{
ALOGE("Optimization failed");
goto bail;
}
...
return result;
}
dvmPreForDexOpt
函数的作用在对函数进行优化前,检测Dalivk虚拟机是否进入工作状态
int dvmPrepForDexOpt(const char* bootClassPath, DexOptimizerMode dexOptMode,
DexClassVerifyMode verifyMode, int dexoptFlags){
...
if (!dvmGcStartup())
goto fail;
if (!dvmThreadStartup())
goto fail;
if (!dvmInlineNativeStartup())
goto fail;
if (!dvmRegisterMapStartup())
goto fail;
if (!dvmInstanceofStartup())
goto fail;
if (!dvmClassStartup())
goto fail;
...
return 0;
fail:
dvmShutdown();
return 1;
}
如果通过dvmPreForDexOpt
检测,则会继续调用dvmContinueOptimization
对Dex文件进行优化
{
...
gDvm.optimizingBootstrapClass = isBootstrap;
{
bool success;
void* mapAddr;
mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);// 1
...
success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,
doVerify, doOpt, &pClassLookup, NULL);
if (success) {
DvmDex* pDvmDex = NULL;
u1* dexAddr = ((u1*) mapAddr) + dexOffset;
if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) { // 2
ALOGE("Unable to create DexFile");
success = false;
} else {
...
}
}
}
...
}
首先通过mmap
将Dex文件映射到内存中,之后会调用 dvmDexFileOpenPartial
, 这个函数主要功能就是完成将内存
中DexDile
转化成Dalvik
的Dex文件DvmDex
。
int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
{
DvmDex* pDvmDex;
DexFile* pDexFile;
int parseFlags = kDexParseDefault;
int result = -1;
pDexFile = dexFileParse((u1*)addr, len, parseFlags);
...
pDvmDex = allocateAuxStructures(pDexFile);
...
*ppDvmDex = pDvmDex;
result = 0;
...
return result;
}
我们来看一下DvmDex
结构,里面有我们熟知的字符串对象
,内对象
,方法
, 字符
等等信息
struct DvmDex {
/* pointer to the DexFile we're associated with */
DexFile* pDexFile;
...
/* interned strings; parallel to "stringIds" */
struct StringObject** pResStrings;
/* resolved classes; parallel to "typeIds" */
struct ClassObject** pResClasses;
/* resolved methods; parallel to "methodIds" */
struct Method** pResMethods;
/* resolved instance fields; parallel to "fieldIds" */
/* (this holds both InstField and StaticField) */
struct Field** pResFields;
...
};
我们再来看下dexFileParse
方法
DexFile* dexFileParse(const u1* data, size_t length, int flags)
{
DexFile* pDexFile = NULL;
const u1* magic;
...
pDexFile = (DexFile*) malloc(sizeof(DexFile));
if (pDexFile == NULL)
goto bail; /* alloc failure */
memset(pDexFile, 0, sizeof(DexFile));
/*
* Peel off the optimized header.
*/
if (memcmp(data, DEX_OPT_MAGIC, 4) == 0) {
...
pDexFile->pOptHeader = (const DexOptHeader*) data;
...
}
dexFileSetupBasicPointers(pDexFile, data);
...
bail:
...
return pDexFile;
}
dexFileParse
方法的功能是完成dex的解析,也有人逆向的时候在这里dump dex
的。有兴趣的同学可以看看DexExtractor的原理分析和使用说明。