好久没写博客了,要深刻检讨下!
前言:
在Android中没有经过加密的Apk给人的感觉就是在裸奔,通过apktool,dex2jar,AndroidKill等各式各样的反编译工具就可以轻松的获取其smail代码,如这个叫SourceProject的helloworld程序被apktool反编译后,对于懂smail语法的逆向工程师来说就一览无余了。破解与反破解是相对的,所以我们尽可能的给自己的Apk多穿点衣服。
原理解析
首先我们先来看下Apk加壳的步骤:
- 源Apk:需要加壳的Apk
- 加密的Apk:源Apk经过加密算法加密后的Apk
- 加壳程序Apk:是有解密源Apk和动态加载启动源Apk的外壳
首先我们拿到需要加壳的源Apk,通过加密算法加密源Apk然后与加壳Apk的dex文件组合成新的Dex文件,然后将加壳程序Apk的Dex文件替换成新的Dex,生成新的Apk重新签名。
我们先来看下Dex文件的结构:
- Magic
Magic数是为了方便虚拟机识别目标文件是否是合格的Dex文件,在Dex文件中magic的值固定值 - checksum
文件校验码 ,使用alder32 算法校验文件除去 maigc ,checksum 外余下的所有文件区域 ,用于检查文件错误 - signature
使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余下的所有文件区域 ,用于唯一识别本文件 。 - file_size
当前Dex 文件的大小 。
所以我们在将Dex与加密算法加密后的Apk合并生成新的Dex后需要修改新Dex文件的这三个值,为了方便从新Dex中获得加密的Apk,我们需要知道加密的Apk的大小,为了方便以后获得,我们将其大小放置在新Dex的后四位,新生成的Dex文件结构:
生成新Dex后,将加壳程序Apk的Dex文件替换,重新签名后加壳的Apk即完成了。如果觉得步骤理清了,我们来看下其具体的实现。
具体实现:
这过程一共要创建三个项目。
首先我们先创建一个需要加密的Apk项目,结构非常简单只有几个Log的打印,结构
SourceApplication.java
package com.jju.yuxin.sourceproject;
import android.app.Application;
import android.util.Log;
public class SourceApplication extends Application {
private static final String TAG=SourceApplication.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG,"-------------onCreate");
}
}
MainActivity.java
package com.jju.yuxin.sourceproject;
import android.app.Activity;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends Activity {
private static final String TAG=MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv_content = new TextView(this);
tv_content.setText("I am Source Apk");
tv_content.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View arg0) {
Intent intent = new Intent(MainActivity.this, SubActivity.class);
startActivity(intent);
}});
setContentView(tv_content);
Log.i(TAG, "onCreate:app:"+getApplicationContext());
}
}
SubActivity.java
package com.jju.yuxin.sourceproject;
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
public class SubActivity extends Activity {
private static final String TAG=SubActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv_content = new TextView(this);
tv_content.setText("I am SubActivity");
setContentView(tv_content);
Log.i(TAG, "SubActivity:app:"+getApplicationContext());
}
}
然后将其打包生成Apk。
第二个项目是一个JAVA项目用于将源Apk加密,并合并加壳程序Dex与加密后的源Apk。在贴出这个代码时我们先不用管加壳程序Dex从何而来,假设已经有了这样一个文件。我们来看下具体实现:
package com.forceapk;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.Adler32;
public class mymain {
public static void main(String[] args) {
try {
//需要加壳的源APK ,以二进制形式读出,并进行加密处理
File srcApkFile = new File("force/SourceAPK.apk");
System.out.println("apk size:"+srcApkFile.length());
byte[] enSrcApkArray = encrpt(readFileBytes(srcApkFile));
//需要解壳的dex 以二进制形式读出dex
File unShellDexFile = new File("force/shelldex.dex");
byte[] unShellDexArray = readFileBytes(unShellDexFile);
//将源APK长度和需要解壳的DEX长度相加并加上存放源APK大小的四位得到总长度
int enSrcApkLen = enSrcApkArray.length;
int unShellDexLen = unShellDexArray.length;
int totalLen = enSrcApkLen + unShellDexLen +4;
//依次将解壳DEX,加密后的源APK,加密后的源APK大小,拼接出新的Dex
byte[] newdex = new byte[totalLen];
System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);
System.arraycopy(enSrcApkArray, 0, newdex, unShellDexLen, enSrcApkLen);
System.arraycopy(intToByte(enSrcApkLen), 0, newdex, totalLen-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();
}
}
//可以修改成自己的加密方法
private static byte[] encrpt(byte[] srcdata){
for(int i = 0;i<srcdata.length;i++){
srcdata[i] = (byte)(0xFF