diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/HideManager.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/HideManager.java index f513ae17c..aaec79535 100644 --- a/app/src/main/java/com/topjohnwu/magisk/asyncs/HideManager.java +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/HideManager.java @@ -7,15 +7,21 @@ import android.widget.Toast; import com.topjohnwu.magisk.MagiskManager; import com.topjohnwu.magisk.R; +import com.topjohnwu.magisk.container.JarMap; import com.topjohnwu.magisk.container.Policy; import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.ZipUtils; import java.io.File; import java.util.List; +import java.util.jar.JarEntry; public class HideManager extends ParallelTask { + private static final String UNHIDE_APK = "unhide.apk"; + private static final String ANDROID_MANIFEST = "AndroidManifest.xml"; + private static final byte[] UNHIDE_PKG_NAME = "com.topjohnwu.unhide\0".getBytes(); + public HideManager(Context context) { super(context); } @@ -34,7 +40,42 @@ public class HideManager extends ParallelTask { // Generate a new unhide app with random package name File unhideAPK = new File(Environment.getExternalStorageDirectory() + "/MagiskManager", "unhide.apk"); unhideAPK.getParentFile().mkdirs(); - String pkg = ZipUtils.generateUnhide(mm, unhideAPK); + String pkg; + + try { + JarMap asset = new JarMap(mm.getAssets().open(UNHIDE_APK)); + JarEntry je = new JarEntry(ANDROID_MANIFEST); + byte xml[] = asset.getRawData(je); + int offset = -1; + + // Linear search pattern offset + for (int i = 0; i < xml.length - UNHIDE_PKG_NAME.length; ++i) { + boolean match = true; + for (int j = 0; j < UNHIDE_PKG_NAME.length; ++j) { + if (xml[i + j] != UNHIDE_PKG_NAME[j]) { + match = false; + break; + } + } + if (match) { + offset = i; + break; + } + } + if (offset < 0) + return false; + + // Patch binary XML with new package name + pkg = Utils.genPackageName("com.", UNHIDE_PKG_NAME.length - 1); + System.arraycopy(pkg.getBytes(), 0, xml, offset, pkg.length()); + asset.getOutputStream(je).write(xml); + + // Sign the APK + ZipUtils.signZip(mm, asset, unhideAPK, false); + } catch (Exception e) { + e.printStackTrace(); + return false; + } // Install the application List ret = getShell().su("pm install " + unhideAPK + ">/dev/null && echo true || echo false"); diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java index 9a3e81432..eee0e2981 100644 --- a/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java @@ -11,7 +11,6 @@ import android.widget.Toast; import com.topjohnwu.magisk.FlashActivity; import com.topjohnwu.magisk.R; -import com.topjohnwu.magisk.utils.Logger; import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.WebService; @@ -26,6 +25,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; public class ProcessRepoZip extends ParallelTask { @@ -46,6 +48,34 @@ public class ProcessRepoZip extends ParallelTask { mInstall = install; } + private void removeTopFolder(InputStream in, File output) throws IOException { + JarInputStream source = new JarInputStream(in); + JarOutputStream dest = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(output))); + JarEntry entry; + String path; + int size; + byte buffer[] = new byte[4096]; + while ((entry = source.getNextJarEntry()) != null) { + // Remove the top directory from the path + path = entry.getName().substring(entry.getName().indexOf("/") + 1); + // If it's the top folder, ignore it + if (path.isEmpty()) { + continue; + } + // Don't include placeholder + if (path.equals("system/placeholder")) { + continue; + } + dest.putNextEntry(new JarEntry(path)); + while((size = source.read(buffer)) != -1) { + dest.write(buffer, 0, size); + } + } + source.close(); + dest.close(); + in.close(); + } + @Override protected void onPreExecute() { Activity activity = getActivity(); @@ -86,7 +116,7 @@ public class ProcessRepoZip extends ParallelTask { temp1.getParentFile().mkdir(); // First remove top folder in Github source zip, Web -> temp1 - ZipUtils.removeTopFolder(in, temp1); + removeTopFolder(in, temp1); conn.disconnect(); publishProgress(SHOW_PROCESSING); @@ -116,7 +146,6 @@ public class ProcessRepoZip extends ParallelTask { return true; } catch (Exception e) { - Logger.error("ProcessRepoZip: Error!"); e.printStackTrace(); return false; } @@ -144,7 +173,8 @@ public class ProcessRepoZip extends ParallelTask { @Override public ParallelTask exec(Void... voids) { - Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> super.exec(voids)); + Utils.runWithPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, + () -> super.exec(voids)); return this; } diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/ZipUtils.java b/app/src/main/java/com/topjohnwu/magisk/utils/ZipUtils.java index 36a4874ca..0831684f9 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/ZipUtils.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/ZipUtils.java @@ -70,14 +70,10 @@ public class ZipUtils { // File name in assets private static final String PUBLIC_KEY_NAME = "public.certificate.x509.pem"; private static final String PRIVATE_KEY_NAME = "private.key.pk8"; - private static final String UNHIDE_APK = "unhide.apk"; private static final String CERT_SF_NAME = "META-INF/CERT.SF"; private static final String CERT_SIG_NAME = "META-INF/CERT.%s"; - private static final String ANDROID_MANIFEST = "AndroidManifest.xml"; - private static final byte[] UNHIDE_PKG_NAME = "com.topjohnwu.unhide\0".getBytes(); - private static Provider sBouncyCastleProvider; // bitmasks for which hash algorithms we need the manifest to include. private static final int USE_SHA1 = 1; @@ -91,78 +87,6 @@ public class ZipUtils { public native static void zipAdjust(String filenameIn, String filenameOut); - public static String generateUnhide(Context context, File output) { - try { - String pkg; - JarMap apk = new JarMap(context.getAssets().open(UNHIDE_APK)); - JarEntry je = new JarEntry(ANDROID_MANIFEST); - byte xml[] = apk.getRawData(je); - int offset = -1; - - // Linear search pattern offset - for (int i = 0; i < xml.length - UNHIDE_PKG_NAME.length; ++i) { - boolean match = true; - for (int j = 0; j < UNHIDE_PKG_NAME.length; ++j) { - if (xml[i + j] != UNHIDE_PKG_NAME[j]) { - match = false; - break; - } - } - if (match) { - offset = i; - break; - } - } - if (offset < 0) - return ""; - - // Patch binary XML with new package name - pkg = Utils.genPackageName("com.", UNHIDE_PKG_NAME.length - 1); - System.arraycopy(pkg.getBytes(), 0, xml, offset, pkg.length()); - apk.getOutputStream(je).write(xml); - - // Sign the APK - signZip(context, apk, output, false); - - return pkg; - } catch (IOException e) { - e.printStackTrace(); - return ""; - } - } - - public static void removeTopFolder(InputStream in, File output) throws IOException { - try { - JarInputStream source = new JarInputStream(in); - JarOutputStream dest = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(output))); - JarEntry entry; - String path; - int size; - byte buffer[] = new byte[4096]; - while ((entry = source.getNextJarEntry()) != null) { - // Remove the top directory from the path - path = entry.getName().substring(entry.getName().indexOf("/") + 1); - // If it's the top folder, ignore it - if (path.isEmpty()) { - continue; - } - // Don't include placeholder - if (path.equals("system/placeholder")) { - continue; - } - dest.putNextEntry(new JarEntry(path)); - while((size = source.read(buffer)) != -1) { - dest.write(buffer, 0, size); - } - } - source.close(); - dest.close(); - in.close(); - } catch (IOException e) { - throw e; - } - } - public static void unzip(File zip, File folder, String path, boolean junkPath) throws Exception { InputStream in = new BufferedInputStream(new FileInputStream(zip)); unzip(in, folder, path, junkPath); @@ -201,63 +125,46 @@ public class ZipUtils { } } - public static void signZip(Context context, InputStream is, File output, boolean minSign) { - try { - signZip(context, new JarMap(is, false), output, minSign); - } catch (IOException e) { - e.printStackTrace(); - } + public static void signZip(Context context, InputStream is, File output, boolean minSign) throws Exception { + signZip(context, new JarMap(is, false), output, minSign); } - public static void signZip(Context context, File input, File output, boolean minSign) { - try { - signZip(context, new JarMap(new FileInputStream(input), false), output, minSign); - } catch (IOException e) { - e.printStackTrace(); - } + public static void signZip(Context context, File input, File output, boolean minSign) throws Exception { + signZip(context, new JarMap(new FileInputStream(input), false), output, minSign); } - public static void signZip(Context context, JarMap input, File output, boolean minSign) { + public static void signZip(Context context, JarMap input, File output, boolean minSign) throws Exception { int alignment = 4; BufferedOutputStream outputFile = null; int hashes = 0; - try { - X509Certificate publicKey = readPublicKey(context.getAssets().open(PUBLIC_KEY_NAME)); - hashes |= getDigestAlgorithm(publicKey); + X509Certificate publicKey = readPublicKey(context.getAssets().open(PUBLIC_KEY_NAME)); + hashes |= getDigestAlgorithm(publicKey); - // Set the ZIP file timestamp to the starting valid time - // of the 0th certificate plus one hour (to match what - // we've historically done). - long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000; - PrivateKey privateKey = readPrivateKey(context.getAssets().open(PRIVATE_KEY_NAME)); + // Set the ZIP file timestamp to the starting valid time + // of the 0th certificate plus one hour (to match what + // we've historically done). + long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000; + PrivateKey privateKey = readPrivateKey(context.getAssets().open(PRIVATE_KEY_NAME)); - outputFile = new BufferedOutputStream(new FileOutputStream(output)); - if (minSign) { - ZipUtils.signWholeFile(input.getInputStream(), publicKey, privateKey, outputFile); - } else { - JarOutputStream outputJar = new JarOutputStream(outputFile); - // For signing .apks, use the maximum compression to make - // them as small as possible (since they live forever on - // the system partition). For OTA packages, use the - // default compression level, which is much much faster - // and produces output that is only a tiny bit larger - // (~0.1% on full OTA packages I tested). - outputJar.setLevel(9); - Manifest manifest = addDigestsToManifest(input, hashes); - copyFiles(manifest, input, outputJar, timestamp, alignment); - signFile(manifest, input, publicKey, privateKey, outputJar); - outputJar.close(); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { - if (input != null) input.close(); - if (outputFile != null) outputFile.close(); - } catch (IOException e) { - e.printStackTrace(); - } + outputFile = new BufferedOutputStream(new FileOutputStream(output)); + if (minSign) { + ZipUtils.signWholeFile(input.getInputStream(), publicKey, privateKey, outputFile); + } else { + JarOutputStream outputJar = new JarOutputStream(outputFile); + // For signing .apks, use the maximum compression to make + // them as small as possible (since they live forever on + // the system partition). For OTA packages, use the + // default compression level, which is much much faster + // and produces output that is only a tiny bit larger + // (~0.1% on full OTA packages I tested). + outputJar.setLevel(9); + Manifest manifest = addDigestsToManifest(input, hashes); + copyFiles(manifest, input, outputJar, timestamp, alignment); + signFile(manifest, input, publicKey, privateKey, outputJar); + outputJar.close(); } + input.close(); + outputFile.close(); }