1. APP加固
1). 原理
加密过程的三个对象:
- 1、需要加密的Apk(源Apk)
- 2、壳程序Apk(负责解密Apk工作)
- 3、加密工具(将源Apk进行加密和壳Dex合并成新的Dex)
2). DEX头内容
需要关注的字段:
- checksum 文件校验码 ,使用alder32 算法校验文件除去 maigc ,checksum 外余下的所有文件区域 ,用于检查文件错误 。
- signature 使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余下的所有文件区域 ,用于唯一识别本文件 。
- fileSize Dex 文件的大小 。
-
在文件的最后,我们需要标注被加密的apk的大小,因此需要增加4个字节。
3). 解密过程
宿主Apk启动 -> 宿主Application中解密Apk -> 替换ClassLoader -> 替换资源路径 -> 替换Application对象
2. 源程序Module(source)
1). SourceApplication
/**
* 源Apk的全局Application
* Created by mazaiting on 2018/6/26.
*/
public class SourceApplication extends Application {
private static final String TAG = SourceApplication.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate: --------");
}
}
2). MainActivity
/**
* 应用主入口
*/
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tvContent = new TextView(this);
tvContent.setText("I am Source Apk");
tvContent.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View arg0) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}});
setContentView(tvContent);
Log.i(TAG, "onCreate:app:"+getApplicationContext());
}
}
3). 第二个页面
/**
* 第二个页面
*/
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv_content = new TextView(this);
tv_content.setText("I am Second Activity");
setContentView(tv_content);
}
}
4). AndroidManifest.xml文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mazaiting.reinforcement">
<application
android:name=".SourceApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
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>
<activity android:name=".SecondActivity">
</activity>
</application>
</manifest>
5). 签名
Build -> Generate Signed APK,对应用进行签名(如果没有keystore, 可以创建一个新的),将签名后的apk文件放置在项目根目录下的force文件夹下,并更名为source.apk
3. 脱壳Module(reforceapk)
1). 替换步骤
* 代理Application
* 步骤:
* ------- 在attachBaseContext -------
* 1. 从当前APK中拿到classes.dex文件,拿到classes.dex文件的二进制数据
* 2. 从dex的二进制数据中分离出解密后的apk,及so文件
* 3. 反射获取主线程对象,并从中获取所有已加载的package信息,找到当前LoadApk的弱引用
* 4. 创建一个新的DexClassLoader,从指定路径加载apk资源
* 5. 加载被加密的apk主Activity入口
* ------- 在onCreate方法 ----------
* 6. 获取配置在清单文件的源apk的Application
* 7. 替换原有的Application
* 8. 调用被加密app的Application
2). 代码
/**
* 代理Application
* 步骤:
* ------- 在attachBaseContext -------
* 1. 从当前APK中拿到classes.dex文件,拿到classes.dex文件的二进制数据
* 2. 从dex的二进制数据中分离出解密后的apk,及so文件
* 3. 反射获取主线程对象,并从中获取所有已加载的package信息,找到当前LoadApk的弱引用
* 4. 创建一个新的DexClassLoader,从指定路径加载apk资源
* 5. 加载被加密的apk主Activity入口
* ------- 在onCreate方法 ----------
* 6. 获取配置在清单文件的源apk的Application
* 7. 替换原有的Application
* 8. 调用被加密app的Application
* Created by mazaiting on 2018/6/26.
*/
public class ProxyApplication extends Application {
private static final String TAG = ProxyApplication.class.getSimpleName();
/**
* APP_KEY获取Activity入口
*/
private static final String APP_KEY = "APPLICATION_CLASS_NAME";
/**ActivityThread包名*/
private static final String CLASS_NAME_ACTIVITY_THREAD = "android.app.ActivityThread";
/**LoadedApk包名*/
private static final String CLASS_NAME_LOADED_APK = "android.app.LoadedApk";
/**
* 源Apk路径
*/
private String mSrcApkFilePath;
/**
* odex路径
*/
private String mOdexPath;
/**
* lib路径
*/
private String mLibPath;
/**
* 加载资源
*/
protected AssetManager mAssetManager;
protected Resources mResources;
protected Resources.Theme mTheme;
/**
* 最先执行的方法
*/
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Log.d(TAG, "attachBaseContext: --------onCreate");
try {
// 创建payload_odex和payload_lib文件夹,payload_odex中放置源apk即源dex,payload_lib放置so文件
File odex = this.getDir("payload_odex", MODE_PRIVATE);
File libs = this.getDir("payload_lib", MODE_PRIVATE);
// 用于存放源apk释放出来的dex
mOdexPath = odex.getAbsolutePath();
// 用于存放源apk用到的so文件
mLibPath = libs.getAbsolutePath();
// 用于存放解密后的apk
mSrcApkFilePath = mOdexPath + "/payload.apk";
File srcApkFile = new File(mSrcApkFilePath);
Log.d(TAG, "attachBaseContext: apk size: " + srcApkFile.length());
// 第一次加载
if (!srcApkFile.exists()) {
Log.d(TAG, "attachBaseContext: isFirstLoading");
srcApkFile.createNewFile();
// 拿到dex文件
byte[] dexData = this.readDexFileFromApk();
// 取出解密后的apk放置在/payload.apk,及其so文件放置在payload_lib下
this.splitPayLoadFromDex(dexData);
}
// 配置动态加载环境
// 反射获取主线程对象,并从中获取所有已加载的package信息,找到当前LoadApk的弱引用
// 获取主线程对象
Object currentActivityThread = RefInvoke.invokeStaticMethod(
CLASS_NAME_ACTIVITY_THREAD, "currentActivityThread",
new Class[]{}, new Object[]{}
);
// 获取当前报名
String packageName = this.getPackageName();
// 获取已加载的所有包
ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldObject(
CLASS_NAME_ACTIVITY_THREAD, currentActivityThread,
"mPackages"
);
// 获取LoadApk的弱引用
WeakReference wr = (WeakReference) mPackages.get(packageName);
// 创建一个新的DexClassLoader用于加载源Apk
// 传入apk路径,dex释放路径,so路径,及父节点的DexClassLoader使其遵循双亲委托模型
// 反射获取属性ClassLoader
Object mClassLoader = RefInvoke.getFieldObject(
CLASS_NAME_LOADED_APK, wr.get(), "mClassLoader"
);
// 定义新的DexClassLoader对象,指定apk路径,odex路径,lib路径
DexClassLoader dLoader = new DexClassLoader(
mSrcApkFilePath, mOdexPath, mLibPath, (ClassLoader) mClassLoader
);
// getClassLoader()等同于 (ClassLoader) RefInvoke.getFieldOjbect()
// 但是为了替换掉父节点我们需要通过反射来获取并修改其值
Log.d(TAG, "attachBaseContext: 父ClassLoader: " + mClassLoader);
// 将父节点DexClassLoader替换
RefInvoke.setFieldObject(
CLASS_NAME_LOADED_APK,
"mClassLoader",
wr.get(),
dLoader
);
Log.d(TAG, "attachBaseContext: 子ClassLoader: " + dLoader);
try {
// 尝试加载源apk的MainActivity
Object actObj = dLoader.loadClass("com.mazaiting.reinforcement.MainActivity");
Log.d(TAG, "attachBaseContext: SrcApk_MainActivity: " + actObj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
Log.d(TAG, "attachBaseContext: LoadSrcActivityErr: " + Log.getStackTraceString(e));
}
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "attachBaseContext: error: " + Log.getStackTraceString(e));
}
}
/**
* 从Dex中分割出资源
*
* @param dexData dex资源
*/
private void splitPayLoadFromDex(byte[] dexData) throws IOException {
// 获取dex数据长度
int len = dexData.length;
// 存储被加壳apk的长度
byte[] dexLen = new byte[4];
// 获取最后4个字节数据
System.arraycopy(dexData, len - 4, dexLen, 0, 4);
ByteArrayInputStream bais = new ByteArrayInputStream(dexLen);
DataInputStream in = new DataInputStream(bais);
// 获取被加密apk的长度
int readInt = in.readInt();
// 打印被加密apk的长度
Log.d(TAG, "splitPayLoadFromDex: Integer.toHexString(readInt): " + Integer.toHexString(readInt));
// 取出apk
byte[] enSrcApk = new byte[readInt];
// 将被加密apk内容复制到二进制数组中
System.arraycopy(dexData, len - 4 - readInt, enSrcApk, 0, readInt);
// 对源apk解密
byte[] srcApk = decrypt(enSrcApk);
// 写入源APK文件
File file = new File(mSrcApkFilePath);
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(srcApk);
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
// 分析源apk文件
ZipInputStream zis = new ZipInputStream(
new BufferedInputStream(
new FileInputStream(file)
)
);
// 遍历压缩包
while (true) {
ZipEntry entry = zis.getNextEntry();
// 判断是否有内容
if (null == entry) {
zis.close();
break;
}
// 依次取出被加壳的apk用到的so文件,放到libPath中(data/data/包名/paytload_lib)
String name = entry.getName();
if (name.startsWith("lib/") && name.endsWith(".so")) {
// 存储文件
File storeFile = new File(
mLibPath + "/" + name.substring(name.lastIndexOf('/'))
);
storeFile.createNewFile();
FileOutputStream fos = new FileOutputStream(storeFile);
byte[] bytes = new byte[1024];
while (true) {
int length = zis.read(bytes);
if (-1 == length) break;
fos.write(bytes);
}
fos.flush();
fos.close();
}
zis.closeEntry();
}
zis.close();
}
/**
* 解密二进制
*
* @param srcData 二进制数
* @return 解密后的二进制数据
*/
private byte[] decrypt(byte[] srcData) {
for (int i = 0; i < srcData.length; i++) {
srcData[i] ^= 0xFF;
}
return srcData;
}
/**
* 从ApK文件中获取DEX文件
*
* @return dex字节数组
*/
private byte[] readDexFileFromApk() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipInputStream zis = new ZipInputStream(
new BufferedInputStream(
new FileInputStream(this.getApplicationInfo().sourceDir)
)
);
// 遍历压缩包
while (true) {
ZipEntry entry = zis.getNextEntry();
if (null == entry) {
zis.close();
break;
}
// 获取dex文件
if ("classes.dex".equals(entry.getName())) {
byte[] bytes = new byte[1024];
while (true) {
int len = zis.read(bytes);
if (len == -1) break;
baos.write(bytes, 0, len);
}
}
zis.closeEntry();
}
zis.close();
return baos.toByteArray();
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate: ---------------");
// 获取配置在清单文件的源apk的Application路径
String appClassName = null;
try {
// 创建应用信息对象
ApplicationInfo ai = this.getPackageManager().getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA);
// 获取metaData数据
Bundle bundle = ai.metaData;
if (null != bundle && bundle.containsKey(APP_KEY)) {
appClassName = bundle.getString(APP_KEY);
} else {
Log.d(TAG, "onCreate: have no application class name");
return;
}
} catch (PackageManager.NameNotFoundException e) {
Log.d(TAG, "onCreate: error: " + Log.getStackTraceString(e));
e.printStackTrace();
}
// 获取当前Activity线程
Object currentActivityThread = RefInvoke.invokeStaticMethod(CLASS_NAME_ACTIVITY_THREAD,
"currentActivityThread", new Class[]{}, new Object[]{});
// 获取绑定的应用
Object mBoundApplication = RefInvoke.getFieldObject(CLASS_NAME_ACTIVITY_THREAD,
currentActivityThread, "mBoundApplication");
// 获取加载apk的信息
Object loadedApkInfo = RefInvoke.getFieldObject(
CLASS_NAME_ACTIVITY_THREAD + "$AppBindData",
mBoundApplication, "info"
);
// 将LoadedApk中的ApplicationInfo设置为null
RefInvoke.setFieldObject(CLASS_NAME_LOADED_APK, "mApplication", loadedApkInfo, null);
// 获取currentActivityThread中注册的Application
Object oldApplication = RefInvoke.getFieldObject(
CLASS_NAME_ACTIVITY_THREAD, currentActivityThread, "mInitialApplication"
);
// 获取ActivityThread中所有已注册的Application, 并将当前壳Apk的Application从中移除
ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke.getFieldObject(
CLASS_NAME_ACTIVITY_THREAD, currentActivityThread, "mAllApplications"
);
mAllApplications.remove(oldApplication);
// 从loadedApk中获取应用信息
ApplicationInfo appInfoInLoadedApk = (ApplicationInfo) RefInvoke.getFieldObject(
CLASS_NAME_LOADED_APK, loadedApkInfo, "mApplicationInfo"
);
// 从AppBindData中获取应用信息
ApplicationInfo appInfoInAppBindData = (ApplicationInfo) RefInvoke.getFieldObject(
CLASS_NAME_ACTIVITY_THREAD + "$AppBindData", mBoundApplication, "appInfo"
);
// 替换原来的Application
appInfoInLoadedApk.className = appClassName;
appInfoInAppBindData.className = appClassName;
// 注册Application
Application app = (Application) RefInvoke.invokeMethod(
CLASS_NAME_LOADED_APK, "makeApplication", loadedApkInfo,
new Class[]{boolean.class, Instrumentation.class},
new Object[]{false, null}
);
// 替换ActivityThread中的Application
RefInvoke.setFieldObject(CLASS_NAME_ACTIVITY_THREAD, "mInitialApplication",
currentActivityThread, app);
ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldObject(
CLASS_NAME_ACTIVITY_THREAD, currentActivityThread, "mProviderMap"
);
// 遍历
for (Object providerClientRecord : mProviderMap.values()) {
Object localProvider = RefInvoke.getFieldObject(
CLASS_NAME_ACTIVITY_THREAD + "$ProviderClientRecord",
providerClientRecord, "mLocalProvider"
);
RefInvoke.setFieldObject("android.content.ContentProvider", "mContext",
localProvider, app);
}
Log.d(TAG, "onCreate: SrcApp: " + app);
// 调用新的Application
app.onCreate();
}
3). RefInvoke类
/**
* 反射类
* Created by mazaiting on 2018/6/26.
*/
public class RefInvoke {
/**
* 反射执行类的静态函数(public)
*
* @param className 类名
* @param methodName 方法名
* @param pareTypes 函数的参数类型
* @param pareValues 调用函数时传入的参数
* @return
*/
public static Object invokeStaticMethod(String className, String methodName, Class[] pareTypes, Object[] pareValues) {
try {
Class objClass = Class.forName(className);
Method method = objClass.getMethod(methodName, pareTypes);
return method.invoke(null, pareValues);
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
/**
* 反射执行的函数(public)
*
* @param className 类名
* @param methodName 方法名
* @param obj 对象
* @param pareTypes 参数类型
* @param pareValues 调用方法传入的参数
* @return
*/
public static Object invokeMethod(String className, String methodName, Object obj, Class[] pareTypes, Object[] pareValues) {
try {
Class objClass = Class.forName(className);
Method method = objClass.getMethod(methodName, pareTypes);
return method.invoke(obj, pareValues);
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
/**
* 反射得到类的属性(包括私有和保护)
*
* @param className 类名
* @param obj 对象
* @param fieldName 属性名
* @return
*/
public static Object getFieldObject(String className, Object obj, String fieldName) {
try {
Class objClass = Class.forName(className);
Field field = objClass.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
return null;
}
/**
* 反射得到类的静态属性(包括私有和保护)
*
* @param className 类名
* @param fieldName 属性名
* @return
*/
public static Object getStaticFieldObject(String className, String fieldName) {
try {
Class objClass = Class.forName(className);
Field field = objClass.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(null);
} catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
return null;
}
/**
* 设置类的属性(包括私有和保护)
*
* @param className 类名
* @param fieldName 属性名
* @param obj 对象
* @param fieldValue 字段值
*/
public static void setFieldObject(String className, String fieldName, Object obj, Object fieldValue) {
try {
Class objClass = Class.forName(className);
Field field = objClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, fieldValue);
} catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
}
/**
* 设置类的静态属性(包括私有和保护)
*
* @param className 类名
* @param fieldName 属性名
* @param fieldValue 属性值
*/
public static void setStaticObject(String className, String fieldName, String fieldValue) {
try {
Class objClass = Class.forName(className);
Field field = objClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(null, fieldValue);
} catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
}
}
4). MainActivity
public class MainActivity extends AppCompatActivity {
private static final String TAG=MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG,"-------------onCreate");
}
}
5). AndroidManifest.xml文件
在这个文件中,需要配置meta-data结点和将源apk中四大组件进行配置,否则无法运行源apk中的内容
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mazaiting.reforceapk">
<application
android:name=".ProxyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<meta-data
android:name="APPLICATION_CLASS_NAME"
android:value="com.mazaiting.reinforcement.SourceApplication"/>
<activity android:name="com.mazaiting.reinforcement.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name="com.mazaiting.reinforcement.SecondActivity">
</activity>
</application>
</manifest>
6). 打包
与源apk相同,对此Module进行打包,打包完成后,更改文件后缀名apk为zip,使用压缩工具解压,将解压后文件夹中的classes.dex文件复制到项目根目录force文件夹下
4. 加密工程
1). 新建一个Java Module
2). 加密步骤
* 步骤:
* 1. 获取待加密的APK, 并对其二进制数据加密
* 2. 取出壳DEX, 并获取其二进制数据
* 3. 计算拼接后的DEX应用的大小, 并创建二进制数组
* 4. 依次将解壳DEX,加密后的源APK,加密后的源APK大小,拼接出新的DEX
* 5. 修改DEX的头,fileSize字段
* 6. 修改DEX的头,SHA1字段
* 7. 修改DEX的头,CheckNum字段
* 8. 输出新的DEX文件
3). DexShellTool
/**
* 加密APK
* 步骤:
* 1. 获取待加密的APK, 并对其二进制数据加密
* 2. 取出壳DEX, 并获取其二进制数据
* 3. 计算拼接后的DEX应用的大小, 并创建二进制数组
* 4. 依次将解壳DEX,加密后的源APK,加密后的源APK大小,拼接出新的DEX
* 5. 修改DEX的头,fileSize字段
* 6. 修改DEX的头,SHA1字段
* 7. 修改DEX的头,CheckNum字段
* 8. 输出新的DEX文件
*/
public class DexShellTool {
public static void main(String[] args) {
try {
// 需要加壳的源APK, 以二进制形式读取,并进行加密处理
File srcApkFile = new File("force/source.apk");
System.out.println("apk path: " + srcApkFile.getAbsolutePath());
System.out.println("apk size: " + srcApkFile.length());
// 加密并返回元apk数据
byte[] enSrcApkArray = encrypt(readFileBytes(srcApkFile));
// 需要解壳的dex, 以二进制形式读出dex
File unShellDexFile = new File("force/shell.dex");
byte[] unShellDexArray = readFileBytes(unShellDexFile);
// 将源APK长度和需要解壳的DEX长度相加并加上存放源APK大小的四位得到总长度
int enSrcApkLen = enSrcApkArray.length;
int unShellDexLen = unShellDexArray.length;
// 多出的四位存放加密后的dex长度
int totalLen = enSrcApkLen + unShellDexLen + 4;
// 依次将解壳DEX,加密后的源APK,加密后的源APK大小,拼接出新的DEX
byte[] newDex = new byte[totalLen];
// 复制加壳数据
System.arraycopy(unShellDexArray, 0, newDex, 0, unShellDexLen);
// 复制加密apk数据
System.arraycopy(enSrcApkArray, 0, newDex, unShellDexLen, enSrcApkLen);
// 赋值加壳后的dex大小
System.arraycopy(intToByte(enSrcApkLen), 0, newDex, totalLen - 4, 4);
// 修改DEX file size 文件头
fixFileSizeHeader(newDex);
// 修改DEX SHA1 文件头
fixSHA1Header(newDex);
// 修改DEX CheckNum文件头
fixCheckSumHeader(newDex);
// 写出新的DEX
String str = "force/classes.dex";
File file = new File(str);
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(str);
fos.write(newDex);
fos.flush();
fos.close();
} catch (IOException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
/**
* 修改DEX头,CheckSum校验码
*
* @param dexBytes 要修改的二进制数据
*/
private static void fixCheckSumHeader(byte[] dexBytes) {
Adler32 adler = new Adler32();
// 从12到文件末尾计算校验码
adler.update(dexBytes, 12, dexBytes.length - 12);
long value = adler.getValue();
int va = (int) value;
byte[] newCs = intToByte(va);
// 高低位互换位置
byte[] reCs = new byte[4];
for (int i = 0; i < 4; i++) {
reCs[i] = newCs[newCs.length - 1 - i];
System.out.println("fixCheckSumHeader:" + Integer.toHexString(newCs[i]));
}
// 校验码赋值(8-11)
System.arraycopy(reCs, 0, dexBytes, 8, 4);
System.out.println("fixCheckSumHeader:" + Long.toHexString(value));
}
/**
* 修改DEX头, sha1值
*
* @param dexBytes 要修改的二进制数组
*/
private static void fixSHA1Header(byte[] dexBytes) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
// 从32位到结束计算sha-1
md.update(dexBytes, 32, dexBytes.length - 32);
byte[] newDt = md.digest();
// 修改sha-1值(12-21)
System.arraycopy(newDt, 0, dexBytes, 12, 20);
// 输出sha-1值
StringBuilder hexStr = new StringBuilder();
for (byte aNewDt : newDt) {
hexStr.append(Integer.toString((aNewDt & 0xFF) + 0x100, 16).substring(1));
}
System.out.println("fixSHA1Header:" + hexStr.toString());
}
/**
* 修改DEX头, file_size值
*
* @param dexBytes 二进制数据
*/
private static void fixFileSizeHeader(byte[] dexBytes) {
// 新文件长度
byte[] newFs = intToByte(dexBytes.length);
System.out.println("fixFileSizeHeader: " + Integer.toHexString(dexBytes.length));
byte[] reFs = new byte[4];
// 高低位换位置
for (int i = 0; i < 4; i++) {
reFs[i] = newFs[newFs.length - 1 - i];
System.out.println("fixFileSizeHeader: " + Integer.toHexString(newFs[i]));
}
// 修改32-35
System.arraycopy(reFs, 0, dexBytes, 32, 4);
}
/**
* int 转 byte[]
*
* @param number 整型
* @return 返回字节数组
*/
private 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;
}
/**
* 加密二进制数据
*
* @param srcData 字节数组
* @return 加密后的二进制数组
*/
private static byte[] encrypt(byte[] srcData) {
for (int i = 0; i < srcData.length; i++) {
srcData[i] ^= 0xFF;
}
return srcData;
}
/**
* 以二进制读出文件内容
*
* @param file 文件
* @return 二进制数据
*/
private static byte[] readFileBytes(File file) throws IOException {
byte[] bytes = new byte[1024];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileInputStream fis = new FileInputStream(file);
while (true) {
int len = fis.read(bytes);
if (-1 == len) break;
baos.write(bytes, 0, len);
}
byte[] byteArray = baos.toByteArray();
fis.close();
baos.close();
return byteArray;
}
}
4). 运行main函数
在项目的根目录的force文件夹下,生成一个classes.dex文件
5. 合并
1). 项目结构
2). 收集文件
将force/classes.dex与reforceapk Module生成的apk放在桌面
3). 替换
将reforceapk-release.apk直接使用压缩工具打开,将classes.dex复制并替换reforceapk-release.apk中原有的classes.dex文件.
4). 重签名
jarsigner -verbose -keystore E:\android\key\release-key.keystore -storepass mazaiting -keypass mazaiting -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA -signedjar Reforce_des.apk reforceapk-release.apk key-alias
del reforceapk-release.apk
参数说明:
jarsigner -verbose -keystore 签名文件 -storepass 密码 -keypass alias的密码 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA 签名后的文件 签名前的apk alias名称
5). 安装运行
adb install C:\Users\mazaiting\Desktop\Reforce_des.apk
7. 参考文章及代码
8. 残留的问题
- 源APP中Activity中的界面组件是由代码构建,xml文件中如何加载?
- 宿主APP中ProxyApplication中使用到了源APP中的入口Application及MainActivity,如何动态获取?
- 宿主APP中AndroidManifest.xml文件中需要配置源APP的四大组件,如何不进行不配置?