1.加壳程序。主要是把需要加壳的原程序加密后,放在壳程序中,一般是追加到壳程序的classes.dex文件的末尾,然后对壳程序的classes.dex文件中的长度、crc校验和sha1校验字段重新计算。
2.壳程序。运行后,将加壳后的原程序从本程序的的classes.dex末尾释放出来,然后在Application类中,设置壳中原程序的运行环境,并加载执行原程序。
3.原程序。
一
项目下载地址
二
简要解析
/**
* 修改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.length; i++) {
//Integer.toString(int i, int radix)将整数i(十进制)转化为radix进制的整数
hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16).substring(1);
}
System.out.println("new dex sha-1:" + hexstr);
}/**
* 修改dex头 file_size值
* @param dexBytes
*/
private static void fixFileSizeHeader(byte[] dexBytes) {
//新文件长度
byte[] newfs = intToByte(dexBytes.length);byte[] refs = new byte[4];
//高位在前 低位在前掉个个
for (int i = 0; i < 4; i++) {
refs[i] = newfs[newfs.length - 1 - i];
}//修改(32-35)
System.arraycopy(refs, 0, dexBytes, 32, 4);System.out.println("new dex file size:" + Integer.toHexString(dexBytes.length));
}/**
* 修改dex头,CheckSum 校验码
* @param dexBytes
*/
private static void fixCheckSumHeader(byte[] dexBytes) {
Adler32 adler = new Adler32();
adler.update(dexBytes, 12, dexBytes.length - 12);
//从12到文件末尾计算校验码
int value = (int)adler.getValue();
byte[] newcs = intToByte(value);
//高位在前,低位在前掉个个
byte[] recs = new byte[4];
for (int i = 0; i < 4; i++) {
recs[i] = newcs[newcs.length - 1 - i];
}//效验码赋值(8-11)
System.arraycopy(recs, 0, dexBytes, 8, 4);System.out.println("new dex checksum:" +Integer.toHexString(value));
}
1.释放出源程序apk。
2.获取当前android.app.ActivityThread类对象currentActivityThread,并在其中找到ArrayMap类型的对象成员mPackages,并在mPackages中找到当前包名的android.app.LoadedApk对象。
3.在上述android.app.ActivityThread实例中,找到ClassLoader类的实例成员mClassLoader。
4 通过DexClassLoader加载释放出来的源程序apk。
5 将上述mClassLoader替换为加载源程序的DexClassLoader。
1.加载资源。
2.获取壳程序AndroidManifest.xml中源程序的包名和Application。
3.执行android.app.ActivityThread类的currentActivityThread方法,获取当前的android.app.ActivityThread实例currentActivityThread。
4.获取currentActivityThread中的android.app.ActivityThread类实例成员mBoundApplication。
5.在上述mBoundApplication中获取android.app.ActivityThread$AppBindData类实例info。
6.将上述info实例中的android.app.LoadedApk类实例成员mApplication设置为null。
7.在currentActivityThread中获取android.app.ActivityThread类实例mInitialApplication。
8.在currentActivityThread中获取android.app.ActivityThread类对象mAllApplications,该对象是个ArrayList<Application>结构,并在次结构中删除mInitialApplication对象。
9.获取info对象中android.app.LoadedApk类成员对象mApplicationInfo。
10.在mBoundApplication对象中获取android.app.ActivityThread$AppBindData类成员对象appInfo。
11.appInfo和mApplicationInfo实例的className成员变量设置为源程序的包名。
12.执行android.app.LoadedApk类对象info中的成员方法makeApplicationinfo,创建一个Application类实例。
13.设置android.app.ActivityThread类实例currentActivityThread中的成员对象mInitialApplication的值为上一步创建的Application类实例。
14.获取android.app.ActivityThread类对象currentActivityThread中的成员对象mProviderMap,该对象是一个ArrayMap结构,其元素是android.app.ActivityThread$ProviderClientRecord类。
15.遍历该成员对象,并获取其成员对象mLocalProvider,该成员对象是android.content.ContentProvider类,设置该android.content.ContentProvider类的成员对象mContext值为上述创建的Application。
16.调用上述步骤所创建的Application类实例中的onCreate方法。
package com.apkUnshell; 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.res.AssetManager;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;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.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import dalvik.system.DexClassLoader;/**
* =============================================================================
* Copyright (c) 2017 yuxin All rights reserved.
* Packname com.jju.yuxin.reforceapk
* Created by yuxin.
* Created time 2017/6/18 0018 下午 5:03.
* Version 1.0;
* Describe :
* History:
* ==============================================================================
*///com.google.android.apps.plus
//com.adobe.flashplayer
//com.loader
//com.setup.loader
//Applicaiton做为整个应用的上下文,会被系统第一时间调用,这也是应用开发者程序代码的第一执行点
public class MyApplication extends Application{private static String DEXFILENAME = "update.apk";
private static final String appkey = "APPLICATION_CLASS_NAME";
private static String cryptKey = "fuck all the android crackers";
public static String PAYLOAD_ODEX = "my_payload_odex";
public static String PAYLOAD_LIB = "my_payload_lib";
private static final String TAG = MyApplication.class.getSimpleName();
private String srcDexFilePath = "";
private String odexPath = "";
private String libPath = "";private static String gIPstr = "";
private static String gUserNameStr = "";private Context context = null;
//以下是加载资源
protected AssetManager mAssetManager = null;
protected Resources mResources = null;
protected Resources.Theme mTheme = null;//why run 2 times?
@SuppressWarnings("rawtypes")
@Override
protected void attachBaseContext(Context base) {super.attachBaseContext(base);
//getApplicationContext() 返回应用的上下文,生命周期是整个应用,应用摧毁它才摧毁
//Activity.this的context 返回当前activity的上下文,属于activity ,activity 摧毁他就摧毁
//getBaseContext() 返回由构造函数指定或setBaseContext()设置的上下文
//this.getApplicationContext()取的是这个应 用程序的Context,Activity.this取的是这个Activity的Context,
//这两者的生命周期是不同 的,前者的生命周期是整个应用,后者的生命周期只是它所在的Activity。
context = base;Log.e(TAG,"attachBaseContext");
try {
// /data/user/0/com.apkunshell/app_payload_odex
File odexPathFile = this.getDir(PAYLOAD_ODEX, MODE_PRIVATE);
// /data/user/0/com.apkunshell/app_payload_libs
File libsPathFile = this.getDir(PAYLOAD_LIB, MODE_PRIVATE);//用于存放源apk释放出来的dex
odexPath = odexPathFile.getAbsolutePath();
//用于存放源Apk用到的so文件
libPath = libsPathFile.getAbsolutePath();
//用于存放解密后的apk
srcDexFilePath = odexPathFile.getAbsolutePath() + "/" + DEXFILENAME;// String apppath = this.getFilesDir().getParent() + "/";
// InputStream is = this.getAssets().open(APKFILENAME);
// int size = is.available();
// byte []buffer = new byte[size];
// is.read(buffer);
// is.close();
// OutputStream os = new FileOutputStream(apppath + APKFILENAME);
// os.write(buffer);
// os.close();File srcDexFile = new File(srcDexFilePath);
//第一次加载
if (srcDexFile.exists() == false)
{
Log.e(TAG, "beFirstLoading");srcDexFile.createNewFile();
//拿到dex文件
byte[] dexdata = this.readDexFileFromApk();
//取出源APK解密后放置在/payload.apk,及其so文件放置在payload_lib/下
this.splitPayLoadFromDex(dexdata);
}// 配置动态加载环境
//反射获取主线程对象,并从中获取所有已加载的package信息,并中找到当前的LoadApk对象的弱引用
//// 配置动态加载环境 获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493
Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread",
"currentActivityThread",new Class[] {}, new Object[] {});
ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect("android.app.ActivityThread",
currentActivityThread,"mPackages");
String packageName = this.getPackageName();
WeakReference wr = (WeakReference) mPackages.get(packageName);///创建被加壳apk的DexClassLoader对象 加载apk内的类和本地代码(c/c++代码)
//创建一个新的DexClassLoader用于加载源Apk,传入apk路径,dex释放路径,so路径,及父节点的DexClassLoader使其遵循双亲委托模型
ClassLoader fathercl = (ClassLoader) RefInvoke.getFieldOjbect("android.app.LoadedApk", wr.get(), "mClassLoader");
DexClassLoader dLoader = new DexClassLoader(srcDexFilePath, odexPath,libPath, fathercl);//getClassLoader()等同于 (ClassLoader) RefInvoke.getFieldOjbect(),但是为了替换掉父节点我们需要通过反射来获取并修改其值
//将父节点DexClassLoader替换
////把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",wr.get(), dLoader);//Object actObj = dLoader.loadClass(LOADCLASSNAME);
//Log.e(TAG, "get class object:" + actObj);
} catch (Exception e) {
Log.e(TAG, "error:"+Log.getStackTraceString(e));
e.printStackTrace();
}
}//java.lang.RuntimeException:
//Unable to create application com.loader.sRelease: java.lang.NullPointerException:
//expected receiver of type android.content.ContentProvider, but got null
//at com.loader.sRefInvoke.setFieldOjbect(sRefInvoke.java:178)
//why run 2 times?
@SuppressWarnings("rawtypes")
public void onCreate() {
try {
Log.e(TAG, "onCreate");Log.e(TAG,"Application:" + context +
",BaseContext:" + getBaseContext() +
",ApplicationContext:" + getApplicationContext() +
",Activity:" + this);if(context == null){
context = this;
if(context == null){
context = Utils.getContext();
}
}Utils.setValue(context,"paramConfig.json","username",gUserNameStr);
Utils.setValue(context,"paramConfig.json","ip",gIPstr);//加载源apk资源
loadResources(srcDexFilePath);//获取配置在清单文件的源Apk的Application路径
String appClassName = null;
try {
ApplicationInfo ai = this.getPackageManager().getApplicationInfo(this.getPackageName(),PackageManager.GET_META_DATA);
Bundle bundle = ai.metaData;
if (bundle != null && bundle.containsKey(appkey)) {
appClassName = bundle.getString(appkey); //className 是配置在xml文件中的
}else {
Log.e(TAG, "not found application class name in bundle");
return;
}
} catch (Exception e) {
Log.e(TAG, "error:"+Log.getStackTraceString(e));
e.printStackTrace();
return;
}//获取当前壳Apk的ApplicationInfo
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");
//将LoadedApk中的ApplicationInfo设置为null
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",loadedApkInfo, null);//获取currentActivityThread中注册的Application
Object oldApplication = RefInvoke.getFieldOjbect("android.app.ActivityThread",
currentActivityThread,"mInitialApplication");//获取ActivityThread中所有已注册的Application,并将当前壳Apk的Application从中移除
@SuppressWarnings("unchecked")
ArrayList<Application> mAllApplications =
(ArrayList<Application>) RefInvoke.getFieldOjbect("android.app.ActivityThread",
currentActivityThread, "mAllApplications");
mAllApplications.remove(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");//替换原来的Application
appinfo_In_LoadedApk.className = appClassName;
appinfo_In_AppBindData.className = appClassName;//注册Application
Application app = (Application) RefInvoke.invokeMethod("android.app.LoadedApk", "makeApplication",
loadedApkInfo,new Class[] { boolean.class, Instrumentation.class },new Object[] { false, null });//替换ActivityThread中的Application
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.e(TAG, "app:"+app);
app.onCreate();
} catch (Exception e) {
e.printStackTrace();
}
}private void splitPayLoadFromDex(byte[] shelldexdata) throws IOException {
//取被加壳apk的长度
int sdlen = shelldexdata.length;
byte[] bytedexlen = new byte[4];
System.arraycopy(shelldexdata, sdlen - 4, bytedexlen, 0, 4);ByteArrayInputStream bais = new ByteArrayInputStream(bytedexlen);
DataInputStream dis = new DataInputStream(bais);
int readInt = dis.readInt();
Log.d(TAG,"Integer.toHexString(readInt):"+Integer.toHexString(readInt));//取出apk
byte[] encryptdata = new byte[readInt];
System.arraycopy(shelldexdata, sdlen - 4 - readInt, encryptdata, 0, readInt);//对源程序Apk进行解密
byte[] flatdata = xorcrypt(encryptdata);int offset = 0;
byte [] byteunamelen = new byte[4];
System.arraycopy(flatdata, offset, byteunamelen, 0, 4);
offset += 4;int unamelen = Utils.bytesToInt(byteunamelen);
byte[] username = new byte[unamelen];
System.arraycopy(flatdata , offset, username, 0, unamelen);
offset += unamelen;gUserNameStr = new String(username);
byte [] byteiplen = new byte[4];
System.arraycopy(flatdata, offset, byteiplen, 0, 4);
offset += 4;int iplen = Utils.bytesToInt(byteiplen);
byte[] ip = new byte[iplen];
System.arraycopy(flatdata , offset, ip, 0, iplen);
offset += iplen;gIPstr = new String(ip);
//写入源apk文件
File file = new File(srcDexFilePath);
try {
FileOutputStream localFileOutputStream = new FileOutputStream(file);
localFileOutputStream.write(flatdata,offset,readInt - offset);
localFileOutputStream.close();
} catch (IOException localIOException) {
throw new RuntimeException(localIOException);
}//分析源apk文件
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(file)));
while (true) {
ZipEntry ze = zis.getNextEntry();
if (ze == null) {
break;
}//依次取出被加壳apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)
String zfn = ze.getName();
if (zfn.startsWith("lib/") && zfn.endsWith(".so")) {
File sofile = new File(libPath + zfn.substring(zfn.lastIndexOf('/')));
sofile.createNewFile();
FileOutputStream fos = new FileOutputStream(sofile);
byte[] readbuf = new byte[0x4000];
while (true) {
int readlen = zis.read(readbuf);
if (readlen == -1){
break;
}
fos.write(readbuf, 0, readlen);
}
fos.flush();
fos.close();
}
zis.closeEntry();
}
zis.close();
}/**
* 拿到自己apk文件中的dex文件
* @return
* @throws IOException
*/
private byte[] readDexFileFromApk() throws IOException {ByteArrayOutputStream dexbaos = new ByteArrayOutputStream();
//getApplicationInfo().sourceDir == /data/user/0/com.adobe.flashplayer/base.apk
//BufferedInputStream会将该输入流数据分批读取,每次读取一部分到缓冲中;操作完缓冲中的这部分数据之后,再从输入流中读取下一部分的数据
//无其他用途
//ZipInputStream zis = new ZipInputStream(new FileInputStream(this.getApplicationInfo().sourceDir));ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(this.getApplicationInfo().sourceDir)));
while (true) {
ZipEntry ze = zis.getNextEntry();
if (ze == null) {
break;
}//拿到dex文件
if (ze.getName().equals("classes.dex")) {
byte[] readbuf = new byte[0x10000];
while (true) {
int readlen = zis.read(readbuf);
if (readlen == -1){
zis.closeEntry();
break;
}dexbaos.write(readbuf, 0, readlen);
}
zis.closeEntry();
break;
}else{
zis.closeEntry();
}
}zis.close();
return dexbaos.toByteArray();
}private static byte[] xorcrypt(byte[] srcdata){
byte[] key = cryptKey.getBytes();
int keylen = cryptKey.length();
for(int i = 0,j = 0; i<srcdata.length; i++){
srcdata[i] = (byte)(key[j] ^ srcdata[i]);
j ++;
if(j >= keylen){
j = 0;
}
}
return srcdata;
}protected void loadResources(String srcApkPath) {
//创建一个AssetManager放置源apk的资源
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, srcApkPath);
mAssetManager = assetManager;
} catch (Exception e) {
Log.i(TAG, "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 Resources.Theme getTheme() {
return mTheme == null ? super.getTheme() : mTheme;
}}
三
加壳的注意事项
<meta-data
android:name="APPLICATION_CLASS_NAME"
android:value="com.adobe.flashplayer.MyApplication"/>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
四
加壳自动化
set path = ".\" rd /q /s .\apkunshell
del .\apkUnshell.apk_new.apk
del .\autosign\update.apk
del .\autosign\update_signed.apkjava -jar apkshell.jar ./apkunshell.apk ./app-release.apk ./apkunshell jy 47.101.204.4
copy .\apkunshell.apk_new.apk .\autosign\update.apk
java -jar ./autosign/signapk.jar ./autosign/testkey.x509.pem ./autosign/testkey.pk8 ./autosign/update.apk ./autosign/update_signed.apk
copy .\autosign\update_signed.apk .\mytest.apk
del .\apkUnshell.apk_new.apk
del .\autosign\update.apk
del .\autosign\update_signed.apk
rd /q /s .\apkunshellpause
五
加壳方案的优劣
TencentLocationManager.setUserAgreePrivacy(true);
AMapLocationClient.updatePrivacyShow(context,true,true);
AMapLocationClient.updatePrivacyAgree(context,true);