Introduction to resources.arsc in Android Reverse Engineering
The resources.arsc file is a cornerstone of Android application structure, serving as a binary index for all compiled resources within an APK, including strings, layouts, drawables, and more. It acts as a crucial lookup table, mapping resource IDs (integers) used in compiled Java/Smali code to their actual values or paths. For reverse engineers, mastering resources.arsc analysis is paramount, especially when dealing with obfuscated applications where meaningful string literals or asset references might be hidden or transformed.
While tools like apktool automate the decompilation of resources.arsc into human-readable XML files (e.g., res/values/*.xml), a deeper understanding of its binary format and the ability to parse it manually can uncover secrets that automated tools might miss or misinterpret in highly sophisticated obfuscation scenarios. This article will guide you through advanced analysis techniques, connecting resource IDs to their corresponding values and identifying potential custom obfuscation patterns.
The Anatomy of resources.arsc
At its core, resources.arsc is a binary file composed of a sequence of structured chunks. These chunks define the various components of the resource table, linking resource IDs to their actual values based on configurations (e.g., language, screen density). Understanding these chunks is fundamental to manual parsing.
Key Structures
The primary structures within resources.arsc, as defined in the Android Open Source Project’s ResourceTypes.h, include:
ResTable_header: The very first chunk, providing global information about the resource table, notably the total number of packages.- Global String Pool: A string pool containing all unique strings referenced by the resource table, such as type names (e.g., “string”, “layout”) and key names (e.g., “app_name”, “activity_main”).
ResTable_package: Each package (typically one, representing the application itself) has its own header. It includes the package ID and name, along with separate string pools for type names and key names specific to that package.ResTable_typeSpec: For each resource type (e.g.,string,layout,drawable), this chunk defines the entry count and configuration flags. It effectively declares which resource IDs are valid for a given type.ResTable_type: This chunk provides configuration-specific data for a resource type (e.g., English strings, German strings, high-density drawables). It contains an array of offsets pointing toRes_entrystructures.Res_entry: The heart of the resource mapping. Each entry contains flags (e.g., whether the resource is public) and an offset into the value string pool or a directRes_valuestructure that holds the actual resource data (e.g., a string literal, a reference to another resource, a dimension, a color).
When apktool decompiles an APK, it parses this binary structure and reconstructs the resource definitions into human-readable XML files, such as res/values/strings.xml, res/layout/activity_main.xml, and importantly, public.xml, which explicitly maps resource names to their corresponding runtime IDs.
Advanced Analysis for Obfuscated Applications
While apktool is excellent for most cases, heavily obfuscated applications might employ techniques that necessitate a deeper dive. This could include manipulating resources.arsc entries, encrypting string values, or dynamically loading resources using custom mechanisms that bypass standard Android resource resolution.
Identifying Resource References in Smali
In obfuscated applications, string literals or file paths might be dynamically constructed or retrieved from resources to avoid detection. Resource IDs, however, are typically fixed and can be traced directly in Smali code. An Android resource ID is a 32-bit integer, usually represented as 0xPPTTIIII, where:
PP: Package ID (e.g.,0x7ffor the application’s own resources).TT: Type ID (e.g.,0x0bfor layout,0x11for string).IIII: Entry ID (the specific resource within that type).
You’ll often find these IDs loaded into registers in Smali:
.method private onCreate(Landroid/os/Bundle;)V .locals 1 .param p1, "savedInstanceState" # Landroid/os/Bundle; invoke-super {p0, p1}, Landroidx/appcompat/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V const v0, 0x7f0b001d # R.layout.activity_main invoke-virtual {p0, v0}, Lcom/example/app/MainActivity;->setContentView(I)V const v0, 0x7f08003a # R.id.my_button invoke-virtual {p0, v0}, Lcom/example/app/MainActivity;->findViewById(I)Landroid/view/View; move-result-object v0 check-cast v0, Landroid/widget/Button; const v1, 0x7f110000 # R.string.welcome_message invoke-virtual {p0}, Lcom/example/app/MainActivity;->getResources()Landroid/content/res/Resources; move-result-object v2 invoke-virtual {v2, v1}, Landroid/content/res/Resources;->getString(I)Ljava/lang/String; move-result-object v1 # ... other code .end method
By searching for these 0xPPTTIIII patterns in the Smali code, you can identify where specific resources are being used, even if the surrounding code is heavily obfuscated.
Programmatic Parsing for Deeper Insight
In rare but critical cases, such as when an app employs custom resource formats or heavily manipulates the resources.arsc file’s integrity, a custom parser can be invaluable. This allows for validation, specialized extraction, or even reconstruction of the resource table.
A Python script can be used to read and interpret the binary data. Here’s a simplified example demonstrating how to parse the initial header and string pool offsets:
import struct # Constants from ResourceTypes.h RES_NULL_TYPE = 0x0000 RES_STRING_POOL_TYPE = 0x0001 RES_TABLE_TYPE = 0x0002 RES_TABLE_PACKAGE_TYPE = 0x0200 def parse_arsc_header(arsc_data): # Res_chunk header (type, headerSize, size) chunk_type, header_size, total_size = struct.unpack('<HHL', arsc_data[0:8]) print(f"Chunk Type: {hex(chunk_type)}, Header Size: {header_size}, Total Size: {total_size}") if chunk_type != RES_TABLE_TYPE: print("Error: Not a ResTable_header") return None # ResTable_header (packageCount) package_count = struct.unpack('<L', arsc_data[8:12])[0] print(f"Package Count: {package_count}") return chunk_type, header_size, total_size, package_count def parse_string_pool_header(data, offset): # Res_chunk header chunk_type, header_size, total_size = struct.unpack('<HHL', data[offset:offset+8]) print(f" String Pool Chunk Type: {hex(chunk_type)}, Header Size: {header_size}, Total Size: {total_size}") # ResStringPool_header string_count, style_count, flags, strings_start, styles_start = struct.unpack('<LLLLL', data[offset+8:offset+28]) print(f" String Count: {string_count}, Styles Count: {style_count}") print(f" Strings Start: {hex(strings_start)}, Styles Start: {hex(styles_start)}") return offset + total_size # Return end of chunk # Example usage (replace 'path/to/resources.arsc' with your file) # with open('path/to/resources.arsc', 'rb') as f: # arsc_data = f.read() # # Parse ResTable_header arsc_chunk_end_offset = parse_arsc_header(arsc_data) # # Next chunk after ResTable_header is usually the Global String Pool # if arsc_chunk_end_offset: # string_pool_end_offset = parse_string_pool_header(arsc_data, arsc_chunk_end_offset) # ... continue parsing packages, type specs, etc.
This snippet demonstrates the basic idea: read bytes, unpack them according to the known binary structures, and iterate through chunks. A full parser would involve more complex logic to handle varying chunk types and string pool encodings, but this gives a starting point for specialized needs.
Extracting Encrypted or Custom Assets
One advanced obfuscation technique involves encrypting resource values (especially strings) or storing assets in a non-standard format. While resources.arsc will still point to these values, the smali code that retrieves them will contain the decryption or custom loading logic.
When you encounter a resource ID in Smali (e.g., `const v1, 0x7f110000`), trace its usage. If it’s passed to standard Android API calls like getResources().getString(ID), then apktool‘s output is likely sufficient. However, if the ID is used in custom methods, array lookups, or combined with other values before being processed, it might indicate custom handling. Look for functions that take integer resource IDs and perform operations like:
- XORing or other bitwise operations.
- Byte-by-byte reading from a custom file or embedded blob.
- Calls to native libraries (JNI) for decryption.
By correlating the resource ID with the surrounding Smali code, you can often reverse-engineer the decryption routine or custom asset loading mechanism.
Hands-On: Decompiling and Analyzing resources.arsc
Let’s walk through a practical example using apktool and command-line tools.
Step 1: Decompile the APK
First, use apktool to decompile your target APK. This will extract all resources and Smali code into a directory.
apktool d my_obfuscated_app.apk -o my_app_decoded
This command creates a directory named my_app_decoded containing the decompiled app structure.
Step 2: Locate and Inspect resources.arsc
Inside my_app_decoded/, you’ll find the original binary resources.arsc. More importantly, apktool will have generated human-readable resource files in my_app_decoded/res/ (e.g., res/values/strings.xml, res/layout/activity_main.xml).
The most crucial file for mapping is my_app_decoded/res/values/public.xml. This file explicitly lists all public resource IDs and their corresponding types and names:
<?xml version="1.0" encoding="utf-8"?> <resources> <public type="drawable" name="ic_launcher_background" id="0x7f080017" /> <public type="layout" name="activity_main" id="0x7f0b001d" /> <public type="string" name="app_name" id="0x7f110000" /> <public type="string" name="welcome_message" id="0x7f110001" /> <!-- ... more resources --> </resources>
Step 3: Mapping Resource IDs to Values
With public.xml, you can easily map a resource ID found in Smali back to its original name. For instance, if you see 0x7f110000 in Smali code, public.xml tells you it refers to R.string.app_name.
Conversely, if you’re looking for where a specific resource is used, you can find its ID in public.xml and then use grep to search the Smali files:
grep -r "0x7f110000" my_app_decoded/smali/
This command will output all lines in the Smali code where the resource ID for app_name is referenced, allowing you to trace its usage even if the surrounding code is heavily obfuscated with meaningless class or method names.
Conclusion
Advanced resources.arsc analysis is an indispensable skill in the Android reverse engineer’s toolkit. By understanding its binary structure, correlating resource IDs with Smali code, and knowing when to employ custom parsing techniques, you can effectively navigate the complexities of even the most heavily obfuscated Android applications. This detailed approach enables the extraction of hidden strings, assets, and the unraveling of custom resource handling mechanisms, ultimately shedding light on the application’s true functionality and hidden capabilities.
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →