apk自我保护的一种实现方式——运行时自篡改dalvik指令【转载】

玩过Android开发的人应该都知道,Android apk的保护是非常差的,辛辛苦苦写的代码,被别人翻个底朝天倒不说,被人改了代码移头换面再拿出来害人就不能忍了。

除自带的SDK外,Android的分析和修改工具还有很多,Android下的静态分析工具,最常见的是利用ApkTool(见http://code.google.com/p/android-apktool/)反编译apk,将dalvik字节码生成smali汇编,通过对smali汇编的阅读分析,结合实际软件运行时行为,搜索定位关键信息点,再针对性的修改汇编指令,再通过ApkTool重新编译打包签名生成apk来运行,以达到破解的目的。除ApkTool之外,还有一些工具,如IDA pro、dex2jar结合jd-gui以及androguard等等。

当然,应对反编译apk来破解的方式有多种,比如加上混淆,使用签名校验等等,这里说的一种方式,并非常规方式,但若实现好之后,对用户想要通过常规的静态分析代码的方式来破解apk,不仅白费力气,还很容易被带进沟里去。

本文的方法并非独创,在实际工作中的需要对dalvik指令分析统计时,意外搜索到bluebox的这篇文章:http://bluebox.com/labs/android-security-challenge/ 。可惜的是,他们没有提供源代码,就只能按照思路,自己实现了。实现的源代码在https://github.com/freshui/dexBytecodeTamper,有兴趣的同学可以对照本文后,下载下来瞅瞅,这里先说说原理吧(只简单挑一些说,详细请查阅android源码文档)。

1.Dalvik文件格式分析

Dex文件的格式,在Android原生代码中,由一个DexFile的结构体描述:

[cpp] view plaincopy

  1. struct DexFile {
  2. const DexHeader*    pHeader;
  3. const DexStringId*  pStringIds;
  4. const DexTypeId*    pTypeIds;
  5. const DexFieldId*   pFieldIds;
  6. const DexMethodId*  pMethodIds;
  7. const DexProtoId*   pProtoIds;
  8. const DexClassDef*  pClassDefs;
  9. const DexLink*      pLinkData;
  10. };

以上dexFile结构描述的,是编译后直接生成的是dex文件的格式,在apk文件(就是一个zip文件,可用winrar或其他解压缩工具直接打开)中,dex文件是classes.dex。

Dalvik格式的详细情况,可以参看2008年的Google IO: Dalvik VM Intenals,说的很清楚。整体结构相比ELF格式来说,要简单一些,如下图(拷贝自Dalvik VM Internals 的ppt),通常由7个部分组成,header记录了dalvik文件的一些信息,并标记其余几个部分在文件中的位置,而其他几个部分的XXX_ids或XXX_defs,实际上都只是索引,索引的内容是存在data部分的。

dalvik与传统的class文件相比的一个优势,就是将所有的常量字符串集统一管理起来了,这样就可以减少冗余(实际我感觉,也压榨不了多少,现在都是几个G的内存了,没多少意义了),最终的dex文件size也能变小一些。在上图的几大部分中,string_ids和elf的string table有点类似,存的是每个字符串的offset的位置,其他几个部分若要引用字符串,则直接使用string_ids的下标即可,方式同elf格式的索引。索引关系可见下图:

详细的dex文件介绍就不说了,有兴趣的可以直接翻看android的源码,虽然表面看起来蛮吓人的,但其结构及复杂度不及elf,解析起来比ELF更是简单。

odex文件格式

apk安装时,会通过dexopt来验证并生产优化后的dalvik字节码odex文件。过程是将apk中的classes.dex解压后,用dexopt处理,并保存到/data/dalvik-cache/[email protected]@<package-name>[email protected]。odex文件会解析相关依赖,将加载所需的依赖库列表附加在文件中,同时会修改部分指令以加快解析和处理的速度。

odex文件可以看做是dex文件的一个超集,其结构如下:

dex文件作为优化后的odex的一部分,在本文的分析中,只需要从odex中找出dexFile的部分即可。

2. Dex文件解析

我们的目标是自修改字节码。要实现自修改字节码,就需要先定位到想要修改得代码的位置,这就需要先解析dex文件。dex文件我们放在native代码中解析处理,naitive代码的分析破解,要比dalvik复杂多了。这里对dex文件解析的实现,可以参考dalvik源码,其中函数可以直接拿过来使用,详情可参考其dexDump模块,代码细节不想说,可参看dalvik源码或示例。

2.1 定位修改文件

要修改自身dalvik指令之前,我们需要查找到要修改的odex文件的map地址,然后通过mprotect调用,增加可写属性以便修改。代码如下:

首先要找到odex文件在本进程中map的位置,注意android对dalvik-cache目录下的odex文件名的命名方式:

[cpp] view plaincopy

  1. void *base = NULL;
  2. int module_size = 0;
  3. char filename[512];
  4. // simple test code  here!
  5. for(int i=0; i<2; i++){
  6. sprintf(filename, "/data/dalvik-cache/[email protected]@%s-%[email protected]", "com.freshui.dextamper", i+1);
  7. base = get_module_base(-1, filename);
  8. if(base != NULL){
  9. break;
  10. }
  11. }
  12. if(base == NULL){
  13. ALOGE("Can not found module: %s", filename);
  14. return;
  15. }

找到odex文件后,还需要知道改odex文件在其中的size,以便mprotect修改属性:

[cpp] view plaincopy

  1. module_size = get_module_size(-1, filename);

odex文件找到后,我们需要确定dex文件在其中的偏移,以便分析dex文件格式,查找Dex文件偏移位置:

[cpp] view plaincopy

  1. // search dex from odex
  2. void *dexBase = searchDexStart(base);
  3. if(checkDexMagic(dexBase) == false){
  4. ALOGE("Error! invalid dex format at: %p", dexBase);
  5. return;
  6. }

找到dex所在的偏移,就可以解析dex文件头,定位dex文件各部分所在的区域,以供下一步解析使用:

[cpp] view plaincopy

  1. DexHeader *dexHeader = (DexHeader *)dexBase;
  2. gDexFile.baseAddr   = (u1*)dexBase;
  3. gDexFile.pHeader    = dexHeader;
  4. gDexFile.pStringIds = (DexStringId*)((u4)dexBase+dexHeader->stringIdsOff);
  5. gDexFile.pTypeIds   = (DexTypeId*)((u4)dexBase+dexHeader->typeIdsOff);
  6. gDexFile.pMethodIds = (DexMethodId*)((u4)dexBase+dexHeader->methodIdsOff);
  7. gDexFile.pFieldIds  = (DexFieldId*)((u4)dexBase+dexHeader->fieldIdsOff);
  8. gDexFile.pClassDefs = (DexClassDef*)((u4)dexBase+dexHeader->classDefsOff);
  9. gDexFile.pProtoIds  = (DexProtoId*)((u4)dexBase+dexHeader->protoIdsOff);
  10. //dumpDexHeader(dexHeader);
  11. //dumpDexStrings(&gDexFile);
  12. //dumpDexTypeIds(&gDexFile);
  13. //dumpDexProtos(&gDexFile);
  14. //dumpFieldIds(&gDexFile);
  15. //dumpClassDefines(&gDexFile);

以上,已经基本确定了dex文件的信息,接下来要修改dalvik字节码了。

修改字节码之前,还需要定位到需要修改的byte Code的存放位置。目前能做到的修改仅针对dalvik指令部分,其他部分的修改未做尝试。dalvik指令的数据结构为:

[cpp] view plaincopy

  1. struct DexCode {
  2. u2  registersSize;
  3. u2  insSize;
  4. u2  outsSize;
  5. u2  triesSize;
  6. u4  debugInfoOff;       /* file offset to debug info stream */
  7. u4  insnsSize;          /* size of the insns array, in u2 units */
  8. u2  insns[1];
  9. /* followed by optional u2 padding */
  10. /* followed by try_item[triesSize] */
  11. /* followed by uleb128 handlersSize */
  12. /* followed by catch_handler_item[handlersSize] */
  13. };

这里的insns数组存放的就是dalvik的字节码。我们只要定位到相关类方法的DexCode数据段,即可通过修改insns数组,篡改指令。

定位字节码之前,需要定位到DexMethod的位置(因为只有method才有指令),在DexMehod结构中,有成员指向DexCode数据结构的偏移:

[cpp] view plaincopy

  1. /* expanded form of encoded_method */
  2. struct DexMethod {
  3. u4 methodIdx;    /* index to a method_id_item */
  4. u4 accessFlags;
  5. u4 codeOff;      /* file offset to a code_item */
  6. };

这里,codeOff即为要找的DexCode所在的偏移位置。

为找到DexMethod,我们需要找到此Method的确定信息:其所属的类,函数名,参数和返回值信息等。知道这些信息后,可以通过Dex文件结构中的class_defs和method_ids表查找到DexMethod结构,并最终返回DexCode。

相关代码封装在一个函数中实现:

[cpp] view plaincopy

  1. static const DexCode *dexFindClassMethod(DexFile *dexFile, const char *clazz, const char *method)
  2. {
  3. ALOGD("found: %s->%s", clazz, method);
  4. DexClassData* classData = dexFindClassData(dexFile, clazz);
  5. if(classData == NULL) return NULL;
  6. const DexCode* code = dexFindMethodInsns(dexFile, classData, method);
  7. if(code != NULL) {
  8. dumpDexCode(code);
  9. }
  10. //dumpDexClassDataMethod(&gDexFile, classData);
  11. return code;
  12. }

找到DexCode后,即可修改指令了:

[cpp] view plaincopy

  1. const DexCode  *code =
  2. dexFindClassMethod(&gDexFile, "Lcom/freshui/dextemper/GameControl;", "setScoreHidden");
  3. const DexCode  *code2 = dexFindClassMethod(&gDexFile, "Lcom/freshui/dextemper/GameControl;", "setScore");
  4. // remap!!!!
  5. if(mprotect(base, module_size, PROT_READ | PROT_WRITE | PROT_EXEC) == 0){
  6. ALOGD("Found the odex module at: %p [%x]", base, module_size);
  7. DexCode *pCode = (DexCode *)code2;
  8. // Modify!
  9. pCode->registersSize = code->registersSize;
  10. for(u4 k=0; k<code->insnsSize; k++){
  11. pCode->insns[k] = code->insns[k];
  12. }
  13. // cleanup write PROT
  14. mprotect(base, module_size, PROT_READ | PROT_EXEC);
  15. }

修改指令的方式:

1. 如上文描述,需要先查找到DexCode的偏移位置

2. 需要将odex文件map的内存位置,用mprotect设置内存访问权限,允许修改。

3. 修改DexCode中,insns所指向的dalvik字节码

4. 将odex所map的内存位置的访问权限改回,当然不做也没关系。

细节不详述,有兴趣可以看看示例代码。

这里需要注意一点:dalvik指令也不是可以任意修改,需要注意size、寄存器及索引不能有异常,一般只能针对每个dex文件都要重新编辑。另外一个重要点就是索引问题,这个也不展开说了。

转自:http://blog.csdn.net/freshui/article/details/13620647#comments

时间: 2025-01-12 20:51:40

apk自我保护的一种实现方式——运行时自篡改dalvik指令【转载】的相关文章

android apk 防止反编译技术第二篇-运行时修改字节码

上一篇我们讲了apk防止反编译技术中的加壳技术,如果有不明白的可以查看我的上一篇博客http://my.oschina.net/u/2323218/blog/393372.接下来我们将介绍另一种防止apk反编译的技术-运行时修改字节码.这种方法是在工作中在实现app wrapping时,看到国外的一篇关于android 安全的介绍实现的并且独创.下面我们来介绍一下这种方法. 我们知道apk生成后所有的java生成的class文件都被dx命令整合成了一个classes.dex文件,当apk运行时d

android apk 防止反编译技术第二篇-运行时修改Dalvik指令

上一篇我们讲了apk防止反编译技术中的加壳技术,如果有不明白的可以查看我的上一篇博客http://my.oschina.net/u/2323218/blog/393372.接下来我们将介绍另一种防止apk反编译的技术-运行时修改字节码.这种方法是在工作中在实现app wrapping时,看到国外的一篇关于android 安全的介绍实现的并且独创.下面我们来介绍一下这种方法. 我们知道apk生成后所有的java生成的class文件都被dx命令整合成了一个classes.dex文件,当apk运行时d

JMeter非GUI方式运行时动态设置线程组及传参

在使用JMeter进行性能测试自动化时,可能会有如下需求: 1.指定运行多少线程,指定运行多少次: 2.访问的目标地址变化了,端口也变化了,需要重新指定. 上面的需求如果有GUI方式运行,这都不是问题,直接在脚本上进行修改即可以了. 但是性能测试自动化是以非GUI方式运行的,如果要修改测试计划就比较麻烦了. 下面来说说如何简单的搞定这些问题: 1.指定运行多少线程   我们知道JMeter测试计划在运行Sampler之前先加载运行属性(jmeter.properties,system.prope

AndroidART运行时无缝替换Dalvik虚拟机的过程分析(转载)

AndroidART运行时无缝替换Dalvik虚拟机的过程分析 Android 4.4发布了一个ART运行时,准备用来替换掉之前一直使用的Dalvik虚拟机,希望籍此解决饱受诟病的性能问题.老罗不打算分析ART的实现原理,只是很有兴趣知道ART是如何无缝替换掉原来的Dalvik虚拟机的.毕竟在原来的系统中,大量的代码都是运行在Dalvik虚拟机里面的.开始觉得这个替换工作是挺复杂的,但是分析了相关代码之后,发现思路是很清晰的.本文就详细分析这个无缝的替换过程. 老罗的新浪微博:http://we

Android60运行时权限处理完全解析(转载)

Android60运行时权限处理完全解析 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/50709663: 本文出自:[张鸿洋的博客] 一.概述 随着Android 6.0发布以及普及,我们开发者所要应对的主要就是新版本SDK带来的一些变化,首先关注的就是权限机制的变化.对于6.0的几个主要的变化,查看查看官网的这篇文章http://developer.android.com/intl/zh-cn/about/version

Objective-C Runtime 运行时之四:Method Swizzling(转载)

理解Method Swizzling是学习runtime机制的一个很好的机会.在此不多做整理,仅翻译由Mattt Thompson发表于nshipster的Method Swizzling一文. Method Swizzling是改变一个selector的实际实现的技术.通过这一技术,我们可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现. 例如,我们想跟踪在程序中每一个view controller展示给用户的次数:当然,我们可以在每个view controller的

运行时动态修复dex

0x00 本文的源代码已经上传至github,地址为https://github.com/jltxgcy/DynamicFixDex.在Android2.3模拟器上可以运行. ForceApkObj:用于动态加载的apk.类似于Android中的Apk的加固(加壳)原理解析和实现一文中的ForceApkObj工程. FixDex:用于分离ForceApkObj里面的classes.dex,把他分离为classes_fix.dex和data.so,具体情况我们后来介绍. DynamicDex:用于

java配置定时任务的几种配置方式及示例

使用java配置定时任务的几种配置方式及示例 (2010-08-21 13:16:10) 转载▼ 标签: spring 定时器 配置 it 分类: java Spring定时器,主要有两种实现方式,包括Java Timer定时和Quartz定时器! 1.Java Timer定时 首先继承java.util.TimerTask类实现run方法 package com.land; import java.util.Date;import java.util.TimerTask; public cla

Android逆向之旅---运行时修改内存中的Dalvik指令来改变代码逻辑

一.前言 最近在弄脱壳的时候发现有些加固平台的加固方式是修改了dex文件结构,然后在加载dex到内存的时候,在进行dex格式修复,从而达到了apk保护的效果,那么在dex加载到内存的时候,如何进行dex格式的修复呢?其实原理就是基于运行时修改内存中的Dalvik数据,本文就来用一个简单的例子来介绍一下如何在内存中去修改Dalvik指令代码来改变代码本生的运行逻辑.在讲解本文之前,一定要先看这篇文章:Android中Dex文件格式详解 这篇文章主要介绍了关于Dex文件的格式介绍,这个对于后面修改内