fix: use common parser for manifest, verify app package
This commit is contained in:
@@ -178,6 +178,7 @@ Plugin options (-P<name>=<value>):
|
||||
- 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
|
||||
|
||||
@@ -108,6 +108,7 @@ public class JCommanderWrapper<T> {
|
||||
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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -76,7 +76,7 @@ public class ResourceStorage {
|
||||
}
|
||||
|
||||
public void setAppPackage(String appPackage) {
|
||||
this.appPackage = appPackage;
|
||||
this.appPackage = XmlSecurity.verifyAppPackage(appPackage);
|
||||
}
|
||||
|
||||
public Map<Integer, String> getResourcesNames() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" ");
|
||||
if (pos > -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 = "<action android:name=\"android.intent.action.MAIN\"";
|
||||
int actionTagLen = 0; // beginning offset. suggested length set after first iteration
|
||||
while (actionPos > -1) {
|
||||
pos = content.indexOf(actionTag, actionPos + actionTagLen);
|
||||
actionPos = pos;
|
||||
int activityPos = content.lastIndexOf("<activity ", pos);
|
||||
if (activityPos > -1) {
|
||||
int aliasPos = content.lastIndexOf("<activity-alias ", pos);
|
||||
boolean isAnAlias = aliasPos > -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user