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