diff --git a/src/mobile-pentesting/xamarin-apps.md b/src/mobile-pentesting/xamarin-apps.md index 457dec6a1dd..6389d16038d 100644 --- a/src/mobile-pentesting/xamarin-apps.md +++ b/src/mobile-pentesting/xamarin-apps.md @@ -42,7 +42,50 @@ pyxamstore unpack -d /path/to/decompressed/apk/assemblies/ pyxamstore pack ``` -Some recent Xamarin/MAUI builds store compressed assemblies using the **XALZ** format inside `/assemblies.blob` or `/resources/assemblies`. You can quickly decompress them with the [xamarout](https://pypi.org/project/xamarout/) library: +#### .NET MAUI 9 / .NET for Android assembly stores inside ELF `.so` + +Recent Android MAUI 9 builds no longer expose `assemblies.blob` directly. Instead, each ABI ships an ELF container such as `lib/arm64-v8a/libassemblies.arm64-v8a.blob.so`. This is a valid shared library with a custom `payload` section that contains the managed assembly store. + +Quick workflow: + +```bash +unzip app.apk -d app_unpacked +llvm-readelf --section-headers app_unpacked/lib/arm64-v8a/libassemblies.arm64-v8a.blob.so +llvm-objcopy --dump-section=payload=payload.bin \ + app_unpacked/lib/arm64-v8a/libassemblies.arm64-v8a.blob.so +hexdump -c -n 4 payload.bin # XABA +``` + +If `llvm-readelf` shows a `payload` section, dump it and verify the extracted blob starts with `XABA` (`0x41424158`). That payload is the assembly store documented by .NET for Android, not a single DLL. + +The store layout is useful when you need to carve assemblies manually or validate an extractor: + +- Header: `struct.unpack('<5I', ...)` for `magic`, `version`, `entry_count`, `index_entry_count`, `index_size` +- Descriptors: `entry_count` records of `struct.unpack('<7I', ...)` with `data_offset` / `data_size` and optional debug/config offsets +- Index: skip `index_size` bytes +- Names: `uint32 length` + UTF-8 bytes +- Data: seek to each `data_offset` and write `data_size` bytes as `.dll` + +Some extracted entries still won't open directly in dnSpy/ILSpy/dotPeek because they are additionally wrapped with **XALZ**. In that case: + +- Check the first 4 bytes of each extracted file for `XALZ` +- Read the uncompressed size from bytes `8:12` as little-endian `uint32` +- Decompress bytes `12:` with `lz4.block.decompress(...)` + +Minimal decompression logic: + +```python +import struct +import lz4.block + +def decompress_xalz(data): + size = struct.unpack('