前言
這次我們對Android的一些基本加固做一個探討。本文將設(shè)計加固和Android的apk動態(tài)加載,我們重點放在加固上。有時間可以以后對apk動態(tài)加載做探討。
原理解析

加殼過程需要三個項目:
1.需要加密的源APK:TargetApk
2.殼程序Apk(負責解密Apk工作):UnDexApk
3.加殼項目(用于加密TargetApk):DexTools
主要加殼過程:
得到解殼apk的dex文件,使用加密算法對源apk進行加密。將源apk合并到解殼apk的dex后面。最后將解殼apk的dex替換為合并后的dex文件就完成了。殼程序在運行時會將dex文件中的apk重新解密并加載起來。這樣就完成了一個簡單的加固。
Dex
因為涉及到dex的合并所有我們必須先了解一下DEX文件格式,以下有一篇對DEX文件格式介紹的文章我這里就不再贅述
Dex文件格式詳解
這里主要注意一下Dex文件頭部信息,因為這里需要修改三個地方

上圖中紅色標記的部分就是需要修改的地方.
-
checksum
文件校驗碼 ,使用alder32 算法校驗文件除去 maigc ,checksum 外余下的所有文件區(qū)域 ,用于檢查文件錯誤 。 -
signature
使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余下的所有文件區(qū)域 ,用于唯一識別本文件 。 -
file_size
Dex 文件的大小
簡單說一下為什么要修改這三個字段:
因為我們需要將一個文件(加密之后的源Apk)寫入到Dex中,那么我們肯定需要修改文件校驗碼(checksum).因為他是檢查文件是否有錯誤。那么signature也是一樣,也是唯一識別文件的算法。還有就是需要修改dex文件的大小。
不過這里還需要一個操作,就是標注一下我們加密的Apk的大小,因為我們在脫殼的時候,需要知道Apk的大小,才能正確的得到Apk。那么這個值放到哪呢?這個值直接放到文件的末尾就可以了。
修改Dex的三個文件頭,將源Apk的大小追加到殼dex的末尾
最后dex結(jié)構(gòu)如下

最后來看一下代碼
代碼
TargetApk
這里我們需要一個Application類(為了驗證Application的動態(tài)加載)
TargetApplication.java
package com.shark.app;
import android.app.Application;
import android.util.Log;
public class TargetApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Log.i("TargetApplication", "source apk onCreate2:" + this);
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shark.targetapk">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name="com.shark.app.TargetApplication"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity.java
package com.shark.targetapk;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("MainActivity","onCreate Ok!");
setContentView(R.layout.activity_main);
}
}
源apk非常簡單就是在Application和MainActivity中打印了日志,以便我們后續(xù)觀察
DexTools
其項目結(jié)構(gòu)如下

DexTools.java
package com.shark.demo;
/**
* 加殼程序
*/
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.Adler32;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
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;
import com.shark.tools.SharkTool;
public class DexTools {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
File payloadSrcFile = new File("force/app-debug.apk"); //需要加殼的程序
File unDecryptDexFile = new File("force/Decrypt.dex"); //解殼dex
byte[] payloadArray = encrptData(readFileContent(payloadSrcFile));//加密OriginalAPK.apk
byte[] unDecryptDexArray = readFileContent(unDecryptDexFile);//以二進制形式讀出dex
int payloadLen = payloadArray.length;
int unDecryptDexLen = unDecryptDexArray.length;
int totalLen = payloadLen + unDecryptDexLen +4;//4字節(jié)存放長度。
byte[] newdex = new byte[totalLen]; // 申請了新的長度
//添加解殼代碼 public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
System.arraycopy(unDecryptDexArray, 0, newdex, 0, unDecryptDexLen);//先拷貝Decryptdex內(nèi)容
//添加加密后的解殼數(shù)據(jù)
System.out.println("unDecryptDexLen:"+unDecryptDexLen);
System.out.println("payloadLen:"+payloadLen);
System.arraycopy(payloadArray, 0, newdex, unDecryptDexLen, payloadLen);//再在dex內(nèi)容后面拷貝加密之后的Original apk的內(nèi)容
//添加解殼數(shù)據(jù)長度到newdex最后4個字節(jié)
System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最后4個字節(jié)為長度playload長度
//修改DEX file size文件頭
updateFileSizeHeader(newdex);//dex中32到35的位置為文件長度
//修改DEX SHA1 文件頭
updateSHA1Header(newdex);//dex中12到31位置,32到結(jié)束參與SHA1計算
//修改DEX CheckSum文件頭
updateCheckSumHeader(newdex);//dex中8到11位置,12到文件結(jié)束計算checksum
String str = "force/classes.dex";
File file = new File(str);
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(str);
fileOutputStream.write(newdex);
fileOutputStream.flush();
fileOutputStream.close();
System.out.println("done!");
} catch (Exception e) {
e.printStackTrace();
}
}
//直接返回數(shù)據(jù),讀者可以添加自己加密方法
private static byte[] encrptData(byte[] srcdata){
for(int i = 0;i<srcdata.length;i++){
srcdata[i] = (byte)(~srcdata[i]);
}
return srcdata;
}
/**
* 修改dex頭,CheckSum 校驗碼
* @param dexBytes
*/
private static void updateCheckSumHeader(byte[] dexBytes) {
Adler32 adler = new Adler32();
adler.update(dexBytes, 12, dexBytes.length - 12);//從12到文件末尾計算校驗碼
long value = adler.getValue();
int va = (int) value;
byte[] newcs = intToByte(va);
for (int i = 0; i < 2; i++) {
byte tmp = newcs[i];
newcs[i]= newcs[newcs.length-1-i];
newcs[newcs.length-1-i]=tmp;
}
System.arraycopy(newcs, 0, dexBytes, 8, 4);//效驗碼賦值(8-11)
}
/**
* int 轉(zhuǎn)byte[]
* @param number
* @return
*/
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;
}
/**
* 修改dex頭 sha1值
* @param dexBytes
* @throws NoSuchAlgorithmException
*/
private static void updateSHA1Header(byte[] dexBytes)
throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(dexBytes, 32, dexBytes.length - 32);//從32到結(jié)束計算sha-1
byte[] newdt = md.digest();
System.arraycopy(newdt, 0, dexBytes, 12, 20);//修改sha-1值(12-31)
}
/**
* 修改dex頭 file_size值
* @param dexBytes
*/
private static void updateFileSizeHeader(byte[] dexBytes) {
//新文件長度
byte[] newfs = intToByte(dexBytes.length);
//高位低位交換
for (int i = 0; i < 2; i++) {
byte tmp = newfs[i];
newfs[i]=newfs[newfs.length-1-i];
newfs[newfs.length-1-i]=tmp;
}
System.arraycopy(newfs, 0, dexBytes, 32, 4);//修改(32-35)
}
/**
* 以二進制讀出文件內(nèi)容
* @param file
* @return
* @throws IOException
*/
private static byte[] readFileContent(File file) throws IOException {
byte[] content = new byte[1024];
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
FileInputStream fileInputStream = new FileInputStream(file);
while (true) {
int i = fileInputStream.read(content);
if (i != -1) {
byteArrayOutputStream.write(content, 0, i);
} else {
return byteArrayOutputStream.toByteArray();
}
}
}
}
加殼程序其實就是一個Java工程,因為我們從上面的分析可以看到,他的工作就是加密源Apk,然后將其寫入到脫殼Dex文件中,修改文件頭,得到一個新的Dex文件即可。其他的看代碼和注釋應該都能看懂,就是按照上面的加殼理論去做的.
UnDexApk
脫殼項目的工作:
1.通過反射置換android.app.ActivityThread 中的mClassLoader為加載解密出APK的DexClassLoader,該DexClassLoader一方面加載了源程序、另一方面以原mClassLoader為父節(jié)點,這就保證了即加載了源程序又沒有放棄原先加載的資源與系統(tǒng)代碼。
2.找到源程序的Application,通過反射建立并運行。
這里需要注意的是,我們現(xiàn)在是加載一個完整的Apk,讓他運行起來,那么我們知道一個Apk運行的時候都是有一個Application對象的,這個也是一個程序運行之后的全局類。所以我們必須找到解密之后的源Apk的Application類,運行的他的onCreate方法,這樣源Apk才開始他的運行生命周期。這里我們?nèi)绾蔚玫皆碅pk的Application的類呢?這個我們后面會說道。使用meta標簽進行設(shè)置。
整體的流程圖:

ProxyApplication.java
package com.shark.application;
import android.app.Application;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;
import com.shark.com.shark.tools.RefInvoke;
import dalvik.system.DexClassLoader;
public class ProxyApplication extends Application {
private static final String appkey = "APPLICATION_CLASS_NAME";
private String apkFileName;
private String odexPath;
private String libPath;
private String libFilePath;
@Override
public void onCreate() {
super.onCreate();
// 如果源應用配置有Appliction對象,則替換為源應用Applicaiton,以便不影響源程序邏輯。
String appClassName = null;
try {
//得到以下信息
//meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.shark.app.TargetApplication"/>
ApplicationInfo ai = this.getPackageManager()
.getApplicationInfo(this.getPackageName(),
PackageManager.GET_META_DATA);
Bundle bundle = ai.metaData;
if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {
//源apk的Application
appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的 mfapplication。
} else {
Log.i("demo", "have no application class name");
return;
}
} catch (NameNotFoundException e) {
Log.i("demo", "error:" + Log.getStackTraceString(e));
e.printStackTrace();
}
//有值的話調(diào)用該Applicaiton
Object currentActivityThread = invokeStaticMethod(
"android.app.ActivityThread", "currentActivityThread",
new Class[]{}, new Object[]{});
Object mBoundApplication = getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mBoundApplication");
Object loadedApkInfo = getFieldOjbect(
"android.app.ActivityThread$AppBindData",
mBoundApplication, "info");
//把當前進程的mApplication 設(shè)置成了null
setFieldOjbect("android.app.LoadedApk", "mApplication",
loadedApkInfo, null);
//得到久oldApplication
Object oldApplication = getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mInitialApplication");
//http://www.codeceo.com/article/android-context.html
ArrayList<Application> mAllApplications = (ArrayList<Application>) getFieldOjbect("android.app.ActivityThread",
currentActivityThread, "mAllApplications");
mAllApplications.remove(oldApplication);//刪除oldApplication
ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) getFieldOjbect("android.app.LoadedApk", loadedApkInfo,
"mApplicationInfo");
ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) getFieldOjbect("android.app.ActivityThread$AppBindData",
mBoundApplication, "appInfo");
//設(shè)置為com.shark.app.TargetApplication
appinfo_In_LoadedApk.className = appClassName;
appinfo_In_AppBindData.className = appClassName;
Application app = (Application) invokeMethod(
"android.app.LoadedApk", "makeApplication", loadedApkInfo,
new Class[]{boolean.class, Instrumentation.class},
new Object[]{false, null});//執(zhí)行 makeApplication(false,null)
setFieldOjbect("android.app.ActivityThread",
"mInitialApplication", currentActivityThread, app);
ArrayMap mProviderMap = (ArrayMap) getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mProviderMap");
Iterator it = mProviderMap.values().iterator();
while (it.hasNext()) {
Object providerClientRecord = it.next();
Object localProvider = getFieldOjbect(
"android.app.ActivityThread$ProviderClientRecord",
providerClientRecord, "mLocalProvider");
setFieldOjbect("android.content.ContentProvider",
"mContext", localProvider, app);
}
app.onCreate();
}
//先于onCreate執(zhí)行
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
//創(chuàng)建兩個文件夾payload_odex,payload_lib 私有的,可寫的文件目錄
///data/data/com.shark.undexapk/app_payload_odex
///data/data/com.shark.undexapk/app_payload_lib
File odex = this.getDir("payload_odex", MODE_PRIVATE);
File libs = this.getDir("payload_lib", MODE_PRIVATE);
odexPath = odex.getAbsolutePath();
libPath = libs.getAbsolutePath();
// 創(chuàng)建源apk
apkFileName = odex.getAbsolutePath() + "/payload.apk";
File apkFile = new File(apkFileName);
if (!apkFile.exists()) {
apkFile.createNewFile(); //在payload_odex文件夾內(nèi),創(chuàng)建payload.apk
// 讀取程序classes.dex文件
byte[] dexdata = this.readDexFileFromApk();//加殼后的classes.dex文件,里面包含originalAPK文件
// 分離出解殼后的apk文件已用于動態(tài)加載
this.splitPayLoadFromDex(dexdata);//從加殼后的classes.dex文件里釋放original apk
}
//到此,Original APK已經(jīng)解密,so等文件也保存了。
// 配置動態(tài)加載環(huán)境
Object currentActivityThread = invokeStaticMethod(
"android.app.ActivityThread", "currentActivityThread",
new Class[]{}, new Object[]{});//獲取主線程對象 http://blog.csdn.net/myarrow/article/details/14223493
String packageName = this.getPackageName();//當前apk的包名
//TODO 這里應該是得到所有的包名 可以打印其key值觀察
ArrayMap mPackages = (ArrayMap) getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mPackages");
WeakReference wr = (WeakReference) mPackages.get(packageName);
//創(chuàng)建被加殼apk的DexClassLoader對象 加載apk內(nèi)的類和本地代碼(c/c++代碼)
DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
libPath, (ClassLoader) getFieldOjbect(
"android.app.LoadedApk", wr.get(), "mClassLoader"));
//把當前進程的DexClassLoader 設(shè)置成了被加殼apk的DexClassLoader
setFieldOjbect("android.app.LoadedApk", "mClassLoader",
wr.get(), dLoader);
try {
//加載園apk的MainActivity文件
Object actObj = dLoader.loadClass("com.shark.targetapk.MainActivity");
} catch (Exception e) {
Log.i("demo", "activity:" + Log.getStackTraceString(e));
}
} catch (Exception e) {
Log.i("demo", "error:" + Log.getStackTraceString(e));
e.printStackTrace();
}
}
/**
* 釋放被加殼的apk文件,so文件
*
* @throws IOException
*/
private void splitPayLoadFromDex(byte[] decryptdexdata) throws IOException {//從classes.dex中拿到original APK文件,并解密
int decryptdexlen = decryptdexdata.length;
//取被加殼apk的長度 這里的長度取值,對應加殼時長度的賦值都可以做些簡化
byte[] orignalapklen = new byte[4];
System.arraycopy(decryptdexdata, decryptdexlen - 4, orignalapklen, 0, 4);//最后4個字節(jié)是加密后的Original apk的長度
ByteArrayInputStream bais = new ByteArrayInputStream(orignalapklen);
DataInputStream in = new DataInputStream(bais);
int readInt = in.readInt();
byte[] originalAPK = new byte[readInt];
//把被加殼apk內(nèi)容拷貝到originalAPK中
System.arraycopy(decryptdexdata, decryptdexlen - 4 - readInt, originalAPK, 0, readInt);
//對源程序Apk進行解密
originalAPK = decryptData(originalAPK);
//寫入apk文件/data/data/com.shark.undexapk/app_payload_odex/payload.apk 即解密后的Original APK
File file = new File(apkFileName);
try {
FileOutputStream localFileOutputStream = new FileOutputStream(file);
localFileOutputStream.write(originalAPK);
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();
break;
}
//取出被加殼apk用到的so文件,放到 libPath中(data/data/包名/app_payload_lib)
String name = localZipEntry.getName();
//如果是以lib/開頭并且以.so結(jié)尾則需要將送拷貝進外面
if (name.startsWith("lib/") && name.endsWith(".so")) {
File storeFile = new File(libPath + "/"
+ name.substring(name.lastIndexOf('/')));
//創(chuàng)建so文件
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();
}
/**
* 從apk包里面獲取dex文件內(nèi)容(byte)
*
* @return
* @throws IOException
*/
private byte[] readDexFileFromApk() throws IOException {
ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
//getApplicationInfo().dataDir: 分配給此Application的存放數(shù)據(jù)的位置。通常是:/data/data/packageName/
//getApplicationInfo().SourceDir: 安裝這個包后的存放位置。 因為APK安裝后,archive文件存放在某個目錄(一般程序和//root程序位置不同)。
// 作為讀取資源是的位置。此位置通常在/data/app/pakcageName-1/base.apk
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")) {//找到classes.dex,里面包含著加密的original apk
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();
}
// //直接返回數(shù)據(jù),讀者可以添加自己解密方法
private byte[] decryptData(byte[] srcdata) {
for (int i = 0; i < srcdata.length; i++) {
srcdata[i] = (byte) (~srcdata[i]);
}
return srcdata;
}
//以下是加載資源
protected AssetManager mAssetManager;//資源管理器
protected Resources mResources;//資源
protected Theme mTheme;//主題
protected void loadResources(String dexPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, dexPath);
mAssetManager = assetManager;
} 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());
}
@Override
public AssetManager getAssets() {
return mAssetManager == null ? super.getAssets() : mAssetManager;
}
@Override
public Resources getResources() {
return mResources == null ? super.getResources() : mResources;
}
@Override
public Theme getTheme() {
return mTheme == null ? super.getTheme() : mTheme;
}
/**
* 反射執(zhí)行類的靜態(tài)函數(shù)(public)
*
* @param class_name 類名
* @param method_name 函數(shù)名
* @param pareTyple 函數(shù)的參數(shù)類型
* @param pareVaules 調(diào)用函數(shù)時傳入的參數(shù)
* @return
*/
public static Object invokeStaticMethod(String class_name, String method_name, Class[] pareTyple, Object[] pareVaules) {
try {
Class obj_class = Class.forName(class_name);
Method method = obj_class.getMethod(method_name, pareTyple);
return method.invoke(null, pareVaules);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 反射執(zhí)行類的函數(shù)(public)
*
* @param class_name
* @param method_name
* @param obj
* @param pareTyple
* @param pareVaules
* @return
*/
public static Object invokeMethod(String class_name, String method_name, Object obj, Class[] pareTyple, Object[] pareVaules) {
try {
Class obj_class = Class.forName(class_name);
Method method = obj_class.getMethod(method_name, pareTyple);
return method.invoke(obj, pareVaules);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 反射得到類的屬性(包括私有和保護)
*
* @param class_name
* @param obj
* @param filedName
* @return
*/
public static Object getFieldOjbect(String class_name, Object obj, String filedName) {
try {
Class obj_class = Class.forName(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
return field.get(obj);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 反射得到類的靜態(tài)屬性(包括私有和保護)
*
* @param class_name
* @param filedName
* @return
*/
public static Object getStaticFieldOjbect(String class_name, String filedName) {
try {
Class obj_class = Class.forName(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
return field.get(null);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 設(shè)置類的屬性(包括私有和保護)
*
* @param classname
* @param filedName
* @param obj
* @param filedVaule
*/
public static void setFieldOjbect(String classname, String filedName, Object obj, Object filedVaule) {
try {
Class obj_class = Class.forName(classname);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
field.set(obj, filedVaule);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 設(shè)置類的靜態(tài)屬性(包括私有和保護)
*
* @param class_name
* @param filedName
* @param filedVaule
*/
public static void setStaticOjbect(String class_name, String filedName, Object filedVaule) {
try {
Class obj_class = Class.forName(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
field.set(null, filedVaule);
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
MainActivity.java
package com.shark.undexapk;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.shark.undexapk">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name="com.shark.application.ProxyApplication"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.shark.app.TargetApplication"/>
<activity android:name="com.shark.targetapk.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
運行
1.現(xiàn)將源程序apk和加殼程序的dex文件拷貝到DexTools項目中,運行項目得到合并后的dex文件

2.將新的classes.dex拷貝覆蓋到殼程序的dex,并且刪除簽名文件后重新簽名

3.將重新簽名的apk丟進虛擬機中,其打印日志如下:

可以看出殼程序正常解殼,執(zhí)行了Application和Activity
尾言
其實了解dex文件格式的話這個加固還是非常簡單的,關(guān)鍵是動態(tài)加載dex,執(zhí)行Application和Activity這個我們可以下一次做分析,還有這里沒有對資源文件進行加殼,資源文件都需要放在殼程序中。