Extract the managed (.NET) assemblies out of a MAUI Android assembly store.
.NET Android apps ship their managed code inside a shared library, usually
libassemblies.<abi>.blob.so (older versions) or libassembly-store.so.
mauidll parses that file and writes every assembly it contains to disk as a
standard .dll file, ready for inspection in a decompiler such as
ILSpy.
The tool is a single self-contained Crystal program with no external dependencies. The LZ4 decompressor it needs is implemented inline, so nothing beyond the standard library is required.
crystal build --release mauidll.cr -o mauidll
./mauidll libassembly-store.so extracted-dllsThe binary produced by crystal build is self-contained: it only needs Crystal
on the build machine, not on any machine where you run it.
mauidll is compiled with the Crystal language. If you do not have Crystal yet:
The most popular way is Homebrew:
brew install crystalCrystal is also available as an official universal tarball (Apple Silicon and Intel) from the downloads page.
On Debian, Ubuntu and related distributions, install the official package repository and then the compiler:
curl -fsSL https://crystal-lang.org/install.sh | sudo bash
sudo apt install crystalAlternatively, on any distribution that supports snaps:
sudo snap install crystal --classicOn Arch Linux:
sudo pacman -S crystal shardscrystal build --release mauidll.cr -o mauidllmauidll was developed and tested with Crystal 1.20.x. It uses only the standard
library, so any reasonably recent release should work.
./mauidll <assembly-store.so> [outdir]| Argument | Meaning |
|---|---|
assembly-store.so |
Path to the store, e.g. libassemblies.arm64-v8a.blob.so |
outdir (optional) |
Output directory, defaults to dlls in the current directory |
Example:
./mauidll /tmp/app64-v8a/libassembly-store.so /tmp/extractedOutput lines report one line per assembly (name: size -> decompressed size, valid PE), followed by a summary such as:
Extracted 235 entries, valid PE (MZ) after extraction: 235/235
- The store file is an ELF object. The assembly store lives in a
non-loadable
payloadsection, whichmauidlllocates via the ELF section headers (32- and 64-bit ELF are both supported). - The payload begins with a 20-byte
XABAheader: magic, version, entry count, index entry count and index size. - After the index come the descriptors, 28 bytes each (mapping index, data offset, data size), then a table of names (uint32 little-endian length + UTF-8 bytes per entry).
- Each blob is either an already-compressed assembly or a raw one. A blob
starting with
XALZis a raw LZ4 block (not the LZ4 frame format) preceded by a 12-byte header that includes the uncompressed size; anything else (starting withMZ) is stored verbatim. - Decompressed blobs are written to disk under their assembly name and
checked to start with the
MZPE signature.