package manifest import ( "bytes" "encoding/binary" "encoding/xml" "errors" "fmt" "io" "io/ioutil" "strconv" "strings" "unsafe" ) type binxmlParseInfo struct { strings stringTable resourceIds []uint32 encoder ManifestEncoder res *ResourceTable } // Some samples have manifest in plaintext, this is an error. // 2c882a2376034ed401be082a42a21f0ac837689e7d3ab6be0afb82f44ca0b859 var ErrPlainTextManifest = errors.New("xml is in plaintext, binary form expected") // Deprecated: just calls ParseXML func ParseManifest(r io.Reader, enc ManifestEncoder, resources *ResourceTable) error { return ParseXml(r, enc, resources) } // the main purpose of this reader is to // count number of bytes readed after parsing string table // so we can calc offset to the end of the string table type myReader struct { read int r io.Reader } type myRead interface { GetRead() int } func (mr *myReader) Read(p []byte) (n int, err error) { n, err = mr.r.Read(p) mr.read += n return } func (mr *myReader) GetRead() int { return mr.read } // Parse the binary Xml format. The resources are optional and can be nil. func ParseXml(r io.Reader, enc ManifestEncoder, resources *ResourceTable) error { x := binxmlParseInfo{ encoder: enc, res: resources, } id, headerLen, totalLen, err := parseChunkHeader(r) if err != nil { return err } //check if manifest is binary not plaintext if (id & 0xFF) == '<' { buf := bytes.NewBuffer(make([]byte, 0, 8)) binary.Write(buf, binary.LittleEndian, &id) binary.Write(buf, binary.LittleEndian, &headerLen) binary.Write(buf, binary.LittleEndian, &totalLen) if s := buf.String(); strings.HasPrefix(s, "> 16) - 1 attrCnt = (attrCnt & 0xFFFF) styleAttrIdx := (classAttrIdx >> 16) - 1 classAttrIdx = (classAttrIdx & 0xFFFF) _ = styleAttrIdx _ = idAttributeIdx namespace, err := x.strings.get(namespaceIdx) if err != nil { return fmt.Errorf("error decoding namespace: %s", err.Error()) } name, err := x.strings.get(nameIdx) if err != nil { return fmt.Errorf("error decoding name: %s", err.Error()) } tok := xml.StartElement{ Name: xml.Name{Local: name, Space: namespace}, } var attrData [attrValuesCount]uint32 for i := uint32(0); i < attrCnt; i++ { if err := binary.Read(r, binary.LittleEndian, &attrData); err != nil { return fmt.Errorf("error reading attrData: %s", err.Error()) } // Android actually reads attributes purely by their IDs (see frameworks/base/core/res/res/values/attrs_manifest.xml // and its generated R class, that's where the indexes come from, namely the AndroidManifestActivity array) // but good guy android actually puts the strings into the string table on the same indexes anyway, most of the time. // This is for the samples that don't have it, mostly due to obfuscators/minimizers. // The ID can't change, because it would break current APKs. // Sample: 98d2e837b8f3ac41e74b86b2d532972955e5352197a893206ecd9650f678ae31 // // The exception to this rule is the "package" attribute in the root manifest tag. That one MUST NOT use // resource ids, instead, it needs to use the string table. The meta attrs 'platformBuildVersion*' // are the same, except Android never parses them so it's just for manual analysis. // Sample: a3ee88cf1492237a1be846df824f9de30a6f779973fe3c41c7d7ed0be644ba37 // // In general, android doesn't care about namespaces, but if a resource ID is used, it has to have been // in the android: namespace, so we fix that up. // frameworks/base/core/jni/android_util_AssetManager.cpp android_content_AssetManager_retrieveAttributes // frameworks/base/core/java/android/content/pm/PackageParser.java parsePackageSplitNames var attrName string if attrData[attrIdxName] < uint32(len(x.resourceIds)) { attrName = getAttributteName(x.resourceIds[attrData[attrIdxName]]) } var attrNameFromStrings string if attrName == "" || name == "manifest" { attrNameFromStrings, err = x.strings.get(attrData[attrIdxName]) if err != nil { if attrName == "" { return fmt.Errorf("error decoding attrNameIdx: %s", err.Error()) } } else if attrName != "" && attrNameFromStrings != "package" && !strings.HasPrefix(attrNameFromStrings, "platformBuildVersion") { attrNameFromStrings = "" } } attrNameSpace, err := x.strings.get(attrData[attrIdxNamespace]) if err != nil { return fmt.Errorf("error decoding attrNamespaceIdx: %s", err.Error()) } if attrNameFromStrings != "" { attrName = attrNameFromStrings } else if attrNameSpace == "" { attrNameSpace = "http://schemas.android.com/apk/res/android" } attr := xml.Attr{ Name: xml.Name{Local: attrName, Space: attrNameSpace}, } switch attrData[attrIdxType] >> 24 { case AttrTypeString: attr.Value, err = x.strings.get(attrData[attrIdxString]) if err != nil { return fmt.Errorf("error decoding attrStringIdx: %s", err.Error()) } case AttrTypeIntBool: attr.Value = strconv.FormatBool(attrData[attrIdxData] != 0) case AttrTypeIntHex: attr.Value = fmt.Sprintf("0x%x", attrData[attrIdxData]) case AttrTypeFloat: val := (*float32)(unsafe.Pointer(&attrData[attrIdxData])) attr.Value = fmt.Sprintf("%g", *val) case AttrTypeReference: isValidString := false if x.res != nil { var e *ResourceEntry if attr.Name.Local == "icon" || attr.Name.Local == "roundIcon" { e, err = x.res.GetIconPng(attrData[attrIdxData]) } else { e, err = x.res.GetResourceEntry(attrData[attrIdxData]) } if err == nil { attr.Value, err = e.value.String() isValidString = err == nil } } if !isValidString && attr.Value == "" { attr.Value = fmt.Sprintf("@%x", attrData[attrIdxData]) } default: attr.Value = strconv.FormatInt(int64(int32(attrData[attrIdxData])), 10) } tok.Attr = append(tok.Attr, attr) } return x.encoder.EncodeToken(tok) } func (x *binxmlParseInfo) parseTagEnd(r *io.LimitedReader) error { var namespaceIdx, nameIdx uint32 if err := binary.Read(r, binary.LittleEndian, &namespaceIdx); err != nil { return fmt.Errorf("error reading namespace idx: %s", err.Error()) } if err := binary.Read(r, binary.LittleEndian, &nameIdx); err != nil { return fmt.Errorf("error reading name idx: %s", err.Error()) } namespace, err := x.strings.get(namespaceIdx) if err != nil { return fmt.Errorf("error decoding namespace: %s", err.Error()) } name, err := x.strings.get(nameIdx) if err != nil { return fmt.Errorf("error decoding name: %s", err.Error()) } return x.encoder.EncodeToken(xml.EndElement{Name: xml.Name{Local: name, Space: namespace}}) } func (x *binxmlParseInfo) parseText(r *io.LimitedReader) error { var idx uint32 if err := binary.Read(r, binary.LittleEndian, &idx); err != nil { return fmt.Errorf("error reading idx: %s", err.Error()) } text, err := x.strings.get(idx) if err != nil { return fmt.Errorf("error decoding idx: %s", err.Error()) } if _, err := io.CopyN(ioutil.Discard, r, 2*4); err != nil { return fmt.Errorf("error skipping: %s", err.Error()) } return x.encoder.EncodeToken(xml.CharData(text)) }