fix: use common parser for manifest, verify app package

This commit is contained in:
Skylot
2024-04-20 16:33:31 +01:00
parent f9c0cad146
commit be25cbf8c2
10 changed files with 127 additions and 139 deletions
+1
View File
@@ -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);
}