From 004e1bc24fedb5f4bd568eb7ec0d79e15e84a214 Mon Sep 17 00:00:00 2001 From: Tim Strazzere Date: Tue, 6 Aug 2024 12:55:31 -0700 Subject: [PATCH] (fix) Fully close issue #8 All tests passing. Properly recover from a mangled start tag. Allow injection correctly. --- .../android/content/res/AXMLResource.java | 44 +++++++++++++++---- .../res/chunk/sections/ResourceSection.java | 4 +- .../res/chunk/sections/StringSection.java | 3 +- .../content/res/chunk/types/AXMLHeader.java | 3 +- .../content/res/chunk/types/Attribute.java | 12 +++-- .../content/res/chunk/types/Buffer.java | 3 +- .../content/res/chunk/types/Chunk.java | 4 +- .../content/res/chunk/types/EndTag.java | 20 ++++++++- .../content/res/chunk/types/NameSpace.java | 3 +- .../content/res/chunk/types/StartTag.java | 30 ++++++++++++- .../content/res/chunk/types/TextTag.java | 3 +- src/main/java/diff/rednaga/AXMLPrinter.java | 2 +- .../java/android/content/res/TestIssue8.java | 20 ++++----- .../res/chunk/types/GenericChunkTest.java | 3 +- 14 files changed, 120 insertions(+), 34 deletions(-) diff --git a/src/main/java/android/content/res/AXMLResource.java b/src/main/java/android/content/res/AXMLResource.java index a241ff1..ef40560 100644 --- a/src/main/java/android/content/res/AXMLResource.java +++ b/src/main/java/android/content/res/AXMLResource.java @@ -22,7 +22,9 @@ import android.content.res.chunk.types.AXMLHeader; import android.content.res.chunk.types.Attribute; import android.content.res.chunk.types.Chunk; +import android.content.res.chunk.types.NameSpace; import android.content.res.chunk.types.StartTag; +import android.content.res.chunk.types.EndTag; import java.io.IOException; import java.io.InputStream; @@ -44,14 +46,17 @@ public class AXMLResource { AXMLHeader header; StringSection stringSection; ResourceSection resourceSection; + List nameSpaces; LinkedHashSet chunks; public AXMLResource() { chunks = new LinkedHashSet(); + nameSpaces = new ArrayList(); } public AXMLResource(InputStream stream) throws IOException { chunks = new LinkedHashSet(); + nameSpaces = new ArrayList(); if (!read(stream)) { throw new IOException(); } @@ -160,11 +165,11 @@ public void write(OutputStream outputStream) throws IOException { } public void print() { - - log("%s", header.toXML(stringSection, resourceSection, 0)); + log("%s", header.toXML(stringSection, resourceSection, nameSpaces, 0)); Iterator iterator = chunks.iterator(); int indents = 0; List namespaceXmlList = new ArrayList(); + int lastStartNameIndex = -1; while (iterator.hasNext()) { Chunk chunk = iterator.next(); @@ -173,14 +178,24 @@ public void print() { } if (chunk.getChunkType() == ChunkType.START_NAMESPACE) { - namespaceXmlList.add(chunk.toXML(stringSection, resourceSection, indents)); + namespaceXmlList.add(chunk.toXML(stringSection, resourceSection, nameSpaces, indents)); + nameSpaces.add((NameSpace)chunk); } else if (chunk.getChunkType() == ChunkType.END_NAMESPACE) { // ignore } else { + if (chunk.getChunkType() == ChunkType.START_TAG && ((StartTag)chunk).isMangled(stringSection)) { + ((StartTag)chunk).fixMangle(stringSection); + lastStartNameIndex = stringSection.getStringIndex(((StartTag)chunk).getName(stringSection)); + } + if (chunk.getChunkType() == ChunkType.END_TAG && ((EndTag)chunk).isMangled(stringSection)) { + ((EndTag)chunk).setName(lastStartNameIndex); + lastStartNameIndex = -1; + } + if (namespaceXmlList.isEmpty()) { - log("%s", chunk.toXML(stringSection, resourceSection, indents)); + log("%s", chunk.toXML(stringSection, resourceSection, nameSpaces, indents)); } else { - log("%s", appendNameSpace(chunk.toXML(stringSection, resourceSection, indents), namespaceXmlList)); + log("%s", appendNameSpace(chunk.toXML(stringSection, resourceSection, nameSpaces, indents), namespaceXmlList)); namespaceXmlList.clear(); } } @@ -194,11 +209,13 @@ public void print() { public String toXML() { StringBuilder xmlStrbui = new StringBuilder(); - xmlStrbui.append(header.toXML(stringSection, resourceSection, 0)).append('\n'); + xmlStrbui.append(header.toXML(stringSection, resourceSection, nameSpaces, 0)).append('\n'); Iterator iterator = chunks.iterator(); int indents = 0; List namespaceXmlList = new ArrayList(); + int lastStartNameIndex = -1; + while (iterator.hasNext()) { Chunk chunk = iterator.next(); if (chunk.getChunkType() == ChunkType.END_TAG) { @@ -206,14 +223,23 @@ public String toXML() { } if (chunk.getChunkType() == ChunkType.START_NAMESPACE) { - namespaceXmlList.add(chunk.toXML(stringSection, resourceSection, indents)); + namespaceXmlList.add(chunk.toXML(stringSection, resourceSection, nameSpaces, indents)); + nameSpaces.add((NameSpace) chunk); } else if (chunk.getChunkType() == ChunkType.END_NAMESPACE) { // ignore } else { + if (chunk.getChunkType() == ChunkType.START_TAG && ((StartTag)chunk).isMangled(stringSection)) { + ((StartTag)chunk).fixMangle(stringSection); + lastStartNameIndex = stringSection.getStringIndex(((StartTag)chunk).getName(stringSection)); + } + if (chunk.getChunkType() == ChunkType.END_TAG && ((EndTag)chunk).isMangled(stringSection)) { + ((EndTag)chunk).setName(lastStartNameIndex); + lastStartNameIndex = -1; + } if (namespaceXmlList.isEmpty()) { - xmlStrbui.append(chunk.toXML(stringSection, resourceSection, indents)).append('\n'); + xmlStrbui.append(chunk.toXML(stringSection, resourceSection, nameSpaces, indents)).append('\n'); } else { - xmlStrbui.append(appendNameSpace(chunk.toXML(stringSection, resourceSection, indents), namespaceXmlList)).append('\n'); + xmlStrbui.append(appendNameSpace(chunk.toXML(stringSection, resourceSection, nameSpaces, indents), namespaceXmlList)).append('\n'); namespaceXmlList.clear(); } } diff --git a/src/main/java/android/content/res/chunk/sections/ResourceSection.java b/src/main/java/android/content/res/chunk/sections/ResourceSection.java index 1bf1889..39c86d1 100644 --- a/src/main/java/android/content/res/chunk/sections/ResourceSection.java +++ b/src/main/java/android/content/res/chunk/sections/ResourceSection.java @@ -18,11 +18,13 @@ import android.content.res.IntReader; import android.content.res.chunk.ChunkType; import android.content.res.chunk.types.Chunk; +import android.content.res.chunk.types.NameSpace; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.List; /** * Concrete class for the section which is specifically for the resource ids. @@ -86,7 +88,7 @@ public int getResourceCount() { * android.content.res.chunk.sections.ResourceSection, int) */ @Override - public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { + public String toXML(StringSection stringSection, ResourceSection resourceSection, List namespaceList, int indent) { return null; } diff --git a/src/main/java/android/content/res/chunk/sections/StringSection.java b/src/main/java/android/content/res/chunk/sections/StringSection.java index 0f567cc..5b85d5c 100644 --- a/src/main/java/android/content/res/chunk/sections/StringSection.java +++ b/src/main/java/android/content/res/chunk/sections/StringSection.java @@ -19,6 +19,7 @@ import android.content.res.chunk.ChunkType; import android.content.res.chunk.PoolItem; import android.content.res.chunk.types.Chunk; +import android.content.res.chunk.types.NameSpace; import java.io.IOException; import java.nio.ByteBuffer; @@ -175,7 +176,7 @@ public String getStyle(int index) { } @Override - public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { + public String toXML(StringSection stringSection, ResourceSection resourceSection, List namespaceList, int indent) { return null; } diff --git a/src/main/java/android/content/res/chunk/types/AXMLHeader.java b/src/main/java/android/content/res/chunk/types/AXMLHeader.java index 1c8c578..b98f8ef 100644 --- a/src/main/java/android/content/res/chunk/types/AXMLHeader.java +++ b/src/main/java/android/content/res/chunk/types/AXMLHeader.java @@ -21,6 +21,7 @@ import android.content.res.chunk.sections.StringSection; import java.io.IOException; +import java.util.List; /** * ChunkType which is for the AXMLHeader, should be at the beginning and only the beginning of the files. @@ -53,7 +54,7 @@ public void readHeader(IntReader inputReader) throws IOException { * android.content.res.chunk.sections.ResourceSection, int) */ @Override - public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { + public String toXML(StringSection stringSection, ResourceSection resourceSection, List namespaceList, int indent) { return indent(indent) + ""; } diff --git a/src/main/java/android/content/res/chunk/types/Attribute.java b/src/main/java/android/content/res/chunk/types/Attribute.java index d8d18db..94b8a85 100644 --- a/src/main/java/android/content/res/chunk/types/Attribute.java +++ b/src/main/java/android/content/res/chunk/types/Attribute.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.List; /** * Specific type of Chunk which contains the metadata @@ -119,11 +120,16 @@ public int getStringDataIndex() { * android.content.res.chunk.sections.ResourceSection, int) */ @Override - public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { + public String toXML(StringSection stringSection, ResourceSection resourceSection, List namespaceList, int indent) { StringBuffer buffer = new StringBuffer(); if ((uri - 1) > 0) { - buffer.append(stringSection.getString(uri - 1)); - buffer.append(":"); + for (NameSpace nameSpace : namespaceList) { + if (nameSpace.getUri() == uri) { + buffer.append(stringSection.getString(nameSpace.getPrefix())); + buffer.append(":"); + break; + } + } } buffer.append(stringSection.getString(name)); diff --git a/src/main/java/android/content/res/chunk/types/Buffer.java b/src/main/java/android/content/res/chunk/types/Buffer.java index 8e9430d..61c7a15 100644 --- a/src/main/java/android/content/res/chunk/types/Buffer.java +++ b/src/main/java/android/content/res/chunk/types/Buffer.java @@ -21,6 +21,7 @@ import android.content.res.chunk.sections.StringSection; import java.io.IOException; +import java.util.List; /** * This "buffer" chunk is currently being used for empty space, though it might not be needed @@ -74,7 +75,7 @@ public int getSize() { * android.content.res.chunk.sections.ResourceSection, int) */ @Override - public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { + public String toXML(StringSection stringSection, ResourceSection resourceSection, List namespaceList, int indent) { return null; } diff --git a/src/main/java/android/content/res/chunk/types/Chunk.java b/src/main/java/android/content/res/chunk/types/Chunk.java index a190aa9..8c1177c 100644 --- a/src/main/java/android/content/res/chunk/types/Chunk.java +++ b/src/main/java/android/content/res/chunk/types/Chunk.java @@ -21,6 +21,7 @@ import android.content.res.chunk.sections.StringSection; import java.io.IOException; +import java.util.List; /** * Generic interface for everything that is at minimum a "chunk" @@ -57,10 +58,11 @@ public interface Chunk { /** * @param stringSection * @param resourceSection + * @param namespaceList * @param indent * @return a String representation in XML form */ - public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent); + public String toXML(StringSection stringSection, ResourceSection resourceSection, List namespaceList, int indent); /** * Get the a byte[] for the chunk diff --git a/src/main/java/android/content/res/chunk/types/EndTag.java b/src/main/java/android/content/res/chunk/types/EndTag.java index 2eebdcb..68ed5c0 100644 --- a/src/main/java/android/content/res/chunk/types/EndTag.java +++ b/src/main/java/android/content/res/chunk/types/EndTag.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.List; /** * Specific chunk for ending sections and/or namespaces @@ -35,9 +36,11 @@ public class EndTag extends GenericChunk implements Chunk { private int commentIndex; private int namespaceUri; private int name; + private boolean mangled; public EndTag(ChunkType chunkType, IntReader inputReader) { super(chunkType, inputReader); + mangled = false; } /* @@ -53,6 +56,17 @@ public void readHeader(IntReader inputReader) throws IOException { name = inputReader.readInt(); } + public boolean isMangled(StringSection stringSection) { + mangled = stringSection.getString(name).isEmpty(); + + return mangled; + } + + public void setName(int newName) { + name = newName; + // Assume this is a fix for now + mangled = false; + } /* * (non-Javadoc) * @@ -60,7 +74,11 @@ public void readHeader(IntReader inputReader) throws IOException { * android.content.res.chunk.sections.ResourceSection, int) */ @Override - public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { + public String toXML(StringSection stringSection, ResourceSection resourceSection, List namespaceList, int indent) { + if (stringSection.getString(name).isEmpty()) { + mangled = true; + } + return indent(indent) + ""; } diff --git a/src/main/java/android/content/res/chunk/types/NameSpace.java b/src/main/java/android/content/res/chunk/types/NameSpace.java index 3fbca86..67770b4 100644 --- a/src/main/java/android/content/res/chunk/types/NameSpace.java +++ b/src/main/java/android/content/res/chunk/types/NameSpace.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.List; /** * Namespace Chunk - used for denoting the borders of the XML boundries @@ -83,7 +84,7 @@ public String toString(StringSection stringSection) { * android.content.res.chunk.sections.ResourceSection, int) */ @Override - public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { + public String toXML(StringSection stringSection, ResourceSection resourceSection, List namespaceList, int indent) { if (isStart()) { return indent(indent) + toString(stringSection); } else { diff --git a/src/main/java/android/content/res/chunk/types/StartTag.java b/src/main/java/android/content/res/chunk/types/StartTag.java index 1ef40b1..d4d4130 100644 --- a/src/main/java/android/content/res/chunk/types/StartTag.java +++ b/src/main/java/android/content/res/chunk/types/StartTag.java @@ -25,6 +25,7 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Iterator; +import java.util.List; /** * StartTag type of Chunk, differs from a Namespace as there will be specific metadata inside of it @@ -41,9 +42,11 @@ public class StartTag extends GenericChunk implements Chunk { private int attributeCount; private int classAttribute; private ArrayList attributes; + private boolean mangled; public StartTag(ChunkType chunkType, IntReader inputReader) { super(chunkType, inputReader); + mangled = false; } /* @@ -98,6 +101,29 @@ public String getName(StringSection stringSection) { return stringSection.getString(name); } + public boolean isMangled(StringSection stringSection) { + mangled = stringSection.getString(name).isEmpty(); + + return mangled; + } + + public void fixMangle(StringSection stringSection) { + int guessedTag = -1; + for (int i = 0; i < attributeCount; i++) { + if (stringSection.getString(attributes.get(i).getNameIndex()).contains("protectionLevel")) { + guessedTag = stringSection.getStringIndex("uses-permission"); + break; + } + } + + if (guessedTag == -1) { + // throw new IOException("Unknown start tag, unable to recover from AXML mangling"); + } + + mangled = false; + name = guessedTag; + } + /* * (non-Javadoc) * @@ -105,7 +131,7 @@ public String getName(StringSection stringSection) { * android.content.res.chunk.sections.ResourceSection, int) */ @Override - public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { + public String toXML(StringSection stringSection, ResourceSection resourceSection, List namespaceList, int indent) { StringBuffer buffer = new StringBuffer(); buffer.append(indent(indent)); buffer.append("<"); @@ -114,7 +140,7 @@ public String toXML(StringSection stringSection, ResourceSection resourceSection for (int i = 0; i < attributeCount; i++) { buffer.append(indent(indent + 1)); - buffer.append(attributes.get(i).toXML(stringSection, resourceSection, indent)); + buffer.append(attributes.get(i).toXML(stringSection, resourceSection, namespaceList, indent)); buffer.append("\n"); } diff --git a/src/main/java/android/content/res/chunk/types/TextTag.java b/src/main/java/android/content/res/chunk/types/TextTag.java index 3510e9d..a4f98e6 100644 --- a/src/main/java/android/content/res/chunk/types/TextTag.java +++ b/src/main/java/android/content/res/chunk/types/TextTag.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.List; /** * Specific Chunk which contains a text key and value @@ -63,7 +64,7 @@ public void readHeader(IntReader inputReader) throws IOException { * android.content.res.chunk.sections.ResourceSection, int) */ @Override - public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { + public String toXML(StringSection stringSection, ResourceSection resourceSection, List namespaceList, int indent) { StringBuffer buffer = new StringBuffer(); buffer.append(indent(indent)); diff --git a/src/main/java/diff/rednaga/AXMLPrinter.java b/src/main/java/diff/rednaga/AXMLPrinter.java index 156deb4..1c5eb52 100644 --- a/src/main/java/diff/rednaga/AXMLPrinter.java +++ b/src/main/java/diff/rednaga/AXMLPrinter.java @@ -57,7 +57,7 @@ public static void main(String[] arguments) throws IOException { if (arguments[0].equalsIgnoreCase("-v") || arguments[0].equalsIgnoreCase("-version")) { System.out.printf("axmlprinter %s (http://github.com/rednaga/axmlprinter2)\n", VERSION); - System.out.printf("Copyright (C) 2015 Red Naga - Tim 'diff' Strazzere (strazz@gmail.com)\n"); + System.out.printf("Copyright (C) 2015-2024 Red Naga - Tim 'diff' Strazzere (diff@protonmail.com)\n"); return; } diff --git a/src/test/java/android/content/res/TestIssue8.java b/src/test/java/android/content/res/TestIssue8.java index 8b157a5..46f311b 100644 --- a/src/test/java/android/content/res/TestIssue8.java +++ b/src/test/java/android/content/res/TestIssue8.java @@ -52,18 +52,18 @@ public void testToXml() throws IOException, ParserConfigurationException { underTest = new AXMLResource(testStream); String xml = underTest.toXML(); - // try { + try { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes())); Node manifestNode = document.getFirstChild(); NamedNodeMap manifestNodeAttributes = manifestNode.getAttributes(); assertEquals("http://schemas.android.com/apk/res/android", manifestNodeAttributes.getNamedItem("xmlns:android").getNodeValue()); - assertEquals("3133", manifestNodeAttributes.getNamedItem("android:versionCode").getNodeValue()); - assertEquals("1.9.3", manifestNodeAttributes.getNamedItem("android:versionName").getNodeValue()); - assertEquals("com.faithcomesbyhearing.android.pt.bibleis", manifestNodeAttributes.getNamedItem("package").getNodeValue()); - // } catch (SAXException e) { - // // Is not xml - // assertTrue(false); - // } + assertEquals("14800", manifestNodeAttributes.getNamedItem("android:versionCode").getNodeValue()); + assertEquals("1.4.8", manifestNodeAttributes.getNamedItem("android:versionName").getNodeValue()); + assertEquals("cn.xiaochuankeji.zuiyouLite", manifestNodeAttributes.getNamedItem("package").getNodeValue()); + } catch (SAXException e) { + // Is not xml + assertTrue(false); + } } @Test @@ -109,9 +109,9 @@ public void testWriteInsertedApplicationAttribute() throws IOException { underTest = new AXMLResource(new FileInputStream(file)); StartTag startTag = underTest.getApplicationTag(); - assertEquals(underTest.getStringSection().getString(startTag.getAttributes().get(3).getNameIndex()), + assertEquals(underTest.getStringSection().getString(startTag.getAttributes().get(9).getNameIndex()), "name"); - assertEquals(underTest.getStringSection().getString(startTag.getAttributes().get(3).getStringDataIndex()), + assertEquals(underTest.getStringSection().getString(startTag.getAttributes().get(9).getStringDataIndex()), "test"); } } diff --git a/src/test/java/android/content/res/chunk/types/GenericChunkTest.java b/src/test/java/android/content/res/chunk/types/GenericChunkTest.java index c790604..9686908 100644 --- a/src/test/java/android/content/res/chunk/types/GenericChunkTest.java +++ b/src/test/java/android/content/res/chunk/types/GenericChunkTest.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.mockito.Mockito.mock; @@ -47,7 +48,7 @@ public void readHeader(IntReader reader) throws IOException { } @Override - public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { + public String toXML(StringSection stringSection, ResourceSection resourceSection, List nameSpaces, int indent) { return null; }