package com.wj.dexknife.shell.jiagu; import com.wj.dexknife.shell.ApkToolPlus; import com.wj.dexknife.shell.AppManager; import com.wj.dexknife.shell.Callback; import com.wj.dexknife.shell.apkparser.ApkParser; import com.wj.dexknife.shell.apkparser.bean.CertificateMeta; import com.wj.dexknife.shell.utils.ClassHelper; import com.wj.dexknife.shell.utils.DataProtector; import com.wj.dexknife.shell.utils.Debug; import com.wj.dexknife.shell.utils.FileHelper; import com.wj.dexknife.shell.utils.ZipHelper; import net.lingala.zip4j.core.ZipFile; import net.lingala.zip4j.exception.ZipException; import net.lingala.zip4j.model.FileHeader; import org.apache.commons.io.FileUtils; import org.dom4j.Attribute; import org.dom4j.Element; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; import org.dom4j.io.XMLWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.List; public class JiaGu { public static final String TAG = JiaGu.class.getSimpleName(); public static final String JIAGU_ZIP = "jiagu.zip"; private static final String JIAGU_DATA_BIN = "jiagu_data.bin"; public static String JIAGU_ZIP_PATH= JiaGu.class.getPackage().getName().replaceAll("\\.","/") + "/" + JIAGU_ZIP; private static String PROXY_APPLICATION_NAME = "com.qianfandu.ProxyApplication"; private static final String METADATA_SRC_APPLICATION = "apktoolplus_jiagu_app"; public static boolean ISSHELL=false; public static String SHELLAPKNAME; private static File workDir = new File(AppManager.getTempDir(), "jiagu"); private static File jiaguZip = new File(workDir, JIAGU_ZIP); public enum Event { DECOMPILEING, ENCRYPTING, RECOMPILING, SIGNING, DECOMPILE_FAIL, RECOMPILE_FAIL, ENCRYPT_FAIL, MENIFEST_FAIL, } public static boolean isEncrypted(File apk) { return ZipHelper.hasFile(apk, "assets/" + JIAGU_DATA_BIN); } public static File encrypt(File apk,KeystoreConfig config,Callback<Event> callback) { if (!FileHelper.exists(apk) || isEncrypted(apk)) { return null; } workDir.mkdir(); // 1.decompile apk handleCallback(callback, Event.DECOMPILEING); File decompile = new File(workDir, "decompile"); FileHelper.cleanDirectory(decompile); boolean decompileResult = ApkToolPlus.decompile(apk, decompile, new Callback<Exception>() { @Override public void callback(Exception e) { if (callback != null) { callback.callback(Event.DECOMPILE_FAIL); } } }); if (!decompileResult) { return null; } handleCallback(callback, Event.ENCRYPTING); if (!encryptDex(apk, decompile)) { handleCallback(callback, Event.ENCRYPT_FAIL); return null; } if (!jiagu(decompile)) { handleCallback(callback, Event.ENCRYPT_FAIL); return null; } signatureProtect(apk, decompile); if (!updateMenifest(new File(decompile, "AndroidManifest.xml"))) { handleCallback(callback, Event.MENIFEST_FAIL); return null; } // 3.recompile apk handleCallback(callback, Event.RECOMPILING); String shellNanme=FileHelper.getNoSuffixName(apk) +"_encrypted.apk"; if(ISSHELL==true && SHELLAPKNAME==null){ shellNanme=apk.getName(); } File encryptedApk = new File(apk.getParentFile(),shellNanme); boolean recompileResult =ApkToolPlus.recompile(decompile, encryptedApk, new Callback<Exception>() { @Override public void callback(Exception e) { handleCallback(callback, Event.RECOMPILE_FAIL); } }); if (!recompileResult) { return null; } // 4.sign apk if (config != null) { handleCallback(callback, Event.SIGNING); File signedApk = ApkToolPlus.signApk(encryptedApk, config); //FileHelper.cleanDirectory(decompile); System.out.println("加固apk完成地址:"+encryptedApk.getAbsolutePath()); return signedApk; } //FileHelper.cleanDirectory(decompile); return encryptedApk; } private static void signatureProtect(File apk, File decompile) { try(ApkParser parser = new ApkParser(apk)){ List<CertificateMeta> certList = parser.getCertificateMetaList(); String certMD5 = certList.get(0).getCertMd5(); byte[] encryptData = DataProtector.encryptXXTEA(certMD5.getBytes()); FileUtils.writeByteArrayToFile(new File(decompile,"assets/sign.bin"), encryptData); }catch (Exception e){ e.printStackTrace(); } } private static void handleCallback(Callback<Event> callback, Event event) { if (callback != null) { callback.callback(event); } } private static boolean updateMenifest(File menifest) { XMLWriter writer = null; try { SAXReader reader = new SAXReader(); org.dom4j.Document document = reader.read(menifest); Element rootElement = document.getRootElement(); Element applicationElement = rootElement.element("application"); Attribute appNameAttribute = applicationElement.attribute("name"); if (appNameAttribute != null) { String appName = appNameAttribute.getValue(); appNameAttribute.setValue(PROXY_APPLICATION_NAME); applicationElement.addElement("meta-data") .addAttribute("android:name", METADATA_SRC_APPLICATION) .addAttribute("android:value", appName); } else { applicationElement.addAttribute("android:name", PROXY_APPLICATION_NAME); } OutputFormat format = OutputFormat.createPrettyPrint(); // OutputFormat format = OutputFormat.createCompactFormat(); format.setEncoding("UTF-8"); writer = new XMLWriter(new FileOutputStream(menifest),format); writer.write(document); writer.close(); return true; } catch (Exception e) { e.printStackTrace(); } finally { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } return false; } private static boolean jiagu(File decompileDir) { if (!jiaguZip.exists()) { if(!JIAGU_ZIP_PATH.contains(":")){ System.out.println(JIAGU_ZIP_PATH+"============"+jiaguZip.getAbsolutePath()); if (!ClassHelper.releaseResourceToFile(JIAGU_ZIP_PATH, jiaguZip)) { return false; } }else{ if(!FileHelper.copy(new File(JIAGU_ZIP_PATH),jiaguZip)){ return false; } } } if(FileHelper.exists(jiaguZip)){ jiaguZip.deleteOnExit(); } File smali = new File(decompileDir, "smali"); FileHelper.delete(smali); File lib = new File(decompileDir, "lib"); String[] platforms = lib.list(); boolean isHasLib = lib.exists() && platforms != null && platforms.length > 0; ZipHelper.list(jiaguZip, new ZipHelper.FileFilter() { @Override public void handle(ZipFile zipFile, FileHeader fileHeader) { if (fileHeader.getFileName().startsWith("smali")) { if (!ZipHelper.unzip(zipFile, fileHeader, smali.getParentFile())) { Debug.e(fileHeader.getFileName() + " unzip failure from " + zipFile.getFile().getAbsolutePath()); } } else if (fileHeader.getFileName().startsWith("libs")) { if (!ZipHelper.unzip(zipFile, fileHeader, decompileDir)) { Debug.e(fileHeader.getFileName() + " unzip failure from " + zipFile.getFile().getAbsolutePath()); } } } }); File libs = new File(decompileDir, "libs"); if (isHasLib) { for (String platform : platforms) { File libFile = new File(libs, platform + "/libapktoolplus_jiagu.so"); File libSOFile =new File(lib, platform + "/" + libFile.getName()); if (libFile.exists() && !libSOFile.exists()) { FileHelper.move(libFile, new File(lib, platform + "/" + libFile.getName())); } } } else { FileHelper.move(libs, lib); } FileHelper.delete(libs); return true; } private static void deleteFile(File file) { if (file.exists()) {//判断文件是否存在 if (file.isFile()) {//判断是否是文件 file.delete();//删除文件 } else if (file.isDirectory()) {//否则如果它是一个目录 File[] files = file.listFiles();//声明目录下所有的文件 files[]; for (int i = 0;i < files.length;i ++) {//遍历目录下所有的文件 JiaGu.deleteFile(files[i]);//把每个文件用这个方法进行迭代 } file.delete();//删除文件夹 } } else { System.out.println("所删除的文件不存在"); } } private static boolean encryptDex(File apk, File decompileDir) { if(decompileDir.isDirectory()){ String[] filenames=decompileDir.list(); int index=0; for (int i=0;i<filenames.length;i++){ if(filenames[i].contains("smali")){ index+=1; String dexName="classes2.dex";//加固之后的dex String unzipDexName="classes.dex";//解压之后的dex if(index!=1){ dexName="classes"+index+".dex"; unzipDexName="classes"+index+".dex"; deleteFile(new File(decompileDir, filenames[i]));//删除问价夹 } File dexFile = new File(decompileDir, unzipDexName); if (dexFile.exists()) { dexFile.delete(); } try { ZipFile zipFile = new ZipFile(apk); ZipHelper.unzip(zipFile,unzipDexName, dexFile.getParentFile()); } catch (ZipException e) { e.printStackTrace(); return false; } File assets = new File(decompileDir, "assets"); assets.mkdirs(); assets = new File(decompileDir, "assets"); File encryptFile = new File(assets, JIAGU_DATA_BIN); if(encryptFile.exists()){ encryptFile.delete(); } encryptFile.mkdirs(); DataProtector.encrypt(dexFile, new File(assets, JIAGU_DATA_BIN+"/"+"classes"+(index+1)+".dex")); dexFile.delete(); } } } return true; } public static File encryptApk(File apk, KeystoreConfig keystoreConfig){ return JiaGu.encrypt(apk, keystoreConfig, new Callback<Event>() { @Override public void callback(Event event) { switch (event){ case DECOMPILEING: break; case ENCRYPTING: break; case RECOMPILING: break; case SIGNING: break; case DECOMPILE_FAIL: break; case RECOMPILE_FAIL: break; case ENCRYPT_FAIL: break; case MENIFEST_FAIL: break; } } }); } }