Programming

.apk 패키지 내에서 AndroidManifest.xml 파일을 구문 분석하는 방법

procodes 2020. 5. 26. 21:18
반응형

.apk 패키지 내에서 AndroidManifest.xml 파일을 구문 분석하는 방법


이 파일은 이진 XML 형식 인 것 같습니다. 이 형식은 무엇이며 프로그래밍 방식으로 구문 분석 할 수있는 방법은 무엇입니까 (SDK의 aapt dump 도구를 사용하는 것과 대조적으로)?

이 바이너리 형식은 여기 문서 에서 다루지 않습니다 .

참고 : Android 환경 외부, 바람직하게는 Java 에서이 정보에 액세스하고 싶습니다.


android-apktool 사용

apk 파일을 읽고 XML을 거의 원래 형식으로 디코딩하는 응용 프로그램이 있습니다.

용법:

apktool d Gmail.apk && cat Gmail/AndroidManifest.xml

확인 안드로이드 - apktool을 자세한 내용은


Android에서 실행되는이 Java 메소드는 .apk 패키지에있는 AndroidManifest.xml 파일의 이진 형식을 문서화합니다. 두 번째 코드 상자는 decompressXML을 호출하는 방법과 장치의 앱 패키지 파일에서 바이트 []를로드하는 방법을 보여줍니다. (의미가 이해되지 않는 필드가 있습니다. 의미가 무엇인지 알고 있으면 정보를 업데이트하겠습니다.)

// 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

이 메소드는 처리를 위해 AndroidManifest를 바이트 []로 읽습니다.

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에 저장되며 루트없이 Evo를 읽을 수 있으며 다른 앱은 / data / app에 있으며 루트를 확인해야합니다. 위의 'path'인수는 "/system/app/Weather.apk"와 같습니다.


무엇 사용에 대한 안드로이드 자산 패키징 도구 파이썬 (또는 무엇이든) 스크립트에, 안드로이드 SDK에서, (aapt에를)?

aapt ( http://elinux.org/Android_aapt )를 통해 실제로 .apk 패키지 및 AndroidManifest.xml 파일 에 대한 정보를 검색 할 수 있습니다 . 특히 'dump' 하위 명령을 통해 .apk 패키지 의 개별 요소 값을 추출 할 수 있습니다 . 예를 들어 .apk 패키지 내의 AndroidManifest.xml 파일 에서 사용자 권한을 다음 과 같이 추출 할 수 있습니다 .

$ aapt dump permissions package.apk

여기서 package.apk.apk 패키지입니다.

또한 Unix pipe 명령을 사용하여 출력을 지울 수 있습니다. 예를 들면 다음과 같습니다.

$ aapt dump permissions package.apk | sed 1d | awk '{ print $NF }'

다음은 프로그래밍 방식으로 파이썬 스크립트입니다.

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의 다른 정보 (예 : package , app name 등)를 추출 할 수 있습니다 .

#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 ""

대신 전체 AndroidManifest XML 트리를 구문 분석하려면 xmltree 명령을 사용하여 비슷한 방식으로 수행 할 수 있습니다 .

aapt dump xmltree package.apk AndroidManifest.xml

이전과 같이 파이썬 사용하기 :

#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"))

android-random 프로젝트 내에서 얼마 전에 개발 된 axml2xml.pl 도구 를 사용할 수 있습니다 . 바이너리에서 텍스트 매니페스트 파일 (AndroidManifest.xml)을 생성합니다.

많은 리버스 엔지니어링 도구와 마찬가지로 완벽 하지 않고 결과 완전하지 않기 때문에 " 원본 "이 아니라 " 텍스트 " 라고 말하고 있습니다 . 나는 그것이 완전한 기능을 갖지 못했거나 단순히 순방향 호환되지 않는 것으로 생각합니다 (새로운 바이너리 인코딩 체계 사용). 이유가 무엇이든, axml2xml.pl 도구는 모든 속성 값을 올바르게 추출 할 수 없습니다. 이러한 속성은 minSdkVersion, targetSdkVersion이며 기본적으로 문자열, 아이콘 등과 같은 리소스를 참조하는 모든 속성, 즉 클래스 이름 (활동, 서비스 등) 만 올바르게 추출됩니다.

그러나 원본 Android 앱 파일 ( .apk ) 에서 aapt 도구를 실행하면 이러한 누락 된 정보를 계속 찾을 수 있습니다 .

aapt l -a <someapp.apk>


속성을 올바르게 디코딩하는 다음 WPF 프로젝트확인하십시오 .


apk-parser, https://github.com/caoqianli/apk-parser 는 aapt 또는 기타 이진에 대한 종속성이없는 java 용 경량 impl이며 이진 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();

최신 SDK-Tools를 사용하면 apkanalyzer라는 도구를 사용하여 APK의 AndroidManifest.xml (및 리소스와 같은 다른 부분)을 인쇄 할 수 있습니다.

[android sdk]/tools/bin/apkanalyzer manifest print [app.apk]

apkanalyzer


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

유용한 경우 Ribo가 게시 한 Java 스 니펫의 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);
    }
};

여기에 참조 용으로 Ribo 코드가 있습니다. 가장 큰 차이점은 decompressXML ()이 String을 직접 반환한다는 것입니다.

참고 : Ribo의 솔루션을 사용하는 유일한 목적은 Manifest XML 파일에서 .APK 파일의 게시 된 버전을 가져 오는 것이 었으며이 목적을 위해 아름답게 작동한다는 것을 확인했습니다.

편집은 [2013년 3월 16일은] : 그것은 아름답게 작동하는 경우 버전이 일반 텍스트로 설정되어 있지만 그것의 세트가 자원 XML을 참조하는 경우, 예를 들어 '자원을 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

다른 사람들도 도울 수 있기를 바랍니다.


Android Studio 2.2에서는 APK를 직접 분석 할 수 있습니다. 고토 빌드-APK 분석. APK를 선택하고 androidmanifest.xml로 이동하십시오. androidmanifest의 세부 사항을 볼 수 있습니다.


코 틀린 버전은 다음과 같습니다 : @Mathieu

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)

이것은 위의 답변의 kotlin 버전입니다.


Android4Me 프로젝트의 Java 응용 프로그램 인 AXMLPrinter2는 내가 가지고있는 AndroidManifest.xml에서 잘 작동하고 XML을 형식이 좋은 방식으로 인쇄합니다. http://code.google.com/p/android4me/downloads/detail?name=AXMLPrinter2.jar

참고 사항 .. (그리고 Ribo 의이 답변의 코드)는 내가 접한 모든 컴파일 된 XML 파일을 처리하는 것으로 보이지 않습니다. 문자열이 가정하는 2 바이트 형식이 아닌 문자 당 1 바이트로 저장된 곳을 찾았습니다.


도움이 될 수 있습니다

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는 내 응용 프로그램 클래스입니다.

public class G extends Application {

나는 위에 게시 된 Ribo 코드를 1 년 넘게 실행 해 왔으며 우리에게 잘 도움이되었습니다. 최근 업데이트 (Gradle 3.x)를 사용하면 더 이상 AndroidManifest.xml을 구문 분석 할 수 없었고 색인에서 범위를 벗어난 오류가 발생했으며 일반적으로 더 이상 파일을 구문 분석 할 수 없었습니다.

업데이트 : 이제 우리의 문제는 Gradle 3.x로 업그레이드하는 데 문제가 있다고 생각합니다. 이 문서에서는 AirWatch에 문제가 있고 aapt2 대신 Gradle 설정을 사용하여 aapt를 사용하여 해결할 수있는 방법을 설명합니다. AirWatch는 Gradle 3.0.0-beta1 용 Android 플러그인과 호환되지 않는 것 같습니다

주변에서 검색 할 때이 오픈 소스 프로젝트를 발견했으며 유지 관리 중이며 이전에 구문 분석 할 수있는 이전 APK와 Ribo의 논리가 예외를 던진 새로운 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();
     }

apkanalyzer가 도움이 될 것입니다

@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

참고 URL : https://stackoverflow.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package

반응형