diff --git a/README.md b/README.md index a0fa8b4ab..090100834 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,7 @@ Plugin options (-P=): - rename-mappings.invert - invert mapping on load, values: [yes, no], default: no Environment variables: + JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files JADX_DISABLE_ZIP_SECURITY - set to 'true' to disable all security checks for zip files JADX_ZIP_MAX_ENTRIES_COUNT - maximum allowed number of entries in zip files (default: 100 000) JADX_TMP_DIR - custom temp directory, using system by default diff --git a/jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java b/jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java index 2f4f29f6d..9725e2a72 100644 --- a/jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java +++ b/jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java @@ -108,6 +108,7 @@ public class JCommanderWrapper { out.println(appendPluginOptions(maxNamesLen)); out.println(); out.println("Environment variables:"); + out.println(" JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files"); out.println(" JADX_DISABLE_ZIP_SECURITY - set to 'true' to disable all security checks for zip files"); out.println(" JADX_ZIP_MAX_ENTRIES_COUNT - maximum allowed number of entries in zip files (default: 100 000)"); out.println(" JADX_TMP_DIR - custom temp directory, using system by default"); diff --git a/jadx-core/src/main/java/jadx/core/utils/android/AndroidManifestParser.java b/jadx-core/src/main/java/jadx/core/utils/android/AndroidManifestParser.java index caaa3e108..3982a5aaf 100644 --- a/jadx-core/src/main/java/jadx/core/utils/android/AndroidManifestParser.java +++ b/jadx-core/src/main/java/jadx/core/utils/android/AndroidManifestParser.java @@ -196,11 +196,9 @@ public class AndroidManifestParser { private static Document parseXml(String xmlContent) { try { - DocumentBuilder builder = XmlSecurity.getSecureDbf().newDocumentBuilder(); + DocumentBuilder builder = XmlSecurity.getDBF().newDocumentBuilder(); Document document = builder.parse(new InputSource(new StringReader(xmlContent))); - document.getDocumentElement().normalize(); - return document; } catch (Exception e) { throw new JadxRuntimeException("Can not parse xml content", e); @@ -211,9 +209,7 @@ public class AndroidManifestParser { if (appStrings == null) { return null; } - String content = appStrings.getText().getCodeStr(); - return parseXml(content); } @@ -221,9 +217,7 @@ public class AndroidManifestParser { if (androidManifest == null) { return null; } - String content = androidManifest.loadContent().getText().getCodeStr(); - return parseXml(content); } } diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java b/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java index 2eaf7b69c..1c6d43722 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java @@ -86,7 +86,7 @@ public class ManifestAttributes { if (xmlStream == null) { throw new JadxRuntimeException(xml + " not found in classpath"); } - DocumentBuilder dBuilder = XmlSecurity.getSecureDbf().newDocumentBuilder(); + DocumentBuilder dBuilder = XmlSecurity.getDBF().newDocumentBuilder(); doc = dBuilder.parse(xmlStream); } catch (Exception e) { throw new JadxRuntimeException("Xml load error, file: " + xml, e); diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResourceStorage.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResourceStorage.java index 5c7a2d23e..82d7509d4 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResourceStorage.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResourceStorage.java @@ -76,7 +76,7 @@ public class ResourceStorage { } public void setAppPackage(String appPackage) { - this.appPackage = appPackage; + this.appPackage = XmlSecurity.verifyAppPackage(appPackage); } public Map getResourcesNames() { diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/XmlSecurity.java b/jadx-core/src/main/java/jadx/core/xmlgen/XmlSecurity.java index c67167c69..912e010eb 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/XmlSecurity.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/XmlSecurity.java @@ -1,28 +1,54 @@ package jadx.core.xmlgen; import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.core.deobf.NameMapper; +import jadx.core.utils.Utils; public class XmlSecurity { + private static final Logger LOG = LoggerFactory.getLogger(XmlSecurity.class); - private static DocumentBuilderFactory secureDbf = null; + private static final boolean DISABLE_CHECKS = Utils.getEnvVarBool("JADX_DISABLE_XML_SECURITY", false); + + private static final DocumentBuilderFactory DBF_INSTANCE = buildDBF(); private XmlSecurity() { } - public static DocumentBuilderFactory getSecureDbf() throws ParserConfigurationException { - synchronized (XmlSecurity.class) { - if (secureDbf == null) { - secureDbf = DocumentBuilderFactory.newInstance(); - secureDbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - secureDbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - secureDbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - secureDbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - secureDbf.setFeature("http://apache.org/xml/features/dom/create-entity-ref-nodes", false); - secureDbf.setXIncludeAware(false); - secureDbf.setExpandEntityReferences(false); - } + public static DocumentBuilderFactory getDBF() { + return DBF_INSTANCE; + } + + public static String verifyAppPackage(String appPackage) { + if (DISABLE_CHECKS) { + return appPackage; + } + if (NameMapper.isValidFullIdentifier(appPackage)) { + return appPackage; + } + LOG.warn("App package '{}' has invalid format and will be ignored", appPackage); + return "INVALID_PACKAGE"; + } + + private static DocumentBuilderFactory buildDBF() { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + if (DISABLE_CHECKS) { + return dbf; + } + try { + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setFeature("http://apache.org/xml/features/dom/create-entity-ref-nodes", false); + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + return dbf; + } catch (Exception e) { + throw new RuntimeException("Fail to build secure XML DocumentBuilderFactory", e); } - return secureDbf; } } diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/DbgUtils.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/DbgUtils.java index 684ab6c01..3935a0aa8 100644 --- a/jadx-gui/src/main/java/jadx/gui/device/debugger/DbgUtils.java +++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/DbgUtils.java @@ -1,6 +1,7 @@ package jadx.gui.device.debugger; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -9,15 +10,19 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.api.JadxDecompiler; import jadx.api.JavaClass; -import jadx.api.ResourceFile; -import jadx.api.ResourceType; +import jadx.core.deobf.NameMapper; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.nodes.ClassNode; +import jadx.core.utils.android.AndroidManifestParser; +import jadx.core.utils.android.AppAttribute; +import jadx.core.utils.android.ApplicationParams; import jadx.gui.device.debugger.smali.Smali; import jadx.gui.treemodel.JClass; -import jadx.gui.treemodel.JNode; import jadx.gui.ui.MainWindow; +import jadx.gui.utils.NLS; +import jadx.gui.utils.UiUtils; public class DbgUtils { private static final Logger LOG = LoggerFactory.getLogger(DbgUtils.class); @@ -99,83 +104,70 @@ public class DbgUtils { return null; } + public static JClass getJClass(JavaClass cls, MainWindow mainWindow) { + return mainWindow.getCacheObject().getNodeCache().makeFrom(cls); + } + public static ClassNode getClassNodeBySig(String clsSig, MainWindow mainWindow) { clsSig = DbgUtils.classSigToFullName(clsSig); return mainWindow.getWrapper().getDecompiler().searchClassNodeByOrigFullName(clsSig); } - public static String searchPackageName(MainWindow mainWindow) { - String content = getManifestContent(mainWindow); - int pos = content.indexOf(" -1) { - pos = content.lastIndexOf(">", pos); - if (pos > -1) { - pos = content.indexOf(" package=\"", pos); - if (pos > -1) { - pos += " package=\"".length(); - return content.substring(pos, content.indexOf("\"", pos)); - } - } - } - return ""; - } - - /** - * @return the Activity class for android.intent.action.MAIN. - */ - @Nullable - public static JClass searchMainActivity(MainWindow mainWindow) { - String content = getManifestContent(mainWindow); - int pos; // current position - int actionPos = 0; // last found action's index - String actionTag = " -1) { - pos = content.indexOf(actionTag, actionPos + actionTagLen); - actionPos = pos; - int activityPos = content.lastIndexOf(" -1) { - int aliasPos = content.lastIndexOf(" -1 && aliasPos > activityPos; - String classPathAttribute = " android:" + (isAnAlias ? "targetActivity" : "name") + "=\""; - pos = content.indexOf(classPathAttribute, isAnAlias ? aliasPos : activityPos); - if (pos > -1) { - pos += classPathAttribute.length(); - String classFullName = content.substring(pos, content.indexOf("\"", pos)); - // in case the MainActivity class has been renamed before, we need raw name. - JavaClass cls = mainWindow.getWrapper().getDecompiler().searchJavaClassByAliasFullName(classFullName); - JNode jNode = mainWindow.getCacheObject().getNodeCache().makeFrom(cls); - if (jNode != null) { - return jNode.getRootClass(); - } - } - } - if (actionTagLen == 0) { - actionTagLen = actionTag.length(); - } - } - return null; - } - - // TODO: parse AndroidManifest.xml instead of looking for keywords - private static String getManifestContent(MainWindow mainWindow) { - try { - ResourceFile androidManifest = mainWindow.getWrapper().getResources() - .stream() - .filter(res -> res.getType() == ResourceType.MANIFEST) - .findFirst() - .orElse(null); - - if (androidManifest != null) { - return androidManifest.loadContent().getText().getCodeStr(); - } - } catch (Exception e) { - LOG.error("AndroidManifest.xml search error", e); - } - return ""; - } - public static boolean isPrintableChar(int c) { return 32 <= c && c <= 126; } + + public static final class AppData { + private final String appPackage; + private final JavaClass mainActivityCls; + + public AppData(String appPackage, JavaClass mainActivityCls) { + this.appPackage = appPackage; + this.mainActivityCls = mainActivityCls; + } + + public String getAppPackage() { + return appPackage; + } + + public JavaClass getMainActivityCls() { + return mainActivityCls; + } + + public String getProcessName() { + return appPackage + '/' + mainActivityCls.getClassNode().getClassInfo().getFullName(); + } + } + + public static @Nullable AppData parseAppData(MainWindow mw) { + JadxDecompiler decompiler = mw.getWrapper().getDecompiler(); + String appPkg = decompiler.getRoot().getAppPackage(); + if (appPkg == null) { + UiUtils.errorMessage(mw, NLS.str("error_dialog.not_found", "App package")); + return null; + } + AndroidManifestParser parser = new AndroidManifestParser( + AndroidManifestParser.getAndroidManifest(decompiler.getResources()), + EnumSet.of(AppAttribute.MAIN_ACTIVITY)); + if (!parser.isManifestFound()) { + UiUtils.errorMessage(mw, NLS.str("error_dialog.not_found", "AndroidManifest.xml")); + return null; + } + ApplicationParams results = parser.parse(); + String mainActivityName = results.getMainActivityName(); + if (mainActivityName == null) { + UiUtils.errorMessage(mw, NLS.str("adb_dialog.msg_read_mani_fail")); + return null; + } + if (!NameMapper.isValidFullIdentifier(mainActivityName)) { + UiUtils.errorMessage(mw, "Invalid main activity name"); + return null; + } + JavaClass mainActivityClass = results.getMainActivity(decompiler); + if (mainActivityClass == null) { + UiUtils.errorMessage(mw, NLS.str("error_dialog.not_found", "Main activity class")); + return null; + } + return new AppData(appPkg, mainActivityClass); + } } diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java index e28f30637..595b1e6c5 100644 --- a/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java +++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/DebugController.java @@ -113,11 +113,12 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe } private void stopAtOnCreate() { - JClass mainActivity = DbgUtils.searchMainActivity(debuggerPanel.getMainWindow()); - if (mainActivity == null) { + DbgUtils.AppData appData = DbgUtils.parseAppData(debuggerPanel.getMainWindow()); + if (appData == null) { debuggerPanel.log("Failed to set breakpoint at onCreate, you have to do it yourself."); return; } + JClass mainActivity = DbgUtils.getJClass(appData.getMainActivityCls(), debuggerPanel.getMainWindow()); lazyQueue.execute(() -> openMainActivityTab(mainActivity)); String clsSig = DbgUtils.getRawFullName(mainActivity); try { @@ -217,15 +218,11 @@ public final class DebugController implements SmaliDebugger.SuspendListener, IDe @Override public String getProcessName() { - String pkg = DbgUtils.searchPackageName(debuggerPanel.getMainWindow()); - if (pkg.isEmpty()) { + DbgUtils.AppData appData = DbgUtils.parseAppData(debuggerPanel.getMainWindow()); + if (appData == null) { return ""; } - JClass cls = DbgUtils.searchMainActivity(debuggerPanel.getMainWindow()); - if (cls == null) { - return ""; - } - return pkg + "/" + cls.getCls().getClassNode().getClassInfo().getFullName(); + return appData.getProcessName(); } private RuntimeType castType(ArgType type) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java index a5868d355..dd07b9057 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/ADBDialog.java @@ -13,7 +13,6 @@ import java.io.File; import java.net.Socket; import java.util.ArrayList; import java.util.Collections; -import java.util.EnumSet; import java.util.List; import java.util.Set; @@ -39,13 +38,9 @@ import javax.swing.tree.TreeSelectionModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jadx.api.JadxDecompiler; -import jadx.api.JavaClass; import jadx.core.utils.StringUtils; -import jadx.core.utils.android.AndroidManifestParser; -import jadx.core.utils.android.AppAttribute; -import jadx.core.utils.android.ApplicationParams; import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.gui.device.debugger.DbgUtils; import jadx.gui.device.debugger.DebugSettings; import jadx.gui.device.protocol.ADB; import jadx.gui.device.protocol.ADBDevice; @@ -515,40 +510,21 @@ public class ADBDialog extends JDialog implements ADB.DeviceStateListener, ADB.J UiUtils.showMessageBox(mainWindow, NLS.str("adb_dialog.no_devices")); return; } - JadxDecompiler decompiler = mainWindow.getWrapper().getDecompiler(); - String appPkg = decompiler.getRoot().getAppPackage(); - if (appPkg == null) { - UiUtils.errorMessage(mainWindow, NLS.str("error_dialog.not_found", "App package")); + DbgUtils.AppData appData = DbgUtils.parseAppData(mainWindow); + if (appData == null) { + // error already reported return; } - if (scrollToProcNode(appPkg)) { + if (scrollToProcNode(appData.getAppPackage())) { return; } - - AndroidManifestParser parser = new AndroidManifestParser( - AndroidManifestParser.getAndroidManifest(decompiler.getResources()), - EnumSet.of(AppAttribute.MAIN_ACTIVITY)); - if (!parser.isManifestFound()) { - UiUtils.errorMessage(mainWindow, NLS.str("error_dialog.not_found", "AndroidManifest.xml")); - return; - } - ApplicationParams results = parser.parse(); - if (results.getMainActivityName() == null) { - UiUtils.errorMessage(mainWindow, NLS.str("adb_dialog.msg_read_mani_fail")); - return; - } - JavaClass mainActivityClass = results.getMainActivity(decompiler); - if (mainActivityClass == null) { - UiUtils.errorMessage(mainWindow, NLS.str("error_dialog.not_found", "Main activity class")); - return; - } - String fullName = appPkg + "/" + mainActivityClass.getClassNode().getClassInfo().getFullName(); + String processName = appData.getProcessName(); ADBDevice device = lastSelectedDeviceNode == null ? deviceNodes.get(0).device : lastSelectedDeviceNode.device; if (device != null) { try { - device.launchApp(fullName); + device.launchApp(processName); } catch (Exception e) { - LOG.error("Failed to launch app: {}", fullName, e); + LOG.error("Failed to launch app: {}", processName, e); UiUtils.showMessageBox(mainWindow, e.getMessage()); } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java index be436549d..f677e7679 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java @@ -309,6 +309,7 @@ public class UiUtils { } public static void errorMessage(Component parent, String message) { + LOG.error(message); JOptionPane.showMessageDialog(parent, message, NLS.str("message.errorTitle"), JOptionPane.ERROR_MESSAGE); }