android逆向手册android 脱壳unpaking_第1页
android逆向手册android 脱壳unpaking_第2页
android逆向手册android 脱壳unpaking_第3页
android逆向手册android 脱壳unpaking_第4页
android逆向手册android 脱壳unpaking_第5页
已阅读5页,还剩147页未读, 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、 关键技术点 4: 只是以前实验了一下,觉得原理可行,但是尚未深究.附件有个例子,里面包含两个程序,一个是 android 应用,一个是命令行的注入程序,里面实现了 so 注入和拦截 socket send recv 函数,但是没有是实现直接向地址里面写入自己的汇编代码的功能. 的/2758509/1334585Android脱壳(unpaking)壳的入口(OEP)android so 壳入口浅析三 13 八月 2014Write By #123前言JNI_OnLoad ,要么 JNI_OnLoad 汇编开年来开始接触一些加固样本,基

2、本都对了 so 进行了处理,拖入 ida 一看,要么没有代码羞涩难懂,让人无法下手。 JNI_OnLoad 是真正入口么? 先看看几个文档1 摘自属性服务一节(深入理解Android 卷1)利用 gcc 的 constructor 属性,这个属性指明了一个 libc_prenit 函数(这个函数内部就将完成共享内存到本地进程的映射工作)。用法:当 bionic libc 库被加载时,将自动调用 libc_prenit 函数。这样在 bionic libc 动态库被装载时,系统属性缓冲区地址就被确定了,后续的 API 调用就能找对位置了。 /* We flag the libc_preinit

3、function as a constructor to ensure * that its address is listed in libc.sos .init_array section. * This ensures that the function is called by the dynamic linker * as soon as the shared library is loaded. */ /constructor 属性指示加载器加载该库之后,首先调用 libc_prenit 函数。这一点和 windows 上的动态库的 DllMain 函数类似void attribu

4、te (constructor) libc_prenit(void); 从英文说明里面提到到.init_array section,我们可以搜索一下这一节的说明2 .init_array section.init_array contains pointers to blocks of code that need to be executed when an application is being initialized (before main() is called). It is used for a number of things, but the primary use is

5、in C+ for running static that is sometimes used is to initialize IO systems in the Cconstructors; a secondary uselibrary. entirely; but youdlive without itIf you are not using C+ you may (depending need to hack your startup code to deal withon your C library) be able to this. its marked read/write t

6、hatprobably ends up in ram becausehappens becausein a dynamic linking.init_array environment environmentthe dynamic linker has to fix up all the pointers it contains before it can be used. In a static you might be able to get away with forcing it into a read-only section.来源: 3 摘自 dlopen 小结(程序员的自我修养)

7、 动态连接器在加载模块时,会执行.init段的代码,用以完成模块的初始化工作,dlopen 的加载过程基本跟动态连接器一致,在完成装载、映射和重定向以后,就会执行.init段的代码然后返回 看完这个 3 段资料,我们可以知道在系统加载 so,在完成装载、映射和重定向以后,就首先执行.init 和.init_array 段的代码. 探本溯源,在源码中追踪我们先从 System.loadLibrary -Runtime.loadLibrarypublic void loadLibrary(String libName) loadLibrary(libName, VMStack.getCalling

8、ClassLoader(); /* * Loads and links a library without security checks. */ void loadLibrary(String libraryName, ClassLoader loader) 代码略. String error = nativeLoad(filename, loader); 代码略. - nativeLoadstatic void Dalvik_java_lang_Runtime_nativeLoad(const u4* args, JValue* pResult) 代码略. StringObject* fi

9、leNameObj = (StringObject*) args0; success = dvmLoadNativeCode(fileName, classLoader, &reason); 代码略. 来源: -dvmLoadNativeCodebooldvmLoadNativeCode(const char* pathName, Object* classLoader, char* detail) 代码略. handle = dlopen(pathName, RTLD_LAZY);代码略. vonLoad = dlsym(handle, JNI_OnLoad); if (vonLoad =

10、NULL) ALOGD(No JNI_OnLoad found in %s %p, skipping init, pathName, classLoader); else/*Call JNI_OnLoad. We have to override the current class loader, which will always be null since the stuff at the top of the stack is around Runtime.loadLibrary(). (See the comments in the JNI FindClass function.) *

11、/ OnLoadFunc func = (OnLoadFunc)vonLoad; Object* prevOverride = self-classLoaderOverride; self-classLoaderOverride = classLoader; oldStatus = dvmChangeStatus(self, THREAD_NATIVE); if (gDvm.verboseJni) ALOGI(Calling JNI_OnLoad for %s, pathName); version = (*func)(gDvmJni.jniVm, NULL); dvmChangeStatus

12、(self, oldStatus); self-classLoaderOverride = prevOverride; 代码略.来源: 通过 dvmLoadNativeCode 函数我们知道系统用 dlopen 加载 so 完成后,会查看有没有 JNI_OnLoad 函数,有的话就调用.我们再到 dlopen 函数探个究竟:void *dlopen(const char *filename, int flag) soinfo *ret; pthread_mutex_lock(&dl_lock); /*find_library 会判断 so 是否已经加载,如果没有加载,对 so 进行加载,完成一

13、些初始化工作,有兴趣的读者可自行分析 */ret = find_library(filename); if (unlikely(ret = NULL) set_dlerror(DL_ERR_CANNOT_LOAD_LIBRARY); else call_constructors_recursive(ret); ret-refcount+; pthread_mutex_unlock(&dl_lock); return ret; -call_constructors_recursive*si)void call_constructors_recursive(soinfo if(si-constru

14、ctors_called) return; Set this before actually callingthe constructors,otherwise it doesnt/protect against recursive constructor calls. One simple example of constructor recursion is the libc debug malloc, which is implemented in libc_malloc_debug_leak.so: 1.2.3.The program depends on libc, so libcs

15、 constructor is called here. The libc constructor calls dlopen() to load libc_malloc_debug_leak.so. dlopen() calls call_constructors_recursive() with the newly created soinfo for libc_malloc_debug_leak.so. The debug so depends on libc, so call_constructors_recursive() is called again with the libc s

16、oinfo. If it doesnt trigger the early- 4.out above, the libcconstructor will be called again (recursively!). 1; si-constructors_called =if (si-flags & FLAG_EXE) TRACE( %5d Calling preinit_array 0x%08x %d for %s n, pid, (unsigned)si-preinit_array, si-preinit_array_count, si-name); call_array(si-prein

17、it_array, si-preinit_array_count, 0); TRACE( %5d Done calling preinit_array for %s n, pid, si-name); else if (si-preinit_array) 0x%08x.DL_ERR(%5d Shared library %s has a preinit_array table This is INVALID., pid, si-name, (unsigned)si-preinit_array); 代码略.if (si-init_func) TRACE( %5d Calling init_fun

18、c 0x%08x for (unsigned)si-init_func, si-name); si-init_func(); TRACE( %5d Done calling init_func for %s if (si-init_array) %s n, pid,n, pid, si-name);TRACE( %5d Calling init_array 0x%08x %d for %s n, pid, (unsigned)si-init_array, si-init_array_count, si-name); /遍历函数数组并执行 call_array(si-init_array, si

19、-init_array_count, 0); TRACE( %5d Done calling init_array for %s n, pid, si-name); /ps:看到这么多 TRACE 这么多调试信息,我们把调试开关打开,是不是能拿到诸多信息? 来源: 通过可以函数我们知道 si-init_func 和 si-init_array 存在的时候,会执行指向的函数(不知道大家注意到么 si-flags & FLAG_EXE 时,还有 si-preinit_array? 再找下 si-init_func 和 si-init_array 的赋值 以后会不会有这方面的东西?)case DT_

20、INIT: si-init_func = (void (*)(void)(si-base + *d); DEBUG(%5d %s constructors (init func) found at %pn,pid, si-name, si-init_func); case DT_INIT_ARRAY: si-init_array = (unsigned *)(si-base + *d); DEBUG(%5d %s constructors (init_array) found at %pn,pid, si-name, si-init_array); break; DEBUG 里面说明了 con

21、structors (init func)和 constructors (init_array)。我们再看看一份文档 Android Dynamic Linker Design Notes DT_INIT Points to that mustDT_INIT_ARRAY Points tothe address of an initialization function be called when the file is loaded. an array of function addresses that must becalled, in-order, to perform initia

22、lization. Some of the entries in the array can be 0 or -1, and should be ignored.Note: this is generally stored in a .init_array section通过层层分析,我们很清楚知道了系统加载 so,在完成装载、映射和重定向以后,就首先执行.init 和.init_array 段的代 码.前面有一篇文章我已经对 so 加壳进行简单说明把源码的 dlopen 复制出来修改,在把自己 so 加载起来的时候 ,把自己内存里面某部分地址soinfo 结构体 然后把当前 soinfo 结

23、构体替换原来的 soinfo 结构体 后,用自己的 dlopen 打开返回一个小结系统加载 so,在完成装载、映射和重定向以后,就首先执行.init 和.init_array 段的代码,之后如果存在 JNI_OnLoad 就调 section,so 加壳一般会在初始化函数进 用该函数.我们要对一个 so 进行分析,需要先看看有没有.init_array section 和.init行脱壳操作。 如何在.init 和.init_array 段添加我们的函数1 共享构造函数,在函数声明时加上 attribute (constructor)属性void attribute (constructor)

24、 init_function(void); 对应有共享虚构函数,在程序 exit()或者 dlclose()返回前执行void attribute (destructor) fini_function(void); 2c+ 静态构造函数在.init 和.init_array 下断点 init_array用 ida 可以看到, 可以对里面的函数数组下断点init ida 有时没识别出来,可用 readelf 查看入口点xxxxx:$ readelf -a /home/xxx/桌面/libsecexe.so0x000000100x0000000c0x000000190x0000001b0x0000

25、001a0x0000001c0x00000004(SYMBOLIC) (INIT) (INIT_ARRAY) (INIT_ARRAYSZ) (FINI_ARRAY) (FINI_ARRAYSZ) (HASH) 0x0 0x11401 0x28ca4 8 (bytes) 0x28cac 12 (bytes) 0xf4 我们看到 INIT 入口为0x11401code,由于对齐关系,要从 0x11401+1 开始)(ps:有时你在 0x11401 是数据,你需要 make样本:梆梆 爱加密 参考:1234深入理解 Android 卷 1 程序员的自我修养-链接、装载与库android linker

26、 浅析 Android Dynamic Linker Design Notes/pages/2014/08/android-soke-ru-kou-qian-xi导出文件(dump) ijiami 快速 dump 法原理浅析前言某段时间 ijiami 的壳的一种内存 dump 法便是通过在 dvmDexFileOpenPartial 函数上下断点,然后直接 dump!好奇便去看了下源码。主要是因为系统的dexoptdexopt 初始化一个vm,加载 dex 文件并执行verification 和optimization 过程。完成后,进程退出,释放所有资源

27、 分析其源代码:01main 函数if (argc 1) if (strcmp(argv1, -zip) = 0) return fromZip(argc, argv);else if (strcmp(argv1, -dex) = 0) return fromDex(argc, argv);else if (strcmp(argv1, -preopt) = 0)return preopt(argc, argv); 分别会对三种类型的文件进行优化处理以 dex 文件为例02fromDex 函数if (dvmPrepForDexOpt(bootClassPath, dexOptMode, veri

28、fyMode, 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;初始化一个虚拟机,然后调用 dvmContinueOptimization 03dv

29、mContinueOptimization函数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) ALOGE(Unable to create DexFile); success =

30、 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

31、generating register maps); success = false; DexHeader* pHeader = (DexHeader*)pDvmDex-pHeader;updateChecksum(dexAddr, dexLength, pHeader);dvmDexFileFree(pDvmDex);调用 rewriteDex 对dex 进行重写,主要是字节重新排序,结构重新安排优化成功返回 true04当优化成功之后,会调用 dvmDexFileOpenPartial 函数创建一个 dex 文件。if (dvmDexFileOpenPartial(dexAddr, dex

32、Length, &pDvmDex) != 0)看此函数的第一第二两个参数。第一个参数表示 dex 文件的起始位置第二个参数表示 dex 文件的大小so,可以在 dvmDexFileOpenPartial 函数上下断点此时,寄存器的值就是 dex 文件的起始位置和文件大小。便可以直接从内存中 dump 出来http:/0/?p=684从零打造简单的 SODUMP 工具 Author: ThomasKing最近翻看之前的帖子,发现基于 linker init_array 加密的 SO 文件的静态分析稍微麻烦。虽然原理很清楚,但是需要 dump 之后再进行 sec

33、tion 修复才能放入 ida。可以看到,上述两步骤其实很机械。那么应该可以实现一个自动化工具,帮助我们解决上述问题,让我们可以精力专注于其他地方,提高效率。实现上述工具需要解决两个问题:1 应对各种加密算法 2 section 重建。Section 重建在 /showthread.php?t=192874 已经 讨论,就不再赘述,仅仅改进一些不足之处。 一、应对各种加密算法策略在 /showthread.php?t=192020&viewgoodnees=1&prefixid= 等帖子中提到的 SO 文件都基于 in

34、it_array 实现。为了静态分析,不得不分析出算法,并实现对 SO,以便静态分析。而且,不同的 SO 采用不同的加密算法。这个步骤时间的花费,取决于分析人员对各种常用的加密算法 RC4、TED 等了解程度,这是比较纠结的事情。 那如何应对各种加密算法,成为工具实现的关键。直接实现各种算法,这个显然是不行的。那只能通过某种方法来绕过。想了很久,也没有很好的解决策略。不过有一点,熟悉 SO 机制的读者都知道,linker 可是应对自如!那看来只能借 linker 之手,来帮助我们实现。 Linker 除了在程序运行初加载外,还有就是通过 dl 函数会调用,即 dlopen、dlsym、dler

35、r 和 dlclose。啰嗦下 dlopen 和 dlsym 函数原型: void * dlopen( const char * pathname, int mode); void*dlsym(void*handle,constchar*symbol); 通常,使用 dlopen 打开加载某 SO 后,会返回一个 handle 对象,然后根据 handle 对象调用 dlsym 查找某函数符号,实现调用。仔细分析可以发现,这个过程和程序在运行初是类似的;另外,linker 只会加载某一 S0 一次,那么 linker 应该维护了一个数据表来记录。进一步分析还可以发现,这个 void *hand

36、le 对象应该指向了当前打开 SO 的数据对象。有了这个思路,翻看 dl 函数源码(位于:bioniclinkerdlfcn.c,不是在 bioniclibdllibdl.c), dlopen 源码: void *dlopen(const char *filename, int flag)soinfo *ret; pthread_mutex_lock(&dl_lock); ret = find_library(filename);if (unlikely(ret = NULL) set_dlerror(DL_ERR_CANNOT_LOAD_LIBRARY); else ret-refcount

37、+;pthread_mutex_unlock(&dl_lock);标红部分可以看到,ret 就是 handle。翻看 linker 源码,其实际为:soinfo 结构体(截取部分结构) struct soinfoconst char nameSOINFO_NAME_LEN; Elf32_Phdr *phdr; /Elf32_Phdr 实际内存地址int phnum;unsigned entry;unsigned base; /SO 起始 unsigned size; /内存对齐后占用大小 int unused;/ DO NOT USE, maintained for compatibility

38、.unsigned *dynamic; /.dynamic 实际内存地址 unsigned wrprotect_start; /mprotect 调用unsigned wrprotect_end;soinfo *next; /下一个soinfounsigned flags; const char *strtab; /.strtab 实际内存地址 Elf32_Sym *symtab; /. symtab 实际内存地址/hash 起 始 位 置 :bucket 2 * sizeof(int) unsigned nbucket; /size = nbucket * sizeof(int) unsig

39、ned nchain; /size = nchain * sizeof(int) unsigned *bucket;unsigned *chain;unsigned *plt_got; /对应.dynamic: DT_PLTGOTElf32_Rel *plt_rel; /函数重定位表unsigned plt_rel_count;Elf32_Rel *rel; /符号重定位表unsigned rel_count;.;从这个结构中,已经可以得到各种信息,直接用于后续重建。另外,还有一点:dlopen 返回 soinfo 时,linker 已经执行了 init_array 中的函数。换句话说,已经实

40、现了自,直接 dump 就是 OK。 二、改进 PLT 和 GOT 重建在 /showthread.php?t=192874 帖子中,PLT 和GOT section 并不能直接从.dynamic 中获取。重建时,通过 section 之间的排列关系,间接的进行修正。如果想对位置变化,这种重建是无意义的。影响相对位置变化主要有:链接脚本的细微区别和 SO 经过 DIY。比如,不同版本的 ndk 存在细微差异: 图 1 图 2另外,GOT 结构也不同,相应链接脚本中也存在顺序不一致。比如:.got : *(.got.plt) *(.igot.plt) *(.

41、got) *(.igot) 就是函数符号在前,其他符号在后, 和之前讨论的是相反的。由于存在上述原因,对 PLT 和GOT 的重建进行如下改进。 2.1 PLT section由于优化,PLT 结构前四项是固定代码。 图 3借鉴window 内核中搜索未导出符号的思路,通过搜索前 16 个字节来确定 plt。恰好这里刚好够 16 个字节(真是无巧不成书),即: static unsigned g_plt_start = 0xe52de004, 0xe59fe004, 0xe08fe00e, 0xe5bef008 ;这样就可以得到 plt section 的起始地址。Size 前面帖子中已经说明

42、,这里就不在赘述。 2.2 GOT section前面已经提到GOT 表存在两种结构。另外,.got 与.rel 的对应关系并不像.got.plt 与.rel.plt 的对应关系那么稳定,受到诸如指针间址寻址影响。但始终有一点:.got 中的项一定出现在.rel section 中。那么重建的思路就是: Step1: 读取DT_PLTGOT,获取 global_offset_table 地址,记为:plt_gotStep2: 读取 plt_got 4 地址的数值,在.rel 表中进行搜索。如果匹配,说明GOT 结构式:.got, .got.plt,转 3.1;否则为.got.plt, .got

43、转 3.2 Step3.1: 继续向前搜索,直到搜索到起始位置。 Step3.2: 调整搜索起始位置:plt_got + 4 * (3 + rel_plt_count),向后搜索,搜索到末尾。这里,存在一种情况:若下面.data 也有重定位项且处于.data 起始连续位置,也会被搜索到。但对于.got 和.data 都对应到.rel 重定位中,重定位方式相同,统一处理也是合理的,实测不存在问题。 通过搜索,即可准确获取GOT 起始地址和长度,完成重建。 另外,对于.data 和.rodata 的重建目前没有很好的思路,仅只通过相对位置重定位。如果有更好的思路,请告知我学习学习,衷心感谢。 三、

44、SODUMP 出炉经过上面讨论,SODUMP 工具基本成型,剩下的就是添砖添瓦,做成一个 apk。通过 EditText 和Button 配合,将待 dumpSO 进行处理,记得使用 dlclose 关闭,最好使用 dlerr 把错误信息打印出来便于定位问题。废话不多说,上几张测试图: 此贴文件脱壳如下,/showthread.php?t=190384&viewgoodnees=1&prefixid=SO/showthread.php?t=188793&viewgoodnees=1&prefixid= 帖子类似,都为某

45、公司一种模式,就不贴了。图 4 脱壳前图 5 脱壳后 一朋友给的某手游游戏,据说是美杜莎壳。当然,这仅仅只是该壳子的一个很小方面,这壳子很生猛的。 图 6 脱壳前 图 7 脱壳后四、参考文献Linker.c、dlfcn.c 源码 /showthread.php?t=192874 /showthread.php?t=190384&viewgoodnees=1&prefixid=/showthread.php?t=193720&viewgoodnees=1&prefixid=脱 dex

46、 文件ALICTF2014 evilAPK400 完整脱壳分析1 简 述该 apk 使用 libmobisec.so 函数实现对 dex 的还原。真正的 dex 为 assets 目录下的 cls.jar 和 jute.data 文件。 本分析文档主要用于讨论脱壳方面的技术,并非以获取该 APK 的登录为目的。所以虽然可以使用很多动态的 Android分析工具进行API 跟踪, 然后快速得到登录,但是我还是选择进行静态脱壳,毕竟没参加比赛,不用担心时间限制啊分析文档只涉及核心逻辑,具体细节需要结合 libmobisec.idb 文档阅读,里面注释还是很详细的。 2 分 析 2.1 初 探关键函

47、数在 sub_24c84(我改名为了 keyFunction),该函数进行一些准备工作后,就会在偏移值 0x24eca 处调用 parse_dex 函数(偏移值为 0x26398)。此函数就是整个 dex的核心函数!下面对它进行详细分析。 2.1.1 openWithHeader 函数解析首先在 0x269c8 调用 openWithHeader 函数。该函数的详细实现在 0x285f0 处,它的功能如下: 获取真正的 cr4key; 打开并 mmap cls.jar,使用真正的 key 对cls.jar 进行cr4,然后解压,解压算法为 lzma,处理后的数据重新 mmap; munmap

48、掉第一次mmap 的内存,将、解压后的 cls.jar(就是一个 dex 文件)的首地址存放到struct1.cls_jar_mmap_addr 中,将它的大小存放到 struct1.umcompress_size 中; Struc1 为一个辅助结构它的内容如下: typedef struct struct1 int mmap_size ; /文件 mmap 大小,需要注意的是,在 cls.jar 操作中它的大小刚好比unpacksize 的大小多 0x10,这是由 mmap 时的参数造成的!详情参见idb void* file_mmap_addr; /这个文件mmap 在内存中的首地址,对于

49、cls.jar 其向后偏移 0x10 才是 dex.035 的开始地 址! void* file_path ; /file 的绝对路径 ; 返回 struct1.cls_jar_mmap_addr 值,并将上层参数 r2 赋值为;struct1.umcompress_size, 及r1 赋值为 cls_jar_mmap_addr; 如何获取真正的 cr4 key? 在 ali:decryptRc4 函数中,先获取原 apk 的 classes.dex 的 crc32 校验码(32bit),然后将一个硬编码的 0x18 字节的字符串按4 字节为单位依次同 crc32 结果进行异或运算。运算得到的

50、 0x18 字节的字符串就是真正的 rc4 密钥。 为了以后叙诉的方便,将、解压后的 cls.jar 称作 cls.dex。 2.1.2 反射调用 openDexFile 函数加载 cls.dex鉴于篇幅有限,这里就不详细说明了,大家可以直接移步到 jack_jia 大牛的博客: /androidsecurity/article/details/9674251 需要提及一点,就是 openDexFile 加载、解析完 cls.dex 之后,会在内存重新生成一个经过优化后的 cls.dex。之后的操作, 都是针对这个优化后的 cls.dex 的。我们可以在

51、libmobisec 的 0x27b14 下断,r0 的值为 openDexFile 加载、解析后的 cls.dex 的开始地址,dump 出来即可。 大家可以查看 openDexFile 函数的具体实现,源码在: dalvik2vmnativedalvik_system_DexFile.cpp 关键函数在 dvmRawDexFileOpenArray(pBytes, length, &pRawDexFile),他完成加载和解析工作。需要注意的是其中的 dvmPrepareDexInMemory 函数会调用rewrite 函数,这又是需要重点关心的函数。这个函数完成 dex 的优化,认证,以及

52、所有类的加载(loadAllClasses)和部分 inline 方法的加载。说个题外话,可以从 loadAllClasses 函数看出,对于 dalvik 虚拟机而言,它在解析dex 文件的时候会且仅会把所有的 DexclassDef 结构加载到内存,而只有在使用到某个类的方法的时候才会具体地加载这个方法!这貌似就是之前有人提及过的基于方法粒度的 dex 加密的前提吧。 按照正常情况,分析到此,就已经可以在 openDexFile 函数下断点,dump 出出来的 dex,使用 backsmali 反编译为 smali 文件,得到的却是如下结果: 后的 dex 了。但是,现实很,我们将 dum

53、p 所有类的所有方法都被替换成了上面这种形式!显然,得到的 cls.dex 并非一个完整的 dex 文件,它真正的方法都被替换掉了! 看来脱壳过程不是想象中的那么简单啊。没办法,继续看 libmobisec 的代码。 2.1.3 ali:dex_juicer.patch 函数分析 在加载完 cls.dex 之后,会接着执行 ali:dex_juicer_patch 函数。从名字就可以看出它是在对 dex 进行修补工作。继续分析,总结此函数的功能如下: 可以在 libmobisec 的 0x27b60 下断,此时的 r5 为 juicer.data后的首地址,r3 为后的长度,dump 出来,保

54、存为 decrypted_juice 即可。 第二个功能总结起来就一句话,但实际情况很复杂,我最初就卡在了 0x2a79c 的函数上(函数名原来是sub_2a79c,后来直到大牛 Flanker_017 发了 一份某个复旦大牛的算法后,才发现它原来就是 readUleb128 函数!这可能就是一个人进行分析时面临的最大问题思维固化。听说那位大牛是初次接触 android 就把它给逆出来了,我 . 只能给他跪下)。当然,理解了这个函数,并不意味着,就可以轻松的进行 dex 还原了。挑战才刚刚开始! 如果仅仅看它的那些函数,是很难得到有用的信息的。我分析了一天都没理出个头绪。本着一切问题睡一觉之后都能解决的原则,我一觉睡到了第二天下午果然,灵感来了! 2.2 换个角度分析问题回顾 2.1.2,我发现所有的方法都被替换成了 RuntimeException。

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论