安卓反破解怎么做?如何给android应用360加固 破解应该哪些方面360加固 破解

Android中的Apk的加固(加壳)原理解析和实现 - 推酷
Android中的Apk的加固(加壳)原理解析和实现
| 发表时间: 13:58 | 作者:jiangwei
出处:http://blog.csdn.net
今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理。现阶段。我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk,结果被人反编译了,那心情真心不舒服。虽然我们混淆,做到native层,但是这都是治标不治本。反编译的技术在更新,那么保护Apk的技术就不能停止。现在网上有很多Apk加固的第三方平台,最有名的应当属于:爱加密和梆梆加固了。其实加固有些人认为很高深的技术,其实不然,说的简单点就是对源Apk进行加密,然后在套上一层壳即可,当然这里还有一些细节需要处理,这就是本文需要介绍的内容了。
二、原理解析
下面就来看一下Android中加壳的原理:
我们在加固的过程中需要三个对象:
1、需要加密的Apk(源Apk)
2、壳程序Apk(负责解密Apk工作)
3、加密工具(将源Apk进行加密和壳Dex合并成新的Dex)
主要步骤:
我们拿到需要加密的Apk和自己的壳程序Apk,然后用加密算法对源Apk进行加密在将壳Apk进行合并得到新的Dex文件,最后替换壳程序中的dex文件即可,得到新的Apk,那么这个新的Apk我们也叫作脱壳程序Apk.他已经不是一个完整意义上的Apk程序了,他的主要工作是:负责解密源Apk.然后加载Apk,让其正常运行起来。
在这个过程中我们可能需要了解的一个知识是:
如何将源Apk和壳Apk进行合并成新的Dex
这里就需要了解Dex文件的格式了。下面就来简单介绍一下Dex文件的格式
具体Dex文件格式的详细介绍可以查看这个文件:
主要来看一下Dex文件的头部信息,其实Dex文件和Class文件的格式分析原理都是一样的,他们都是有固定的格式,我们知道现在反编译的一些工具:
1、jd-gui:可以查看jar中的类,其实他就是解析class文件,只要了解class文件的格式就可以
2、dex2jar:将dex文件转化成jar,原理也是一样的,只要知道Dex文件的格式,能够解析出dex文件中的类信息就可以了
当然我们在分析这个文件的时候,最重要的还是头部信息,应该他是一个文件的开始部分,也是索引部分,内部信息很重要。
我们今天只要关注上面红色标记的三个部分:
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
package com.example.
import android.app.A
import android.util.L
public class MyApplication extends Application{
public void onCreate() {
super.onCreate();
Log.i(&demo&, &source apk onCreate:&+this);
就是打印一下onCreate方法。
MainActivity.java
package com.example.
import android.app.A
import android.content.I
import android.os.B
import android.util.L
import android.view.V
import android.view.View.OnClickL
import android.widget.TextV
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView content = new TextView(this);
content.setText(&I am Source Apk&);
content.setOnClickListener(new OnClickListener(){
public void onClick(View arg0) {
Intent intent = new Intent(MainActivity.this, SubActivity.class);
startActivity(intent);
setContentView(content);
Log.i(&demo&, &app:&+getApplicationContext());
也是打印一下内容。
2、加壳程序项目:DexShellTools
加壳程序其实就是一个Java工程,因为我们从上面的分析可以看到,他的工作就是加密源Apk,然后将其写入到脱壳Dex文件中,修改文件头,得到一个新的Dex文件即可。
看一下代码:
package com.example.
import java.io.ByteArrayOutputS
import java.io.F
import java.io.FileInputS
import java.io.FileOutputS
import java.io.IOE
import java.security.MessageD
import java.security.NoSuchAlgorithmE
import java.util.zip.Adler32;
public class mymain {
* @param args
public static void main(String[] args) {
// TODO Auto-generated method stub
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.
int unShellDexLen = unShellDexArray.
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();
//直接返回数据,读者可以添加自己加密方法
private static byte[] encrpt(byte[] srcdata){
for(int i = 0;i&srcdata.i++){
srcdata[i] = (byte)(0xFF ^ srcdata[i]);
* 修改dex头,CheckSum 校验码
* @param dexBytes
private static void fixCheckSumHeader(byte[] dexBytes) {
Adler32 adler = new Adler32();
adler.update(dexBytes, 12, dexBytes.length - 12);//从12到文件末尾计算校验码
long value = adler.getValue();
int va = (int)
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, 8, 4);//效验码赋值(8-11)
System.out.println(Long.toHexString(value));
System.out.println();
* int 转byte[]
* @param number
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;
* 修改dex头 sha1值
* @param dexBytes
* @throws NoSuchAlgorithmException
private static void fixSHA1Header(byte[] dexBytes)
throws NoSuchAlgorithmException {
MessageDigest md = 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值,可有可无
String hexstr = &&;
for (int i = 0; i & newdt. i++) {
hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16)
.substring(1);
System.out.println(hexstr);
* 修改dex头 file_size值
* @param dexBytes
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, 32, 4);//修改(32-35)
* 以二进制读出文件内容
* @param file
* @throws IOException
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);
return localByteArrayOutputStream.toByteArray();
下面来分析一下:
红色部分其实就是最核心的工作:
1&、加密源程序Apk文件
byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));//以二进制形式读出apk,并进行加密处理//对源Apk进行加密操作
加密算法很简单:
//直接返回数据,读者可以添加自己加密方法
private static byte[] encrpt(byte[] srcdata){
for(int i = 0;i&srcdata.i++){
srcdata[i] = (byte)(0xFF ^ srcdata[i]);
对每个字节进行异或一下即可。
(说明:这里是为了简单,所以就用了很简单的加密算法了,其实为了增加破解难度,我们应该使用更高效的加密算法,同事最好将加密操作放到native层去做)
2&、合并文件:将加密之后的Apk和原脱壳Dex进行合并
int payloadLen = payloadArray.
int unShellDexLen = unShellDexArray.
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的内容
3&、在文件的末尾追加源程序Apk的长度
//添加解壳数据长度
System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最后4为长度
4&、修改新Dex文件的文件头信息:file_ sha1; check_sum
//修改DEX file size文件头
fixFileSizeHeader(newdex);
//修改DEX SHA1 文件头
fixSHA1Header(newdex);
//修改DEX CheckSum文件头
fixCheckSumHeader(newdex);
具体修改可以参照之前说的文件头格式,修改指定位置的字节值即可。
这里我们还需要两个输入文件:
1&、源Apk文件:ForceApkObj.apk
2&、脱壳程序的Dex文件:ForceApkObj.dex
那么第一个文件我们都知道,就是上面的源程序编译之后的Apk文件,那么第二个文件我们怎么得到呢?这个就是我们要讲到的第三个项目:脱壳程序项目,他是一个Android项目,我们在编译之后,能够得到他的classes.dex文件,然后修改一下名称就可。
3、脱壳项目:ReforceApk
在讲解这个项目之前,我们先来了解一下这个脱壳项目的工作:
1&、通过反射置换android.app.ActivityThread 中的mClassLoader为加载解密出APK的DexClassLoader,该DexClassLoader一方面加载了源程序、另一方面以原mClassLoader为父节点,这就保证了即加载了源程序又没有放弃原先加载的资源与系统代码。
关于这部分内容,不了解的同学可以看一下ActivityThread.java的源码:
或者直接看一下这篇文章:
如何得到系统加载Apk的类加载器,然后我们怎么将加载进来的Apk运行起来等问题都在这篇文章中说到了。
2&、找到源程序的Application,通过反射建立并运行。
这里需要注意的是,我们现在是加载一个完整的Apk,让他运行起来,那么我们知道一个Apk运行的时候都是有一个Application对象的,这个也是一个程序运行之后的全局类。所以我们必须找到解密之后的源Apk的Application类,运行的他的onCreate方法,这样源Apk才开始他的运行生命周期。这里我们如何得到源Apk的Application的类呢?这个我们后面会说道。使用meta标签进行设置。
下面来看一下整体的流程图:
所以我们看到这里还需要一个核心的技术就是动态加载。关于动态加载技术,不了解的同学可以看这篇文章:
下面来看一下代码:
package com.example.
import java.io.BufferedInputS
import java.io.ByteArrayInputS
import java.io.ByteArrayOutputS
import java.io.DataInputS
import java.io.F
import java.io.FileInputS
import java.io.FileOutputS
import java.io.IOE
import java.lang.ref.WeakR
import java.lang.reflect.M
import java.util.ArrayL
import java.util.HashM
import java.util.I
import java.util.zip.ZipE
import java.util.zip.ZipInputS
import android.app.A
import android.app.I
import android.content.C
import android.content.pm.ApplicationI
import android.content.pm.PackageM
import android.content.pm.PackageManager.NameNotFoundE
import android.content.res.AssetM
import android.content.res.R
import android.content.res.Resources.T
import android.os.B
import android.util.ArrayM
import android.util.L
import dalvik.system.DexClassL
public class ProxyApplication extends Application{
private static final String appkey = &APPLICATION_CLASS_NAME&;
private String apkFileN
private String odexP
private String libP
//这是context 赋值
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//创建两个文件夹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/
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);
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();
public void onCreate() {
//loadResources(apkFileName);
Log.i(&demo&, &onCreate&);
// 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
String appClassName =
ApplicationInfo ai = this.getPackageManager()
.getApplicationInfo(this.getPackageName(),
PackageManager.GET_META_DATA);
Bundle bundle = ai.metaD
if (bundle != null && bundle.containsKey(&APPLICATION_CLASS_NAME&)) {
appClassName = bundle.getString(&APPLICATION_CLASS_NAME&);//className 是配置在xml文件中的。
Log.i(&demo&, &have no application class name&);
} catch (NameNotFoundException e) {
Log.i(&demo&, &error:&+Log.getStackTraceString(e));
e.printStackTrace();
//有值的话调用该Applicaiton
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&);
//把当前进程的mApplication 设置成了null
RefInvoke.setFieldOjbect(&android.app.LoadedApk&, &mApplication&,
loadedApkInfo, null);
Object oldApplication = RefInvoke.getFieldOjbect(
&android.app.ActivityThread&, currentActivityThread,
&mInitialApplication&);
///article/android-context.html
ArrayList&Application& mAllApplications = (ArrayList&Application&) RefInvoke
.getFieldOjbect(&android.app.ActivityThread&,
currentActivityThread, &mAllApplications&);
mAllApplications.remove(oldApplication);//删除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 = appClassN
appinfo_In_AppBindData.className = appClassN
Application app = (Application) RefInvoke.invokeMethod(
&android.app.LoadedApk&, &makeApplication&, loadedApkInfo,
new Class[] { boolean.class, Instrumentation.class },
new Object[] { false, null });//执行 makeApplication(false,null)
RefInvoke.setFieldOjbect(&android.app.ActivityThread&,
&mInitialApplication&, currentActivityThread, app);
ArrayMap mProviderMap = (ArrayMap) 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);
Log.i(&demo&, &app:&+app);
app.onCreate();
* 释放被加壳的apk文件,so文件
* @param data
* @throws IOException
private void splitPayLoadFromDex(byte[] apkdata) throws IOException {
int ablen = apkdata.
//取被加壳apk的长度
这里的长度取值,对应加壳时长度的赋值都可以做些简化
byte[] dexlen = new byte[4];
System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);
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];
//把被加壳apk内容拷贝到newdex中
System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);
//这里应该加上对于apk的解密操作,若加壳是加密处理的话
//对源程序Apk进行解密
newdex = decrypt(newdex);
//写入apk文件
File file = new File(apkFileName);
FileOutputStream localFileOutputStream = new FileOutputStream(file);
localFileOutputStream.write(newdex);
localFileOutputStream.close();
} catch (IOException localIOException) {
throw new RuntimeException(localIOException);
//分析被加壳的apk文件
ZipInputStream localZipInputStream = new ZipInputStream(
new BufferedInputStream(new FileInputStream(file)));
while (true) {
ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解这个是否也遍历子目录,看样子应该是遍历的
if (localZipEntry == null) {
localZipInputStream.close();
//取出被加壳apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)
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)
fos.write(arrayOfByte, 0, i);
fos.flush();
fos.close();
localZipInputStream.closeEntry();
localZipInputStream.close();
* 从apk包里面获取dex文件内容(byte)
* @throws IOException
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();
if (localZipEntry.getName().equals(&classes.dex&)) {
byte[] arrayOfByte = new byte[1024];
while (true) {
int i = localZipInputStream.read(arrayOfByte);
if (i == -1)
dexByteArrayOutputStream.write(arrayOfByte, 0, i);
localZipInputStream.closeEntry();
localZipInputStream.close();
return dexByteArrayOutputStream.toByteArray();
// //直接返回数据,读者可以添加自己解密方法
private byte[] decrypt(byte[] srcdata) {
for(int i=0;i&srcdata.i++){
srcdata[i] = (byte)(0xFF ^ srcdata[i]);
//以下是加载资源
protected AssetManager mAssetM//资源管理器
protected Resources mR//资源
protected Theme mT//主题
protected void loadResources(String dexPath) {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod(&addAssetPath&, String.class);
addAssetPath.invoke(assetManager, dexPath);
mAssetManager = assetM
} catch (Exception e) {
Log.i(&inject&, &loadResource error:&+Log.getStackTraceString(e));
e.printStackTrace();
Resources superRes = super.getResources();
superRes.getDisplayMetrics();
superRes.getConfiguration();
mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
public AssetManager getAssets() {
return mAssetManager == null ? super.getAssets() : mAssetM
public Resources getResources() {
return mResources == null ? super.getResources() : mR
public Theme getTheme() {
return mTheme == null ? super.getTheme() : mT
首先我们来看一下具体步骤的代码实现:
1&、得到脱壳Apk中的dex文件,然后从这个文件中得到源程序Apk.进行解密,然后加载
//这是context 赋值
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//创建两个文件夹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/
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);
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();
这里需要注意的一个问题,就是我们需要找到一个时机,就是在脱壳程序还没有运行起来的时候,来加载源程序的Apk,执行他的onCreate方法,那么这个时机不能太晚,不然的话,就是运行脱壳程序,而不是源程序了。查看源码我们知道。Application中有一个方法:
attachBaseContext
这个方法,他在Application的onCreate方法执行前就会执行了,那么我们的工作就需要在这里进行
1)、从脱壳程序Apk中找到源程序Apk,并且进行解密操作
//创建两个文件夹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);
这个脱壳解密操作一定要和我们之前的加壳以及加密操作对应,不然就会出现Dex加载错误问题
A) 从Apk中获取到Dex文件
* 从apk包里面获取dex文件内容(byte)
* @throws IOException
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();
if (localZipEntry.getName().equals(&classes.dex&)) {
byte[] arrayOfByte = new byte[1024];
while (true) {
int i = localZipInputStream.read(arrayOfByte);
if (i == -1)
dexByteArrayOutputStream.write(arrayOfByte, 0, i);
localZipInputStream.closeEntry();
localZipInputStream.close();
return dexByteArrayOutputStream.toByteArray();
其实就是解压Apk文件,直接得到dex文件即可
B) 从脱壳Dex中得到源Apk文件
* 释放被加壳的apk文件,so文件
* @param data
* @throws IOException
private void splitPayLoadFromDex(byte[] apkdata) throws IOException {
int ablen = apkdata.
//取被加壳apk的长度
这里的长度取值,对应加壳时长度的赋值都可以做些简化
byte[] dexlen = new byte[4];
System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);
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];
//把被加壳apk内容拷贝到newdex中
System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);
//这里应该加上对于apk的解密操作,若加壳是加密处理的话
//对源程序Apk进行解密
newdex = decrypt(newdex);
//写入apk文件
File file = new File(apkFileName);
FileOutputStream localFileOutputStream = new FileOutputStream(file);
localFileOutputStream.write(newdex);
localFileOutputStream.close();
} catch (IOException localIOException) {
throw new RuntimeException(localIOException);
//分析被加壳的apk文件
ZipInputStream localZipInputStream = new ZipInputStream(
new BufferedInputStream(new FileInputStream(file)));
while (true) {
ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解这个是否也遍历子目录,看样子应该是遍历的
if (localZipEntry == null) {
localZipInputStream.close();
//取出被加壳apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)
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)
fos.write(arrayOfByte, 0, i);
fos.flush();
fos.close();
localZipInputStream.closeEntry();
localZipInputStream.close();
C) 解密源程序Apk
////直接返回数据,读者可以添加自己解密方法
private byte[] decrypt(byte[] srcdata) {
for(int i=0;i&srcdata.i++){
srcdata[i] = (byte)(0xFF ^ srcdata[i]);
这个解密算法和加密算法是一致的
2&、加载解密之后的源程序Apk
//配置动态加载环境
Object currentActivityThread = RefInvoke.invokeStaticMethod(
&android.app.ActivityThread&, &currentActivityThread&,
new Class[] {}, new Object[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/
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);
Object actObj = dLoader.loadClass(&com.example.forceapkobj.MainActivity&);
Log.i(&demo&, &actObj:&+actObj);
}catch(Exception e){
Log.i(&demo&, &activity:&+Log.getStackTraceString(e));
2)、找到源程序的Application程序,让其运行
public void onCreate() {
//loadResources(apkFileName);
Log.i(&demo&, &onCreate&);
// 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
String appClassName =
ApplicationInfo ai = this.getPackageManager()
.getApplicationInfo(this.getPackageName(),
PackageManager.GET_META_DATA);
Bundle bundle = ai.metaD
if (bundle != null && bundle.containsKey(&APPLICATION_CLASS_NAME&)) {
appClassName = bundle.getString(&APPLICATION_CLASS_NAME&);//className 是配置在xml文件中的。
Log.i(&demo&, &have no application class name&);
} catch (NameNotFoundException e) {
Log.i(&demo&, &error:&+Log.getStackTraceString(e));
e.printStackTrace();
//有值的话调用该Applicaiton
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&);
//把当前进程的mApplication 设置成了null
RefInvoke.setFieldOjbect(&android.app.LoadedApk&, &mApplication&,
loadedApkInfo, null);
Object oldApplication = RefInvoke.getFieldOjbect(
&android.app.ActivityThread&, currentActivityThread,
&mInitialApplication&);
///article/android-context.html
ArrayList&Application& mAllApplications = (ArrayList&Application&) RefInvoke
.getFieldOjbect(&android.app.ActivityThread&,
currentActivityThread, &mAllApplications&);
mAllApplications.remove(oldApplication);//删除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 = appClassN
appinfo_In_AppBindData.className = appClassN
Application app = (Application) RefInvoke.invokeMethod(
&android.app.LoadedApk&, &makeApplication&, loadedApkInfo,
new Class[] { boolean.class, Instrumentation.class },
new Object[] { false, null });//执行 makeApplication(false,null)
RefInvoke.setFieldOjbect(&android.app.ActivityThread&,
&mInitialApplication&, currentActivityThread, app);
ArrayMap mProviderMap = (ArrayMap) 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);
Log.i(&demo&, &app:&+app);
app.onCreate();
直接在脱壳的Application中的onCreate方法中进行就可以了。这里我们还可以看到是通过AndroidManifest.xml中的meta标签获取源程序Apk中的Application对象的。
下面来看一下AndoridManifest.xml文件中的内容:
在这里我们定义了源程序Apk的Application类名。
项目下载: http://download.csdn.net/detail/jiangwei/9102741
四、运行程序
那么到这里我们就介绍完了,这三个项目的内容,下面就来看看如何运行吧:
运行步骤:
第一步:得到源程序Apk文件和脱壳程序的Dex文件
运行源程序和脱壳程序项目,之后得到这两个文件(记得将classes.dex文件改名ForceApkObj.dex),然后使用加壳程序进行加壳:
这里的ForceApkObj.apk文件和ForceApkObj.dex文件是输入文件,输出的是classes.dex文件。
第二步:替换脱壳程序中的classes.dex文件
我们在第一步中得到加壳之后的classes.dex文件之后,并且我们在第一步运行脱壳项目的时候得到一个ReforceApk.apk文件,这时候我们使用解压缩软件进行替换:
第三步:我们在第二步的时候得到替换之后的ReforceApk.apk文件,这个文件因为被修改了,所以我们需要从新对他签名,不然运行也是报错的。
工具下载:
下载之后的工具需要用ReforeceApk.apk文件替换ReforceApk_des.apk文件,然后运行run.bat就可以得到签名之后的文件了。
run.bat文件的命令如下:
cd C:\Users\i\Desktop\forceapks
jarsigner -verbose -keystore forceapk -storepass 123456 -keypass 123456 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA -signedjar ReforceApk_des.apk ReforceApk.apk jiangwei
del ReforceApk.apk
这里最主要的命令就是中间的一条签名的命令,关于命令的参数说明如下:
jarsigner -verbose -keystore 签名文件 -storepass 密码 &-keypass alias的密码 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA &签名后的文件 签名前的apk alias名称
jarsigner -verbose -keystore forceapk -storepass 123456 -keypass 123456 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA -signedjar ReforceApk_des.apk ReforceApk_src.apk jiangwei
签名文件的密码:123456
alais的密码:123456
所以这里我们在得到ReforceApk.apk文件的时候,需要签名,关于Eclipse中如何签名一个Apk的话,这里就不多说了,自己google一下吧:
那么通过上面的三个步骤之后我们得到一个签名之后的最终文件:
ReforceApk_des.apk
我们安装这个Apk,然后运行,效果如下:
看到运行结果的那一瞬间,我们是多么的开心,多么的有成就感,但是这个过程中遇到的问题,是可想而知的。
我们这个时候再去反编译一下源程序Apk(这个文件是我们脱壳出来的payload.apk,看ReforeceApk中的代码,就知道他的位置了)
发现dex文件格式是不正确的。说明我们的加固是成功的。
五、遇到的问题
1、研究的过程中遇到签名不正确的地方,开始的时候,直接替换dex文件之后,就直接运行了Apk,但是总是提示签名不正确。
2、运行的过程中说找不到源程序中的Activity,这个问题其实我在动态加载的那篇文章中说道了,我们需要在脱壳程序中的AndroidManifest.xml中什么一下源程序中的Activiity:
六、技术要点
1、对Dex文件格式的了解
2、动态加载技术的深入掌握
3、Application的执行流程的了解
4、如何从Apk中得到Dex文件
5、如何从新签名一个Apk程序
七、综合概述
我们通过上面的过程可以看到,关于Apk加固的工作还是挺复杂的,涉及到的东西也挺多的,下面就在来总结一下吧:
1、加壳程序
任务:对源程序Apk进行加密,合并脱壳程序的Dex文件 ,然后输入一个加壳之后的Dex文件
语言:任何语言都可以,不限于Java语言
技术点:对Dex文件格式的解析
2、脱壳程序
任务:获取源程序Apk,进行解密,然后动态加载进来,运行程序
语言:Android项目(Java)
技术点:如何从Apk中获取Dex文件,动态加载Apk,使用反射运行Application
Android中的Apk反编译可能是每个开发都会经历的事,但是在反编译的过程中,对于源程序的开发者来说那是不公平的,那么Apk加固也是应运而生,但是即使是这样,我们也还是做不到那么的安全,现在网上也是有很多文章在解析梆梆加固的原理了。而且有人破解成功了,那么加固还不是怎么安全。最后一句话:逆向和加固是一个永不停息的战争。
作者:jiangwei 发表于 13:58:55
阅读:0 评论:0
已发表评论数()
已收藏到推刊!
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
没有分页内容
图片无法显示
视频无法显示
与原文不一致

我要回帖

更多关于 百度加固破解 的文章

 

随机推荐