mcpField
R
reversing-xamarin-maui

reversing-xamarin-maui

github.com/trilwuSecurity17748 installsUpdated 12w ago
Repository

About reversing-xamarin-maui

A reverse-engineering skill for Android and iOS apps built with Xamarin or .NET MAUI. It explains how to identify and extract packaged .NET assemblies, inspect recovered C#, and use Mono-aware runtime hooks when examining app behavior.

Key features

  1. 01Identify Xamarin packaging generations
  2. 02Extract AssemblyStore and XALZ assemblies
  3. 03Decompile DLLs into readable C#
  4. 04Hook Mono methods with Frida
  5. 05Inspect secrets and app-side logic
  6. 06Patch and repackage assemblies

Use cases

  1. 01Analyze a Xamarin APK or IPA
  2. 02Find embedded credentials in app assemblies
  3. 03Observe managed methods at runtime
  4. 04Modify and rebuild an app package

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/trilwu/secskills --skill reversing-xamarin-maui

After installing, type/reversing-xamarin-mauiin Claude Code to confirm it loaded.

Details

From the project README

Reversing Xamarin and .NET MAUI Apps

Xamarin apps hide a complete .NET application inside an Android package. Once you extract and decompress the assemblies you get near-source-quality C#, which makes this one of the highest-return reversing jobs — but only if you get past the container format, which changed twice and defeats naive unzipping.

When to Use

  • The APK contains lib/*/libmonodroid.so, libmonosgen-2.0.so, or libxamarin-app.so
  • The APK contains assemblies/assemblies.blob or assemblies/*.dll
  • jadx shows only mono.MonoPackageManager and Xamarin bootstrap classes
  • An IPA contains .dll files or a Frameworks/Mono* layout
  • The target is described as Xamarin, Xamarin.Forms, or .NET MAUI

When NOT to Use

  • Unity (global-metadata.dat, libil2cpp.so) — use reversing-unity-il2cpp; Unity is also .NET but uses a completely different packaging
  • Flutter or React Native — use reversing-flutter-apps or reversing-react-native-apps
  • TLS interception problems — use bypassing-mobile-pinning
  • The wider assessment — use testing-mobile-applications

Identify the Packaging Generation

Three formats shipped over the product's life, and the extraction differs.

unzip -l target.apk | rg 'assemblies|libmonodroid|libxamarin|libmonosgen'
What you seeGenerationExtraction
assemblies/*.dll, readable PE headers (MZ)Classic, uncompressedUnzip and decompile directly
assemblies/*.dll starting with XALZClassic, LZ4-compressedDecompress the XALZ wrapper first
assemblies/assemblies.blob + .manifestAssemblyStorepyxamstore to unpack
lib/*/libxamarin-app.so with no assemblies/.NET 6+ / MAUI, assemblies in the ELFExtract from the shared library
No managed assemblies anywhereNativeAOTNot .NET reversing — use analyzing-binaries
# Which one is it? Check the first four bytes of an assembly
unzip -p target.apk 'assemblies/Mono.Android.dll' 2>/dev/null | xxd | head -1
# 4d5a...  → MZ, a plain .NET PE, decompile now
# 58414c5a → "XALZ", LZ4-compressed, decompress first

Extraction

# AssemblyStore (assemblies.blob)
pyxamstore unpack -d ./out target.apk
# produces the individual .dll files under ./out

# XALZ-compressed individual DLLs
# The header is: magic "XALZ" | descriptor index | uncompressed size | LZ4 block
python3 -c "
import lz4.block, struct, sys
d = open(sys.argv[1],'rb').read()
assert d[:4] == b'XALZ'
size = struct.unpack('<I', d[8:12])[0]
open(sys.argv[2],'wb').write(lz4.block.decompress(d[12:], uncompressed_size=size))
" Mono.Android.dll Mono.Android.decompressed.dll

# MAUI / .NET 6+ assemblies embedded in libxamarin-app.so
# They are stored as sections; carve by MZ header or use pyxamstore's newer modes
unzip -j target.apk 'lib/arm64-v8a/*' -d libs && binwalk -Me libs/libxamarin-app.so

Once you have plain .dll files it is ordinary .NET work:

ilspycmd ./out/YourApp.dll -o ./decompiled     # or dnSpy / dnSpyEx / dotPeek
rg -n 'https?://|ApiKey|ConnectionString|Bearer|password' ./decompiled | head -40

Start with the app's own assembly, not the framework ones. It is usually named after the product; Mono.Android.dll, System.*, and Xamarin.* are stock and waste your time.

Runtime Hooking

Frida's Java bridge does not see Mono methods — the app's logic is not in the JVM. You need Mono-aware instrumentation.

# Enumerate the Mono runtime and its loaded assemblies
frida -U -f com.target.app -l frida-mono-api.js
# Fridax targets Xamarin specifically; also useful:
#   mono_get_root_domain, mono_assembly_foreach, mono_class_get_methods,
#   mono_compile_method  → resolve a managed method to a native address, then
#   Interceptor.attach at that address

The practical route: decompile first, find the exact class and method you want, then resolve it through the Mono API and attach. Blind hooking of Mono is far slower than reading the C# you already recovered.

Certificate validation in Xamarin lives in managed code, which is why generic Android pinning bypasses miss it entirely:

// The two hook targets, both in the managed layer
ServicePointManager.ServerCertificateValidationCallback
HttpClientHandler.ServerCertificateCustomValidationCallback

Force these to return true via Mono hooking, or patch the assembly and repack. See bypassing-mobile-pinning for the surrounding diagnosis.

Where the Findings Are

Xamarin apps concentrate risk in ways that are easy to spot once decompiled:

  • Hardcoded secrets in C#. Developers treat compiled assemblies as opaque; API keys, connection strings, and storage credentials ship in plain IL.
  • Full backend logic in the client. Shared code between the mobile app and the server-side project is a Xamarin idiom, so the client often contains the authoritative business rules — and sometimes the server's own models and validation, which tells you exactly how to shape a malicious request.
  • Custom ServerCertificateValidationCallback returning true. Common in development builds that shipped.
  • Insecure local storage. Xamarin.Essentials.SecureStorage is usually fine; Preferences and raw file writes are not.
  • Embedded SQLite with credentials or full offline datasets.

Patching and Repacking

# Edit IL directly in dnSpy, save the assembly, then rebuild the container
pyxamstore pack -d ./out          # rebuild assemblies.blob
apktool b target -o patched.apk && apksigner sign --ks debug.keystore patched.apk

Repacking must reproduce the original container format exactly — an uncompressed DLL where the loader expects XALZ, or a mis-sized blob, fails at startup with an unhelpful native crash. Prefer runtime hooking when you only need to observe.

Rationalizations to Reject

  • "jadx found nothing, so it's obfuscated." There is nothing in the dex. The app is in assemblies/.
  • "Unzipping gave me DLLs but dnSpy rejects them." They are XALZ-compressed. Check the first four bytes.
  • "Frida's Java hooks aren't working." The methods are Mono, not JVM. Use the Mono API.
  • "It's compiled to a DLL, so the key is safe." IL decompiles to readable C#. Treat every assembly constant as public.
  • "The pinning bypass ran and traffic still fails." Xamarin validates in managed code; the Java-layer bypass never touched it.
  • "There are hundreds of assemblies." Most are stock framework. Read the one named after the product.

References

  • testing-mobile-applications — the wider assessment and platform storage
  • bypassing-mobile-pinning — diagnosing the interception failure around this
  • reversing-unity-il2cpp — the other .NET-based mobile framework
  • analyzing-binaries — NativeAOT builds with no managed assemblies
  • pyxamstore, dnSpy/dnSpyEx, ILSpy, Fridax, frida-mono-api

Comments

Markdown supported