feat(api): add JadxArgs property to adjust xml security checks (#2291)

This commit is contained in:
Skylot 2024-10-09 20:48:15 +01:00
parent 2d10537050
commit 063af8cd62
No known key found for this signature in database
GPG Key ID: 47A4975761262B6A
24 changed files with 271 additions and 209 deletions

View File

@ -18,7 +18,6 @@ dependencies {
testImplementation("ch.qos.logback:logback-classic:1.5.9")
testImplementation("org.assertj:assertj-core:3.26.3")
testImplementation("org.mockito:mockito-core:5.14.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.11.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

View File

@ -1,5 +1,7 @@
package jadx.cli;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -8,8 +10,11 @@ import jadx.api.JadxDecompiler;
import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.NoOpCodeCache;
import jadx.api.impl.SimpleCodeWriter;
import jadx.api.security.JadxSecurityFlag;
import jadx.api.security.impl.JadxSecurity;
import jadx.cli.LogHelper.LogLevelEnum;
import jadx.cli.plugins.JadxFilesGetter;
import jadx.commons.app.JadxCommonEnv;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.files.FileUtils;
import jadx.plugins.tools.JadxExternalPluginsLoader;
@ -49,6 +54,7 @@ public class JadxCLI {
jadxArgs.setPluginLoader(new JadxExternalPluginsLoader());
jadxArgs.setFilesGetter(JadxFilesGetter.INSTANCE);
initCodeWriterProvider(jadxArgs);
applyEnvVars(jadxArgs);
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load();
if (checkForErrors(jadx)) {
@ -81,6 +87,22 @@ public class JadxCLI {
}
}
private static void applyEnvVars(JadxArgs jadxArgs) {
Set<JadxSecurityFlag> flags = JadxSecurityFlag.all();
boolean modified = false;
boolean disableXmlSecurity = JadxCommonEnv.getBool("JADX_DISABLE_XML_SECURITY", false);
if (disableXmlSecurity) {
flags.remove(JadxSecurityFlag.SECURE_XML_PARSER);
// TODO: not related to 'xml security', but kept for compatibility
flags.remove(JadxSecurityFlag.VERIFY_APP_PACKAGE);
modified = true;
}
// TODO: migrate 'ZipSecurity'
if (modified) {
jadxArgs.setSecurity(new JadxSecurity(flags));
}
}
private static boolean checkForErrors(JadxDecompiler jadx) {
if (jadx.getRoot().getClasses().isEmpty()) {
if (jadx.getArgs().isSkipResources()) {

View File

@ -30,6 +30,9 @@ import jadx.api.impl.AnnotatedCodeWriter;
import jadx.api.impl.InMemoryCodeCache;
import jadx.api.plugins.loader.JadxBasePluginLoader;
import jadx.api.plugins.loader.JadxPluginLoader;
import jadx.api.security.IJadxSecurity;
import jadx.api.security.JadxSecurityFlag;
import jadx.api.security.impl.JadxSecurity;
import jadx.api.usage.IUsageInfoCache;
import jadx.api.usage.impl.InMemoryUsageInfoCache;
import jadx.core.deobf.DeobfAliasProvider;
@ -174,6 +177,11 @@ public class JadxArgs implements Closeable {
*/
private IJadxFilesGetter filesGetter = TempFilesGetter.INSTANCE;
/**
* Additional data validation and security checks
*/
private IJadxSecurity security = new JadxSecurity(JadxSecurityFlag.all());
/**
* Don't save files (can be using for performance testing)
*/
@ -726,6 +734,14 @@ public class JadxArgs implements Closeable {
this.filesGetter = filesGetter;
}
public IJadxSecurity getSecurity() {
return security;
}
public void setSecurity(IJadxSecurity security) {
this.security = security;
}
public boolean isSkipFilesSave() {
return skipFilesSave;
}

View File

@ -51,7 +51,6 @@ import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.tasks.TaskExecutor;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
/**
@ -92,8 +91,6 @@ public final class JadxDecompiler implements Closeable {
private List<JavaClass> classes;
private List<ResourceFile> resources;
private BinaryXMLParser binaryXmlParser;
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
private final JadxEventsImpl events = new JadxEventsImpl();
private final ResourcesLoader resourcesLoader = new ResourcesLoader(this);
@ -168,7 +165,6 @@ public final class JadxDecompiler implements Closeable {
root = null;
classes = null;
resources = null;
binaryXmlParser = null;
events.reset();
}
@ -467,13 +463,6 @@ public final class JadxDecompiler implements Closeable {
return root;
}
synchronized BinaryXMLParser getBinaryXmlParser() {
if (binaryXmlParser == null) {
binaryXmlParser = new BinaryXMLParser(root);
}
return binaryXmlParser;
}
/**
* Get JavaClass by ClassNode without loading and decompilation
*/

View File

@ -0,0 +1,20 @@
package jadx.api.security;
import java.io.InputStream;
import org.w3c.dom.Document;
public interface IJadxSecurity {
/**
* Check if application package is safe
*
* @return normalized/sanitized string or same string if safe
*/
String verifyAppPackage(String appPackage);
/**
* XML document parser
*/
Document parseXml(InputStream in);
}

View File

@ -0,0 +1,18 @@
package jadx.api.security;
import java.util.EnumSet;
import java.util.Set;
public enum JadxSecurityFlag {
VERIFY_APP_PACKAGE,
SECURE_XML_PARSER;
public static Set<JadxSecurityFlag> all() {
return EnumSet.allOf(JadxSecurityFlag.class);
}
public static Set<JadxSecurityFlag> none() {
return EnumSet.noneOf(JadxSecurityFlag.class);
}
}

View File

@ -0,0 +1,73 @@
package jadx.api.security.impl;
import java.io.InputStream;
import java.util.Set;
import javax.xml.parsers.DocumentBuilderFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import jadx.api.security.IJadxSecurity;
import jadx.api.security.JadxSecurityFlag;
import jadx.core.deobf.NameMapper;
public class JadxSecurity implements IJadxSecurity {
private static final Logger LOG = LoggerFactory.getLogger(JadxSecurity.class);
private final Set<JadxSecurityFlag> flags;
public JadxSecurity(Set<JadxSecurityFlag> flags) {
this.flags = flags;
}
@Override
public String verifyAppPackage(String appPackage) {
if (flags.contains(JadxSecurityFlag.VERIFY_APP_PACKAGE)
&& !NameMapper.isValidFullIdentifier(appPackage)) {
LOG.warn("App package '{}' has invalid format and will be ignored", appPackage);
return "INVALID_PACKAGE";
}
return appPackage;
}
@Override
public Document parseXml(InputStream in) {
DocumentBuilderFactory dbf;
if (flags.contains(JadxSecurityFlag.SECURE_XML_PARSER)) {
dbf = SecureDBFHolder.INSTANCE;
} else {
dbf = SimpleDBFHolder.INSTANCE;
}
try {
return dbf.newDocumentBuilder().parse(in);
} catch (Exception e) {
throw new RuntimeException("Failed to parse xml", e);
}
}
private static final class SimpleDBFHolder {
private static final DocumentBuilderFactory INSTANCE = DocumentBuilderFactory.newInstance();
}
private static final class SecureDBFHolder {
private static final DocumentBuilderFactory INSTANCE = buildSecureDBF();
private static DocumentBuilderFactory buildSecureDBF() {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
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);
}
}
}
}

View File

@ -98,6 +98,8 @@ public class RootNode {
*/
private @Nullable JadxDecompiler decompiler;
private @Nullable ManifestAttributes manifestAttributes;
public RootNode(JadxArgs args) {
this.args = args;
this.preDecompilePasses = Jadx.getPreDecompilePassesList();
@ -211,7 +213,7 @@ public class RootNode {
if (parser != null) {
processResources(parser.getResStorage());
updateObfuscatedFiles(parser, resources);
ManifestAttributes.getInstance().updateAttributes(parser);
initManifestAttributes().updateAttributes(parser);
}
} catch (Exception e) {
LOG.error("Failed to parse 'resources.pb'/'.arsc' file", e);
@ -721,4 +723,13 @@ public class RootNode {
public GradleInfoStorage getGradleInfoStorage() {
return gradleInfoStorage;
}
public synchronized ManifestAttributes initManifestAttributes() {
ManifestAttributes attrs = manifestAttributes;
if (attrs == null) {
attrs = new ManifestAttributes(args.getSecurity());
manifestAttributes = attrs;
}
return attrs;
}
}

View File

@ -95,7 +95,8 @@ public class ExportGradleProject {
AppAttribute.MIN_SDK_VERSION,
AppAttribute.TARGET_SDK_VERSION,
AppAttribute.VERSION_CODE,
AppAttribute.VERSION_NAME));
AppAttribute.VERSION_NAME),
root.getArgs().getSecurity());
return parser.parse();
}
}

View File

@ -495,6 +495,11 @@ public class Utils {
}
}
/**
* @deprecated env vars shouldn't be used in core modules.
* Prefer to parse in `app` (use JadxCommonEnv from 'app-commons') and set in jadx args.
*/
@Deprecated
public static boolean getEnvVarBool(String varName, boolean defValue) {
String strValue = System.getenv(varName);
if (strValue == null) {
@ -503,6 +508,11 @@ public class Utils {
return strValue.equalsIgnoreCase("true");
}
/**
* @deprecated env vars shouldn't be used in core modules.
* Prefer to parse in `app` (use JadxCommonEnv from 'app-commons') and set in jadx args.
*/
@Deprecated
public static int getEnvVarInt(String varName, int defValue) {
String strValue = System.getenv(varName);
if (strValue == null) {

View File

@ -1,38 +1,37 @@
package jadx.core.utils.android;
import java.io.StringReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.EnumSet;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.security.IJadxSecurity;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.XmlSecurity;
public class AndroidManifestParser {
private static final Logger LOG = LoggerFactory.getLogger(AndroidManifestParser.class);
private final Document androidManifest;
private final Document appStrings;
private final EnumSet<AppAttribute> parseAttrs;
private final IJadxSecurity security;
public AndroidManifestParser(ResourceFile androidManifestRes, EnumSet<AppAttribute> parseAttrs) {
this(androidManifestRes, null, parseAttrs);
public AndroidManifestParser(ResourceFile androidManifestRes, EnumSet<AppAttribute> parseAttrs, IJadxSecurity security) {
this(androidManifestRes, null, parseAttrs, security);
}
public AndroidManifestParser(ResourceFile androidManifestRes, ResContainer appStrings, EnumSet<AppAttribute> parseAttrs) {
public AndroidManifestParser(ResourceFile androidManifestRes, ResContainer appStrings,
EnumSet<AppAttribute> parseAttrs, IJadxSecurity security) {
this.parseAttrs = parseAttrs;
this.security = Objects.requireNonNull(security);
this.androidManifest = parseAndroidManifest(androidManifestRes);
this.appStrings = parseAppStrings(appStrings);
@ -206,10 +205,9 @@ public class AndroidManifestParser {
return false;
}
private static Document parseXml(String xmlContent) {
try {
DocumentBuilder builder = XmlSecurity.getDBF().newDocumentBuilder();
Document document = builder.parse(new InputSource(new StringReader(xmlContent)));
private Document parseXml(String xmlContent) {
try (InputStream xmlStream = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8))) {
Document document = security.parseXml(xmlStream);
document.getDocumentElement().normalize();
return document;
} catch (Exception e) {
@ -217,7 +215,7 @@ public class AndroidManifestParser {
}
}
private static Document parseAppStrings(ResContainer appStrings) {
private Document parseAppStrings(ResContainer appStrings) {
if (appStrings == null) {
return null;
}
@ -225,7 +223,7 @@ public class AndroidManifestParser {
return parseXml(content);
}
private static Document parseAndroidManifest(ResourceFile androidManifest) {
private Document parseAndroidManifest(ResourceFile androidManifest) {
if (androidManifest == null) {
return null;
}

View File

@ -23,21 +23,13 @@ import jadx.core.utils.android.AndroidResourcesMap;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.xmlgen.entry.ValuesParser;
/*
* TODO:
* Don't die when error occurs
* Check error cases, maybe checked const values are not always the same
* Better error messages
* What to do, when Binary XML Manifest is > size(int)?
* Check for missing chunk size types
* Implement missing data types
* Use line numbers to recreate EXACT AndroidManifest
* Check Element chunk size
*/
public class BinaryXMLParser extends CommonBinaryParser {
private static final Logger LOG = LoggerFactory.getLogger(BinaryXMLParser.class);
private final RootNode rootNode;
private final ManifestAttributes manifestAttributes;
private final boolean attrNewLine;
private final Map<Integer, String> resNames;
private Map<String, String> nsMap;
private Set<String> nsMapGenerated;
@ -53,16 +45,13 @@ public class BinaryXMLParser extends CommonBinaryParser {
private boolean isOneLine = true;
private int namespaceDepth = 0;
private @Nullable int[] resourceIds;
private final RootNode rootNode;
private String appPackageName;
private Map<String, ClassNode> classNameCache;
private final boolean attrNewLine;
public BinaryXMLParser(RootNode rootNode) {
this.rootNode = rootNode;
this.manifestAttributes = rootNode.initManifestAttributes();
this.attrNewLine = !rootNode.getArgs().isSkipXmlPrettyPrint();
try {
ConstStorage constStorage = rootNode.getConstValues();
@ -325,7 +314,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
writer.add(' ');
}
writer.add(attrFullName).add("=\"");
String decodedAttr = ManifestAttributes.getInstance().decode(attrName, attrValData);
String decodedAttr = manifestAttributes.decode(attrName, attrValData);
if (decodedAttr != null) {
memorizePackageName(attrName, decodedAttr);
if (isDeobfCandidateAttr(attrFullName)) {
@ -419,8 +408,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
return "NOT_FOUND_STR_0x" + Integer.toHexString(strId);
}
private void decodeAttribute(int attributeNS, int attrValDataType, int attrValData,
String attrFullName) {
private void decodeAttribute(int attributeNS, int attrValDataType, int attrValData, String attrFullName) {
if (attrValDataType == TYPE_REFERENCE) {
// reference custom processing
String resName = resNames.get(attrValData);

View File

@ -7,8 +7,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
@ -16,11 +14,17 @@ import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import jadx.api.security.IJadxSecurity;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.xmlgen.entry.RawNamedValue;
import jadx.core.xmlgen.entry.ResourceEntry;
import jadx.core.xmlgen.entry.ValuesParser;
// TODO: move to Android specific module!
/**
* Load and store Android Manifest attributes specification
*/
public class ManifestAttributes {
private static final Logger LOG = LoggerFactory.getLogger(ManifestAttributes.class);
@ -53,24 +57,12 @@ public class ManifestAttributes {
}
}
private final IJadxSecurity security;
private final Map<String, MAttr> attrMap = new HashMap<>();
private final Map<String, MAttr> appAttrMap = new HashMap<>();
private static ManifestAttributes instance;
public static ManifestAttributes getInstance() {
if (instance == null) {
try {
instance = new ManifestAttributes();
} catch (Exception e) {
LOG.error("Failed to create ManifestAttributes", e);
}
}
return instance;
}
private ManifestAttributes() {
public ManifestAttributes(IJadxSecurity security) {
this.security = security;
parseAll();
}
@ -86,8 +78,7 @@ public class ManifestAttributes {
if (xmlStream == null) {
throw new JadxRuntimeException(xml + " not found in classpath");
}
DocumentBuilder dBuilder = XmlSecurity.getDBF().newDocumentBuilder();
doc = dBuilder.parse(xmlStream);
doc = security.parseXml(xmlStream);
} catch (Exception e) {
throw new JadxRuntimeException("Xml load error, file: " + xml, e);
}

View File

@ -87,7 +87,7 @@ public class ResTableBinaryParser extends CommonBinaryParser implements IResTabl
public void decode(InputStream inputStream) throws IOException {
long start = System.currentTimeMillis();
is = new ParserStream(inputStream);
resStorage = new ResourceStorage();
resStorage = new ResourceStorage(root.getArgs().getSecurity());
decodeTableChunk();
resStorage.finish();
if (LOG.isDebugEnabled()) {
@ -99,7 +99,7 @@ public class ResTableBinaryParser extends CommonBinaryParser implements IResTabl
@Override
public ResContainer decodeFiles() {
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
ResXmlGen resGen = new ResXmlGen(resStorage, vp, root.initManifestAttributes());
ICodeInfo content = XmlGenUtils.makeXmlDump(root.makeCodeWriter(), resStorage);
List<ResContainer> xmlFiles = resGen.makeResourcesXml(root.getArgs());

View File

@ -43,10 +43,12 @@ public class ResXmlGen {
private final ResourceStorage resStorage;
private final ValuesParser vp;
private final ManifestAttributes manifestAttributes;
public ResXmlGen(ResourceStorage resStorage, ValuesParser vp) {
public ResXmlGen(ResourceStorage resStorage, ValuesParser vp, ManifestAttributes manifestAttributes) {
this.resStorage = resStorage;
this.vp = vp;
this.manifestAttributes = manifestAttributes;
}
public List<ResContainer> makeResourcesXml(JadxArgs args) {
@ -191,7 +193,7 @@ public class ResXmlGen {
if (dataType == ParserConstants.TYPE_INT_DEC && nameStr != null) {
try {
int intVal = Integer.parseInt(valueStr);
String newVal = ManifestAttributes.getInstance().decode(nameStr.replace("android:", "").replace("attr.", ""), intVal);
String newVal = manifestAttributes.decode(nameStr.replace("android:", "").replace("attr.", ""), intVal);
if (newVal != null) {
valueStr = newVal;
}
@ -202,7 +204,7 @@ public class ResXmlGen {
if (dataType == ParserConstants.TYPE_INT_HEX && nameStr != null) {
try {
int intVal = Integer.decode(valueStr);
String newVal = ManifestAttributes.getInstance().decode(nameStr.replace("android:", "").replace("attr.", ""), intVal);
String newVal = manifestAttributes.decode(nameStr.replace("android:", "").replace("attr.", ""), intVal);
if (newVal != null) {
valueStr = newVal;
}

View File

@ -7,6 +7,7 @@ import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import jadx.api.security.IJadxSecurity;
import jadx.core.xmlgen.entry.ResourceEntry;
public class ResourceStorage {
@ -16,6 +17,8 @@ public class ResourceStorage {
.thenComparing(ResourceEntry::getKeyName);
private final List<ResourceEntry> list = new ArrayList<>();
private final IJadxSecurity security;
private String appPackage;
/**
@ -28,6 +31,10 @@ public class ResourceStorage {
*/
private final Map<Integer, String> renames = new HashMap<>();
public ResourceStorage(IJadxSecurity security) {
this.security = security;
}
public void add(ResourceEntry resEntry) {
list.add(resEntry);
uniqNameEntries.put(resEntry, resEntry);
@ -76,7 +83,7 @@ public class ResourceStorage {
}
public void setAppPackage(String appPackage) {
this.appPackage = XmlSecurity.verifyAppPackage(appPackage);
this.appPackage = security.verifyAppPackage(appPackage);
}
public Map<Integer, String> getResourcesNames() {

View File

@ -1,54 +0,0 @@
package jadx.core.xmlgen;
import javax.xml.parsers.DocumentBuilderFactory;
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 final boolean DISABLE_CHECKS = Utils.getEnvVarBool("JADX_DISABLE_XML_SECURITY", false);
private static final DocumentBuilderFactory DBF_INSTANCE = buildDBF();
private XmlSecurity() {
}
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);
}
}
}

View File

@ -1,17 +0,0 @@
package jadx.api;
import jadx.core.dex.nodes.RootNode;
import jadx.core.xmlgen.BinaryXMLParser;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class JadxDecompilerTestUtils {
public static JadxDecompiler getMockDecompiler() {
JadxDecompiler decompiler = mock(JadxDecompiler.class);
RootNode rootNode = new RootNode(new JadxArgs());
when(decompiler.getRoot()).thenReturn(rootNode);
when(decompiler.getBinaryXmlParser()).thenReturn(new BinaryXMLParser(rootNode));
return decompiler;
}
}

View File

@ -8,6 +8,9 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jadx.api.JadxArgs;
import jadx.api.security.IJadxSecurity;
import jadx.api.security.JadxSecurityFlag;
import jadx.api.security.impl.JadxSecurity;
import jadx.core.xmlgen.entry.RawNamedValue;
import jadx.core.xmlgen.entry.RawValue;
import jadx.core.xmlgen.entry.ResourceEntry;
@ -17,6 +20,8 @@ import static org.assertj.core.api.Assertions.assertThat;
class ResXmlGenTest {
private final JadxArgs args = new JadxArgs();
private final IJadxSecurity security = new JadxSecurity(JadxSecurityFlag.all());
private final ManifestAttributes manifestAttributes = new ManifestAttributes(security);
@BeforeEach
void init() {
@ -25,13 +30,13 @@ class ResXmlGenTest {
@Test
void testSimpleAttr() {
ResourceStorage resStorage = new ResourceStorage();
ResourceStorage resStorage = new ResourceStorage(security);
ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "attr", "size", "");
re.setNamedValues(Lists.list(new RawNamedValue(16777216, new RawValue(16, 64))));
resStorage.add(re);
ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertThat(files).hasSize(1);
@ -46,14 +51,14 @@ class ResXmlGenTest {
@Test
void testAttrEnum() {
ResourceStorage resStorage = new ResourceStorage();
ResourceStorage resStorage = new ResourceStorage(security);
ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "attr", "size", "");
re.setNamedValues(
Lists.list(new RawNamedValue(16777216, new RawValue(16, 65536)), new RawNamedValue(17039620, new RawValue(16, 1))));
resStorage.add(re);
ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertThat(files).hasSize(1);
@ -69,14 +74,14 @@ class ResXmlGenTest {
@Test
void testAttrFlag() {
ResourceStorage resStorage = new ResourceStorage();
ResourceStorage resStorage = new ResourceStorage(security);
ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "attr", "size", "");
re.setNamedValues(
Lists.list(new RawNamedValue(16777216, new RawValue(16, 131072)), new RawNamedValue(17039620, new RawValue(16, 1))));
resStorage.add(re);
ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertThat(files).hasSize(1);
@ -92,14 +97,14 @@ class ResXmlGenTest {
@Test
void testAttrMin() {
ResourceStorage resStorage = new ResourceStorage();
ResourceStorage resStorage = new ResourceStorage(security);
ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "attr", "size", "");
re.setNamedValues(
Lists.list(new RawNamedValue(16777216, new RawValue(16, 4)), new RawNamedValue(16777217, new RawValue(16, 1))));
resStorage.add(re);
ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertThat(files).hasSize(1);
@ -114,7 +119,7 @@ class ResXmlGenTest {
@Test
void testStyle() {
ResourceStorage resStorage = new ResourceStorage();
ResourceStorage resStorage = new ResourceStorage(security);
ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "style", "JadxGui", "");
re.setNamedValues(Lists.list(new RawNamedValue(16842836, new RawValue(1, 17170445))));
resStorage.add(re);
@ -124,7 +129,7 @@ class ResXmlGenTest {
re.setNamedValues(new ArrayList<>());
resStorage.add(re);
ValuesParser vp = new ValuesParser(null, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertThat(files).hasSize(1);
@ -142,7 +147,7 @@ class ResXmlGenTest {
@Test
void testString() {
ResourceStorage resStorage = new ResourceStorage();
ResourceStorage resStorage = new ResourceStorage(security);
ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "string", "app_name", "");
re.setSimpleValue(new RawValue(3, 0));
re.setNamedValues(Lists.list());
@ -151,7 +156,7 @@ class ResXmlGenTest {
BinaryXMLStrings strings = new BinaryXMLStrings();
strings.put(0, "Jadx Decompiler App");
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertThat(files).hasSize(1);
@ -165,7 +170,7 @@ class ResXmlGenTest {
@Test
void testStringFormattedFalse() {
ResourceStorage resStorage = new ResourceStorage();
ResourceStorage resStorage = new ResourceStorage(security);
ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "string", "app_name", "");
re.setSimpleValue(new RawValue(3, 0));
re.setNamedValues(Lists.list());
@ -174,7 +179,7 @@ class ResXmlGenTest {
BinaryXMLStrings strings = new BinaryXMLStrings();
strings.put(0, "%s at %s");
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertThat(files).hasSize(1);
@ -188,7 +193,7 @@ class ResXmlGenTest {
@Test
void testArrayEscape() {
ResourceStorage resStorage = new ResourceStorage();
ResourceStorage resStorage = new ResourceStorage(security);
ResourceEntry re = new ResourceEntry(2130903103, "jadx.gui.app", "array", "single_quote_escape_sample", "");
re.setNamedValues(
Lists.list(new RawNamedValue(16777216, new RawValue(3, 0))));
@ -197,7 +202,7 @@ class ResXmlGenTest {
BinaryXMLStrings strings = new BinaryXMLStrings();
strings.put(0, "Let's go");
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp, manifestAttributes);
List<ResContainer> files = resXmlGen.makeResourcesXml(args);
assertThat(files).hasSize(1);

View File

@ -2,58 +2,44 @@ package jadx.tests.api;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.io.TempDir;
import jadx.api.ICodeInfo;
import jadx.api.JadxDecompiler;
import jadx.api.JadxDecompilerTestUtils;
import jadx.api.JadxArgs;
import jadx.api.ResourceFile;
import jadx.api.ResourceFileContent;
import jadx.api.ResourceType;
import jadx.api.impl.SimpleCodeInfo;
import jadx.core.dex.nodes.RootNode;
import jadx.core.export.ExportGradleProject;
import jadx.core.export.ExportGradleTask;
import jadx.core.xmlgen.ResContainer;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public abstract class ExportGradleTest {
private final RootNode root;
public ExportGradleTest() {
final JadxDecompiler decompiler = JadxDecompilerTestUtils.getMockDecompiler();
root = decompiler.getRoot();
}
private static final String MANIFEST_TESTS_DIR = "src/test/manifest";
private final RootNode root = new RootNode(new JadxArgs());
@TempDir
private File exportDir;
protected ResContainer createResourceContainer(String filename) {
final ResContainer container = mock(ResContainer.class);
ICodeInfo codeInfo = mock(ICodeInfo.class);
when(codeInfo.getCodeStr()).thenReturn(loadFileContent(new File(MANIFEST_TESTS_DIR, filename)));
when(container.getText()).thenReturn(codeInfo);
return container;
protected ICodeInfo loadResource(String filename) {
return new SimpleCodeInfo(loadFileContent(new File(MANIFEST_TESTS_DIR, filename)));
}
private static String loadFileContent(File filePath) {
StringBuilder contentBuilder = new StringBuilder();
try (Stream<String> stream = Files.lines(filePath.toPath(), StandardCharsets.UTF_8)) {
stream.forEach(s -> contentBuilder.append(s).append("\n"));
try {
return Files.readString(filePath.toPath());
} catch (IOException e) {
fail("Loading file failed: %s", e.getMessage());
fail("Loading file failed", e);
return "";
}
return contentBuilder.toString();
}
protected RootNode getRootNode() {
@ -61,30 +47,24 @@ public abstract class ExportGradleTest {
}
protected void exportGradle(String manifestFilename, String stringsFileName) {
ResourceFile androidManifest = mock(ResourceFile.class);
final ResContainer androidManifestContainer = createResourceContainer(manifestFilename);
when(androidManifest.loadContent()).thenReturn(androidManifestContainer);
final ResContainer strings = createResourceContainer(stringsFileName);
ResourceFile androidManifest = new ResourceFileContent(manifestFilename,
ResourceType.XML, loadResource(manifestFilename));
ResContainer strings = ResContainer.textResource(stringsFileName, loadResource(stringsFileName));
final ExportGradleTask exportGradleTask = new ExportGradleTask(List.of(androidManifest), root, exportDir);
ExportGradleTask exportGradleTask = new ExportGradleTask(List.of(androidManifest), root, exportDir);
exportGradleTask.init();
assertThat(exportGradleTask.getSrcOutDir()).exists();
assertThat(exportGradleTask.getResOutDir()).exists();
final ExportGradleProject export =
new ExportGradleProject(root, exportDir, androidManifest, strings);
ExportGradleProject export = new ExportGradleProject(root, exportDir, androidManifest, strings);
export.generateGradleFiles();
}
protected String getAppGradleBuild() {
File appBuildGradle = new File(exportDir, "app/build.gradle");
assertThat(appBuildGradle).exists();
return loadFileContent(appBuildGradle);
return loadFileContent(new File(exportDir, "app/build.gradle"));
}
protected String getSettingsGradle() {
File settingsGradle = new File(exportDir, "settings.gradle");
assertThat(settingsGradle).exists();
return loadFileContent(settingsGradle);
return loadFileContent(new File(exportDir, "settings.gradle"));
}
}

View File

@ -342,7 +342,7 @@ public abstract class IntegrationTest extends TestUtils {
if (resMap.isEmpty()) {
return;
}
ResourceStorage resStorage = new ResourceStorage();
ResourceStorage resStorage = new ResourceStorage(root.getArgs().getSecurity());
for (Map.Entry<Integer, String> entry : resMap.entrySet()) {
Integer id = entry.getKey();
String name = entry.getValue();

View File

@ -148,7 +148,8 @@ public class DbgUtils {
}
AndroidManifestParser parser = new AndroidManifestParser(
AndroidManifestParser.getAndroidManifest(decompiler.getResources()),
EnumSet.of(AppAttribute.MAIN_ACTIVITY));
EnumSet.of(AppAttribute.MAIN_ACTIVITY),
decompiler.getArgs().getSecurity());
if (!parser.isManifestFound()) {
UiUtils.errorMessage(mw, NLS.str("error_dialog.not_found", "AndroidManifest.xml"));
return null;

View File

@ -952,7 +952,8 @@ public class MainWindow extends JFrame {
public void goToMainActivity() {
AndroidManifestParser parser = new AndroidManifestParser(
AndroidManifestParser.getAndroidManifest(getWrapper().getResources()),
EnumSet.of(AppAttribute.MAIN_ACTIVITY));
EnumSet.of(AppAttribute.MAIN_ACTIVITY),
getWrapper().getArgs().getSecurity());
if (!parser.isManifestFound()) {
JOptionPane.showMessageDialog(MainWindow.this,
NLS.str("error_dialog.not_found", "AndroidManifest.xml"),
@ -982,7 +983,8 @@ public class MainWindow extends JFrame {
public void goToApplication() {
AndroidManifestParser parser = new AndroidManifestParser(
AndroidManifestParser.getAndroidManifest(getWrapper().getResources()),
EnumSet.of(AppAttribute.APPLICATION));
EnumSet.of(AppAttribute.APPLICATION),
getWrapper().getArgs().getSecurity());
if (!parser.isManifestFound()) {
JOptionPane.showMessageDialog(MainWindow.this,
NLS.str("error_dialog.not_found", "AndroidManifest.xml"),

View File

@ -34,7 +34,7 @@ public class ResTableProtoParser extends CommonProtoParser implements IResTableP
@Override
public void decode(InputStream inputStream) throws IOException {
resStorage = new ResourceStorage();
resStorage = new ResourceStorage(root.getArgs().getSecurity());
ResourceTable table = ResourceTable.parseFrom(FileUtils.streamToByteArray(inputStream));
for (Package p : table.getPackageList()) {
parse(p);
@ -45,7 +45,7 @@ public class ResTableProtoParser extends CommonProtoParser implements IResTableP
@Override
public synchronized ResContainer decodeFiles() {
ValuesParser vp = new ValuesParser(new BinaryXMLStrings(), resStorage.getResourcesNames());
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
ResXmlGen resGen = new ResXmlGen(resStorage, vp, root.initManifestAttributes());
ICodeInfo content = XmlGenUtils.makeXmlDump(root.makeCodeWriter(), resStorage);
List<ResContainer> xmlFiles = resGen.makeResourcesXml(root.getArgs());
return ResContainer.resourceTable("res", xmlFiles, content);