From 60b141ac1a92450a6422a888ffac74e3eb484d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Tue, 2 Apr 2019 19:58:23 +0200 Subject: [PATCH 1/4] 8: Create ZipTool --- build.gradle | 1 + dependency-versions.gradle | 1 + .../com/bartek/esa/file/di/FileModule.java | 6 ++ .../java/com/bartek/esa/file/zip/ZipTool.java | 59 +++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 src/main/java/com/bartek/esa/file/zip/ZipTool.java diff --git a/build.gradle b/build.gradle index 58a64e2..b04e14e 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,7 @@ dependencies { compile "commons-cli:commons-cli:${commonCliVersion}" compile "io.vavr:vavr:${vavrVersion}" compile "com.github.javaparser:javaparser-core:${javaParserVersion}" + compile "commons-io:commons-io:${commonsIoVersion}" } jar { diff --git a/dependency-versions.gradle b/dependency-versions.gradle index 879c4c0..7faffed 100644 --- a/dependency-versions.gradle +++ b/dependency-versions.gradle @@ -4,4 +4,5 @@ ext { commonCliVersion = '1.4' vavrVersion = '1.0.0-alpha-2' javaParserVersion = '3.13.4' + commonsIoVersion = '2.6' } \ No newline at end of file diff --git a/src/main/java/com/bartek/esa/file/di/FileModule.java b/src/main/java/com/bartek/esa/file/di/FileModule.java index 815b3d4..d4e51bb 100644 --- a/src/main/java/com/bartek/esa/file/di/FileModule.java +++ b/src/main/java/com/bartek/esa/file/di/FileModule.java @@ -3,6 +3,7 @@ package com.bartek.esa.file.di; import com.bartek.esa.file.matcher.GlobMatcher; import com.bartek.esa.file.provider.FileContentProvider; import com.bartek.esa.file.provider.FileProvider; +import com.bartek.esa.file.zip.ZipTool; import dagger.Module; import dagger.Provides; @@ -23,4 +24,9 @@ public class FileModule { public GlobMatcher globMatcher() { return new GlobMatcher(); } + + @Provides + public ZipTool zipTool() { + return new ZipTool(); + } } diff --git a/src/main/java/com/bartek/esa/file/zip/ZipTool.java b/src/main/java/com/bartek/esa/file/zip/ZipTool.java new file mode 100644 index 0000000..2999958 --- /dev/null +++ b/src/main/java/com/bartek/esa/file/zip/ZipTool.java @@ -0,0 +1,59 @@ +package com.bartek.esa.file.zip; + +import com.bartek.esa.error.EsaException; +import io.vavr.control.Try; +import org.apache.commons.io.IOUtils; + +import javax.inject.Inject; +import java.io.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class ZipTool { + + @Inject + public ZipTool() { + + } + + public void unzipArchive(File archive, File outputDir) { + Try.run(() -> tryToUnzipArchive(archive, outputDir)) + .getOrElseThrow(EsaException::new); + } + + private void tryToUnzipArchive(File archive, File outputDir) throws IOException { + ZipFile zipFile = new ZipFile(archive); + + zipFile.stream().forEach(entry -> unzipEntry(zipFile, entry, outputDir)); + } + + private void unzipEntry(ZipFile zipfile, ZipEntry entry, File outputDir) { + if (entry.isDirectory()) { + createDir(new File(outputDir, entry.getName())); + return; + } + + File outputFile = new File(outputDir, entry.getName()); + if (!outputFile.getParentFile().exists()){ + createDir(outputFile.getParentFile()); + } + + BufferedInputStream inputStream = Try.of(() -> new BufferedInputStream(zipfile.getInputStream(entry))) + .getOrElseThrow(EsaException::new); + BufferedOutputStream outputStream = Try.of(() -> new BufferedOutputStream(new FileOutputStream(outputFile))) + .getOrElseThrow(EsaException::new); + + try { + IOUtils.copy(inputStream, outputStream); + } catch (IOException e) { + throw new EsaException(e); + } finally { + Try.run(outputStream::close).getOrElseThrow(EsaException::new); + Try.run(inputStream::close).getOrElseThrow(EsaException::new); + } + } + + private void createDir(File dir) { + if(!dir.mkdirs()) throw new EsaException("Cannot create directory " + dir); + } +} From ced7b1017def05a9e4c73e1945cb9950c7d0d1a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Tue, 2 Apr 2019 20:04:09 +0200 Subject: [PATCH 2/4] 8: Create ProcessExecutor --- .../decompiler/process/ProcessExecutor.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/java/com/bartek/esa/decompiler/process/ProcessExecutor.java diff --git a/src/main/java/com/bartek/esa/decompiler/process/ProcessExecutor.java b/src/main/java/com/bartek/esa/decompiler/process/ProcessExecutor.java new file mode 100644 index 0000000..1b59dbd --- /dev/null +++ b/src/main/java/com/bartek/esa/decompiler/process/ProcessExecutor.java @@ -0,0 +1,31 @@ +package com.bartek.esa.decompiler.process; + +import com.bartek.esa.error.EsaException; +import io.vavr.control.Try; + +import javax.inject.Inject; + +public class ProcessExecutor { + + @Inject + public ProcessExecutor() { + + } + + public void execute(String[] command) { + Process process = Try.of(() -> Runtime.getRuntime().exec(command)) + .getOrElseThrow(EsaException::new); + waitForProcess(process); + checkExitValue(process, command[0]); + } + + private void waitForProcess(Process process) { + Try.run(process::waitFor).getOrElseThrow(EsaException::new); + } + + private void checkExitValue(Process process, String commandName) { + if(process.exitValue() != 0) { + throw new EsaException("'" + commandName + "' process has finished with non-zero code. Interrupting..."); + } + } +} From f432327f3bf7c7d36f7fd8036668c68381741b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Tue, 2 Apr 2019 20:30:39 +0200 Subject: [PATCH 3/4] 8: Create FileCleaner --- .../bartek/esa/file/cleaner/FileCleaner.java | 26 +++++++++++++++++++ .../com/bartek/esa/file/di/FileModule.java | 6 +++++ 2 files changed, 32 insertions(+) create mode 100644 src/main/java/com/bartek/esa/file/cleaner/FileCleaner.java diff --git a/src/main/java/com/bartek/esa/file/cleaner/FileCleaner.java b/src/main/java/com/bartek/esa/file/cleaner/FileCleaner.java new file mode 100644 index 0000000..3695ef2 --- /dev/null +++ b/src/main/java/com/bartek/esa/file/cleaner/FileCleaner.java @@ -0,0 +1,26 @@ +package com.bartek.esa.file.cleaner; + +import com.bartek.esa.error.EsaException; +import io.vavr.control.Try; + +import javax.inject.Inject; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; + +public class FileCleaner { + + @Inject + public FileCleaner() { + + } + + public void deleteRecursively(File directory) { + Try.run(() -> Files.walk(directory.toPath()) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete)) + .getOrElseThrow(EsaException::new); + } +} diff --git a/src/main/java/com/bartek/esa/file/di/FileModule.java b/src/main/java/com/bartek/esa/file/di/FileModule.java index d4e51bb..8d927cc 100644 --- a/src/main/java/com/bartek/esa/file/di/FileModule.java +++ b/src/main/java/com/bartek/esa/file/di/FileModule.java @@ -1,5 +1,6 @@ package com.bartek.esa.file.di; +import com.bartek.esa.file.cleaner.FileCleaner; import com.bartek.esa.file.matcher.GlobMatcher; import com.bartek.esa.file.provider.FileContentProvider; import com.bartek.esa.file.provider.FileProvider; @@ -29,4 +30,9 @@ public class FileModule { public ZipTool zipTool() { return new ZipTool(); } + + @Provides + public FileCleaner fileCleaner() { + return new FileCleaner(); + } } From 478e4b8328b12106250c151237bc32534bdc82ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Tue, 2 Apr 2019 20:30:53 +0200 Subject: [PATCH 4/4] 8: Fix Decompiler --- .../esa/decompiler/decompiler/Decompiler.java | 94 ++++++++++++------- .../esa/decompiler/di/DecompilerModule.java | 12 ++- 2 files changed, 72 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/bartek/esa/decompiler/decompiler/Decompiler.java b/src/main/java/com/bartek/esa/decompiler/decompiler/Decompiler.java index 7654e5e..de8d41b 100644 --- a/src/main/java/com/bartek/esa/decompiler/decompiler/Decompiler.java +++ b/src/main/java/com/bartek/esa/decompiler/decompiler/Decompiler.java @@ -1,50 +1,80 @@ package com.bartek.esa.decompiler.decompiler; -import com.bartek.esa.error.EsaException; +import com.bartek.esa.decompiler.process.ProcessExecutor; +import com.bartek.esa.file.cleaner.FileCleaner; import com.bartek.esa.file.provider.FileProvider; -import io.vavr.control.Try; +import com.bartek.esa.file.zip.ZipTool; +import org.apache.commons.io.FilenameUtils; import javax.inject.Inject; import java.io.File; +import java.util.Set; public class Decompiler { + public static final String XML_FILES_DIR = "xml"; + public static final String JAVA_FILES_DIR = "java"; + + private static final String APK_UNZIPPED_DIR = "apk_unzipped"; + private static final String JAR_FILES_DIR = "jar"; + private final FileProvider fileProvider; + private final ProcessExecutor processExecutor; + private final ZipTool zipTool; + private final FileCleaner fileCleaner; @Inject - public Decompiler(FileProvider fileProvider) { - + public Decompiler(FileProvider fileProvider, ProcessExecutor processExecutor1, ZipTool zipTool, FileCleaner fileCleaner) { this.fileProvider = fileProvider; + this.processExecutor = processExecutor1; + this.zipTool = zipTool; + this.fileCleaner = fileCleaner; } public File decompile(File inputApk) { - File target = provideTargetDirectory(); - Process process = decompile(inputApk, target); - waitForProcess(process); - checkExitValue(process); - return target; - } - - private void checkExitValue(Process process) { - if(process.exitValue() != 0) { - throw new EsaException("'apktool' process has finished with non-zero code. Interrupting..."); - } - } - - private void waitForProcess(Process process) { - Try.run(process::waitFor).getOrElseThrow(EsaException::new); - } - - private Process decompile(File inputApk, File target) { - return Try.of(() -> Runtime.getRuntime().exec(provideCommandArray(inputApk, target))) - .getOrElseThrow(EsaException::new); - } - - private String[] provideCommandArray(File inputApk, File target) { - return new String[]{"apktool", "d", inputApk.getAbsolutePath(), "-o", target.getAbsolutePath()}; - } - - private File provideTargetDirectory() { File tmp = fileProvider.createTemporaryDirectory(); - return new File(tmp, "apktool_out"); + File javaDirectory = new File(tmp, JAVA_FILES_DIR); + File xmlDirectory = new File(tmp, XML_FILES_DIR); + + decompileJavaFiles(inputApk, tmp, javaDirectory); + decompileXmlFiles(inputApk, xmlDirectory); + + return tmp; + } + + private void decompileJavaFiles(File inputApk, File tmp, File javaDirectory) { + File unzippedApkDirectory = new File(tmp, APK_UNZIPPED_DIR); + File jarDirectory = new File(tmp, JAR_FILES_DIR); + + zipTool.unzipArchive(inputApk, unzippedApkDirectory); + + Set dexFiles = fileProvider.getGlobMatchedFiles(unzippedApkDirectory.getAbsolutePath(), "**/*.dex"); + convertDexToJar(dexFiles, jarDirectory); + + Set jarFiles = fileProvider.getGlobMatchedFiles(jarDirectory.getAbsolutePath(), "**/*.jar"); + decompileJar(jarFiles, javaDirectory); + + fileCleaner.deleteRecursively(unzippedApkDirectory); + fileCleaner.deleteRecursively(jarDirectory); + } + + private void decompileXmlFiles(File inputApk, File target) { + String[] command = {"apktool", "d", inputApk.getAbsolutePath(), "-o", target.getAbsolutePath()}; + processExecutor.execute(command); + } + + private void convertDexToJar(Set dexFiles, File target) { + dexFiles.forEach(dex -> { + String jarFilename = FilenameUtils.removeExtension(dex.getName()) + ".jar"; + File jarFile = new File(target, jarFilename); + String[] command = {"dex2jar", dex.getAbsolutePath(), "-o", jarFile.getAbsolutePath()}; + processExecutor.execute(command); + }); + } + + private void decompileJar(Set jarFiles, File target) { + jarFiles.forEach(jar -> { + String[] command = {"cfr", jar.getAbsolutePath(), "--outputdir", target.getAbsolutePath()}; + processExecutor.execute(command); + }); } } diff --git a/src/main/java/com/bartek/esa/decompiler/di/DecompilerModule.java b/src/main/java/com/bartek/esa/decompiler/di/DecompilerModule.java index eac8e30..77534af 100644 --- a/src/main/java/com/bartek/esa/decompiler/di/DecompilerModule.java +++ b/src/main/java/com/bartek/esa/decompiler/di/DecompilerModule.java @@ -1,7 +1,10 @@ package com.bartek.esa.decompiler.di; import com.bartek.esa.decompiler.decompiler.Decompiler; +import com.bartek.esa.decompiler.process.ProcessExecutor; +import com.bartek.esa.file.cleaner.FileCleaner; import com.bartek.esa.file.provider.FileProvider; +import com.bartek.esa.file.zip.ZipTool; import dagger.Module; import dagger.Provides; @@ -9,7 +12,12 @@ import dagger.Provides; public class DecompilerModule { @Provides - public Decompiler decompiler(FileProvider fileProvider) { - return new Decompiler(fileProvider); + public ProcessExecutor processExecutor() { + return new ProcessExecutor(); + } + + @Provides + public Decompiler decompiler(FileProvider fileProvider, ProcessExecutor processExecutor, ZipTool zipTool, FileCleaner fileCleaner) { + return new Decompiler(fileProvider, processExecutor, zipTool, fileCleaner); } }