diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index d41d7a7c3..03cd422de 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -203,20 +203,21 @@ public final class JadxDecompiler { reset(); root = new RootNode(); LOG.info("loading ..."); + root.load(inputFiles); + if (this.args.isXMLTest()) { InputFile inf = inputFiles.get(0); try { byte[] buffer = InputFile.loadXMLBuffer(inf.getFile()); if (buffer != null) { File out = new File(args.getOutDir(), "AndroidManifest.xml"); - BinaryXMLParser bxp = new BinaryXMLParser(); + BinaryXMLParser bxp = new BinaryXMLParser(root); bxp.parse(buffer, out); } } catch (Exception e) { LOG.info("Decompiling AndroidManifest.xml failed!", e); } } - root.load(inputFiles); } void processClass(ClassNode cls) { diff --git a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java index 02c74da92..904049108 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java +++ b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java @@ -174,6 +174,10 @@ public class CodeWriter { updateIndent(); } + public int getIndent() { + return indent; + } + public int getLine() { return line; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index 03ca73aca..d7ba7f25c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -104,6 +104,10 @@ public class RootNode { return searchClassByName(fullName); } + public List getDexNodes() { + return dexNodes; + } + public ErrorsCounter getErrorsCounter() { return errorsCounter; } diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java index 128610f1a..26f5ff8fb 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java @@ -1,6 +1,10 @@ package jadx.core.xmlgen; import jadx.core.codegen.CodeWriter; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.nodes.DexNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.JadxRuntimeException; import java.io.File; @@ -40,16 +44,28 @@ public class BinaryXMLParser { private boolean wasOneLiner = false; private CodeWriter writer; - private Map styleMap = null; + private Map styleMap = new HashMap(); + private Map localStyleMap = new HashMap(); + private final ManifestAttributes attributes; - public BinaryXMLParser() { - styleMap = new HashMap(); + public BinaryXMLParser(RootNode root) { try { for (Field f : AndroidR.style.class.getFields()) { styleMap.put(f.getInt(f.getType()), f.getName()); } - //TODO: also add application constant fields - } catch (IllegalAccessException e) { + // add application constants + for (DexNode dexNode : root.getDexNodes()) { + for (Map.Entry entry : dexNode.getConstFields().entrySet()) { + Object key = entry.getKey(); + FieldNode field = entry.getValue(); + if (field.getType().equals(ArgType.INT) && key instanceof Integer) { + localStyleMap.put((Integer) key, field); + } + } + } + attributes = new ManifestAttributes(); + attributes.parse(); + } catch (Exception e) { throw new JadxRuntimeException("BinaryXMLParser init error", e); } } @@ -194,7 +210,7 @@ public class BinaryXMLParser { } wasOneLiner = false; currentTag = strings[startNSName]; - writer.startLine("<" + strings[startNSName]); + writer.startLine("<").add(strings[startNSName]); int attributeStart = cInt16(bytes, count); if (attributeStart != 0x14) { die("startNS's attributeStart is not 0x14"); @@ -208,7 +224,7 @@ public class BinaryXMLParser { int classIndex = cInt16(bytes, count); int styleIndex = cInt16(bytes, count); if ("manifest".equals(strings[startNSName])) { - writer.add(" xmlns:\"" + nsURI + "\""); + writer.add(" xmlns:\"").add(nsURI).add("\""); } if (attributeCount > 0) { writer.add(" "); @@ -227,53 +243,59 @@ public class BinaryXMLParser { int attrValDataType = cInt8(bytes, count); int attrValData = cInt32(bytes, count); if (attributeNS != -1) { - writer.add(nsPrefix + ":"); + writer.add(nsPrefix).add(':'); } - writer.add(strings[attributeName] + "=\""); - if (attrValDataType == 0x3) { - writer.add(strings[attrValData]); - } else if (attrValDataType == 0x10) { - writer.add(String.valueOf(attrValData)); - } else if (attrValDataType == 0x12) { - // FIXME: What to do, when data is always -1? - if (attrValData == 0) { - writer.add("false"); - } else if (attrValData == 1 || attrValData == -1) { - writer.add("true"); - } else { - writer.add("UNKNOWN_BOOLEAN_TYPE"); - } - } else if (attrValDataType == 0x1) { - String name = styleMap.get(attrValData); - if (name != null) { - writer.add("@*"); - if (attributeNS != -1) { - writer.add(nsPrefix + ":"); - } - writer.add("style/" + name.replaceAll("_", ".")); - } else { - writer.add("0x" + Integer.toHexString(attrValData)); - } + String attrName = strings[attributeName]; + writer.add(attrName).add("=\""); + String decodedAttr = attributes.decode(attrName, attrValData); + if (decodedAttr != null) { + writer.add(decodedAttr); } else { - // TODO: extract values names from here: - // https://github.com/android/platform_frameworks_base/blob/master/core/res/res/values/attrs_manifest.xml - if ("configChanges".equals(strings[attributeName])) { - if (attrValData == 1152) { - writer.add("orientation"); - } else if (attrValData == 4016) { - writer.add("keyboard|keyboardHidden|orientation|screenLayout|uiMode"); - } else if (attrValData == 176) { - writer.add("keyboard|keyboardHidden|orientation"); - } else if (attrValData == 160) { - writer.add("keyboardHidden|orientation"); - } else { - writer.add("UNKNOWN_DATA_" + Integer.toHexString(attrValData)); - } - } else { - writer.add("UNKNOWN_DATA_TYPE_" + attrValDataType); + switch (attrValDataType) { + case 0x3: + writer.add(strings[attrValData]); + break; + + case 0x10: + writer.add(String.valueOf(attrValData)); + break; + + case 0x12: + // FIXME: What to do, when data is always -1? + if (attrValData == 0) { + writer.add("false"); + } else if (attrValData == 1 || attrValData == -1) { + writer.add("true"); + } else { + writer.add("UNKNOWN_BOOLEAN_TYPE"); + } + break; + + case 0x1: + String name = styleMap.get(attrValData); + if (name != null) { + writer.add("@*"); + if (attributeNS != -1) { + writer.add(nsPrefix).add(':'); + } + writer.add("style/").add(name.replaceAll("_", ".")); + } else { + FieldNode field = localStyleMap.get(attrValData); + if (field != null) { + String cls = field.getParentClass().getShortName().toLowerCase(); + writer.add("@").add(cls).add("/").add(field.getName()); + } else { + writer.add("0x").add(Integer.toHexString(attrValData)); + } + } + break; + + default: + writer.add("UNKNOWN_DATA_TYPE_" + attrValDataType); + break; } } - writer.add("\""); + writer.add('"'); if ((i + 1) < attributeCount) { writer.add(" "); } @@ -292,16 +314,18 @@ public class BinaryXMLParser { int elementNS = cInt32(bytes, count); int elementName = cInt32(bytes, count); if (currentTag.equals(strings[elementName])) { - writer.add("/>"); + writer.add(" />"); wasOneLiner = true; } else { writer.startLine(""); + writer.add(strings[elementName]).add(">"); + } + if (writer.getIndent() != 0) { + writer.decIndent(); } - writer.decIndent(); } private int cInt8(byte[] bytes, int offset) { diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java b/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java new file mode 100644 index 000000000..a6388bc4b --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ManifestAttributes.java @@ -0,0 +1,159 @@ +package jadx.core.xmlgen; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class ManifestAttributes { + private static final Logger LOG = LoggerFactory.getLogger(ManifestAttributes.class); + + private static final String MANIFEST_ATTR_XML = "/attrs_manifest.xml"; + + private enum MAttrType { + ENUM, FLAG + } + + private static class MAttr { + private final MAttrType type; + private final Map values = new LinkedHashMap(); + + public MAttr(MAttrType type) { + this.type = type; + } + + public MAttrType getType() { + return type; + } + + public Map getValues() { + return values; + } + + @Override + public String toString() { + return "[" + type + ", " + values + "]"; + } + } + + private final Document doc; + private final Map attrMap = new HashMap(); + + public ManifestAttributes() throws Exception { + InputStream xmlStream = ManifestAttributes.class.getResourceAsStream(MANIFEST_ATTR_XML); + DocumentBuilder dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + doc = dBuilder.parse(xmlStream); + } + + public void parse() { + NodeList nodeList = doc.getChildNodes(); + for (int count = 0; count < nodeList.getLength(); count++) { + Node node = nodeList.item(count); + if (node.getNodeType() == Node.ELEMENT_NODE) { + if (node.hasChildNodes()) { + parseAttrList(node.getChildNodes()); + } + } + } + } + + private void parseAttrList(NodeList nodeList) { + for (int count = 0; count < nodeList.getLength(); count++) { + Node tempNode = nodeList.item(count); + if (tempNode.getNodeType() == Node.ELEMENT_NODE + && tempNode.hasAttributes() + && tempNode.hasChildNodes()) { + String name = null; + NamedNodeMap nodeMap = tempNode.getAttributes(); + for (int i = 0; i < nodeMap.getLength(); i++) { + Node node = nodeMap.item(i); + if (node.getNodeName().equals("name")) { + name = node.getNodeValue(); + break; + } + } + if (name != null && tempNode.getNodeName().equals("attr")) { + parseValues(name, tempNode.getChildNodes()); + } else { + parseAttrList(tempNode.getChildNodes()); + } + } + } + } + + private void parseValues(String name, NodeList nodeList) { + MAttr attr = null; + for (int count = 0; count < nodeList.getLength(); count++) { + Node tempNode = nodeList.item(count); + if (tempNode.getNodeType() == Node.ELEMENT_NODE + && tempNode.hasAttributes()) { + + if (attr == null) { + if (tempNode.getNodeName().equals("enum")) { + attr = new MAttr(MAttrType.ENUM); + } else if (tempNode.getNodeName().equals("flag")) { + attr = new MAttr(MAttrType.FLAG); + } + if (attr == null) { + return; + } + attrMap.put(name, attr); + } + + NamedNodeMap attributes = tempNode.getAttributes(); + Node nameNode = attributes.getNamedItem("name"); + if (nameNode != null) { + Node valueNode = attributes.getNamedItem("value"); + if (valueNode != null) { + try { + int key; + String nodeValue = valueNode.getNodeValue(); + if (attr.getType() == MAttrType.ENUM) { + key = Integer.parseInt(nodeValue); + } else { + nodeValue = nodeValue.replace("0x", ""); + key = Integer.parseInt(nodeValue, 16); + } + attr.getValues().put(key, nameNode.getNodeValue()); + } catch (NumberFormatException e) { + LOG.debug("Failed parse manifest number", e); + } + } + } + } + } + } + + public String decode(String attrName, int value) { + MAttr attr = attrMap.get(attrName); + if (attr == null) { + return null; + } + if (attr.getType() == MAttrType.ENUM) { + String name = attr.getValues().get(value); + if (name != null) { + return name; + } + } else if (attr.getType() == MAttrType.FLAG) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : attr.getValues().entrySet()) { + if ((value & entry.getKey()) != 0) { + sb.append(entry.getValue()).append('|'); + } + } + if (sb.length() != 0) { + return sb.deleteCharAt(sb.length() - 1).toString(); + } + } + return "UNKNOWN_DATA_" + Integer.toHexString(value); + } +} diff --git a/jadx-core/src/main/resources/attrs_manifest.xml b/jadx-core/src/main/resources/attrs_manifest.xml new file mode 100644 index 000000000..21f3bfa85 --- /dev/null +++ b/jadx-core/src/main/resources/attrs_manifest.xml @@ -0,0 +1,1665 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +