androidbinary Go Library

repository·main·Indexed 43 days ago

https://github.com/shogo82148/androidbinary

A Go library for parsing Android binary files, including APK files, XML binaries, and resource tables (.arsc). It provides high-level APIs for inspecting APK metadata and low-level tools for interacting with ResStringPool, ResTableConfig, and binary XML chunks.

Tokens
4.8K
Snippets
14
Records
22
Agent score
55%

What's inside androidbinary

  1. Parse APK files using the High Level API

    main

    Use the apk package to open and inspect Android APK files. The apk.OpenFile function allows you to access high-level metadata such as the application's icon, package name, and localized labels.

    Note that retrieving labels requires a ResTableConfig to specify the desired language configuration (e.g., using androidbinary.ResTableConfig).

    package main
    
    import (
    	"github.com/shogo82148/androidbinary/apk"
    	"github.com/shogo82148/androidbinary"
    )
    
    func main() {
    	pkg, _ := apk.OpenFile("your-android-app.apk")
    	defer pkg.Close()
    
    	icon, _ := pkg.Icon(nil) // returns the icon of APK as image.Image
    	pkgName := pkg.PackageName() // returns the package name
    
    	resConfigEN := &androidbinary.ResTableConfig{
    		Language: [2]uint8{uint8('e'), uint8('n')},
    	}
    	appLabel, _ := pkg.Label(resConfigEN) // get app label for en translation
    }
  2. Understand ResStringPoolHeader and Flags

    main

    The ResStringPoolHeader defines the layout of a string pool chunk. The Flags field determines how strings are encoded.

    Flags

    • SortedFlag: 1 << 0 (Indicates strings are sorted)
    • UTF8Flag: 1 << 8 (Indicates strings are UTF-8 encoded; if not set, they are UTF-16)
  3. Configure ResTableConfig for resource matching

    main

    The ResTableConfig struct is used to specify device parameters (like language, screen density, or orientation) to ensure the correct resource variant is selected from the table.

    Key fields include:

    • Language, Country: For locale-specific resources.
    • Orientation, Touchscreen: For physical device characteristics.
    • ScreenWidthDp, ScreenHeightDp, SmallestScreenWidthDp: For responsive layout matching.
    • SDKVersion, MinorVersion: For API level compatibility.
    • UIMode: For night mode or desktop/car modes.
    • ScreenLayout: For screen size and layout direction (LTR/RTL).

    Methods for logic:

    • Match(settings *ResTableConfig) bool: Checks if the resource's configuration is compatible with the provided settings.
    • IsMoreSpecificThan(o *ResTableConfig) bool: Determines if this configuration is a more specialized version of another.
    • Locale() string: Returns a string representation of the language and country (e.g., en-US).
  4. Resolve resource references in Bool, Int32, and String

    main

    The Bool, Int32, and String types are designed to work with Android's resource system. Because XML attributes might contain a resource ID (e.g., @id/something) instead of a literal value, these types require a TableFile and ResTableConfig to perform resolution.

    You must link these dependencies to the types using the WithTableFile and WithResTableConfig methods before calling the resolution methods (Bool(), Int32(), or String()).

    // 1. Define your struct with the special types
    type MyXmlStruct struct {
    	MyValue androidbinary.String `xml:"my_attr,attr"`
    }
    
    // 2. After unmarshaling, link the table and config
    var data MyXmlStruct
    // ... xml.Unmarshal(data) ...
    
    // This is required for resolution to work!
    data.MyValue = data.MyValue.WithTableFile(table).WithResTableConfig(config)
    
    // 3. Now you can resolve the reference
    resolved, err := data.MyValue.String()
  5. Parse Resource files (.arsc)

    main

    To interact with Android resource table files (resources.arsc), use androidbinary.NewTableFile. This allows you to retrieve specific resources by their ResID using the GetResource method. You can pass a ResTableConfig to GetResource to handle specific language or density configurations.

    package main
    
    import (
    	"fmt"
    	"github.com/shogo82148/androidbinary"
    )
    
    func main() {
    	f, _ := os.Open("resources.arsc")
    	rsc, _ := androidbinary.NewTableFile(f)
    	resource, _ := rsc.GetResource(androidbinary.ResID(0xCAFEBABE), nil)
    	fmt.Println(resource)
    }
  6. Parse XML binary files

    main

    For low-level access to Android XML binary files (like AndroidManifest.xml), use androidbinary.NewXMLFile to create a parser. You can then use xml.Reader() to obtain a reader that implements standard Go interfaces, allowing you to use xml.Unmarshal to populate custom structs (such as apk.Manifest).

    package main
    
    import (
    	"encoding/xml"
    	"io/ioutil"
    	"os"
    
    	"github.com/shogo82148/androidbinary"
    	"github.com/shogo82148/androidbinary/apk"
    )
    
    func main() {
    	f, _ := os.Open("AndroidManifest.xml")
    	xmlFile, _ := androidbinary.NewXMLFile(f)
    	reader := xmlFile.Reader()
    
    	// read XML from reader
    	var manifest apk.Manifest
    	data, _ := ioutil.ReadAll(reader)
    	xml.Unmarshal(data, &manifest)
    }
  7. Access strings in a ResStringPool

    main

    The ResStringPool type provides methods to retrieve strings stored in an Android resource pool using a ResStringPoolRef.

    • GetString(ref ResStringPoolRef) string: Returns the string at the specified reference. Warning: This method will panic if the reference is out of bounds.
    • HasString(ref ResStringPoolRef) bool: Safely checks if the pool contains the given reference without panicking. Returns false if the pool is nil or the reference is invalid.
    // Example usage
    if pool.HasString(ref) {
        str := pool.GetString(ref)
        fmt.Println(str)
    }
  8. Parse ResID from strings

    main

    Android resource IDs are often represented as strings in the format @0xXXXXXXXX. Use ParseResID to convert these strings into a ResID type, which allows you to programmatically access the package, type, and entry indices.

    • IsResID(s string) bool: Checks if a string follows the @0x prefix format.
    • ParseResID(s string) (ResID, error): Converts the hex string to a ResID.
    • ResID.String(): Returns the string representation (e.g., @0x01010003).
    • ResID.Package() uint32: Extracts the package index.
    • ResID.Type() int: Extracts the type index.
    • ResID.Entry() int: Extracts the entry index.
    id, err := androidbinary.ParseResID("@0x01010003")
    if err == nil {
    	fmt.Println("Package:", id.Package())
    	fmt.Println("Type:", id.Type())
    	fmt.Println("Entry:", id.Entry())
    }
  9. Parse and access resources using TableFile

    main

    The TableFile struct is the primary entry point for interacting with Android resource tables. You can initialize it from an io.ReaderAt (such as a file or a memory-mapped buffer) and use it to retrieve specific resources by their ResID while considering device configurations.

    To get a resource, use GetResource(id ResID, config *ResTableConfig). This method handles the complexity of finding the correct resource variant that matches your device's current configuration (locale, screen size, orientation, etc.).

    // Assuming 'r' is an io.ReaderAt containing the resource table
    table, err := androidbinary.NewTableFile(r)
    if err != nil {
    	return err
    }
    
    // Define the current device configuration
    config := &androidbinary.ResTableConfig{
    	Language: [2]uint8{'e', 'n'}, // English
    	Country:  [2]uint8{'U', 'S'}, // US
    }
    
    // Parse a resource ID (e.g., from a string like "@0x01010003")
    id, err := androidbinary.ParseResID("@0x01010003")
    if err != nil {
    	return err
    }
    
    // Retrieve the resource value
    val, err := table.GetResource(id, config)
    if err != nil {
    	return err
    }
    
    // 'val' will be the underlying type (string, int, bool, etc.)
    fmt.Printf("Resource value: %v\n", val)
  10. Decode binary XML into Go structs with Decode

    main

    The Decode method allows you to unmarshal the parsed text-based XML directly into a Go struct. To correctly resolve resource references (like @id/something) into actual values, you must provide a *TableFile and a *ResTableConfig.

    This method uses reflection to inject resolved resource values into your target struct.

    var target MyStruct
    err := xmlFile.Decode(&target, tableFile, resTableConfig)
    if err != nil {
    	panic(err)
    }
  11. Parse Android binary XML files with NewXMLFile

    main

    Use NewXMLFile to parse an Android binary XML file from an io.ReaderAt. This function reads the binary chunks, processes the string pool, and converts the binary XML into a text-based XML format stored within the XMLFile struct.

    To access the parsed content, use the Reader() method, which returns a *bytes.Reader containing the text-formatted XML.

    import (
    	"io"
    	"os"
    	"github.com/shogo82148/shogo82148/androidbinary"
    )
    
    func main() {
    	f, err := os.Open("AndroidManifest.xml")
    	if err != nil {
    		panic(err)
    	}
    	defer f.Close()
    
    	// NewXMLFile requires an io.ReaderAt
    	xmlFile, err := androidbinary.NewXMLFile(f)
    	if err != nil {
    		panic(err)
    	}
    
    	// Get a reader for the text-formatted XML
    	reader := xmlFile.Reader()
    	// Now you can use standard encoding/xml to parse the text XML
    }
  12. Retrieve strings from the XML string pool

    main

    The XMLFile maintains a ResStringPool. You can retrieve strings using their ResStringPoolRef via GetString. If you need to check if a reference is valid before attempting to access it, use HasString.

    // Get a string by its reference
    str := xmlFile.GetString(someRef)
    
    // Check if a reference exists
    if xmlFile.HasString(someRef) {
    	str := xmlFile.GetString(someRef)
    }