转:

android apk 防止反编译技术

做android framework方面的工作将近三年的时间了,现在公司让做一下android apk安全方面的研究,于是最近就在网上找大量的资料来学习。现在将最近学习成果做一下整理总结。学习的这些成果我会做成一个系列慢慢写出来与大家分享,共同进步。这篇主要讲apk的加壳技术,废话不多说了直接进入正题。

一、加壳技术原理

所谓apk的加壳技术和pc exe的加壳原理一样,就是在程序的外面再包裹上另外一段代码,保护里面的代码不被非法修改或反编译,在程序运行的时候优先取得程序的控制权做一些我们自己想做的工作。(哈哈,跟病毒的原理差不多)

PC exe的加壳原理如下:

二、android apk加壳实现

要想实现加壳需要解决的技术点如下:

(1)怎么第一时间执行我们的加壳程序?

首先根据上面的原理我们在apk中要想优先取得程序的控制权作为android apk的开发人员都知道Application会被系统第一时间调用而我们的程序也会放在这里执行。

(2)怎么将我们的加壳程序和原有的android apk文件合并到一起?

我们知道android apk最终会打包生成dex文件,我们可以将我们的程序生成dex文件后,将我们要进行加壳的apk和我们dex文件合并成一个文件,然后修改dex文件头中的checksum、signature和file_size的信息,并且要附加加壳的apk的长度信息在dex文件中,以便我们进行解壳保证原来apk的正常运行。加完壳后整个文件的结构如下:

(3)怎么将原来的apk正常的运行起来?

按照(2)中的合并方式在当我们的程序首先运行起来后,逆向读取dex文件获取原来的apk文件通过DexClassLoader动态加载。

具体实现如下:

(1)修改原来apk的AndroidMainfest.xml文件,假如原来apk的AndroidMainfest.xml文件内容如下:

1.  <application

2.      android:icon="@drawable/ic_launcher"

3.      android:label="@string/app_name"

4.      android:theme="@style/AppTheme" android:name="com.android.MyApplication" >

5.  </application>

修改后的内容如下:

1.  <application

2.      android:icon="@drawable/ic_launcher"

3.      android:label="@string/app_name"

4.      android:theme="@style/AppTheme" android:name="com.android.shellApplication" >

5.  <meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.android.MyApplication"/>

6.  </application>

com.android.shellApplication这个就是我们的程序的的application的名称,而

7.  <meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.android.MyApplication"/>

是原来的apk的application名称。

(2)合并文件代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
public class ShellTool {
  /**
   * @param args
   */
  public static void main(String[] args) {
         // TODO Auto-generated method stub
         try {
                File payloadSrcFile = new File("payload.apk");//我们要加壳的apk文件
                File unShellDexFile = new File("classes.dex");//我们的程序生成的dex文件
                byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));
                byte[] unShellDexArray = readFileBytes(unShellDexFile);
                int payloadLen = payloadArray.length;
                int unShellDexLen = unShellDexArray.length;
                int totalLen = payloadLen + unShellDexLen +4;
                byte[] newdex = new byte[totalLen];
                //添加我们程序的dex
                System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);
                //添加要加壳的apk文件
                System.arraycopy(payloadArray, 0, newdex, unShellDexLen,
                              payloadLen);
                //添加apk文件长度
                System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-44);
                        //修改DEX file size文件头
                fixFileSizeHeader(newdex);
                //修改DEX SHA1 文件头
                fixSHA1Header(newdex);
                //修改DEX CheckSum文件头
                fixCheckSumHeader(newdex);
  
  
                String str = "outdir/classes.dex";
                File file = new File(str);
                if (!file.exists()) {
                       file.createNewFile();
                }
                 
                FileOutputStream localFileOutputStream = new FileOutputStream(str);
                localFileOutputStream.write(newdex);
                localFileOutputStream.flush();
                localFileOutputStream.close();
  
  
         catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
         }
  }
   
  //直接返回数据,读者可以添加自己加密方法
  private static byte[] encrpt(byte[] srcdata){
         return srcdata;
  }
  
  
  private static void fixCheckSumHeader(byte[] dexBytes) {
         Adler32 adler = new Adler32();
         adler.update(dexBytes, 12, dexBytes.length - 12);
         long value = adler.getValue();
         int va = (int) value;
         byte[] newcs = intToByte(va);
         byte[] recs = new byte[4];
         for (int i = 0; i < 4; i++) {
                recs[i] = newcs[newcs.length - 1 - i];
                System.out.println(Integer.toHexString(newcs[i]));
         }
         System.arraycopy(recs, 0, dexBytes, 84);
         System.out.println(Long.toHexString(value));
         System.out.println();
  }
  
  
  public static byte[] intToByte(int number) {
         byte[] b = new byte[4];
         for (int i = 3; i >= 0; i--) {
                b[i] = (byte) (number % 256);
                number >>= 8;
         }
         return b;
  }
  
  
  private static void fixSHA1Header(byte[] dexBytes)
                throws NoSuchAlgorithmException {
         MessageDigest md = MessageDigest.getInstance("SHA-1");
         md.update(dexBytes, 32, dexBytes.length - 32);
         byte[] newdt = md.digest();
         System.arraycopy(newdt, 0, dexBytes, 1220);
         String hexstr = "";
         for (int i = 0; i < newdt.length; i++) {
                hexstr += Integer.toString((newdt[i] & 0xff) + 0x10016)
                              .substring(1);
         }
         System.out.println(hexstr);
  }
  
  
  private static void fixFileSizeHeader(byte[] dexBytes) {
  
  
         byte[] newfs = intToByte(dexBytes.length);
         System.out.println(Integer.toHexString(dexBytes.length));
         byte[] refs = new byte[4];
         for (int i = 0; i < 4; i++) {
                refs[i] = newfs[newfs.length - 1 - i];
                System.out.println(Integer.toHexString(newfs[i]));
         }
         System.arraycopy(refs, 0, dexBytes, 324);
  }
  
  
  private static byte[] readFileBytes(File file) throws IOException {
         byte[] arrayOfByte = new byte[1024];
         ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
         FileInputStream fis = new FileInputStream(file);
         while (true) {
                int i = fis.read(arrayOfByte);
                if (i != -1) {
                       localByteArrayOutputStream.write(arrayOfByte, 0, i);
                else {
                       return localByteArrayOutputStream.toByteArray();
                }
         }
  }
  
  
}

(3)在我们的程序中加载运行原来的apk文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
public class shellApplication extends Application {
  
  
  private static final String appkey = "APPLICATION_CLASS_NAME";
  private String apkFileName;
  private String odexPath;
  private String libPath;
  
  
  protected void attachBaseContext(Context base) {
         super.attachBaseContext(base);
         try {
                File odex = this.getDir("payload_odex", MODE_PRIVATE);
                File libs = this.getDir("payload_lib", MODE_PRIVATE);
                odexPath = odex.getAbsolutePath();
                libPath = libs.getAbsolutePath();
                apkFileName = odex.getAbsolutePath() + "/payload.apk";
                File dexFile = new File(apkFileName);
                if (!dexFile.exists())
                       dexFile.createNewFile();
                // 读取程序classes.dex文件
                byte[] dexdata = this.readDexFileFromApk();
                // 分离出解壳后的apk文件已用于动态加载
                this.splitPayLoadFromDex(dexdata);
                // 配置动态加载环境
                Object currentActivityThread = RefInvoke.invokeStaticMethod(
                              "android.app.ActivityThread""currentActivityThread",
                              new Class[] {}, new Object[] {});
                String packageName = this.getPackageName();
                HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect(
                              "android.app.ActivityThread", currentActivityThread,
                              "mPackages");
                WeakReference wr = (WeakReference) mPackages.get(packageName);
                DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
                              libPath, (ClassLoader) RefInvoke.getFieldOjbect(
                                            "android.app.LoadedApk", wr.get(), "mClassLoader"));
                RefInvoke.setFieldOjbect("android.app.LoadedApk""mClassLoader",
                              wr.get(), dLoader);
  
  
         catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
         }
  }
  
  
  public void onCreate() {
         {
  
  
                // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
                String appClassName = null;
                try {
                       ApplicationInfo ai = this.getPackageManager()
                                     .getApplicationInfo(this.getPackageName(),
                                                   PackageManager.GET_META_DATA);
                       Bundle bundle = ai.metaData;
                       if (bundle != null
                                     && bundle.containsKey("APPLICATION_CLASS_NAME")) {
                              appClassName = bundle.getString("APPLICATION_CLASS_NAME");
                       else {
                              return;
                       }
                catch (NameNotFoundException e) {
                       // TODO Auto-generated catch block
                       e.printStackTrace();
                }
  
  
                Object currentActivityThread = RefInvoke.invokeStaticMethod(
                              "android.app.ActivityThread""currentActivityThread",
                              new Class[] {}, new Object[] {});
                Object mBoundApplication = RefInvoke.getFieldOjbect(
                              "android.app.ActivityThread", currentActivityThread,
                              "mBoundApplication");
                Object loadedApkInfo = RefInvoke.getFieldOjbect(
                              "android.app.ActivityThread$AppBindData",
                              mBoundApplication, "info");
                RefInvoke.setFieldOjbect("android.app.LoadedApk""mApplication",
                              loadedApkInfo, null);
                Object oldApplication = RefInvoke.getFieldOjbect(
                              "android.app.ActivityThread", currentActivityThread,
                              "mInitialApplication");
                ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke
                              .getFieldOjbect("android.app.ActivityThread",
                                            currentActivityThread, "mAllApplications");
                mAllApplications.remove(oldApplication);
                ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
                              .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,
                                            "mApplicationInfo");
                ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
                              .getFieldOjbect("android.app.ActivityThread$AppBindData",
                                            mBoundApplication, "appInfo");
                appinfo_In_LoadedApk.className = appClassName;
                appinfo_In_AppBindData.className = appClassName;
                Application app = (Application) RefInvoke.invokeMethod(
                              "android.app.LoadedApk""makeApplication", loadedApkInfo,
                              new Class[] { boolean.class, Instrumentation.class },
                              new Object[] { falsenull });
                RefInvoke.setFieldOjbect("android.app.ActivityThread",
                              "mInitialApplication", currentActivityThread, app);
  
  
                HashMap mProviderMap = (HashMap) RefInvoke.getFieldOjbect(
                              "android.app.ActivityThread", currentActivityThread,
                              "mProviderMap");
                Iterator it = mProviderMap.values().iterator();
                while (it.hasNext()) {
                       Object providerClientRecord = it.next();
                       Object localProvider = RefInvoke.getFieldOjbect(
                                     "android.app.ActivityThread$ProviderClientRecord",
                                     providerClientRecord, "mLocalProvider");
                       RefInvoke.setFieldOjbect("android.content.ContentProvider",
                                     "mContext", localProvider, app);
                }
                app.onCreate();
         }
  }
  
  
  private void splitPayLoadFromDex(byte[] data) throws IOException {
         byte[] apkdata = decrypt(data);
         int ablen = apkdata.length;
         byte[] dexlen = new byte[4];
         System.arraycopy(apkdata, ablen - 4, dexlen, 04);
         ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
         DataInputStream in = new DataInputStream(bais);
         int readInt = in.readInt();
         System.out.println(Integer.toHexString(readInt));
         byte[] newdex = new byte[readInt];
         System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);
         File file = new File(apkFileName);
         try {
                FileOutputStream localFileOutputStream = new FileOutputStream(file);
                localFileOutputStream.write(newdex);
                localFileOutputStream.close();
  
  
         catch (IOException localIOException) {
                throw new RuntimeException(localIOException);
         }
  
  
         ZipInputStream localZipInputStream = new ZipInputStream(
                       new BufferedInputStream(new FileInputStream(file)));
         while (true) {
                ZipEntry localZipEntry = localZipInputStream.getNextEntry();
                if (localZipEntry == null) {
                       localZipInputStream.close();
                       break;
                }
                String name = localZipEntry.getName();
                if (name.startsWith("lib/") && name.endsWith(".so")) {
                       File storeFile = new File(libPath + "/"
                                     + name.substring(name.lastIndexOf('/')));
                       storeFile.createNewFile();
                       FileOutputStream fos = new FileOutputStream(storeFile);
                       byte[] arrayOfByte = new byte[1024];
                       while (true) {
                              int i = localZipInputStream.read(arrayOfByte);
                              if (i == -1)
                                     break;
                              fos.write(arrayOfByte, 0, i);
                       }
                       fos.flush();
                       fos.close();
                }
                localZipInputStream.closeEntry();
         }
         localZipInputStream.close();
  
  
  }
  
  
  private byte[] readDexFileFromApk() throws IOException {
         ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
         ZipInputStream localZipInputStream = new ZipInputStream(
                       new BufferedInputStream(new FileInputStream(
                                     this.getApplicationInfo().sourceDir)));
         while (true) {
                ZipEntry localZipEntry = localZipInputStream.getNextEntry();
                if (localZipEntry == null) {
                       localZipInputStream.close();
                       break;
                }
                if (localZipEntry.getName().equals("classes.dex")) {
                       byte[] arrayOfByte = new byte[1024];
                       while (true) {
                              int i = localZipInputStream.read(arrayOfByte);
                              if (i == -1)
                                     break;
                              dexByteArrayOutputStream.write(arrayOfByte, 0, i);
                       }
                }
                localZipInputStream.closeEntry();
         }
         localZipInputStream.close();
         return dexByteArrayOutputStream.toByteArray();
  }
  
  
  // //直接返回数据,读者可以添加自己解密方法
  private byte[] decrypt(byte[] data) {
         return data;
  }

根据上面的讲述相信大家对apk的加壳技术有了一定的了解,下一篇我们将讲解另一种android apk防止反编译技术-运行时修改dalvik指令(http://my.oschina.net/u/2323218/blog/396203)。如果对这篇讲的技术有任何疑问及想要获得这篇文章讲的技术的工程源码

欢迎关注个人微信公众平台:程序员互动联盟(coder_online),扫一扫下方二维码或搜索微信号coder_online即可关注,我们可以在线交流。

转: android apk 防止反编译技术(1~5连载)的更多相关文章

  1. android apk 防止反编译技术第四篇-对抗JD-GUI

    又到周末一个人侘在家里无事可干,这就是程序员的悲哀啊.好了我们利用周末的时间继续介绍android apk防止反编译技术的另一种方法.前三篇我们讲了加壳技术(http://my.oschina.net ...

  2. android apk 防止反编译技术第一篇-加壳技术

    做android framework方面的工作将近三年的时间了,现在公司让做一下android apk安全方面的研究,于是最近就在网上找大量的资料来学习.现在将最近学习成果做一下整理总结.学习的这些成 ...

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

    上一篇我们讲了apk防止反编译技术中的加壳技术,如果有不明白的可以查看我的上一篇博客http://my.oschina.net/u/2323218/blog/393372.接下来我们将介绍另一种防止a ...

  4. android apk 防止反编译技术第三篇-加密

    上一篇我们讲了apk防止反编译技术中的加壳技术,如果有不明白的可以查看我的上一篇博客http://my.oschina.net/u/2323218/blog/393372.接下来我们将介绍另一种防止a ...

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

    上一篇我们讲了apk防止反编译技术中的加壳技术,如果有不明白的可以查看我的上一篇博客http://my.oschina.net/u/2323218/blog/393372.接下来我们将介绍另一种防止a ...

  6. APK防反编译技术

    APK防反编译技术 下载地址:地址 我们的APK实际上就是一个ZIP压缩文件,里面包括有一个classes.dex.我们编译后生成的程序代码就所有在那里了, 通过apktool等工具能够轻松地将它们反 ...

  7. Android Apk的反编译和加密

    这几天在上海出差,忙里偷闲学习了一下Apk的反编译工具的基本使用.下面就简单介绍一下如何将我们从网上下载的Apk文件进行反编译得到我们想要获得的资源文件和源码. Android的应用程序APK文件说到 ...

  8. Android Apk的反编译与代码混淆

    一.反编译 1.获取工具: 既然是反编译,肯定要用到一些相关的工具,工具可以到这里下载,里面包含三个文件夹,用于反编译,查看反编译之后的代码: 其实这两工具都是google官方出的,也可在google ...

  9. android apk 文件反编译

    最近,自己坑逼的把一个android 项目修改版本的代码删除了.这个项目居然还没上传到源代码管理器.幸好还有apk文件,修改的代码也不多可以反编译一下. 1.下载 dex2jar  获取源码工具  地 ...

随机推荐

  1. 【转】Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)

    Android-Universal-Image-Loader 原文地址:http://blog.csdn.net/vipzjyno1/article/details/23206387 这个图片异步加载 ...

  2. Ubuntu ENet 的下载和编译

    ENet的目的是提供一个相对轻便.简单和强大的网络通信层的UDP(用户数据报协议). 它提供的主要功能是可选的.可靠的.顺序的数据包发送. ENet省略了一些更高层次的网络功能,如身份验证.加密,尤其 ...

  3. DevExpress 13.2.6源码、安装包、汉化包下载和教程

    DevExpress比DotNetBar控件成熟很多,当然源码是公开的,但是最新版本需要9K多.如果不是土豪,用已经破解的版本或者自己拿源码编译一份就可以了,老外就是这么好. 首先在这里下载然后解压准 ...

  4. QML学习笔记之一

    摘自<Qt Quick中文手册> Qt Quick提供了一套高动态,丰富的QML元素来定制用户界面的说明性框架. Qt Quick包含了QtDeclarative C++模块.QML,并且 ...

  5. linux 认证方式

  6. IOS之以UIBezierPath绘制饼状图

    1.绘制的饼状图是通过多个扇形拼和而成,绘制一个扇形也是比较简单的,核心代码如下: 先画一条圆弧,再画半径,接着再画一条圆弧,最后闭合路径: UIBezierPath*  aPath = [[UIBe ...

  7. java 图的邻接矩阵

    有向图 在有向图中,结点对<x ,y>是有序的,结点对<x,y>称为从结点x到结点y的一条有向边,因此,<x,y>与<y,x>是两条不同的边.有向图中的 ...

  8. [原创]SQL SERVER 2008R2安装

    配置系统环境说明 操作系统:Windows 7 操作系统版本:旗舰版 SP1 操作系统位数:x64 注:其它系统配置也基本相似,只是可能菜单的名字或者所处位置不一样,具体的配置如有不同,请自行搜索 安 ...

  9. Docker学习笔记2

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化.容器是完全使用沙箱机制,相互之间不会有任何 ...

  10. 动态引入Js文件

    var src = "/Scripts/Test.js"; $("<script type = 'text/javascript' src='" + sr ...