Android中的Apk的加固加壳原理解析和实现Word文档下载推荐.docx
《Android中的Apk的加固加壳原理解析和实现Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《Android中的Apk的加固加壳原理解析和实现Word文档下载推荐.docx(35页珍藏版)》请在冰豆网上搜索。
1)checksum
文件校验码,使用alder32算法校验文件除去maigc,checksum外余下的所有文件区域,用于检查文件错误。
2)signature
使用SHA-1算法hash除去magic,checksum和signature外余下的所有文件区域,用于唯一识别本文件。
3)file_size
Dex文件的大小。
为什么说我们只需要关注这三个字段呢?
因为我们需要将一个文件(加密之后的源Apk)写入到Dex中,那么我们肯定需要修改文件校验码(checksum).因为他是检查文件是否有错误。
那么signature也是一样,也是唯一识别文件的算法。
还有就是需要修改dex文件的大小。
不过这里还需要一个操作,就是标注一下我们加密的Apk的大小,因为我们在脱壳的时候,需要知道Apk的大小,才能正确的得到Apk。
那么这个值放到哪呢?
这个值直接放到文件的末尾就可以了。
所以总结一下我们需要做:
修改Dex的三个文件头,将源Apk的大小追加到壳dex的末尾就可以了。
我们修改之后得到新的Dex文件样式如下:
那么我们知道原理了,下面就是代码实现了。
所以这里有三个工程:
1、源程序项目(需要加密的Apk)
2、脱壳项目(解密源Apk和加载Apk)
3、对源Apk进行加密和脱壳项目的Dex的合并
三、项目案例
下面先来看一下源程序
1、需要加密的源程序Apk项目:
ForceApkObj
需要一个Application类,这个到后面说为什么需要:
MyApplication.Java
[java]viewplaincopy
packagecom.example.forceapkobj;
importandroid.app.Application;
importandroid.util.Log;
publicclassMyApplicationextendsApplication{
@Override
publicvoidonCreate(){
super.onCreate();
Log.i("
demo"
"
sourceapkonCreate:
"
+this);
}
}
就是打印一下onCreate方法。
MainActivity.Java
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.view.View.OnClickListener;
importandroid.widget.TextView;
publicclassMainActivityextendsActivity{
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
TextViewcontent=newTextView(this);
content.setText("
IamSourceApk"
);
content.setOnClickListener(newOnClickListener(){
publicvoidonClick(Viewarg0){
Intentintent=newIntent(MainActivity.this,SubActivity.class);
startActivity(intent);
}});
setContentView(content);
app:
+getApplicationContext());
也是打印一下内容。
2、加壳程序项目:
DexShellTools
加壳程序其实就是一个Java工程,因为我们从上面的分析可以看到,他的工作就是加密源Apk,然后将其写入到脱壳Dex文件中,修改文件头,得到一个新的Dex文件即可。
看一下代码:
packagecom.example.reforceapk;
importjava.io.ByteArrayOutputStream;
importjava.io.File;
importjava.io.FileInputStream;
importjava.io.FileOutputStream;
importjava.io.IOException;
importjava.security.MessageDigest;
importjava.security.NoSuchAlgorithmException;
importjava.util.zip.Adler32;
publicclassmymain{
/**
*@paramargs
*/
publicstaticvoidmain(String[]args){
//TODOAuto-generatedmethodstub
try{
FilepayloadSrcFile=newFile("
force/ForceApkObj.apk"
//需要加壳的程序
System.out.println("
apksize:
+payloadSrcFile.length());
FileunShellDexFile=newFile("
force/ForceApkObj.dex"
//解客dex
byte[]payloadArray=encrpt(readFileBytes(payloadSrcFile));
//以二进制形式读出apk,并进行加密处理//对源Apk进行加密操作
byte[]unShellDexArray=readFileBytes(unShellDexFile);
//以二进制形式读出dex
intpayloadLen=payloadArray.length;
intunShellDexLen=unShellDexArray.length;
inttotalLen=payloadLen+unShellDexLen+4;
//多出4字节是存放长度的。
byte[]newdex=newbyte[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为长度
//修改DEXfilesize文件头
fixFileSizeHeader(newdex);
//修改DEXSHA1文件头
fixSHA1Header(newdex);
//修改DEXCheckSum文件头
fixCheckSumHeader(newdex);
Stringstr="
force/classes.dex"
;
Filefile=newFile(str);
if(!
file.exists()){
file.createNewFile();
FileOutputStreamlocalFileOutputStream=newFileOutputStream(str);
localFileOutputStream.write(newdex);
localFileOutputStream.flush();
localFileOutputStream.close();
}catch(Exceptione){
e.printStackTrace();
//直接返回数据,读者可以添加自己加密方法
privatestaticbyte[]encrpt(byte[]srcdata){
for(inti=0;
i<
srcdata.length;
i++){
srcdata[i]=(byte)(0xFF^srcdata[i]);
returnsrcdata;
*修改dex头,CheckSum校验码
*@paramdexBytes
privatestaticvoidfixCheckSumHeader(byte[]dexBytes){
Adler32adler=newAdler32();
adler.update(dexBytes,12,dexBytes.length-12);
//从12到文件末尾计算校验码
longvalue=adler.getValue();
intva=(int)value;
byte[]newcs=intToByte(va);
//高位在前,低位在前掉个个
byte[]recs=newbyte[4];
for(inti=0;
i<
4;
i++){
recs[i]=newcs[newcs.length-1-i];
System.out.println(Integer.toHexString(newcs[i]));
System.arraycopy(recs,0,dexBytes,8,4);
//效验码赋值(8-11)
System.out.println(Long.toHexString(value));
System.out.println();
*int转byte[]
*@paramnumber
*@return
publicstaticbyte[]intToByte(intnumber){
byte[]b=newbyte[4];
for(inti=3;
i>
=0;
i--){
b[i]=(byte)(number%256);
number>
>
=8;
returnb;
*修改dex头sha1值
*@throwsNoSuchAlgorithmException
privatestaticvoidfixSHA1Header(byte[]dexBytes)
throwsNoSuchAlgorithmException{
MessageDigestmd=MessageDigest.getInstance("
SHA-1"
md.update(dexBytes,32,dexBytes.length-32);
//从32为到结束计算sha--1
byte[]newdt=md.digest();
System.arraycopy(newdt,0,dexBytes,12,20);
//修改sha-1值(12-31)
//输出sha-1值,可有可无
Stringhexstr="
newdt.length;
hexstr+=Integer.toString((newdt[i]&
0xff)+0x100,16)
.substring
(1);
System.out.println(hexstr);
*修改dex头file_size值
privatestaticvoidfixFileSizeHeader(byte[]dexBytes){
//新文件长度
byte[]newfs=intToByte(dexBytes.length);
System.out.println(Integer.toHexString(dexBytes.length));
byte[]refs=newbyte[4];
refs[i]=newfs[newfs.length-1-i];
System.out.println(Integer.toHexString(newfs[i]));
System.arraycopy(refs,0,dexBytes,32,4);
//修改(32-35)
*以二进制读出文件内容
*@paramfile
*@throwsIOException
privatestaticbyte[]readFileBytes(Filefile)throwsIOException{
byte[]arrayOfByte=newbyte[1024];
ByteArrayOutputStreamlocalByteArrayOutputStream=newByteArrayOutputStream();
FileInputStreamfis=newFileInputStream(file);
while(true){
inti=fis.read(arrayOfByte);
if(i!
=-1){
localByteArrayOutputStream.write(arrayOfByte,0,i);
}else{
returnlocalByteArrayOutputStream.toByteArray();
下面来分析一下:
红色部分其实就是最核心的工作:
1>
、加密源程序Apk文件
byte[]payloadArray=encrpt(readFileBytes(payloadSrcFile));
加密算法很简单:
//直接返回数据,读者可以添加自己加密方法
privatestaticbyte[]encrpt(byte[]srcdata){
对每个字节进行异或一下即可。
(说明:
这里是为了简单,所以就用了很简单的加密算法了,其实为了增加破解难度,我们应该使用更高效的加密算法,同事最好将加密操作放到native层去做)
2>
、合并文件:
将加密之后的Apk和原脱壳Dex进行合并
intpayloadLen=payloadArray.length;
intunShellDexLen=unShellDexArray.length;
inttotalLen=payloadLen+unShellDexLen+4;
byte[]newdex=newbyte[totalLen];
//添加解壳代码
System.arraycopy(unShellDexArray,0,newdex,0,unShellDexLen);
//添加加密后的解壳数据
System.arraycopy(payloadArray,0,newdex,unShellDexLen,payloadLen);
3>
、在文件的末尾追加源程序Apk的长度
//添加解壳数据长度
System.arraycopy(intToByte(payloadLen),0,newdex,totalLen-4,4);
4>
、修改新Dex文件的文件头信息:
file_size;
sha1;
check_sum
//修改DEXfilesize文件头
fixFileSizeHeader(newdex);
//修改DEXSHA1文件头
fixSHA1Header(newdex);
//修改DEXCheckSum文件头
fixCheckSumHeader(newdex);
具体修改可以参照之前说的文件头格式,修改指定位置的字节值即可。
这里我们还需要两个输入文件:
、源Apk文件:
ForceApkObj.apk
、脱壳程序的Dex文件:
ForceApkObj.dex
那么第一个文件我们都知道,就是上面的源程序编译之后的Apk文件,那么第二个文件我们怎么得到呢?
这个就是我们要讲到的第三个项目:
脱壳程序项目,他是一个Android项目,我们在编译之后,能够得到他的classes.dex文件,然后修改一下名称就可。
3、脱壳项目:
ReforceApk
在讲解这个项目之前,我们先来了解一下这个脱壳项目的工作:
、通过反射置换android.app.ActivityThread中的mClassLoader为加载解密出APK的DexClassLoader,该DexClassLoader一方面加载了源程序、另一方面以原mClassLoader为父节点,这就保证了即加载了源程序又没有放弃原先加载的资源与系统代码。
关于这部分内容,不了解的同学可以看一下ActivityThread.java的源码:
如何得到系统加载Apk的类加载器,然后我们怎么将加载进来的Apk运行起来等问题都在这篇文章中说到了。
、找到源程序的Application,通过反射建立并运行。
这里需要注意的是,我们现在是加载一个完整的Apk,让他运行起来,那么我们知道一个Apk运行的时候都是有一个Application对象的,这个也是一个程序运行之后的全局类。
所以我们必须找到解密之后的源Apk的Application类,运行的他的onCreate方法,这样源Apk才开始他的运行生命周期。
这里我们如何得到源Apk的Application的类呢?
这个我们后面会说道。
使用meta标签进行设置。
下面来看一下代码:
importjava.io.BufferedInputStream;
importjava.io.ByteArrayInputStream;
importjava.io.DataInputStream;
importjava.lang.ref.WeakReference;
importjava