android dex 加固,Dex文件加固

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

这篇文章介绍一下自己学习dex加固的过程。虽然市面上比这篇文章介绍的技术要先进的多了,但还是从基础抓起吧。而且这种最简单的加固实践起来也遇到好多坑,一开始看reference里的文章真是云里雾里,所以记录一下以免以后忘了思想。

代码见github。

原理

1460186676854.png

我们在加固的过程中需要三个对象:需要加密的Apk(源Apk)

壳程序Apk(负责解密Apk工作)

加密工具(将源Apk进行加密和壳Dex合并成新的Dex)解释一下,熟悉android的朋友们都知道。运行android程序后会有一个class.dex文件。它的格式我就不多说了。我们本身有一个source Apk,是我们需要进行加密的Apk。然后我们有一个解壳apk,它负责进行解密,还有一个进行加密的java程序,它负责对apk进行加密。

加密java程序工作过程:也就是上面那张图。其实这里有种A要B,B要A的循环过程,所以比较难以理解。现在假设我们已经有了解壳apk的class.dex文件了。那么我们把source apk进行加密后,附在解壳dex的后面,这样就形成新的dex了。

解壳apk工作过程:我们先假设解壳apk的class.dex已经是加固过的dex了,解壳Apk就从这个dex去读取加过密的source apk,然后进行解密,再进行运行。所以,我们最终真正运行的是解壳apk。

理一下全部过程:

①编写source apk

②编写解壳apk,假设自己的class.dex已经是加固过的dex。

③编写加密java程序过程,把解壳dex和source apk进行整合,得到新的class.dex文件。

④关键步骤,幸亏看了reference的文章才知道。跪谢大神们。应该把解壳apk变成.zip文件,再替换里面的class.dex,再重打包重签名。一开始傻逼的我以为替换掉后在eclipse再运行就可,然而这会重新生成class.dex,因此总是找不到source apk。

过程

截图来自于Reference<1>。

source apk

1460187837027.png

加密java程序

1460187936132.png

这里脱壳程序的dex文件,就是解壳dex,作者改了个名而已。

class.dex是运行java程序后自动生成的文件。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

41public static void (String[] args){

try {

File payloadSrcFile = new File("force/ForceApkObj.apk"); //需要加壳的程序

System.out.println("apk size:"+payloadSrcFile.length());

File unShellDexFile = new File("force/ForceApkObj.dex");//解客dex

byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));//以二进制形式读出apk,并进行加密处理//对源Apk进行加密操作

byte[] unShellDexArray = readFileBytes(unShellDexFile);//以二进制形式读出dex

int payloadLen = payloadArray.length;

int unShellDexLen = unShellDexArray.length;

int totalLen = payloadLen + unShellDexLen +4;//多出4字节是存放长度的。

byte[] newdex = new byte[totalLen]; // 申请了新的长度

//添加解壳代码

System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);//先拷贝dex内容

//添加加密后的解壳数据

System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);//再在dex内容后面拷贝apk的内容

//添加解壳数据长度

System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最后4为长度

//修改DEX file size文件头

fixFileSizeHeader(newdex);

//修改DEX SHA1 文件头

fixSHA1Header(newdex);

//修改DEX CheckSum文件头

fixCheckSumHeader(newdex);

String str = "force/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) {

e.printStackTrace();

}

}

上面的程序很好的体现了下图:

1460188585849.png

最后多了四个字节用来存放source dex长度。1

2

3

4

5

6

7

8public 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;

}

这个是将int转化为byte[4]数组,因为int是32位,每八位换成一个byte。

更改头部

更改filesize。1

2

3

4

5

6

7

8

9

10

11

12private 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, 32, 4);//修改(32-35)

}

解壳apk

1460193831891.png

RefInvoke.java是封装反射类工具Android程序由不同的组件构成,系统在有需要的时候启动程序组件。因此解壳程序必须在Android系统启动组件之前运行,完成对解壳数据的解壳及APK文件的动态加载,否则会使程序出现加载类失败的异常。Android开发者都知道Applicaiton做为整个应用的上下文,会被系统第一时间调用,这也是应用开发者程序代码的第一执行点。因此通过对AndroidMainfest.xml的application的配置可以实现解壳代码第一时间运行。

我们需要找到一个时机,就是在脱壳程序还没有运行起来的时候,来加载源程序的Apk,执行他的onCreate方法,那么这个时机不能太晚,不然的话,就是运行脱壳程序,而不是源程序了。查看源码我们知道。Application中有一个方法:attachBaseContext这个方法,他在Application的onCreate方法执行前就会执行了,那么我们的工作就需要在这里进行。

当在AndroidMainfest.xml文件配置为解壳代码的Application时。源程序原有的Applicaiton将被替换,为了不影响源程序代码逻辑,我们需要在解壳代码运行完成后,替换回源程序原有的Application对象。我们通过在AndroidMainfest.xml文件中配置原有Applicaiton类信息来达到我们 的目的。解壳程序要在运行完毕后通过创建配置的Application对象,并通过反射修改回原Application。1

在attachBaseContext中,解壳apk1

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//这是context 赋值

protected void attachBaseContext(Context base){

super.attachBaseContext(base);

try {

//创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录

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);

Log.i("demo", "apk size:"+dexFile.length());

if (!dexFile.exists())

{

dexFile.createNewFile(); //在payload_odex文件夹内,创建payload.apk

// 读取程序classes.dex文件

byte[] dexdata = this.readDexFileFromApk();

// 分离出解壳后的apk文件已用于动态加载

this.splitPayLoadFromDex(dexdata);

}

// 配置动态加载环境

Object currentActivityThread = RefInvoke.invokeStaticMethod(

"android.app.ActivityThread", "currentActivityThread",

new Class[] {}, new Object[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493

String packageName = this.getPackageName();//当前apk的包名

//下面两句不是太理解

ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(

"android.app.ActivityThread", currentActivityThread,

"mPackages");

WeakReference wr = (WeakReference) mPackages.get(packageName);

//创建被加壳apk的DexClassLoader对象 加载apk内的类和本地代码(c/c++代码)

DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,

libPath, (ClassLoader) RefInvoke.getFieldOjbect(

"android.app.LoadedApk", wr.get(), "mClassLoader"));

//base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//?

//把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader ----有点c++中进程环境的意思~~

RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",

wr.get(), dLoader);

Log.i("demo","classloader:"+dLoader);

try{

Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");

Log.i("demo", "actObj:"+actObj);

}catch(Exception e){

Log.i("demo", "activity:"+Log.getStackTraceString(e));

}

} catch (Exception e) {

Log.i("demo", "error:"+Log.getStackTraceString(e));

e.printStackTrace();

}

}

通过反射置换android.app.ActivityThread 中的mClassLoader为加载解密出APK的DexClassLoader,该DexClassLoader一方面加载了源程序、另一方面以原mClassLoader为父节点,这就保证了即加载了源程序又没有放弃原先加载的资源与系统代码。

ZipInputStream 类

ZipInputStream是InputStream的子类,通过此类可以方便地读取ZIP格式的压缩文件,使用ZipInputStream可以像ZipFile一样取得ZIP压缩文件中的每一个ZipEntry。1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17package org.lxh.demo12.zipdemo;

import java.io.File;

import java.io.FileInputStream;

import java.util.zip.ZipEntry;

import java.util.zip.ZipInputStream;

public class ZipInputStreamDemo01 {

public static void main(String[] args) throws Exception { //所有异常抛出

File zipFile = new File("d:" + File.separator + "mldn.zip");

ZipInputStream input = null; // 定义压缩输入流

input = new ZipInputStream(new FileInputStream(zipFile)); // 实例化压缩输入流

ZipEntry entry = input.getNextEntry(); // 得到一个压缩实体

System.out.println("压缩实体名称:" + entry.getName()) ;

// 输出实体名称

input.close();

// 关闭压缩输入流

}

}

Reference

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值