安卓加固基礎(二)

4.反調試

4.1 思路一(一個進程最多隻能被一個進程ptrace)

本文章主要針對安卓so反調試和最初的加殼方法進行了一下總結

在處於調試狀態時,Linux會向/proc/pid/status寫入一些進程狀態信息,比如TracerPid字段會寫入調試進程的pid,因此我們可以自己ptrace自己,然後讓android_server不能調試
代碼如下:

#include<sys/ptrace.h>  //這個頭文件很重要
void anti_debug01()
{
    ptrace(PTRACE_TRACEME,0,0,0);
}
jint JNI_Onload(JavaVM* vm,void* reserved)
{
    anti_debug01();

}

一旦開始調試,就會出現
在這裏插入圖片描述

反反調試思路:nop掉anti_debug01()函數調用

4.2檢測Tracepid的值

根據第一種分析得出Tracepid的值只要不爲0 就能說明進程正在被調試,因此我們只需要檢測Tracepid的值是不是0,如果不爲0,直接退出就行了


void anti_debug02()
{
    try
    {
        const int bufsize =1024;
        char filename[bufsize];
        char line[bufsize];
        int pid=getpid();
        sprintf(filename,"/proc/%d/status",pid);
        FILE* fd=fopen(filename,"r");
        if(fd!=NULL)
        {
            while(fgets(line,bufsize,fd))
            {
                if(strncmp(line,"TracerPid",9)==0)
                {
                    int statue = atoi(&line[10]);    //atoi,將字符串轉化爲int
                    if(statue !=0)
                    {
                        fclose(fd);
                        int ret =kill(pid,SIGKILL);
                    }
                    break;
                }
            }
        }
    }
}
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
void* thread_function(void *arg){
    int pid = getpid();
    char file_name[20] = {'\0'};
    sprintf(file_name,"proc/%d/status",pid);
    char line_str[256];
    int i = 0,traceid;
    FILE *fp;
    while(1){
        i = 0;
        fp = fopen(file_name,"r");
        if(fp == NULL){
            break;
        }
        while(!feof(fp)){
            fgets(line_str,256,fp);
            if(i == 5){
                // traceid = getnumberfor_str(line_str);
                traceid = atoi(&line_str[10]);
                if(traceid > 0){
                    exit(0);
                }
                break;
            }
            i++;
        }
        fclose(fp);
        sleep(5);
    }
}


void create_thread_check_traceid(){
    pthread_t thread_id;
    int err = pthread_create(&thread_id,NULL,thread_function,NULL);
    if(err != 0){

    }
}

在這裏插入圖片描述

反反調試思路:使用IDA動態調試在函數調用前下斷,對比當前TracerPid爲4591,將TracerPid對應的寄存器修改爲0,達到“0==0”的效果,繞開反調試。

5.自定義DexClassLoader

5.1 將殼dex放在待加密dex的外面

5.1.1 .原理

在這裏插入圖片描述

加密的工程中存在的三個加密對象

1.需要加密的APK(源APK)

2.殼程序APK

3.加密工具(負責將源APK進行加密和殼DEX合併成新的DEX)

在這裏插入圖片描述

5.1.2.DEX頭文件的內容

字段名稱 偏移值 長度 說明
magic 0x0 8 魔數字段,值爲"dex\n035\0"
checksum 0x8 4 校驗碼
signature 0xc 20 sha-1簽名
file_size 0x20 4 dex文件總長度
header_size 0x24 4 文件頭長度,009版本=0x5c,035版本=0x70
endian_tag 0x28 4 標示字節順序的常量
link_size 0x2c 4 鏈接段的大小,如果爲0就是靜態鏈接
link_off 0x30 4 鏈接段的開始位置
map_off 0x34 4 map數據基址
string_ids_size 0x38 4 字符串列表中字符串個數
string_ids_off 0x3c 4 字符串列表基址
type_ids_size 0x40 4 類列表裏的類型個數
type_ids_off 0x44 4 類列表基址
proto_ids_size 0x48 4 原型列表裏面的原型個數
proto_ids_off 0x4c 4 原型列表基址
field_ids_size 0x50 4 字段個數
field_ids_off 0x54 4 字段列表基址
method_ids_size 0x58 4 方法個數
method_ids_off 0x5c 4 方法列表基址
class_defs_size 0x60 4 類定義標中類的個數
class_defs_off 0x64 4 類定義列表基址
data_size 0x68 4 數據段的大小,必須4k對齊
data_off 0x6c 4 數據段基址

這裏面需要注意的字段:

1.checksum文件校驗碼,使alder32算法校驗文件除去magic,checksum外餘下的所有文件區域,用於檢查文件錯誤。
2.signature使用SHA-1算法hash除去magic,checksum和signature外餘下的所有文件區域,用於唯一識別本文件。
3.file_Size Dex文件的大小。
4.在文件的最後,我們需要標註被加密的apk大小,因此需要增加4個字節。
關注的原因如下:

因爲我們需要將一個文件(加密之後的源Apk)寫入到Dex中,那麼我們肯定需要修改文件校驗碼(checksum).因爲他是檢查文件是否有錯誤。那麼signature也是一樣,也是唯一識別文件的算法。還有就是需要修改dex文件的大小。
不過這裏還需要一個操作,就是標註一下我們加密的Apk的大小,因爲我們在脫殼的時候,需要知道Apk的大小,才能正確的得到Apk。那麼這個值放到哪呢?這個值直接放到文件的末尾就可以了。
總的來說:我們需要做:修改Dex的三個文件頭,將源Apk的大小追加到殼dex的末尾就可以了

根據上述修改後的dex文件樣式如下

在這裏插入圖片描述
由原理可知這裏需要進行三個步驟了:
1、自己編寫一個源程序項目(需要加密的APK)
2、脫殼項目(解密源APK和加載APK)
3、對源APK進行加密和脫殼項目的DEX合併

5.1.3.項目實現

5.1.3.1源APK(待加密的apk)

命名爲:SourceApk

MyApplication:

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

ps:MyApplication的作用:MyApplication類繼承Application,查看源碼我們知道,Application中有一個attachBaseContext方法,它在Application的onCreate方法執行前就會執行了,這個關鍵點爲後面的加密程序做鋪墊。

MainActivity:

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());   
    }
}

就是源程序的一個主要類

SubActivity:

package com.example.sourceapk;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class SubActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		TextView content = new TextView(this);
		content.setText("I am Source Apk SubMainActivity");
		
		setContentView(content);
		
		Log.i("demo", "app:"+getApplicationContext());
		
	}

}

```java
>同MainActivity

AndroidManifest.xml
```java
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.sourceapk">

    <application
        android:allowBackup="true"
        android:label="@string/app_name"
        android:name="com.example.sourceapk.MyApplication" >

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".SubActivity" />
    </application>

</manifest>

注意一定要加上android:name="com.example.sourceapk.MyApplication"

5.1.3.2加密APK(對加密後的源apk進行解密加載的apk)

ProxyActivity:

package com.example.packapk;

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.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 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;

	// 這是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();
		}
	}

	@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();
		}
	}

	/**
	 * 釋放被加殼的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();
	}

	/**
	 * 從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();
	}


	//直接返回數據,讀者可以添加自己解密方法
	private byte[] decrypt(byte[] srcdata) {
		for(int i=0;i<srcdata.length;i++){
			srcdata[i] = (byte)(0xFF ^ 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;  
	} 
	
}

RelInvoke(反射工具類):

package com.example.packapk;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Field;

public class RefInvoke {
	/**
	 * 反射執行類的靜態函數(public)
	 * @param class_name	類名
	 * @param method_name	函數名
	 * @param pareTyple		函數的參數類型
	 * @param pareVaules	調用函數時傳入的參數
	 * @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;
		
	}
	
	/**
	 * 反射執行類的函數(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;
		
	}
	
	/**
	 * 反射得到類的靜態屬性(包括私有和保護)
	 * @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;
		
	}
	
	/**
	 * 設置類的屬性(包括私有和保護)
	 * @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();
		}	
	}
	
	/**
	 * 設置類的靜態屬性(包括私有和保護)
	 * @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();
		}		
	}

}

分析:
1.首先通過java的反射,置換掉android.app.ActivityTread中的mClassLoader,作爲加載解密出的APK的DexClassLoader,,該DexClassloader既加載了源程序,還以mClassLoader作爲其父類,使得資源文件和系統代碼能正確的被加載
2. 找到源程序的Application(即上面我們定義的MyApplication類),通過這個類,運行待加密APK的onCreate方法,達到運行的效果

代碼解析:

  • attachBaseContext方法
  • 得到殼程序APK中的DEX文件,然後從這個文件中得到源程序APK進行解密和加載
  • 我們需要在殼程序還沒有運行的時候,加載源程序APK,執行它的onCreate方法。而通過查看Application源碼發現了attachBaseContext方法,它會在Application的onCreate方法執行前執行
  • 我們注意到attachBaseContext方法中,進行了dex文件的分離,通過該將加殼dex中的源dex源so文件分離出來,進行解密操作後,將他們放到相應的位置
  • onCreate方法
  • 通過java反射,找到源程序的Application類,然後運行

加密程序的AndroidManifest文件也一定要添加上:

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

++且其他的activity必須和源程序保持一致性++

例如:源程序爲:

<activity
            android:name="com.example.sourceapk.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <activity
            android:name="com.example.sourceapk.SubActivity"></activity>
        

那麼加密程序必須和其一模一樣

5.1.3.3加密工具(使用java語言編寫,eclipse運行,也可以是其他的腳本語言)

需要源程序apk和加密程序apk的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;
    }
    ...
}

完成所有的操作後還需要把新的apk進行簽名操作,否則會導致錯誤發生

脫殼操作簡述:獲取加殼所使用的加密算法,先進行解密操作,在使用010Editor等工具查看源apk的地址,進行dump獲取源apk

github地址:https://github.com/lzh18972615051/AndroidShellCode

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章