From 41d861b0115dc170b8d2c15b86c3db49dbfc62f2 Mon Sep 17 00:00:00 2001 From: REAndroid Date: Sat, 11 May 2024 20:40:15 +0200 Subject: [PATCH] [Experimental] disassemble/assemble smali with ARSCLib --- build.gradle | 5 ++ .../com/reandroid/apkeditor/APKEditor.java | 3 + .../apkeditor/smali/SmaliCompiler.java | 69 +++++++++++++++++- .../apkeditor/smali/SmaliDecompiler.java | 72 ++++++++++++++++--- 4 files changed, 140 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 90268b96..0c93a374 100755 --- a/build.gradle +++ b/build.gradle @@ -53,3 +53,8 @@ task fatJar(type: Jar) { with jar } +task betaJar() { + version = "${version}-beta" + finalizedBy(fatJar) +} + diff --git a/src/main/java/com/reandroid/apkeditor/APKEditor.java b/src/main/java/com/reandroid/apkeditor/APKEditor.java index 84c225a5..43b6ef56 100644 --- a/src/main/java/com/reandroid/apkeditor/APKEditor.java +++ b/src/main/java/com/reandroid/apkeditor/APKEditor.java @@ -60,6 +60,9 @@ public static String getJarName(){ } return getName()+".jar"; } + public static boolean isExperimental() { + return APKEditor.getVersion().contains("beta"); + } public static final String PATH_properties = "/apkeditor.properties"; diff --git a/src/main/java/com/reandroid/apkeditor/smali/SmaliCompiler.java b/src/main/java/com/reandroid/apkeditor/smali/SmaliCompiler.java index 32b040cb..d1da08b2 100644 --- a/src/main/java/com/reandroid/apkeditor/smali/SmaliCompiler.java +++ b/src/main/java/com/reandroid/apkeditor/smali/SmaliCompiler.java @@ -18,10 +18,17 @@ import com.reandroid.apk.APKLogger; import com.reandroid.apk.ApkModuleEncoder; import com.reandroid.apk.DexEncoder; +import com.reandroid.apkeditor.APKEditor; import com.reandroid.archive.FileInputSource; import com.reandroid.archive.InputSource; import com.reandroid.arsc.chunk.xml.AndroidManifestBlock; +import com.reandroid.dex.model.DexFile; +import com.reandroid.dex.sections.Marker; +import com.reandroid.dex.sections.SectionType; +import com.reandroid.dex.smali.SmaliReader; import com.reandroid.utils.StringsUtil; +import com.reandroid.utils.io.FileIterator; +import com.reandroid.utils.io.IOUtil; import org.jf.dexlib2.extra.DexMarker; import org.jf.smali.Smali; import org.jf.smali.SmaliOptions; @@ -73,6 +80,12 @@ private InputSource build(String progress, File classesDir) throws IOException { } } private InputSource build(String progress, File classesDir, File dexCacheFile) throws IOException { + if(APKEditor.isExperimental()) { + return buildExperimental(progress, classesDir, dexCacheFile); + } + return buildJesusFreke(progress, classesDir, dexCacheFile); + } + private InputSource buildJesusFreke(String progress, File classesDir, File dexCacheFile) throws IOException { logMessage(progress + "Smali: " + dexCacheFile.getName()); SmaliOptions smaliOptions = new SmaliOptions(); File dir = dexCacheFile.getParentFile(); @@ -88,7 +101,7 @@ private InputSource build(String progress, File classesDir, File dexCacheFile) t smaliOptions.jobs = 1; } if (this.minSdkVersion != null) { - smaliOptions.apiLevel = this.minSdkVersion.intValue(); + smaliOptions.apiLevel = this.minSdkVersion; } boolean success = Smali.assemble(smaliOptions, classesDir.getAbsolutePath()); if(!success){ @@ -96,6 +109,48 @@ private InputSource build(String progress, File classesDir, File dexCacheFile) t } return new FileInputSource(dexCacheFile, dexCacheFile.getName()); } + private InputSource buildExperimental(String progress, File classesDir, File dexCacheFile) throws IOException { + logMessage(progress + "Smali: " + dexCacheFile.getName()); + DexFile dexFile = DexFile.createDefault(); + FileIterator fileIterator = new FileIterator(classesDir, + FileIterator.getExtensionFilter(".smali")); + while (fileIterator.hasNext()) { + File file = fileIterator.next(); + try { + dexFile.fromSmali(SmaliReader.of(file)); + }catch (Exception e) { + throw new IOException("Error at: " + file, e); + } + } + dexFile.refresh(); + readMarkers(dexFile, classesDir); + int version = 0; + if (this.minSdkVersion != null) { + version = minSdkVersion; + } + version = apiToDexVersion(version); + dexFile.setVersion(version); + dexFile.clearEmptySections(); + dexFile.sortSection(SectionType.getR8Order()); + dexFile.refreshFull(); + dexFile.write(dexCacheFile); + dexFile.close(); + return new FileInputSource(dexCacheFile, dexCacheFile.getName()); + } + private void readMarkers(DexFile dexFile, File classesDir) throws IOException { + File markersFile = new File(classesDir, DexMarker.FILE_NAME); + if(markersFile.isFile()){ + logMessage("Reading markers ..."); + String[] content = StringsUtil.split(IOUtil.readUtf8(markersFile), '\n'); + for(String markerString : content) { + Marker marker = Marker.parse(markerString); + if(marker != null) { + dexFile.addMarker(marker); + } + } + } + } + private boolean isModified(File classesDir, File dexCacheFile){ if(noCache || !dexCacheFile.isFile()){ return true; @@ -155,4 +210,16 @@ private void logMessage(String msg){ } } + public static int apiToDexVersion(int api) { + if (api <= 23) { + return 35; + } + if (api <= 25) { + return 37; + } + if (api <= 27) { + return 38; + } + return 39; + } } diff --git a/src/main/java/com/reandroid/apkeditor/smali/SmaliDecompiler.java b/src/main/java/com/reandroid/apkeditor/smali/SmaliDecompiler.java index bccec0ec..1a133c1c 100644 --- a/src/main/java/com/reandroid/apkeditor/smali/SmaliDecompiler.java +++ b/src/main/java/com/reandroid/apkeditor/smali/SmaliDecompiler.java @@ -16,10 +16,16 @@ package com.reandroid.apkeditor.smali; import com.reandroid.apk.APKLogger; +import com.reandroid.apk.ApkModule; import com.reandroid.apk.DexDecoder; import com.reandroid.apk.DexFileInputSource; +import com.reandroid.apkeditor.APKEditor; import com.reandroid.apkeditor.decompile.DecompileOptions; import com.reandroid.arsc.chunk.TableBlock; +import com.reandroid.dex.model.DexDirectory; +import com.reandroid.dex.model.DexFile; +import com.reandroid.dex.smali.SmaliWriter; +import com.reandroid.dex.smali.SmaliWriterSetting; import org.jf.baksmali.Baksmali; import org.jf.baksmali.BaksmaliOptions; import org.jf.dexlib2.Opcodes; @@ -28,6 +34,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.util.List; public class SmaliDecompiler implements DexDecoder { private final TableBlock tableBlock; @@ -43,15 +50,44 @@ public SmaliDecompiler(TableBlock tableBlock){ this(tableBlock, new DecompileOptions()); } @Override - public boolean decodeDex(DexFileInputSource inputSource, File mainDir) throws IOException { + public void decodeDex(DexFileInputSource inputSource, File mainDir) throws IOException { logMessage("Baksmali: " + inputSource.getAlias()); - disassembleDexFile(inputSource, mainDir); - File cache = new File(mainDir, SmaliUtil.CACHE_DIR); - cache = new File(cache, inputSource.getAlias()); - inputSource.write(cache); - return true; + if(APKEditor.isExperimental()) { + disassembleDexFileExperimental(inputSource, mainDir); + }else { + disassembleJesusFreke(inputSource, mainDir); + } + writeDexCache(inputSource, mainDir); + } + @Override + public void decodeDex(ApkModule apkModule, File mainDirectory) throws IOException { + if(!canLoadFullDex(apkModule) || !APKEditor.isExperimental()) { + DexDecoder.super.decodeDex(apkModule, mainDirectory); + return; + } + logMessage("Loading full dex ..."); + DexDirectory directory = DexDirectory.fromZip(apkModule.getZipEntryMap()); + File smali = toSmaliRoot(mainDirectory); + SmaliWriterSetting setting = new SmaliWriterSetting(); + setting.setResourceIdComment(tableBlock.pickOne()); + setting.addClassComments(directory); + setting.addMethodComments(directory); + SmaliWriter smaliWriter = new SmaliWriter(); + smaliWriter.setWriterSetting(setting); + logMessage("Baksmali ..."); + directory.writeSmali(smaliWriter, smali); + directory.close(); + + List dexList = apkModule.listDexFiles(); + for(DexFileInputSource inputSource : dexList) { + writeDexCache(inputSource, mainDirectory); + } } - private void disassembleDexFile(DexFileInputSource inputSource, File mainDir) throws IOException { + private boolean canLoadFullDex(ApkModule apkModule) { + return apkModule.listDexFiles().size() < 5; + } + + private void disassembleJesusFreke(DexFileInputSource inputSource, File mainDir) throws IOException { File dir = toOutDir(inputSource, mainDir); BaksmaliOptions options = new BaksmaliOptions(); options.localsDirective = true; @@ -63,19 +99,39 @@ private void disassembleDexFile(DexFileInputSource inputSource, File mainDir) th DexBackedDexFile dexFile = getInputDexFile(inputSource, options); Baksmali.disassembleDexFile(dexFile, dir, 1, options); } + private void disassembleDexFileExperimental(DexFileInputSource inputSource, File mainDir) throws IOException { + DexFile dexFile = DexFile.read(inputSource.openStream()); + dexFile.setSimpleName(inputSource.getAlias()); + SmaliWriterSetting setting = new SmaliWriterSetting(); + setting.setResourceIdComment(tableBlock.pickOne()); + setting.addClassComments(dexFile); + setting.addMethodComments(dexFile); + SmaliWriter smaliWriter = new SmaliWriter(); + smaliWriter.setWriterSetting(setting); + dexFile.writeSmali(smaliWriter, toSmaliRoot(mainDir)); + dexFile.close(); + } + private void writeDexCache(DexFileInputSource inputSource, File mainDir) throws IOException { + File cache = new File(mainDir, SmaliUtil.CACHE_DIR); + cache = new File(cache, inputSource.getAlias()); + inputSource.write(cache); + } private File toOutDir(DexFileInputSource inputSource, File mainDir){ String name = "classes"; int num = inputSource.getDexNumber(); if(num != 0){ name = name + num; } - File dir = new File(mainDir, "smali"); + File dir = toSmaliRoot(mainDir); dir = new File(dir, name); if(!dir.exists()){ dir.mkdirs(); } return dir; } + private File toSmaliRoot(File mainDir){ + return new File(mainDir, DexDecoder.SMALI_DIRECTORY_NAME); + } private DexBackedDexFile getInputDexFile(DexFileInputSource inputSource, BaksmaliOptions options) throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream();