如何徹底防止反編譯,dex加密怎么做 [復制鏈接]

2019-9-17 10:08
hardwork 閱讀:368 評論:0 贊:2
Tag:  反編譯 dex

原理解析

下面看一下Android中加殼的原理: [

在加固過程中需要三個對象:

  • 需要加密的APK(源程序APK)
  • 殼程序APK(負責解密APK工作)
  • 加密工具(將源APK進行加密和殼程序的DEX合并)

主要步驟 用加密算法對源程序APK進行加密,再將其與殼程序APK的DEX文件合并生成新的DEX文件,最后替換殼程序中的原DEX文件即可。得到新的APK也叫做脫殼程序APK,它已經不是一個完整意義上的APK程序了,它的主要工作是:負責解密源程序APK,然后加載APK,讓其正常運行起來。 在這個過程中需要了解的知識是:如何將源程序APK和殼程序APK進行合并 這需要了解DEX文件的格式,下面簡單介紹一下:

addressnamesize/bytevalue0magic[8]80x6465 780a 3033 35008checksum40xc136 5e17csignature[20]2020file_size40x02e424header_size40x7028endian_tag40x123456782Clink_size40x0030link_off40x0034map_off40x024438string_ids_size40x0e3cstring_ids_off40x7040type_ids_size40x0744type_ids_off40xa848proto_ids_size40x034Cproto_ids_off40xc450field_ids_size40x0154field_ids_off40xe858method_ids_size40x045Cmethod_ids_off40xf060class_defs_size40x0164class_defs_off40x011068data_size40x01b46Cdata_off40x0130

現在只要關注其中三個部分:

  • checksum(文件校驗碼)使用alder32算法校驗文件,除去magic、checksum外余下的所有文件區域,用于檢查文件錯誤。
  • signature 使用SHA-1算法hash出去magic、checksum和signature外余下的所有文件區域,用于唯一識別本文件。
  • file_size DEX文件大小。

我們需要將加密之后的源程序APK文件寫入到DEX中,那么就需要修改checksum,因為它的值和文件內容有關。signature也是一樣,也是唯一識別文件的算法,還有DEX文件的大小。 還需要一個操作,就是標注加密之后的源程序APK文件的大小,因為運行解密的時候,需要知道APK的大小,才能正確得到源程序APK。這個值直接放到文件的末尾就可以了。 修改之后的DEX文件的格式如下:

如何徹底防止反編譯,dex加密怎么做

知道了原理,下面就是代碼實現了。這里有三個工程:

  • 源程序項目(需要加密的APK)
  • 殼項目(解密源程序APK和加載APK)
  • 對源APK進行加密和殼項目的DEX的合并

項目案例

下面先來看一下源程序 1.需要加密的源程序項目:SourceApk [

如何徹底防止反編譯,dex加密怎么做

需要一個Application類,這個到后面說為什么需要: MyApplication.java

package com.example.sourceapk;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Log.i("demo", "source apk onCreate:" + this);
}
}

就是打印一下onCreate方法。 MainActivity.java

package com.example.sourceapk;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

TextView content = new TextView(this);
content.setText("I am Source Apk");
content.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View arg0) {
Intent intent = new Intent(MainActivity.this, SubActivity.class);
startActivity(intent);
}});
setContentView(content);

Log.i("demo", "app:"+getApplicationContext());
}
}

2.加殼程序項目:DexPackTool

如何徹底防止反編譯,dex加密怎么做

加殼程序其實就是一個Java工程,它的工作就是加密源程序APK,然后將其寫入到殼程序的DEX文件里,修改文件頭,得到一個新的DEX文件。 看一下代碼:

package com.example.packdex;
public class mymain {
public static void main(String[] args) {
try {
File payloadSrcFile = new File("files/SourceApk.apk"); // 需要加殼的源程序
System.out.println("apk size:"+payloadSrcFile.length());
File packDexFile = new File("files/SourceApk.dex"); // 殼程序dex
byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile)); // 以二進制形式讀出源apk,并進行加密處理
byte[] packDexArray = readFileBytes(packDexFile); // 以二進制形式讀出dex
/* 合并文件 */
int payloadLen = payloadArray.length;
int packDexLen = packDexArray.length;
int totalLen = payloadLen + packDexLen + 4; // 多出4字節是存放長度的
byte[] newdex = new byte[totalLen]; // 申請了新的長度
// 添加解殼代碼
System.arraycopy(packDexArray, 0, newdex, 0, packDexLen); // 先拷貝dex內容
// 添加加密后的解殼數據
System.arraycopy(payloadArray, 0, newdex, packDexLen, 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 = "files/classes.dex"; // 創建一個新文件
File file = new File(str);
if (!file.exists()) {
file.createNewFile();
}

FileOutputStream localFileOutputStream = new FileOutputStream(str);
localFileOutputStream.write(newdex); // 將新計算出的二進制dex數據寫入文件
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 ^ srcdata[i]);
}
return srcdata;
}
...
}

加密算法很簡單,只是對每個字節進行異或一下。

這里是為了簡單,所以就用了很簡單的加密算法,其實為了增加破解難度,我們應該使用更高效的加密算法,同時最好將加密操作放到native層去做。

這里需要兩個輸入文件:

  • 源程序APK文件:SourceApk.apk
  • 殼程序的DEX文件:SourceApk.dex

第一個文件就是源程序項目編譯之后的APK文件,第二個文件是下面要講的第三個項目:殼程序項目中的classes.dex文件,修改名稱之后得到。 3.殼程序項目:PackApk

如何徹底防止反編譯,dex加密怎么做

先來了解一下殼程序項目的工作:

  • 通過反射置換android.app.ActivityThread中的mClassLoader為加載解密出APK的DexClassLoader,該DexClassLoader一方面加載了源程序,另一方面以原mClassLoader為父節點,這就保證即加載了源程序,又沒有放棄原先加載的資源與系統代碼。 關于這部分內容不了解的可以看一下Android動態加載之免安裝運行程序這篇文章。
  • 找到源程序的Application,通過反射建立并運行。 這里需要注意的是,我們現在是加載一個完整的APK,讓他運行起來。一個APK運行的時候都是有一個Application對象的,這個也是一個程序運行之后的全局類,所以我們必須找到解密之后的源程序APK的Application類,運行它的onCreate方法,這樣源程序APK才開始它的運行生命周期。后面會說如何得到源程序APK的Application類:使用meta標簽進行設置。

下面看一下整體流程:

如何徹底防止反編譯,dex加密怎么做

下面看一下代碼: ProxyApplication.java

  1. 得到殼程序APK中的DEX文件,然后從這個文件中得到源程序APK進行解密、加載
// 這是context賦值
@Override
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[] {});//獲取主線程對象
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"));
//把當前進程的mClassLoader設置成了被加殼apk的DexClassLoader
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",
wr.get(), dLoader);

Log.i("demo","classloader:"+dLoader);
try{
Object actObj = dLoader.loadClass("com.example.sourceapk.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方法執行前就會執行了,所以我們的工作就需要在這里進行。 A) 從APK中獲取到DEX文件

/**
* 從apk包里面獲取dex文件內容(byte)
* @return
* @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();
break;
}
if (localZipEntry.getName().equals("classes.dex")) {
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();
}

B) 從殼程序DEX中得到源程序APK文件

/**
* 釋放被加殼的apk文件,so文件
* @param data
* @throws IOException
*/
private void splitPayLoadFromDex(byte[] apkdata) throws IOException {
int ablen = apkdata.length;
//取被加殼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);
try {
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();
break;
}
// 取出被加殼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)
break;
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.length;i++){
srcdata[i] = (byte)(0xFF ^ srcdata[i]);
}
return srcdata;
}
  1. 找到源程序的Application程序,讓其運行
@Override
public void onCreate() {
{
//loadResources(apkFileName);
Log.i("demo", "onCreate");
// 如果源應用配置有Appliction對象,則替換為源應用Applicaiton,以便不影響源程序邏輯。
String appClassName = null;
try {
ApplicationInfo ai = this.getPackageManager()
.getApplicationInfo(this.getPackageName(),
PackageManager.GET_META_DATA);
Bundle bundle = ai.metaData;
if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {
appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。
} else {
Log.i("demo", "have no application class name");
return;
}
} 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");
//http://www.codeceo.com/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 = appClassName;
appinfo_In_AppBindData.className = appClassName;
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對象的。 下面來看一下AndroidManifest.xml文件中的內容:

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:name="com.example.packapk.ProxyApplication">

<meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.example.sourceapk.MyApplication"/>

這里我們定義了源程序APK的Application類名。 項目下載

運行程序

下面就看看程序的運行步驟:

  • 第一步:得到源程序APK文件和殼程序的DEX文件 運行源程序和殼程序項目,之后得到這兩個文件(將殼程序的classes.dex文件改名為SourceApk.dex),然后使用加密工具進行加殼。
  • 第二步:替換殼程序中的classes.dex文件 我們在第一步中得到加殼之后的classes.dex文件之后,將其與PackApk.apk中的原classes.dex文件替換。
  • 第三步:在第二步的時候得到替換之后的PackApk.apk文件,這個文件因為被修改了,所以我們需要重新對它簽名,不然運行也是報錯的。 簽名之后的文件就可以運行了,效果如下:
如何徹底防止反編譯,dex加密怎么做



我來說兩句
您需要登錄后才可以評論 登錄 | 立即注冊
facelist
所有評論(0)
領先的中文移動開發者社區
18620764416
7*24全天服務
意見反饋:[email protected]

掃一掃關注我們

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粵ICP備15117877號 )

重庆时时彩开奖模拟器 天津十一选五开奖公告 广东快中彩玩法 南通五人斗地主三副牌 韩国快乐8官网下载 幸运赛车走势图爱彩 91棋牌游戏大厅手机版 双色球单式投和复式投注 江苏11选5五码遗漏 最快递赚钱吗 彩票复式组合器 新疆11选5规律 有水有电怎么赚钱