1.文章难易度【★★★】
2.文章作者:penguin_wwy
3.本文参与i春秋社区原创文章奖励计划,未经许可禁止转载
4.阅读基础:自备Android源码
【预备~~~起】
曾经有个女(xue)的(jie)问我,为什么APP的壳代码一般都放在Application类的attachBaseContext函数中,如图
能不能放在其他函数里。当时我说可以,当时没办法达到同样的效果,因为应用启动时,ActivityThread类负责整个环境,它有一个Application类型的app变量,指向当前的APK的Application,在ActivityThread启动onCreate函数之前会调用app.attachBaseContext,所以Application的attachBaseContext会在整个APP启动前调用。。。。(我编不下去了)
PS:本人看的Android源码版本为4.3-r2和5.0-r1,文章中主要介绍跟Dalvik虚拟机相关的,所以使用4.3-r2,如果遇到Art虚拟机部分再看5.0-r1
【一二三四】
脱壳的入门教程都会教大家在dvmDexFileOpenPartial函数下断,该函数第一个参数是dex文件在内存中的位置,然后从dex头部获取dex文件大小,dump出整个dex文件,那么这个dvmDexFileOpenPartial函数是怎么来的呢,如何调用到它的,我们从Android源码一步一步分析。
【二二三四】
首先是加载过程,我们先省略掉之前的内容,从dex文件的加载开始。Dalvik中的ClassLoader有DexClassLoader和PathClassLoader两种
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
区别在于optimizedDirectory这个参数,DexClassLoader通过它new出一个File对象。这里直接告诉大家,这个参数的作用是用于dex的优化。前面我也提到过,dex文件经过优化后变为odex文件,执行效率更高。PathClassLoader载入的是已经经过优化的odex,相应的DexClassLoader是没有经过优化的,这里的new File(optimizedDirectory)就是通过optimizedDirectory对dex进行优化。
继续看BaseDexClassLoader
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
下面一步是DexPathList,我们关注的重点在optimizedDirectory
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);
如果optimizedDirectory存在(PathClassLoader的情况),那么验证文件是否存在,是否可读写,然后传入makeDexElements函数。这个函数稍微有点长,看重点
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
|| name.endsWith(ZIP_SUFFIX)) {
zip = file;
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ignored) {
/*
* IOException might get thrown "legitimately" by
* the DexFile constructor if the zip file turns
* out to be resource-only (that is, no
* classes.dex file in it). Safe to just ignore
* the exception here, and let dex == null.
*/
}
} else if (file.isDirectory()) {
// We support directories for looking up resources.
// This is only useful for running libcore tests.
elements.add(new Element(file, true, null, null));
} else {
System.logW("Unknown file type for: " + file);
}
如果文件是dex、apk、jar或者zip那么就会调用loadDexFile。而这个loadDexFile其实是对DexFile构造函数的封装,所以直接看DexFile的构造函数
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");
//System.out.println("DEX FILE cookie is " + mCookie);
}
重点在openDexFile,从这里进入到native层,之后的过程不(又)是(臭)重(又)点(长),我们简单看下。
const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {
{ "openDexFile", "(Ljava/lang/String;Ljava/lang/String;I)I",
Dalvik_dalvik_system_DexFile_openDexFile },
{ "openDexFile", "([B)I",
Dalvik_dalvik_system_DexFile_openDexFile_bytearray },
{ "closeDexFile", "(I)V",
Dalvik_dalvik_system_DexFile_closeDexFile },
{ "defineClass", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;",
Dalvik_dalvik_system_DexFile_defineClass },
{ "getClassNameList", "(I)[Ljava/lang/String;",
Dalvik_dalvik_system_DexFile_getClassNameList },
{ "isDexOptNeeded", "(Ljava/lang/String;)Z",
Dalvik_dalvik_system_DexFile_isDexOptNeeded },
{ NULL, NULL, NULL },
};
Dalvik_dalvik_system_DexFile_openDexFile是Java层对应的函数,该函数会调用dvmRawDexFileOpen
if (newFile) {
u8 startWhen, copyWhen, endWhen;
bool result;
off_t dexOffset;
dexOffset = lseek(optFd, 0, SEEK_CUR);
result = (dexOffset > 0);
if (result) {
startWhen = dvmGetRelativeTimeUsec();
result = copyFileToFile(optFd, dexFd, fileSize) == 0;
copyWhen = dvmGetRelativeTimeUsec();
}
if (result) {
result = dvmOptimizeDexFile(optFd, dexOffset, fileSize,
fileName, modTime, adler32, isBootstrap);
}
if (!result) {
ALOGE("Unable to extract+optimize DEX from '%s'", fileName);
goto bail;
}
endWhen = dvmGetRelativeTimeUsec();
ALOGD("DEX prep '%s': copy in %dms, rewrite %dms",
fileName,
(int) (copyWhen - startWhen) / 1000,
(int) (endWhen - copyWhen) / 1000);
}
这个函数调用dvmOptimizeDexFile对dex文件进行优化,优化过程:产生一个空文件——>构造文件头——>拷贝——>优化。优化会执行/bin/dexopt,dexopt的main函数
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)
return fromDex(argc, argv);
else if (strcmp(argv[1], "--preopt") == 0)
return preopt(argc, argv);
}
fprintf(stderr,
"Usage:\n\n"
"Short version: Don't use this.\n\n"
"Slightly longer version: This system-internal tool is used to\n"
"produce optimized dex files. See the source code for details.\n");
return 1;
}
根据文件类型选择fromDex函数
static int fromDex(int argc, char* const argv[])
{
。。。
。。。
if (dvmPrepForDexOpt(bootClassPath, dexOptMode, verifyMode, flags) != 0) {
ALOGE("VM init failed");
goto bail;
}
vmStarted = true;
/* do the optimization */
if (!dvmContinueOptimization(fd, offset, length, debugFileName,
modWhen, crc, (flags & DEXOPT_IS_BOOTSTRAP) != 0))
{
ALOGE("Optimization failed");
goto bail;
}
result = 0;
bail:
/*
* In theory we should gracefully shut the VM down at this point. In
* practice that only matters if we're checking for memory leaks with
* valgrind -- simply exiting is much faster.
*
* As it turns out, the DEX optimizer plays a little fast and loose
* with class loading. We load all of the classes from a partially-
* formed DEX file, which is unmapped when we're done. If we want to
* do clean shutdown here, perhaps for testing with valgrind, we need
* to skip the munmap call there.
*/
#if 0
if (vmStarted) {
ALOGI("DexOpt shutting down, result=%d", result);
dvmShutdown();
}
#endif
free(bootClassPath);
ALOGV("DexOpt command complete (result=%d)", result);
return result;
}
函数之前执行一些切割参数的工作被我省略了,重点在最后的dvmContinueOptimization函数。由于这个函数很长,截取我们需要的一部分
if (success) {
DvmDex* pDvmDex = NULL;
u1* dexAddr = ((u1*) mapAddr) + dexOffset;
if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) {
ALOGE("Unable to create DexFile");
success = false;
} else {
/*
* If configured to do so, generate register map output
* for all verified classes. The register maps were
* generated during verification, and will now be serialized.
*/
if (gDvm.generateRegisterMaps) {
pRegMapBuilder = dvmGenerateRegisterMaps(pDvmDex);
if (pRegMapBuilder == NULL) {
ALOGE("Failed generating register maps");
success = false;
}
}
DexHeader* pHeader = (DexHeader*)pDvmDex->pHeader;
updateChecksum(dexAddr, dexLength, pHeader);
dvmDexFileFree(pDvmDex);
}
}
可以看到我们找了半天的dvmDexFileOpenPartial函数就在这里。简单说下dvmContinueOptimization的内容,大概分为几部分:声明内部变量,检查文件长度将文件mmap到mapAddr,从gDvm中获取信息设置选项。dvmDexFileOpenPartial所在的这部分内容是文件重写,比特位、结构重排,类型核实,字节码优化等等。
【二二三四】
到这里我们就捋顺了dvmDexFileOpenPartial这个函数的意义了。总结下,通过DexClassLoader加载dex文件 ——> 执行dexopt对dex进行优化 ——> dvmContinueOptimization执行优化的内容 ——> 最终到达dvmDexFileOpenPartial。
更多安全技术、精品好文、白帽黑客大佬尽在:http://bbs.ichunqiu.com/portal.php