Android-APK加固-简单版

article/2025/10/6 13:51:23

Android-APK加固-简单版

  • Proguard的使用与配置
    • 介绍
    • 开启proguard
    • 常用配置
  • 加固
    • 大体思路
  • 源码(浅析)
    • 思路
  • 撸码
    • 解密
      • 工具类-AES(解密时用)
      • 工具类-Zip(压缩、解压)
      • 工具类-Utils(反射操作)
    • 解密开始
    • 加密
    • 截图

Proguard的使用与配置

介绍

Proguard是一个代码优化和混淆工具。
能够提供对Java类文件的压缩、优化、混淆,和预校验。压缩的步骤是检测并移除未使用的类、字段、方法和属性。优化的步骤是分析和优化方法的字节码。混淆的步骤是使用短的毫无意义的名称重命名剩余的类、字段和方法。压缩、优化、混淆使得代码更小,更高效。

开启proguard

minifyEnabled true

buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}debug {minifyEnabled trueproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}

常用配置

	-keep 指定类和类成员(变量和方法)不被混淆。(保护了类名)-keep class com.lk.proxy.guard.test.Bug(保护了 public static void的没有参数的函数 不被混淆)-keep class com.lk.proxy.guard.test.Bug{public static void *();}(保护com.dongnao.proxy.guard.test.Bug类里面所有 不被混淆)-keep class com.lk.proxy.guard.test.Bug{*;}-keepclassmembers 指定类成员不被混淆(就是-keep的缩小版,不管类名了)。-keepclassmembersclass com.lk.proxy.guard.test.Bug(都被混淆了)
-keepclasseswithmembers 指定类和类成员不被混淆,前提是指定的类成员存在。-keepclasseswithmembers class 	com.lk.proxy.guard.test.Bug(保护类名,但是没指定成员,所以函数名被混淆)-keepclasseswithmembers class   	com.lk.proxy.guard.test.Bug{native <methods>;	//这个类里面的native方法不被混淆}

加固

大体思路

在这里插入图片描述
思路

1、编写一个Tools工具(java项目)负责把APK解压出来的所有dex文件抽出来进行加密(注意这不是在运行时做的);
2、加密后再和APK解压出来的其他文件一起打包得到一个全新的APK;
里面需要创建一个ProxyApplication(代理Application),让这个代理去和Android的系统对接,这个代理需要负责解密再交给Android系统去对接执行;确保程序能正常运行;

和Android系统对接的这个过程,就是插桩…
这个插桩听着是不是很熟悉,Tinker热修复里面的dex是进行插桩修复…

源码(浅析)

我们再来看看这个系统的源码 - -!我们只看关键部分,我这里是6.0的源码;其他版本的源码,需要自己去做适配;

打印出来的 ClassLoader 是:dalvik.system.PathClassLoader

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Log.i("Kai",getClassLoader().toString());}
}

dalvik.system.PathClassLoader

跟进来,发现PathClassLoader这个类里面基本没干什么事,就是继承了BaseDexClassLoader,和两个构造方法;所以我们得到BaseDexClassLoader这个类里面去看看;

package dalvik.system;public class PathClassLoader extends BaseDexClassLoader {public PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);}public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {super(dexPath, null, librarySearchPath, parent);}
}

dalvik.system.BaseDexClassLoader

成员属性 DexPathList 类:看这个名字,就应该知道这个是存放dex文件路径的集合类
findClass方法:从dexpathList类中的findClass方法获取到类的字节码;

package dalvik.system;......
/*** Base class for common functionality between various dex-based* {@link ClassLoader} implementations.*/
public class BaseDexClassLoader extends ClassLoader {//看这个名字,就应该知道这个是存放dex文件路径的集合类private final DexPathList pathList;public BaseDexClassLoader(String dexPath, File optimizedDirectory,String librarySearchPath, ClassLoader parent) {super(parent);//初始化这个 存放dex文件路径的集合类this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);}//查找类的方法@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {List<Throwable> suppressedExceptions = new ArrayList<Throwable>();//从dexpathList类中的findClass方法获取到类的字节码Class c = pathList.findClass(name, suppressedExceptions);if (c == null) {ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);for (Throwable t : suppressedExceptions) {cnfe.addSuppressed(t);}throw cnfe;}return c;}/*** @hide*/public void addDexPath(String dexPath) {pathList.addDexPath(dexPath, null /*optimizedDirectory*/);}............
}

dalvik.system.DexPathList

看到这个类里面的findClass方法,我们也就能明白:dex文件都是存放在dexElements这个数组里面;
dexElements的初始化时通过 makePathElements方法;
注意:6.0这个里面还是通过makePatrhElements方法初始化这个数组,但是到了7.0的时候就是用makeDexElements这个和方法初始化这个数组了,所以如果需要反射系统层面的东西,需要做好适配…

package dalvik.system;
......
/*package*/ final class DexPathList {
/*** Finds the named class in one of the dex files pointed at by* this instance. This will find the one in the earliest listed* path element. If the class is found but has not yet been* defined, then this method will define it in the defining* context that this instance was constructed with.** @param name of class to find* @param suppressed exceptions encountered whilst finding the class* @return the named class or {@code null} if the class is not* found in any of the dex files*/......//存放了 Dex文件的数组private Element[] dexElements;......public DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory) {......// dexElements 数组的初始化 makePathElements方法// save dexPath for BaseDexClassLoaderthis.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);......}....../*** Makes an array of dex/resource path elements, one per element of* the given array.*/private static Element[] makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions) {List<Element> elements = new ArrayList<>();/** Open all files and load the (direct or contained) dex files* up front.*/for (File file : files) {File zip = null;File dir = new File("");DexFile dex = null;String path = file.getPath();String name = file.getName();if (path.contains(zipSeparator)) {String split[] = path.split(zipSeparator, 2);zip = new File(split[0]);dir = new File(split[1]);} else if (file.isDirectory()) {// We support directories for looking up resources and native libraries.// Looking up resources in directories is useful for running libcore tests.elements.add(new Element(file, true, null, null));} else if (file.isFile()) {if (name.endsWith(DEX_SUFFIX)) {// Raw dex file (not inside a zip/jar).try {dex = loadDexFile(file, optimizedDirectory);} catch (IOException ex) {System.logE("Unable to load dex file: " + file, ex);}} else {zip = file;try {dex = loadDexFile(file, optimizedDirectory);} catch (IOException suppressed) {/** IOException might get thrown "legitimately" by the DexFile constructor if* the zip file turns out to be resource-only (that is, no classes.dex file* in it).* Let dex == null and hang on to the exception to add to the tea-leaves for* when findClass returns null.*/suppressedExceptions.add(suppressed);}}} else {System.logW("ClassLoader referenced unknown path: " + file);}if ((zip != null) || (dex != null)) {elements.add(new Element(dir, false, zip, dex));}}return elements.toArray(new Element[elements.size()]);}......public Class findClass(String name, List<Throwable> suppressed) {//dex文件都是存放在 dexElements这个数组里面for (Element element : dexElements) {//获取到dex文件  DexFile类DexFile dex = element.dexFile;if (dex != null) {//通过dex文件  DexFile类的loadClassBinaryName方法获取到Class字节码Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);if (clazz != null) {return clazz;}}}if (dexElementsSuppressedExceptions != null) {suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));}return null;}
}

思路

通过上面的源码,和前面写的Tinker热修复的插桩原理,我们可以了解到:

1、程序的ClassLoader类加载器是一个PathClassLoader;
2、这个ClassLoader继承了BaseDexClassLoader这个父类;
3、这个父类中的findClass方法中,通过pathList.findClass获取Class字节码,而pathList是DexPathList对象;
4、跟进DexPathList类的findClass方法中,发现遍历的是一个Element数组—dexElements,数组里面的元素通过.dexFile获取到DexFile对象,也就是可以理解成Dex文件;
5、这个dex文件其实就是程序的dex文件,也就是我们上面说的交给系统的dex文件;

思路

我们只需要反射到这个dexElements数组,把我们解密后dex文件与这个dexElements数组合并到一起,然后再赋值给系统;
而dexElements的初始化是通过makePathElements方法获取的;
可以通过反射makePathElements方法来获取到dexElements对象
也可以直接反射dexElements变量获取到dexElements对象(这种方式的获取,可以去看看我的Tinker热修复这一篇)
Android-Tinker热修复原理

撸码

我们从解密开始看~~

解密

我们上面说过解密是需要用代理ProxyApplication来做解密,并且把解密后的dex文件交给Android系统去加载;

我们需要新建一个Android Library;这个是用来代理、解密、和系统对接操作时用的;

工具类-AES(解密时用)

package com.example.proxy_core;import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;public class AES {//16字节public static final String DEFAULT_PWD = "abcdefghijklmnop";//填充方式private static final String algorithmStr = "AES/ECB/PKCS5Padding";private static Cipher encryptCipher;private static Cipher decryptCipher;public static void init(String password) {try {// 生成一个实现指定转换的 Cipher 对象。encryptCipher = Cipher.getInstance(algorithmStr);decryptCipher = Cipher.getInstance(algorithmStr);// algorithmStrbyte[] keyStr = password.getBytes();SecretKeySpec key = new SecretKeySpec(keyStr, "AES");encryptCipher.init(Cipher.ENCRYPT_MODE, key);decryptCipher.init(Cipher.DECRYPT_MODE, key);} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (NoSuchPaddingException e) {e.printStackTrace();} catch (InvalidKeyException e) {e.printStackTrace();}}public static byte[] encrypt(byte[] content) {try {byte[] result = encryptCipher.doFinal(content);return result;} catch (IllegalBlockSizeException e) {e.printStackTrace();} catch (BadPaddingException e) {e.printStackTrace();}return null;}public static byte[] decrypt(byte[] content) {try {byte[] result = decryptCipher.doFinal(content);return result;} catch (IllegalBlockSizeException e) {e.printStackTrace();} catch (BadPaddingException e) {e.printStackTrace();}return null;}
}

工具类-Zip(压缩、解压)

也可以自己去用第三方的来压缩和解压

package com.example.proxy_core;import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;public class Zip {private static void deleteFile(File file){if (file.isDirectory()){File[] files = file.listFiles();for (File f: files) {deleteFile(f);}}else{file.delete();}}/*** 解压zip文件至dir目录* @param zip* @param dir*/public static void unZip(File zip, File dir) {try {deleteFile(dir);ZipFile zipFile = new ZipFile(zip);//zip文件中每一个条目Enumeration<? extends ZipEntry> entries = zipFile.entries();//遍历while (entries.hasMoreElements()) {ZipEntry zipEntry = entries.nextElement();//zip中 文件/目录名String name = zipEntry.getName();//原来的签名文件 不需要了if (name.equals("META-INF/CERT.RSA") || name.equals("META-INF/CERT.SF") || name.equals("META-INF/MANIFEST.MF")) {continue;}//空目录不管if (!zipEntry.isDirectory()) {File file = new File(dir, name);//创建目录if (!file.getParentFile().exists()) {file.getParentFile().mkdirs();}//写文件FileOutputStream fos = new FileOutputStream(file);InputStream is = zipFile.getInputStream(zipEntry);byte[] buffer = new byte[2048];int len;while ((len = is.read(buffer)) != -1) {fos.write(buffer, 0, len);}is.close();fos.close();}}zipFile.close();} catch (Exception e) {e.printStackTrace();}}/*** 压缩目录为zip* @param dir 待压缩目录* @param zip 输出的zip文件* @throws Exception*/public static void zip(File dir, File zip) throws Exception {zip.delete();// 对输出文件做CRC32校验CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream(zip), new CRC32());ZipOutputStream zos = new ZipOutputStream(cos);//压缩compress(dir, zos, "");zos.flush();zos.close();}/*** 添加目录/文件 至zip中* @param srcFile 需要添加的目录/文件* @param zos   zip输出流* @param basePath  递归子目录时的完整目录 如 lib/x86* @throws Exception*/private static void compress(File srcFile, ZipOutputStream zos,String basePath) throws Exception {if (srcFile.isDirectory()) {File[] files = srcFile.listFiles();for (File file : files) {// zip 递归添加目录中的文件compress(file, zos, basePath + srcFile.getName() + "/");}} else {compressFile(srcFile, zos, basePath);}}private static void compressFile(File file, ZipOutputStream zos, String dir)throws Exception {// temp/lib/x86/libdn_ssl.soString fullName = dir + file.getName();// 需要去掉tempString[] fileNames = fullName.split("/");//正确的文件目录名 (去掉了temp)StringBuffer sb = new StringBuffer();if (fileNames.length > 1){for (int i = 1;i<fileNames.length;++i){sb.append("/");sb.append(fileNames[i]);}}else{sb.append("/");}//添加一个zip条目ZipEntry entry = new ZipEntry(sb.substring(1));zos.putNextEntry(entry);//读取条目输出到zip中FileInputStream fis = new FileInputStream(file);int len;byte data[] = new byte[2048];while ((len = fis.read(data, 0, 2048)) != -1) {zos.write(data, 0, len);}fis.close();zos.closeEntry();}}

工具类-Utils(反射操作)

package com.example.proxy_core;import java.io.File;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;public class Utils {/*** 读取文件* @param file* @return* @throws Exception*/public static byte[] getBytes(File file) throws Exception {RandomAccessFile r = new RandomAccessFile(file, "r");byte[] buffer = new byte[(int) r.length()];r.readFully(buffer);r.close();return buffer;}/*** 反射获得 指定对象(当前-》父类-》父类...)中的 成员属性* @param instance* @param name* @return* @throws NoSuchFieldException*/public static Field findField(Object instance, String name) throws NoSuchFieldException {Class clazz = instance.getClass();//反射获得while (clazz != null) {try {Field field = clazz.getDeclaredField(name);//如果无法访问 设置为可访问if (!field.isAccessible()) {field.setAccessible(true);}return field;} catch (NoSuchFieldException e) {//如果找不到往父类找clazz = clazz.getSuperclass();}}throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());}/*** 反射获得 指定对象(当前-》父类-》父类...)中的 函数* @param instance* @param name* @param parameterTypes* @return* @throws NoSuchMethodException*/public static Method findMethod(Object instance, String name, Class... parameterTypes)throws NoSuchMethodException {Class clazz = instance.getClass();while (clazz != null) {try {Method method = clazz.getDeclaredMethod(name, parameterTypes);if (!method.isAccessible()) {method.setAccessible(true);}return method;} catch (NoSuchMethodException e) {//如果找不到往父类找clazz = clazz.getSuperclass();}}throw new NoSuchMethodException("Method " + name + " with parameters " + Arrays.asList(parameterTypes) + " not found in " + instance.getClass());}
}

解密开始

上面的准备工作,工具类准备好了后,准备开始;

先用meta-data标签,把最终需要加载的MyApplication全类名、解密后的dex目录都定义出来;
是需要解密后把项目的MyApplication运行出来;
这两个meta-data标签的数据,需要等会儿反射拿到;

		<!--真实的Application的全名--><meta-data android:name="app_name" android:value="com.lk.reinforce_demo.MyApplication"/><!--用于dex后的目录名_版本号--><meta-data android:name="app_version" android:value="\dexDir_1.0"/>

attachBaseContext方法中,获取到主项目的meta-data标签里面的值(终需要加载的MyApplication全类名、dex解密后需要存放的目录)

 /*** ActivityThread创建Application之后调用的第一个方法* 可以在这个方法中进行解密,同时把dex交给android去加载*/@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);//获取定义的的metadatagetMetaData();}/*** 读取 meta-data标签的值*/private void getMetaData() {try{//获取 ApplicationInfo`applicationInfo = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);Bundle metaData=applicationInfo.metaData;if(null!=metaData){//获取到清单文件里面的 meta-data 标签里面的数据if(metaData.containsKey("app_name")){//获取到的是 最终需要加载的MyApplication全类名app_name=metaData.getString("app_name");}if(metaData.containsKey("app_version")){app_version=metaData.getString("app_version");}}}catch(Exception e){e.printStackTrace();}}

把apk解压后;
classes.dex 是存放我们这个代理 ProxyApplication的,所以我们加密的时候也不会把是这个classes.dex进行加密,所以这里不需要解密;
解密后再写入文件中,然后将文件存入到集合,后面方便我们将所有的dex文件给到系统去对接;

//得到当前加密了的APK文件File apkFile=new File(getApplicationInfo().sourceDir);//把apk解压   app_name+"_"+app_version目录中的内容 别的应用需要boot权限才能用File versionDir = getDir(app_name+"_"+app_version,MODE_PRIVATE);//创建两个目录//这个目录用来存放 apk解压后,除了dex以外的文件File appDir=new File(versionDir,"app");//这个目录用来存放dex文件File dexDir=new File(appDir,"dexDir");//得到我们需要加载的Dex文件List<File> dexFiles=new ArrayList<>();//进行解密(最好做MD5文件校验)if(!dexDir.exists() || dexDir.list().length==0){//把apk解压到appDirZip.unZip(apkFile,appDir);//获取目录下所有的文件File[] files=appDir.listFiles();for (File file : files) {String name=file.getName();//classes.dex 是存放我们这个代理 ProxyApplication的// 所以我们加密的时候也不会把是这个classes.dex进行加密,所以这里不需要解密if(name.endsWith(".dex") && !TextUtils.equals(name,"classes.dex")){try{AES.init(AES.DEFAULT_PWD);//读取文件内容byte[] bytes=Utils.getBytes(file);//解密byte[] decrypt=AES.decrypt(bytes);//写到指定的目录FileOutputStream fos=new FileOutputStream(file);fos.write(decrypt);fos.flush();fos.close();dexFiles.add(file);}catch (Exception e){e.printStackTrace();}}}}else{for (File file : dexDir.listFiles()) {dexFiles.add(file);}}

将我们解密后的dex文件集合,与系统对接;
也就是反射将dex文件存放到系统的dexElements这个数组中;

//将我们解密后的dex文件集合,与系统对接
private void loadDex(List<File> dexFiles, File versionDir) throws Exception{//1.获取pathlistField pathListField = Utils.findField(getClassLoader(), "pathList");Object pathList = pathListField.get(getClassLoader());//2.获取数组dexElementsField dexElementsField=Utils.findField(pathList,"dexElements");Object[] dexElements=(Object[])dexElementsField.get(pathList);//3.反射到初始化dexElements的方法Method makeDexElements=Utils.findMethod(pathList,"makePathElements",List.class,File.class,List.class);ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();Object[] addElements=(Object[])makeDexElements.invoke(pathList,dexFiles,versionDir,suppressedExceptions);//合并数组Object[] newElements= (Object[])Array.newInstance(dexElements.getClass().getComponentType(),dexElements.length+addElements.length);System.arraycopy(dexElements,0,newElements,0,dexElements.length);System.arraycopy(addElements,0,newElements,dexElements.length,addElements.length);//替换classloader中的element数组dexElementsField.set(pathList,newElements);}

一定要注意的点:这个主项目的application用到的是 代理的ProxyApplication,不然是没法运行到代理去进行解密dex、和系统对接的!!!!!!

<applicationandroid:name="com.example.proxy_core.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">

加密

这里用java项目来做加密操作,因为是编译操作的加密,所以这里用到一个java项目来做的;
这里有时候执行命令的时候process.exitValue()会不等于0,建议先把指令打印出来,再执行命令通不过的地方去用cmd执行~~
加密的步骤:
1、将Android Library生成的aar文件解压到自己定义的目录中;
2、解压之后会有个classes.jar,将这个jar用dx命令,生成dex文件,classes.dex
3、解压我们主的APK文件到临时目录,并将解压后的所有的.dex后缀的文件,读取字节,然后进行加密,再变成新的文件;
4、然后将第二步生成的classes.dex文件拷入到这个临时目录中,然后压缩成apk,这个时候的apk就是没有签名对齐的apk,里面除了classes.dex文件没有加密以外,其余的dex文件都是加密的;
5、然后对新生成的apk,进行签名和对齐;

package com.example.proxy_tools;import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;public class Main {public static void main(String[] args) throws Exception {/*** 1.制作只包含解密代码的dex文件*/File aarFile=new File("proxy_core/build/outputs/aar/proxy_core-debug.aar");File aarTemp=new File("proxy_tools/temp");Zip.unZip(aarFile,aarTemp);File classesJar=new File(aarTemp,"classes.jar");File classesDex=new File(aarTemp,"classes.dex");String dxStr = "cmd /c dx --dex --output "+classesDex.getAbsolutePath()+" "+classesJar.getAbsolutePath();   System.out.println("dxStr::"+dxStr);//dx --dex --output F:\AndroidTools\AndroidObj\dn\Lsn_11_Demo2\proxy_tools\temp\classes.dex F:\AndroidTools\AndroidObj\dn\Lsn_11_Demo2\proxy_tools\temp\classes.jar//dx --dex --output out.dex in.jarProcess process=Runtime.getRuntime().exec(dxStr);process.waitFor();if(process.exitValue()!=0){throw new RuntimeException("dex error");}/*** 2.加密APK中所有的dex文件*/File apkFile=new File("app/build/outputs/apk/debug/app-debug.apk");File apkTemp=new File("app/build/outputs/apk/debug/temp");Zip.unZip(apkFile,apkTemp);//只要dex文件拿出来加密File[] dexFiles=apkTemp.listFiles(new FilenameFilter() {@Overridepublic boolean accept(File file, String s) {return s.endsWith(".dex");}});//AES加密了AES.init(AES.DEFAULT_PWD);for (File dexFile : dexFiles) {byte[] bytes = Utils.getBytes(dexFile);byte[] encrypt = AES.encrypt(bytes);FileOutputStream fos=new FileOutputStream(new File(apkTemp,"secret-"+dexFile.getName()));fos.write(encrypt);fos.flush();fos.close();dexFile.delete();}/*** 3.把dex放入apk解压目录,重新压成apk文件*/classesDex.renameTo(new File(apkTemp,"classes.dex"));File unSignedApk=new File("app/build/outputs/apk/debug/app-unsigned.apk");Zip.zip(apkTemp,unSignedApk);/*** 4.对齐和签名*/
//        zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apkFile alignedApk=new File("app/build/outputs/apk/debug/app-unsigned-aligned.apk");// 可以用cmd执行命令:zipalign -v -p 4 F:\AndroidTools\AndroidObj\dn\Lsn_11_Demo2\app\build\outputs\apk\debug\app-unsigned.apk F:\AndroidTools\AndroidObj\dn\Lsn_11_Demo2\app\build\outputs\apk\debug\app-unsigned-aligned.apkprocess=Runtime.getRuntime().exec("cmd /c zipalign -v -p 4 "+unSignedApk.getAbsolutePath()+" "+alignedApk.getAbsolutePath());process.waitFor();if(process.exitValue()!=0){throw new RuntimeException("dex error");}//        apksigner sign --ks my-release-key.jks --out my-app-release.apk my-app-unsigned-aligned.apk
//        apksigner sign  --ks jks文件地址 --ks-key-alias 别名 --ks-pass pass:jsk密码 --key-pass pass:别名密码 --out  out.apk in.apkFile signedApk=new File("app/build/outputs/apk/debug/app-signed-aligned.apk");File jks=new File("proxy_tools/proxy2.jks");//可以用cmd执行命令:apksigner sign --ks F:\AndroidTools\AndroidObj\dn\Lsn_11_Demo2\proxy_tools\proxy2.jks --ks-key-alias jett --ks-pass pass:123456 --key-pass pass:123456 --out F:\AndroidTools\AndroidObj\dn\Lsn_11_Demo2\app\build\outputs\apk\debug\app-signed-aligned.apk F:\AndroidTools\AndroidObj\dn\Lsn_11_Demo2\app\build\outputs\apk\debug\app-unsigned-aligned.apkprocess=Runtime.getRuntime().exec("cmd /c apksigner sign --ks "+jks.getAbsolutePath()+" --ks-key-alias kaizi --ks-pass pass:123456 --key-pass pass:123456 --out "+signedApk.getAbsolutePath()+" "+alignedApk.getAbsolutePath());process.waitFor();if(process.exitValue()!=0){throw new RuntimeException("dex error");}System.out.println("执行成功");}
}

截图

1、解压aar后的存放目录解压aar后的存放目录
2、将这个jar用dx命令,生成dex文件,classes.dex
在这里插入图片描述
3、解压我们主的APK文件到临时目录,并将解压后的所有的.dex后缀的文件,读取字节,然后进行加密,再变成新的文件;这里的classes.dex是有第四步的:将第二步生成的classes.dex文件拷入到这个临时目录中了
在这里插入图片描述
后续的压缩apk,签名对齐的操作生成的apk,最终app-signed-aligned.apk这个apk是最后需要的apk
在这里插入图片描述
缺点:第一次启动的时候,会慢一点,想要后续不响应速度,我们就需要自己去做个md5的文件效验,看看文件是否已经解密过了~

后续的替换MyApplication的操作,童鞋们可以自己去研究下~~

辛苦各位童鞋观看到最后,如果博客中有不对的地方望指出,大神勿喷,谢谢~~


http://chatgpt.dhexx.cn/article/RCfT15rB.shtml

相关文章

Android Apk加固后手动签名

手动签名 &#xff1a; 不用任何第三方可视化工具签名 &#xff0c;使用命令做签名。手动签名原因&#xff1a;以前加固签名都是使用第三方工具操作&#xff0c;最近发现工具都开始收费了&#xff0c;免费的羊毛没得薅了&#xff0c;收费价格极高 5000/年/App, &#xff08;加固…

手写apk加固

手写apk加固 加壳解压原apk并加密重命名dex文件对壳文件操作打包压缩成apk文件签名 脱壳运行解压原apk, 解密原dex文件加载原dex文件 demo下载 apk加固的目的其实就是对app的核心代码做防护工作&#xff0c;避免被其他人反编译&#xff1b; 废话不多说了&#xff0c;直接开始! …

android apk 加固后重新签名

针对于加固平台在加固的过程中不能配置签名文件&#xff0c;加固后的apk需要进行重新签名才能安装&#xff0c;并发布到应用市场。 第一步&#xff0c;用AS对项目进行打包&#xff0c;生成签名的apk文件。 第二步&#xff0c;使用加固平台&#xff0c;对apk包进行加固&#xff…

Android Apk加固原理解析

前言 为什么要加固 对APP进行加固&#xff0c;可以有效防止移动应用被破解、盗版、二次打包、注入、反编译等&#xff0c;保障程序的安全性、稳定性。 常见的加固方案有很多&#xff0c;本文主要介绍如果通过对dex文件进行加密来达到apk加固的目的&#xff1b; APK加固整体…

apk加固后再签名

目录 前言v1签名v1v2签名 前言 apk更新之前需要做安全检测&#xff0c;检测之前一版会做加固处理&#xff0c;加固后还需要重新进行签名。本文介绍一下v1签名和v1v2签名两种方式。 有文章说需要把apk原来的签名文件&#xff0c;即META-INF文件夹删除&#xff0c;实测不删好像也…

Android apk 加固混淆的作用之解决apk报毒

现在市面上对apk的安全合规管控越来越严格了&#xff0c;也就要求了apk在上架之前一定要做合规检测和加固处理。对apk就是加固的好处&#xff0c;可以提高apk的安全性&#xff0c;提高apk被逆向分析破解的门槛&#xff0c;同时通过加固保护可以提高过安全合规的检测。由于APP加…

简书 android 加固,Android apk加固(加壳)整理

一、Dex加壳由来 最近在学习apk加密&#xff0c;在网上看了一篇《Android中的Apk的加固(加壳)原理解析和实现》&#xff0c;我发现原文把整个apk都写入到dex文件中&#xff0c;如果apk小还好&#xff0c;当原APK大于200M&#xff0c;客户端解壳很费劲&#xff0c;打开后应用就卡…

019 Android加固之APK加固的原理和实现

文章目录 前言加载Activity遇到的问题APK的启动过程替换ClassLoader流程获取ActivityThread类对象获取AppBindData类对象mBoundApplication获取LoadedApk类对象info获取info对象中的ClassLoader 设计傀儡dex文件手工加固APK代码实现APK加固实现步骤 总结 前言 动态加载dex之后…

【Android 安全】Android 应用 APK 加固总结 ( 加固原理 | 应用加固完整的实现方案 | 源码资源 )

文章目录 一、 APK 加固原理1、 Android 应用反编译2、 ProGuard 混淆3、 多 dex 加载原理4、 代理 Application 开发5、Java 工具开发6、Application 替换 二、 应用加固完整的实现方案1、 代理 Application( 1 ) ProxyApplication( 2 ) OpenSSL 解码 Kotlin 类( 3 ) 反射工具…

android资源加固,Android apk加固实现原理

apk加固是每一个app发布之前必须要做的事情;如果一个apk没有加固那么别人就很容易被别人反编译&#xff0c;看到这其中的原码&#xff0c;虽然现在有代码混淆、把业务写到native层&#xff0c;但是这都是治标不治本。反编译的技术在更新&#xff0c;那么保护Apk的技术就不能停止…

Android中Apk加固代码实现

前言&#xff1a;上一篇博客已经把Apk加固的思路详细的介绍过了&#xff0c;也开始创建了一个空的demo进行&#xff0c;然后在项目中添加一个代理module&#xff08;解密&#xff0c;和系统源码交互功能&#xff09;和tools工具加密Java library 的module &#xff0c;这里开始…

Android APK加固原理

一、前言 Android作为开源框架&#xff0c;开放之余&#xff0c;所要面临的就是安全问题&#xff0c;世间之事&#xff0c;有正就有邪&#xff0c;有攻就有守&#xff0c;作为开发者虽然不需要进入专业安全领域&#xff0c;但还是需要掌握基本的安全常识和原理。 二、加壳 加…

APK加固原理详解

一、前言 之前使用的360加固&#xff0c;挺好用的&#xff0c;从2021年底的时候限制每天每个账号仅上传2次apk&#xff08;免费的&#xff0c;不知道VIP的是不是这样&#xff09;。通过这个事情&#xff0c;感觉技术还是掌握在自己手里稳妥点&#xff0c;不用受制于人&#xf…

Android中的Apk的加固(加壳)原理解析和实现

本文转载自&#xff1a;Android中的Apk的加固(加壳)原理解析和实现 - roccheung - 博客园 一、前言 今天又到周末了&#xff0c;憋了好久又要出博客了&#xff0c;今天来介绍一下Android中的如何对Apk进行加固的原理。现阶段。我们知道Android中的反编译工作越来越让人操作熟…

浅谈安卓apk加固原理和实现

转载本文需注明出处&#xff1a;微信公众号EAWorld&#xff0c;违者必究。 引言&#xff1a; 在安卓开发中&#xff0c;打包发布是开发的最后一个环节&#xff0c;apk是整个项目的源码和资源的结合体&#xff1b;对于懂点反编译原理的人可以轻松编译出apk的源码资源&#xff0c…

安卓逆向笔记--apk加固

安卓逆向笔记–apk加固 资料来源: 浅谈安卓apk加固原理和实现 Android中的Apk的加固(加壳)原理解析和实现 前两个太老了所以具体代码借鉴下面的 Android Apk加壳技术实战详解 一、apk常见加固方法 (1)代码层级加密–代码混淆 代码混淆是一种常见的加密方式。本质是把工程中原…

imx6ull uboot移植

以下内容来自&#xff1a;正点原子Linux驱动文档 一、简介 uboot移植主要是根据原厂的uboot移植&#xff1a;芯片厂商通常会做一块自己的评估板并发布BSP&#xff0c;当我们需要定制自己的开发板时可以根据自己的需求&#xff08;硬件上的不同&#xff09;&#xff0c;对原厂…

全志V3S嵌入式驱动开发(uboot移植)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 安装了ubuntu操作系统,有了开发板,下面就可以进行我们的开发工作了。第一步,我们要面临的问题就是uboot移植。一般来说,嵌入式的基础环境就是uboot、linux和rootfs。uboot完成一…

2021-09-14 uboot移植开发

引言&#xff1a;最近要改动uboot&#xff0c;实现像微软PC上&#xff0c;u盘一键刷机或手机上安全模式下刷机的操作 专门去好好研究了点uboot的启动过程&#xff1b;以下为总结&#xff1a; 嵌入式系统 微软-PC ——…

iTOP4412 uboot移植教程

好多刚开始学习uboot移植的同学总是觉得uboot好难&#xff0c;但是再难的问题如果把它一步步拆开&#xff0c;一个个解决&#xff0c;问题也就将迎刃而解。做uboot移植&#xff0c;我们首先就得了解uboot的编译流程&#xff0c;这里以在iTOP4412精英版2G内存的板子上移植u-boot…