.apk 패키지 내의 AndroidManifest.xml 파일을 구문 분석하는 방법
이 파일은 이진 XML 형식으로 되어 있습니다.이 형식은 무엇이며 (SDK에서 apt 덤프 도구를 사용하는 것과 달리) 프로그래밍 방식으로 구문 분석할 수 있는 방법은 무엇입니까?
이 이진 형식은 여기 설명서에서 설명하지 않습니다.
참고: Android 환경 외부에서, 가급적이면 Java에서 이 정보에 액세스하고 싶습니다.
Android-apk 도구 사용
apk 파일을 읽고 XML을 거의 원래 형태로 디코딩하는 응용 프로그램이 있습니다.
용도:
apktool d Gmail.apk && cat Gmail/AndroidManifest.xml
자세한 내용은 Android-apk tool 확인
Android에서 실행되는 이 Java 메서드는 .apk 패키지에 AndroidManifest.xml 파일의 이진 형식을 문서화합니다.두 번째 코드 상자에는 압축 해제XML을 호출하는 방법과 장치의 앱 패키지 파일에서 바이트[]를 로드하는 방법이 나와 있습니다.(목적을 이해하지 못하는 분야가 있는데, 무슨 뜻인지 아시는 분 말씀해주시면 정보를 업데이트하겠습니다.)
// decompressXML -- Parse the 'compressed' binary form of Android XML docs
// such as for AndroidManifest.xml in .apk files
public static int endDocTag = 0x00100101;
public static int startTag = 0x00100102;
public static int endTag = 0x00100103;
public void decompressXML(byte[] xml) {
// Compressed XML file/bytes starts with 24x bytes of data,
// 9 32 bit words in little endian order (LSB first):
// 0th word is 03 00 08 00
// 3rd word SEEMS TO BE: Offset at then of StringTable
// 4th word is: Number of strings in string table
// WARNING: Sometime I indiscriminently display or refer to word in
// little endian storage format, or in integer format (ie MSB first).
int numbStrings = LEW(xml, 4*4);
// StringIndexTable starts at offset 24x, an array of 32 bit LE offsets
// of the length/string data in the StringTable.
int sitOff = 0x24; // Offset of start of StringIndexTable
// StringTable, each string is represented with a 16 bit little endian
// character count, followed by that number of 16 bit (LE) (Unicode) chars.
int stOff = sitOff + numbStrings*4; // StringTable follows StrIndexTable
// XMLTags, The XML tag tree starts after some unknown content after the
// StringTable. There is some unknown data after the StringTable, scan
// forward from this point to the flag for the start of an XML start tag.
int xmlTagOff = LEW(xml, 3*4); // Start from the offset in the 3rd word.
// Scan forward until we find the bytes: 0x02011000(x00100102 in normal int)
for (int ii=xmlTagOff; ii<xml.length-4; ii+=4) {
if (LEW(xml, ii) == startTag) {
xmlTagOff = ii; break;
}
} // end of hack, scanning for start of first start tag
// XML tags and attributes:
// Every XML start and end tag consists of 6 32 bit words:
// 0th word: 02011000 for startTag and 03011000 for endTag
// 1st word: a flag?, like 38000000
// 2nd word: Line of where this tag appeared in the original source file
// 3rd word: FFFFFFFF ??
// 4th word: StringIndex of NameSpace name, or FFFFFFFF for default NS
// 5th word: StringIndex of Element Name
// (Note: 01011000 in 0th word means end of XML document, endDocTag)
// Start tags (not end tags) contain 3 more words:
// 6th word: 14001400 meaning??
// 7th word: Number of Attributes that follow this tag(follow word 8th)
// 8th word: 00000000 meaning??
// Attributes consist of 5 words:
// 0th word: StringIndex of Attribute Name's Namespace, or FFFFFFFF
// 1st word: StringIndex of Attribute Name
// 2nd word: StringIndex of Attribute Value, or FFFFFFF if ResourceId used
// 3rd word: Flags?
// 4th word: str ind of attr value again, or ResourceId of value
// TMP, dump string table to tr for debugging
//tr.addSelect("strings", null);
//for (int ii=0; ii<numbStrings; ii++) {
// // Length of string starts at StringTable plus offset in StrIndTable
// String str = compXmlString(xml, sitOff, stOff, ii);
// tr.add(String.valueOf(ii), str);
//}
//tr.parent();
// Step through the XML tree element tags and attributes
int off = xmlTagOff;
int indent = 0;
int startTagLineNo = -2;
while (off < xml.length) {
int tag0 = LEW(xml, off);
//int tag1 = LEW(xml, off+1*4);
int lineNo = LEW(xml, off+2*4);
//int tag3 = LEW(xml, off+3*4);
int nameNsSi = LEW(xml, off+4*4);
int nameSi = LEW(xml, off+5*4);
if (tag0 == startTag) { // XML START TAG
int tag6 = LEW(xml, off+6*4); // Expected to be 14001400
int numbAttrs = LEW(xml, off+7*4); // Number of Attributes to follow
//int tag8 = LEW(xml, off+8*4); // Expected to be 00000000
off += 9*4; // Skip over 6+3 words of startTag data
String name = compXmlString(xml, sitOff, stOff, nameSi);
//tr.addSelect(name, null);
startTagLineNo = lineNo;
// Look for the Attributes
StringBuffer sb = new StringBuffer();
for (int ii=0; ii<numbAttrs; ii++) {
int attrNameNsSi = LEW(xml, off); // AttrName Namespace Str Ind, or FFFFFFFF
int attrNameSi = LEW(xml, off+1*4); // AttrName String Index
int attrValueSi = LEW(xml, off+2*4); // AttrValue Str Ind, or FFFFFFFF
int attrFlags = LEW(xml, off+3*4);
int attrResId = LEW(xml, off+4*4); // AttrValue ResourceId or dup AttrValue StrInd
off += 5*4; // Skip over the 5 words of an attribute
String attrName = compXmlString(xml, sitOff, stOff, attrNameSi);
String attrValue = attrValueSi!=-1
? compXmlString(xml, sitOff, stOff, attrValueSi)
: "resourceID 0x"+Integer.toHexString(attrResId);
sb.append(" "+attrName+"=\""+attrValue+"\"");
//tr.add(attrName, attrValue);
}
prtIndent(indent, "<"+name+sb+">");
indent++;
} else if (tag0 == endTag) { // XML END TAG
indent--;
off += 6*4; // Skip over 6 words of endTag data
String name = compXmlString(xml, sitOff, stOff, nameSi);
prtIndent(indent, "</"+name+"> (line "+startTagLineNo+"-"+lineNo+")");
//tr.parent(); // Step back up the NobTree
} else if (tag0 == endDocTag) { // END OF XML DOC TAG
break;
} else {
prt(" Unrecognized tag code '"+Integer.toHexString(tag0)
+"' at offset "+off);
break;
}
} // end of while loop scanning tags and attributes of XML tree
prt(" end at offset "+off);
} // end of decompressXML
public String compXmlString(byte[] xml, int sitOff, int stOff, int strInd) {
if (strInd < 0) return null;
int strOff = stOff + LEW(xml, sitOff+strInd*4);
return compXmlStringAt(xml, strOff);
}
public static String spaces = " ";
public void prtIndent(int indent, String str) {
prt(spaces.substring(0, Math.min(indent*2, spaces.length()))+str);
}
// compXmlStringAt -- Return the string stored in StringTable format at
// offset strOff. This offset points to the 16 bit string length, which
// is followed by that number of 16 bit (Unicode) chars.
public String compXmlStringAt(byte[] arr, int strOff) {
int strLen = arr[strOff+1]<<8&0xff00 | arr[strOff]&0xff;
byte[] chars = new byte[strLen];
for (int ii=0; ii<strLen; ii++) {
chars[ii] = arr[strOff+2+ii*2];
}
return new String(chars); // Hack, just use 8 byte chars
} // end of compXmlStringAt
// LEW -- Return value of a Little Endian 32 bit word from the byte array
// at offset off.
public int LEW(byte[] arr, int off) {
return arr[off+3]<<24&0xff000000 | arr[off+2]<<16&0xff0000
| arr[off+1]<<8&0xff00 | arr[off]&0xFF;
} // end of LEW
이 메서드는 Android 매니페스트를 바이트[]로 읽어서 다음을 처리합니다.
public void getIntents(String path) {
try {
JarFile jf = new JarFile(path);
InputStream is = jf.getInputStream(jf.getEntry("AndroidManifest.xml"));
byte[] xml = new byte[is.available()];
int br = is.read(xml);
//Tree tr = TrunkFactory.newTree();
decompressXML(xml);
//prt("XML\n"+tr.list());
} catch (Exception ex) {
console.log("getIntents, ex: "+ex); ex.printStackTrace();
}
} // end of getIntents
대부분의 앱은 루트 없이 읽을 수 있는 /system/app에 저장되어 있으며, 다른 앱은 루트가 필요한 /data/app에 저장되어 있습니다.위의 'path' 인수는 "/system/app/Weather.apk"와 같습니다.
Android Asset Packaging Tool(aapt)을 Android SDK에서 Python(또는 다른 것) 스크립트로 사용하는 것은 어떻습니까?
aapt(http://elinux.org/Android_aapt), 를 통해 .apk 패키지 및 AndroidManifest.xml 파일에 대한 정보를 검색할 수 있습니다.특히 'dump' 하위 명령을 통해 .apk 패키지의 개별 요소 값을 추출할 수 있습니다.예를 들어 .apk 패키지 내의 AndroidManifest.xml 파일에서 다음과 같은 방법으로 사용자 권한을 추출할 수 있습니다.
$ aapt dump permissions package.apk
어디 패키지.apk는 당신의 .apk 패키지입니다.
또한 Unix pipe 명령을 사용하여 출력을 지울 수 있습니다.예를 들어,
$ aapt dump permissions package.apk | sed 1d | awk '{ print $NF }'
다음은 Python 스크립트로 프로그래밍된 내용입니다.
import os
import subprocess
#Current directory and file name:
curpath = os.path.dirname( os.path.realpath(__file__) )
filepath = os.path.join(curpath, "package.apk")
#Extract the AndroidManifest.xml permissions:
command = "aapt dump permissions " + filepath + " | sed 1d | awk '{ print $NF }'"
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=None, shell=True)
permissions = process.communicate()[0]
print permissions
비슷한 방식으로 AndroidManifest.xml의 다른 정보(예: 패키지, 앱 이름 등)를 추출할 수 있습니다.
#Extract the APK package info:
shellcommand = "aapt dump badging " + filepath
process = subprocess.Popen(shellcommand, stdout=subprocess.PIPE, stderr=None, shell=True)
apkInfo = process.communicate()[0].splitlines()
for info in apkInfo:
#Package info:
if string.find(info, "package:", 0) != -1:
print "App Package: " + findBetween(info, "name='", "'")
print "App Version: " + findBetween(info, "versionName='", "'")
continue
#App name:
if string.find(info, "application:", 0) != -1:
print "App Name: " + findBetween(info, "label='", "'")
continue
def findBetween(s, prefix, suffix):
try:
start = s.index(prefix) + len(prefix)
end = s.index(suffix, start)
return s[start:end]
except ValueError:
return ""
대신 전체 Android Manifest XML 트리를 구문 분석하려면 xmltree 명령을 사용하여 비슷한 방법으로 이 작업을 수행할 수 있습니다.
aapt dump xmltree package.apk AndroidManifest.xml
이전과 마찬가지로 Python 사용:
#Extract the AndroidManifest XML tree:
shellcommand = "aapt dump xmltree " + filepath + " AndroidManifest.xml"
process = subprocess.Popen(shellcommand, stdout=subprocess.PIPE, stderr=None, shell=True)
xmlTree = process.communicate()[0]
print "Number of Activities: " + str(xmlTree.count("activity"))
print "Number of Services: " + str(xmlTree.count("service"))
print "Number of BroadcastReceivers: " + str(xmlTree.count("receiver"))
이제 최신 SDK-Tools를 사용하여 apkanalyzer라는 도구를 사용하여 APK의 AndroidManifest.xml(및 리소스와 같은 다른 부분)을 출력할 수 있습니다.
[android sdk]/tools/bin/apkanalyzer manifest print [app.apk]
얼마 전에 개발된 axml2xml.pl 도구를 안드로이드 운영 프로젝트 내에서 사용할 수 있습니다.이진 파일에서 텍스트 매니페스트 파일(Android Manifest.xml)을 생성합니다.
"원본"이 아니라 "원본"이 아닌 "원본"을 말하는 것입니다. 왜냐하면 많은 역공학 도구들처럼 이 도구는 완벽하지 않고 결과도 완벽하지 않기 때문입니다.기능이 완전하지 않거나 순방향 호환성이 없는 것으로 추정됩니다(새로운 이진 인코딩 방식으로).이유가 무엇이든 간에 axml2xml.pl 도구는 모든 속성 값을 올바르게 추출할 수 없습니다.이러한 속성은 minSdkVersion, targetSdkVersion이며 기본적으로 리소스( 문자열, 아이콘 등)를 참조하는 모든 속성, 즉 클래스 이름(활동, 서비스 등)만 올바르게 추출됩니다.
그러나 원래 Android 앱 파일(.apk)에서 apt 도구를 실행하면 누락된 정보를 찾을 수 있습니다.
앱 l-a <some appapk>
apk-parser, https://github.com/caoqianli/apk-parser, 앱이나 다른 바이너리에 대한 의존성이 없는 java용 경량 imple은 바이너리 xml 파일을 파싱하고 다른 apk 정보를 파싱하는 데 좋습니다.
ApkParser apkParser = new ApkParser(new File(filePath));
// set a locale to translate resource tag into specific strings in language the locale specified, you set locale to Locale.ENGLISH then get apk title 'WeChat' instead of '@string/app_name' for example
apkParser.setPreferredLocale(locale);
String xml = apkParser.getManifestXml();
System.out.println(xml);
String xml2 = apkParser.transBinaryXml(xmlPathInApk);
System.out.println(xml2);
ApkMeta apkMeta = apkParser.getApkMeta();
System.out.println(apkMeta);
Set<Locale> locales = apkParser.getLocales();
for (Locale l : locales) {
System.out.println(l);
}
apkParser.close();
속성을 올바르게 디코딩하는 다음 WPF 프로젝트를 확인합니다.
Python을 사용하거나 Androguard를 사용하는 경우 Androguard Androaxml 기능이 이 변환을 수행합니다.이 기능은 이 블로그 게시물에 자세히 설명되어 있으며 여기에 추가 문서와 출처가 있습니다.
용도:
$ ./androaxml.py -h
Usage: androaxml.py [options]
Options:
-h, --help show this help message and exit
-i INPUT, --input=INPUT
filename input (APK or android's binary xml)
-o OUTPUT, --output=OUTPUT
filename output of the xml
-v, --version version of the API
$ ./androaxml.py -i yourfile.apk -o output.xml
$ ./androaxml.py -i AndroidManifest.xml -o output.xml
@Mathieu Kotlin 버전은 다음과 같습니다.
fun main(args : Array<String>) {
val fileName = "app.apk"
ZipFile(fileName).use { zip ->
zip.entries().asSequence().forEach { entry ->
if(entry.name == "AndroidManifest.xml") {
zip.getInputStream(entry).use { input ->
val xml = decompressXML(input.readBytes())
//TODO: parse the XML
println(xml)
}
}
}
}
}
/**
* Binary XML doc ending Tag
*/
var endDocTag = 0x00100101
/**
* Binary XML start Tag
*/
var startTag = 0x00100102
/**
* Binary XML end Tag
*/
var endTag = 0x00100103
/**
* Reference var for spacing
* Used in prtIndent()
*/
var spaces = " "
/**
* Parse the 'compressed' binary form of Android XML docs
* such as for AndroidManifest.xml in .apk files
* Source: http://stackoverflow.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package/4761689#4761689
*
* @param xml Encoded XML content to decompress
*/
fun decompressXML(xml: ByteArray): String {
val resultXml = StringBuilder()
// Compressed XML file/bytes starts with 24x bytes of data,
// 9 32 bit words in little endian order (LSB first):
// 0th word is 03 00 08 00
// 3rd word SEEMS TO BE: Offset at then of StringTable
// 4th word is: Number of strings in string table
// WARNING: Sometime I indiscriminently display or refer to word in
// little endian storage format, or in integer format (ie MSB first).
val numbStrings = LEW(xml, 4 * 4)
// StringIndexTable starts at offset 24x, an array of 32 bit LE offsets
// of the length/string data in the StringTable.
val sitOff = 0x24 // Offset of start of StringIndexTable
// StringTable, each string is represented with a 16 bit little endian
// character count, followed by that number of 16 bit (LE) (Unicode) chars.
val stOff = sitOff + numbStrings * 4 // StringTable follows StrIndexTable
// XMLTags, The XML tag tree starts after some unknown content after the
// StringTable. There is some unknown data after the StringTable, scan
// forward from this point to the flag for the start of an XML start tag.
var xmlTagOff = LEW(xml, 3 * 4) // Start from the offset in the 3rd word.
// Scan forward until we find the bytes: 0x02011000(x00100102 in normal int)
run {
var ii = xmlTagOff
while (ii < xml.size - 4) {
if (LEW(xml, ii) == startTag) {
xmlTagOff = ii
break
}
ii += 4
}
} // end of hack, scanning for start of first start tag
// XML tags and attributes:
// Every XML start and end tag consists of 6 32 bit words:
// 0th word: 02011000 for startTag and 03011000 for endTag
// 1st word: a flag?, like 38000000
// 2nd word: Line of where this tag appeared in the original source file
// 3rd word: FFFFFFFF ??
// 4th word: StringIndex of NameSpace name, or FFFFFFFF for default NS
// 5th word: StringIndex of Element Name
// (Note: 01011000 in 0th word means end of XML document, endDocTag)
// Start tags (not end tags) contain 3 more words:
// 6th word: 14001400 meaning??
// 7th word: Number of Attributes that follow this tag(follow word 8th)
// 8th word: 00000000 meaning??
// Attributes consist of 5 words:
// 0th word: StringIndex of Attribute Name's Namespace, or FFFFFFFF
// 1st word: StringIndex of Attribute Name
// 2nd word: StringIndex of Attribute Value, or FFFFFFF if ResourceId used
// 3rd word: Flags?
// 4th word: str ind of attr value again, or ResourceId of value
// TMP, dump string table to tr for debugging
//tr.addSelect("strings", null);
//for (int ii=0; ii<numbStrings; ii++) {
// // Length of string starts at StringTable plus offset in StrIndTable
// String str = compXmlString(xml, sitOff, stOff, ii);
// tr.add(String.valueOf(ii), str);
//}
//tr.parent();
// Step through the XML tree element tags and attributes
var off = xmlTagOff
var indent = 0
var startTagLineNo = -2
while (off < xml.size) {
val tag0 = LEW(xml, off)
//int tag1 = LEW(xml, off+1*4);
val lineNo = LEW(xml, off + 2 * 4)
//int tag3 = LEW(xml, off+3*4);
val nameNsSi = LEW(xml, off + 4 * 4)
val nameSi = LEW(xml, off + 5 * 4)
if (tag0 == startTag) { // XML START TAG
val tag6 = LEW(xml, off + 6 * 4) // Expected to be 14001400
val numbAttrs = LEW(xml, off + 7 * 4) // Number of Attributes to follow
//int tag8 = LEW(xml, off+8*4); // Expected to be 00000000
off += 9 * 4 // Skip over 6+3 words of startTag data
val name = compXmlString(xml, sitOff, stOff, nameSi)
//tr.addSelect(name, null);
startTagLineNo = lineNo
// Look for the Attributes
val sb = StringBuffer()
for (ii in 0 until numbAttrs) {
val attrNameNsSi = LEW(xml, off) // AttrName Namespace Str Ind, or FFFFFFFF
val attrNameSi = LEW(xml, off + 1 * 4) // AttrName String Index
val attrValueSi = LEW(xml, off + 2 * 4) // AttrValue Str Ind, or FFFFFFFF
val attrFlags = LEW(xml, off + 3 * 4)
val attrResId = LEW(xml, off + 4 * 4) // AttrValue ResourceId or dup AttrValue StrInd
off += 5 * 4 // Skip over the 5 words of an attribute
val attrName = compXmlString(xml, sitOff, stOff, attrNameSi)
val attrValue = if (attrValueSi != -1)
compXmlString(xml, sitOff, stOff, attrValueSi)
else
"resourceID 0x" + Integer.toHexString(attrResId)
sb.append(" $attrName=\"$attrValue\"")
//tr.add(attrName, attrValue);
}
resultXml.append(prtIndent(indent, "<$name$sb>"))
indent++
} else if (tag0 == endTag) { // XML END TAG
indent--
off += 6 * 4 // Skip over 6 words of endTag data
val name = compXmlString(xml, sitOff, stOff, nameSi)
resultXml.append(prtIndent(indent, "</$name> (line $startTagLineNo-$lineNo)"))
//tr.parent(); // Step back up the NobTree
} else if (tag0 == endDocTag) { // END OF XML DOC TAG
break
} else {
println(" Unrecognized tag code '" + Integer.toHexString(tag0)
+ "' at offset " + off
)
break
}
} // end of while loop scanning tags and attributes of XML tree
println(" end at offset $off")
return resultXml.toString()
} // end of decompressXML
/**
* Tool Method for decompressXML();
* Compute binary XML to its string format
* Source: Source: http://stackoverflow.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package/4761689#4761689
*
* @param xml Binary-formatted XML
* @param sitOff
* @param stOff
* @param strInd
* @return String-formatted XML
*/
fun compXmlString(xml: ByteArray, sitOff: Int, stOff: Int, strInd: Int): String? {
if (strInd < 0) return null
val strOff = stOff + LEW(xml, sitOff + strInd * 4)
return compXmlStringAt(xml, strOff)
}
/**
* Tool Method for decompressXML();
* Apply indentation
*
* @param indent Indentation level
* @param str String to indent
* @return Indented string
*/
fun prtIndent(indent: Int, str: String): String {
return spaces.substring(0, Math.min(indent * 2, spaces.length)) + str
}
/**
* Tool method for decompressXML()
* Return the string stored in StringTable format at
* offset strOff. This offset points to the 16 bit string length, which
* is followed by that number of 16 bit (Unicode) chars.
*
* @param arr StringTable array
* @param strOff Offset to get string from
* @return String from StringTable at offset strOff
*/
fun compXmlStringAt(arr: ByteArray, strOff: Int): String {
val strLen = (arr[strOff + 1] shl (8 and 0xff00)) or (arr[strOff].toInt() and 0xff)
val chars = ByteArray(strLen)
for (ii in 0 until strLen) {
chars[ii] = arr[strOff + 2 + ii * 2]
}
return String(chars) // Hack, just use 8 byte chars
} // end of compXmlStringAt
/**
* Return value of a Little Endian 32 bit word from the byte array
* at offset off.
*
* @param arr Byte array with 32 bit word
* @param off Offset to get word from
* @return Value of Little Endian 32 bit word specified
*/
fun LEW(arr: ByteArray, off: Int): Int {
return (arr[off + 3] shl 24 and -0x1000000 or ((arr[off + 2] shl 16) and 0xff0000)
or (arr[off + 1] shl 8 and 0xff00) or (arr[off].toInt() and 0xFF))
} // end of LEW
private infix fun Byte.shl(i: Int): Int = (this.toInt() shl i)
private infix fun Int.shl(i: Int): Int = (this shl i)
이것은 위의 답변의 코틀린 버전입니다.
유용한 경우 리보가 게시한 자바 스니펫의 C++ 버전은 다음과 같습니다.
struct decompressXML
{
// decompressXML -- Parse the 'compressed' binary form of Android XML docs
// such as for AndroidManifest.xml in .apk files
enum
{
endDocTag = 0x00100101,
startTag = 0x00100102,
endTag = 0x00100103
};
decompressXML(const BYTE* xml, int cb) {
// Compressed XML file/bytes starts with 24x bytes of data,
// 9 32 bit words in little endian order (LSB first):
// 0th word is 03 00 08 00
// 3rd word SEEMS TO BE: Offset at then of StringTable
// 4th word is: Number of strings in string table
// WARNING: Sometime I indiscriminently display or refer to word in
// little endian storage format, or in integer format (ie MSB first).
int numbStrings = LEW(xml, cb, 4*4);
// StringIndexTable starts at offset 24x, an array of 32 bit LE offsets
// of the length/string data in the StringTable.
int sitOff = 0x24; // Offset of start of StringIndexTable
// StringTable, each string is represented with a 16 bit little endian
// character count, followed by that number of 16 bit (LE) (Unicode) chars.
int stOff = sitOff + numbStrings*4; // StringTable follows StrIndexTable
// XMLTags, The XML tag tree starts after some unknown content after the
// StringTable. There is some unknown data after the StringTable, scan
// forward from this point to the flag for the start of an XML start tag.
int xmlTagOff = LEW(xml, cb, 3*4); // Start from the offset in the 3rd word.
// Scan forward until we find the bytes: 0x02011000(x00100102 in normal int)
for (int ii=xmlTagOff; ii<cb-4; ii+=4) {
if (LEW(xml, cb, ii) == startTag) {
xmlTagOff = ii; break;
}
} // end of hack, scanning for start of first start tag
// XML tags and attributes:
// Every XML start and end tag consists of 6 32 bit words:
// 0th word: 02011000 for startTag and 03011000 for endTag
// 1st word: a flag?, like 38000000
// 2nd word: Line of where this tag appeared in the original source file
// 3rd word: FFFFFFFF ??
// 4th word: StringIndex of NameSpace name, or FFFFFFFF for default NS
// 5th word: StringIndex of Element Name
// (Note: 01011000 in 0th word means end of XML document, endDocTag)
// Start tags (not end tags) contain 3 more words:
// 6th word: 14001400 meaning??
// 7th word: Number of Attributes that follow this tag(follow word 8th)
// 8th word: 00000000 meaning??
// Attributes consist of 5 words:
// 0th word: StringIndex of Attribute Name's Namespace, or FFFFFFFF
// 1st word: StringIndex of Attribute Name
// 2nd word: StringIndex of Attribute Value, or FFFFFFF if ResourceId used
// 3rd word: Flags?
// 4th word: str ind of attr value again, or ResourceId of value
// TMP, dump string table to tr for debugging
//tr.addSelect("strings", null);
//for (int ii=0; ii<numbStrings; ii++) {
// // Length of string starts at StringTable plus offset in StrIndTable
// String str = compXmlString(xml, sitOff, stOff, ii);
// tr.add(String.valueOf(ii), str);
//}
//tr.parent();
// Step through the XML tree element tags and attributes
int off = xmlTagOff;
int indent = 0;
int startTagLineNo = -2;
while (off < cb) {
int tag0 = LEW(xml, cb, off);
//int tag1 = LEW(xml, off+1*4);
int lineNo = LEW(xml, cb, off+2*4);
//int tag3 = LEW(xml, off+3*4);
int nameNsSi = LEW(xml, cb, off+4*4);
int nameSi = LEW(xml, cb, off+5*4);
if (tag0 == startTag) { // XML START TAG
int tag6 = LEW(xml, cb, off+6*4); // Expected to be 14001400
int numbAttrs = LEW(xml, cb, off+7*4); // Number of Attributes to follow
//int tag8 = LEW(xml, off+8*4); // Expected to be 00000000
off += 9*4; // Skip over 6+3 words of startTag data
std::string name = compXmlString(xml, cb, sitOff, stOff, nameSi);
//tr.addSelect(name, null);
startTagLineNo = lineNo;
// Look for the Attributes
std::string sb;
for (int ii=0; ii<numbAttrs; ii++) {
int attrNameNsSi = LEW(xml, cb, off); // AttrName Namespace Str Ind, or FFFFFFFF
int attrNameSi = LEW(xml, cb, off+1*4); // AttrName String Index
int attrValueSi = LEW(xml, cb, off+2*4); // AttrValue Str Ind, or FFFFFFFF
int attrFlags = LEW(xml, cb, off+3*4);
int attrResId = LEW(xml, cb, off+4*4); // AttrValue ResourceId or dup AttrValue StrInd
off += 5*4; // Skip over the 5 words of an attribute
std::string attrName = compXmlString(xml, cb, sitOff, stOff, attrNameSi);
std::string attrValue = attrValueSi!=-1
? compXmlString(xml, cb, sitOff, stOff, attrValueSi)
: "resourceID 0x"+toHexString(attrResId);
sb.append(" "+attrName+"=\""+attrValue+"\"");
//tr.add(attrName, attrValue);
}
prtIndent(indent, "<"+name+sb+">");
indent++;
} else if (tag0 == endTag) { // XML END TAG
indent--;
off += 6*4; // Skip over 6 words of endTag data
std::string name = compXmlString(xml, cb, sitOff, stOff, nameSi);
prtIndent(indent, "</"+name+"> (line "+toIntString(startTagLineNo)+"-"+toIntString(lineNo)+")");
//tr.parent(); // Step back up the NobTree
} else if (tag0 == endDocTag) { // END OF XML DOC TAG
break;
} else {
prt(" Unrecognized tag code '"+toHexString(tag0)
+"' at offset "+toIntString(off));
break;
}
} // end of while loop scanning tags and attributes of XML tree
prt(" end at offset "+off);
} // end of decompressXML
std::string compXmlString(const BYTE* xml, int cb, int sitOff, int stOff, int strInd) {
if (strInd < 0) return std::string("");
int strOff = stOff + LEW(xml, cb, sitOff+strInd*4);
return compXmlStringAt(xml, cb, strOff);
}
void prt(std::string str)
{
printf("%s", str.c_str());
}
void prtIndent(int indent, std::string str) {
char spaces[46];
memset(spaces, ' ', sizeof(spaces));
spaces[min(indent*2, sizeof(spaces) - 1)] = 0;
prt(spaces);
prt(str);
prt("\n");
}
// compXmlStringAt -- Return the string stored in StringTable format at
// offset strOff. This offset points to the 16 bit string length, which
// is followed by that number of 16 bit (Unicode) chars.
std::string compXmlStringAt(const BYTE* arr, int cb, int strOff) {
if (cb < strOff + 2) return std::string("");
int strLen = arr[strOff+1]<<8&0xff00 | arr[strOff]&0xff;
char* chars = new char[strLen + 1];
chars[strLen] = 0;
for (int ii=0; ii<strLen; ii++) {
if (cb < strOff + 2 + ii * 2)
{
chars[ii] = 0;
break;
}
chars[ii] = arr[strOff+2+ii*2];
}
std::string str(chars);
free(chars);
return str;
} // end of compXmlStringAt
// LEW -- Return value of a Little Endian 32 bit word from the byte array
// at offset off.
int LEW(const BYTE* arr, int cb, int off) {
return (cb > off + 3) ? ( arr[off+3]<<24&0xff000000 | arr[off+2]<<16&0xff0000
| arr[off+1]<<8&0xff00 | arr[off]&0xFF ) : 0;
} // end of LEW
std::string toHexString(DWORD attrResId)
{
char ch[20];
sprintf_s(ch, 20, "%lx", attrResId);
return std::string(ch);
}
std::string toIntString(int i)
{
char ch[20];
sprintf_s(ch, 20, "%ld", i);
return std::string(ch);
}
};
안드로이드 스튜디오 2.2에서는 직접 apk를 분석할 수 있습니다.apk를 구축 분석하러 갑니다.apk를 선택하고 Androidmanifest.xml로 이동합니다.안드로이드 매니페스트에 대한 자세한 내용을 보실 수 있습니다.
참고로 여기 리보의 코드 버전이 있습니다.주요 차이점은 압축 해제 XML()이 직접 문자열을 반환한다는 점인데, 이 문자열은 제가 사용하기에 더 적합했습니다.
참고: 리보의 솔루션을 사용한 유일한 목적은 를 가져오는 것이었습니다.Manifest XML 파일에서 APK 파일이 공개된 버전이며, 이를 위해 잘 작동함을 확인합니다.
편집 [2013-03-16]:버전이 일반 텍스트로 설정되어 있으면 잘 작동하지만 리소스 XML을 참조하도록 설정되어 있으면 'Resource 0x1'로 표시됩니다.이 경우 적절한 문자열 리소스 참조를 가져올 다른 솔루션에 이 솔루션을 연결해야 합니다.
/**
* Binary XML doc ending Tag
*/
public static int endDocTag = 0x00100101;
/**
* Binary XML start Tag
*/
public static int startTag = 0x00100102;
/**
* Binary XML end Tag
*/
public static int endTag = 0x00100103;
/**
* Reference var for spacing
* Used in prtIndent()
*/
public static String spaces = " ";
/**
* Parse the 'compressed' binary form of Android XML docs
* such as for AndroidManifest.xml in .apk files
* Source: http://stackoverflow.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package/4761689#4761689
*
* @param xml Encoded XML content to decompress
*/
public static String decompressXML(byte[] xml) {
StringBuilder resultXml = new StringBuilder();
// Compressed XML file/bytes starts with 24x bytes of data,
// 9 32 bit words in little endian order (LSB first):
// 0th word is 03 00 08 00
// 3rd word SEEMS TO BE: Offset at then of StringTable
// 4th word is: Number of strings in string table
// WARNING: Sometime I indiscriminently display or refer to word in
// little endian storage format, or in integer format (ie MSB first).
int numbStrings = LEW(xml, 4*4);
// StringIndexTable starts at offset 24x, an array of 32 bit LE offsets
// of the length/string data in the StringTable.
int sitOff = 0x24; // Offset of start of StringIndexTable
// StringTable, each string is represented with a 16 bit little endian
// character count, followed by that number of 16 bit (LE) (Unicode) chars.
int stOff = sitOff + numbStrings*4; // StringTable follows StrIndexTable
// XMLTags, The XML tag tree starts after some unknown content after the
// StringTable. There is some unknown data after the StringTable, scan
// forward from this point to the flag for the start of an XML start tag.
int xmlTagOff = LEW(xml, 3*4); // Start from the offset in the 3rd word.
// Scan forward until we find the bytes: 0x02011000(x00100102 in normal int)
for (int ii=xmlTagOff; ii<xml.length-4; ii+=4) {
if (LEW(xml, ii) == startTag) {
xmlTagOff = ii; break;
}
} // end of hack, scanning for start of first start tag
// XML tags and attributes:
// Every XML start and end tag consists of 6 32 bit words:
// 0th word: 02011000 for startTag and 03011000 for endTag
// 1st word: a flag?, like 38000000
// 2nd word: Line of where this tag appeared in the original source file
// 3rd word: FFFFFFFF ??
// 4th word: StringIndex of NameSpace name, or FFFFFFFF for default NS
// 5th word: StringIndex of Element Name
// (Note: 01011000 in 0th word means end of XML document, endDocTag)
// Start tags (not end tags) contain 3 more words:
// 6th word: 14001400 meaning??
// 7th word: Number of Attributes that follow this tag(follow word 8th)
// 8th word: 00000000 meaning??
// Attributes consist of 5 words:
// 0th word: StringIndex of Attribute Name's Namespace, or FFFFFFFF
// 1st word: StringIndex of Attribute Name
// 2nd word: StringIndex of Attribute Value, or FFFFFFF if ResourceId used
// 3rd word: Flags?
// 4th word: str ind of attr value again, or ResourceId of value
// TMP, dump string table to tr for debugging
//tr.addSelect("strings", null);
//for (int ii=0; ii<numbStrings; ii++) {
// // Length of string starts at StringTable plus offset in StrIndTable
// String str = compXmlString(xml, sitOff, stOff, ii);
// tr.add(String.valueOf(ii), str);
//}
//tr.parent();
// Step through the XML tree element tags and attributes
int off = xmlTagOff;
int indent = 0;
int startTagLineNo = -2;
while (off < xml.length) {
int tag0 = LEW(xml, off);
//int tag1 = LEW(xml, off+1*4);
int lineNo = LEW(xml, off+2*4);
//int tag3 = LEW(xml, off+3*4);
int nameNsSi = LEW(xml, off+4*4);
int nameSi = LEW(xml, off+5*4);
if (tag0 == startTag) { // XML START TAG
int tag6 = LEW(xml, off+6*4); // Expected to be 14001400
int numbAttrs = LEW(xml, off+7*4); // Number of Attributes to follow
//int tag8 = LEW(xml, off+8*4); // Expected to be 00000000
off += 9*4; // Skip over 6+3 words of startTag data
String name = compXmlString(xml, sitOff, stOff, nameSi);
//tr.addSelect(name, null);
startTagLineNo = lineNo;
// Look for the Attributes
StringBuffer sb = new StringBuffer();
for (int ii=0; ii<numbAttrs; ii++) {
int attrNameNsSi = LEW(xml, off); // AttrName Namespace Str Ind, or FFFFFFFF
int attrNameSi = LEW(xml, off+1*4); // AttrName String Index
int attrValueSi = LEW(xml, off+2*4); // AttrValue Str Ind, or FFFFFFFF
int attrFlags = LEW(xml, off+3*4);
int attrResId = LEW(xml, off+4*4); // AttrValue ResourceId or dup AttrValue StrInd
off += 5*4; // Skip over the 5 words of an attribute
String attrName = compXmlString(xml, sitOff, stOff, attrNameSi);
String attrValue = attrValueSi!=-1
? compXmlString(xml, sitOff, stOff, attrValueSi)
: "resourceID 0x"+Integer.toHexString(attrResId);
sb.append(" "+attrName+"=\""+attrValue+"\"");
//tr.add(attrName, attrValue);
}
resultXml.append(prtIndent(indent, "<"+name+sb+">"));
indent++;
} else if (tag0 == endTag) { // XML END TAG
indent--;
off += 6*4; // Skip over 6 words of endTag data
String name = compXmlString(xml, sitOff, stOff, nameSi);
resultXml.append(prtIndent(indent, "</"+name+"> (line "+startTagLineNo+"-"+lineNo+")"));
//tr.parent(); // Step back up the NobTree
} else if (tag0 == endDocTag) { // END OF XML DOC TAG
break;
} else {
Log.e(TAG, " Unrecognized tag code '"+Integer.toHexString(tag0)
+"' at offset "+off);
break;
}
} // end of while loop scanning tags and attributes of XML tree
Log.i(TAG, " end at offset "+off);
return resultXml.toString();
} // end of decompressXML
/**
* Tool Method for decompressXML();
* Compute binary XML to its string format
* Source: Source: http://stackoverflow.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package/4761689#4761689
*
* @param xml Binary-formatted XML
* @param sitOff
* @param stOff
* @param strInd
* @return String-formatted XML
*/
public static String compXmlString(byte[] xml, int sitOff, int stOff, int strInd) {
if (strInd < 0) return null;
int strOff = stOff + LEW(xml, sitOff+strInd*4);
return compXmlStringAt(xml, strOff);
}
/**
* Tool Method for decompressXML();
* Apply indentation
*
* @param indent Indentation level
* @param str String to indent
* @return Indented string
*/
public static String prtIndent(int indent, String str) {
return (spaces.substring(0, Math.min(indent*2, spaces.length()))+str);
}
/**
* Tool method for decompressXML()
* Return the string stored in StringTable format at
* offset strOff. This offset points to the 16 bit string length, which
* is followed by that number of 16 bit (Unicode) chars.
*
* @param arr StringTable array
* @param strOff Offset to get string from
* @return String from StringTable at offset strOff
*
*/
public static String compXmlStringAt(byte[] arr, int strOff) {
int strLen = arr[strOff+1]<<8&0xff00 | arr[strOff]&0xff;
byte[] chars = new byte[strLen];
for (int ii=0; ii<strLen; ii++) {
chars[ii] = arr[strOff+2+ii*2];
}
return new String(chars); // Hack, just use 8 byte chars
} // end of compXmlStringAt
/**
* Return value of a Little Endian 32 bit word from the byte array
* at offset off.
*
* @param arr Byte array with 32 bit word
* @param off Offset to get word from
* @return Value of Little Endian 32 bit word specified
*/
public static int LEW(byte[] arr, int off) {
return arr[off+3]<<24&0xff000000 | arr[off+2]<<16&0xff0000
| arr[off+1]<<8&0xff00 | arr[off]&0xFF;
} // end of LEW
다른 사람들에게도 도움이 되길 바랍니다.
Android4Me 프로젝트의 자바 앱인 AXMLPrinter2가 내가 가지고 있던 AndroidManifest.xml에서 잘 작동하는 것을 발견했습니다(그리고 XML을 잘 포맷된 방식으로 출력합니다).http://code.google.com/p/android4me/downloads/detail?name=AXMLPrinter2.jar
음 하나..그것(그리고 리보의 이 답변의 코드)은 제가 발견한 모든 컴파일된 XML 파일을 처리하는 것처럼 보이지 않습니다.두 배 바이트 형식이 아닌 문자열을 문자당 한 바이트씩 저장한 것을 찾았습니다.
저는 위에 게시된 리보 코드로 1년 넘게 운영해 왔고, 그것은 우리에게 좋은 도움이 되었습니다.그러나 최근 업데이트(Gradle 3.x)로 Android Manifest.xml을 더 이상 구문 분석할 수 없었고, 인덱스 아웃오브바운즈 오류가 발생했으며, 일반적으로 파일을 구문 분석할 수 없었습니다.
업데이트: 이제 Gradle 3.x로 업그레이드하는 것이 저희의 문제라고 생각합니다.이 문서에서는 AirWatch에 문제가 있었던 방법을 설명합니다. apt2 대신 apt2를 사용하는 Gradle 설정을 사용하면 AirWatch가 Android Plugin for Gradle 3.0.0-beta1과 호환되지 않는 것 같습니다.
검색 중에 이 오픈 소스 프로젝트를 발견했습니다. 이 프로젝트는 유지보수되고 있으며 핵심을 파악하고 이전에 파싱할 수 있었던 이전 APK와 리보의 로직이 예외를 허용한 새로운 APK를 모두 읽을 수 있었습니다.
https://github.com/xgouchet/AXML
그의 예로 볼 때 제가 하고 있는 일은 이렇습니다.
zf = new ZipFile(apkFile);
//Getting the manifest
ZipEntry entry = zf.getEntry("AndroidManifest.xml");
InputStream is = zf.getInputStream(entry);
// Read our manifest Document
Document manifestDoc = new CompressedXmlParser().parseDOM(is);
// Make sure we got a doc, and that it has children
if (null != manifestDoc && manifestDoc.getChildNodes().getLength() > 0) {
//
Node firstNode = manifestDoc.getFirstChild();
// Now get the attributes out of the node
NamedNodeMap nodeMap = firstNode.getAttributes();
// Finally to a point where we can read out our values
versionName = nodeMap.getNamedItem("android:versionName").getNodeValue();
versionCode = nodeMap.getNamedItem("android:versionCode").getNodeValue();
}
apkan analyzer가 도움이 될 것입니다.
@echo off
::##############################################################################
::##
::## apkanalyzer start up script for Windows
::##
::## converted by ewwink
::##
::##############################################################################
::Attempt to set APP_HOME
SET SAVED=%cd%
SET APP_HOME=C:\android\sdk\tools
SET APP_NAME="apkanalyzer"
::Add default JVM options here. You can also use JAVA_OPTS and APKANALYZER_OPTS to pass JVM options to this script.
SET DEFAULT_JVM_OPTS=-Dcom.android.sdklib.toolsdir=%APP_HOME%
SET CLASSPATH=%APP_HOME%\lib\dvlib-26.0.0-dev.jar;%APP_HOME%\lib\util-2.2.1.jar;%APP_HOME%\lib\jimfs-1.1.jar;%APP_HOME%\lib\annotations-13.0.jar;%APP_HOME%\lib\ddmlib-26.0.0-dev.jar;%APP_HOME%\lib\repository-26.0.0-dev.jar;%APP_HOME%\lib\sdk-common-26.0.0-dev.jar;%APP_HOME%\lib\kotlin-stdlib-1.1.3-2.jar;%APP_HOME%\lib\protobuf-java-3.0.0.jar;%APP_HOME%\lib\apkanalyzer-cli.jar;%APP_HOME%\lib\gson-2.3.jar;%APP_HOME%\lib\httpcore-4.2.5.jar;%APP_HOME%\lib\dexlib2-2.2.1.jar;%APP_HOME%\lib\commons-compress-1.12.jar;%APP_HOME%\lib\generator.jar;%APP_HOME%\lib\error_prone_annotations-2.0.18.jar;%APP_HOME%\lib\commons-codec-1.6.jar;%APP_HOME%\lib\kxml2-2.3.0.jar;%APP_HOME%\lib\httpmime-4.1.jar;%APP_HOME%\lib\annotations-12.0.jar;%APP_HOME%\lib\bcpkix-jdk15on-1.56.jar;%APP_HOME%\lib\jsr305-3.0.0.jar;%APP_HOME%\lib\explainer.jar;%APP_HOME%\lib\builder-model-3.0.0-dev.jar;%APP_HOME%\lib\baksmali-2.2.1.jar;%APP_HOME%\lib\j2objc-annotations-1.1.jar;%APP_HOME%\lib\layoutlib-api-26.0.0-dev.jar;%APP_HOME%\lib\jcommander-1.64.jar;%APP_HOME%\lib\commons-logging-1.1.1.jar;%APP_HOME%\lib\annotations-26.0.0-dev.jar;%APP_HOME%\lib\builder-test-api-3.0.0-dev.jar;%APP_HOME%\lib\animal-sniffer-annotations-1.14.jar;%APP_HOME%\lib\bcprov-jdk15on-1.56.jar;%APP_HOME%\lib\httpclient-4.2.6.jar;%APP_HOME%\lib\common-26.0.0-dev.jar;%APP_HOME%\lib\jopt-simple-4.9.jar;%APP_HOME%\lib\sdklib-26.0.0-dev.jar;%APP_HOME%\lib\apkanalyzer.jar;%APP_HOME%\lib\shared.jar;%APP_HOME%\lib\binary-resources.jar;%APP_HOME%\lib\guava-22.0.jar
SET APP_ARGS=%*
::Collect all arguments for the java command, following the shell quoting and substitution rules
SET APKANALYZER_OPTS=%DEFAULT_JVM_OPTS% -classpath %CLASSPATH% com.android.tools.apk.analyzer.ApkAnalyzerCli %APP_ARGS%
::Determine the Java command to use to start the JVM.
SET JAVACMD="java"
where %JAVACMD% >nul 2>nul
if %errorlevel%==1 (
echo ERROR: 'java' command could be found in your PATH.
echo Please set the 'java' variable in your environment to match the
echo location of your Java installation.
echo.
exit /b 0
)
:: execute apkanalyzer
%JAVACMD% %APKANALYZER_OPTS%
원본 게시물 https://stackoverflow.com/a/51905063/1383521
도움이 될 수 있습니다.
public static int vCodeApk(String path) {
PackageManager pm = G.context.getPackageManager();
PackageInfo info = pm.getPackageArchiveInfo(path, 0);
return info.versionCode;
// Toast.makeText(this, "VersionCode : " + info.versionCode + ", VersionName : " + info.versionName, Toast.LENGTH_LONG).show();
}
G는 나의 Application 클래스입니다.
public class G extends Application {
언급URL : https://stackoverflow.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package
'programing' 카테고리의 다른 글
"기타" 범주의 지정되지 않은 값을 계산하기 위한 쿼리 (0) | 2023.09.11 |
---|---|
STDOUT을 PHP의 파일로 리디렉션하는 방법? (0) | 2023.09.11 |
Node.js 스크립트에 적합한 hashbang (0) | 2023.09.06 |
jQuery Data() API를 사용하여 데이터 속성을 설정할 수 없습니다. (0) | 2023.09.06 |
JUNIT-Spring @Asyncvoid 서비스 방법 (0) | 2023.09.06 |