Delphi / RAD Studio¶
Delphi for Android (Embarcadero RAD Studio) compiles Object Pascal to native ARM code via an LLVM-based backend. The UI framework is FireMonkey (FMX), which renders its own cross-platform widgets rather than using Android's native view system. The resulting APK contains a thin Java shell that bootstraps a single large native .so library holding the entire application -- UI, business logic, and runtime support. Delphi Android apps are relatively uncommon on Google Play but appear in enterprise tooling, legacy business apps, and occasionally in malware samples targeting specific regions.
Architecture¶
Compilation Pipeline¶
| Stage | Description |
|---|---|
| Source | Object Pascal (.pas, .dpr) with optional inline assembly |
| Compiler | dccaarm64 (ARM64) or dccaarm (ARM32) -- Delphi's LLVM-based compiler |
| Output | Native ARM shared library (libFMXNativeActivity.so or custom name) |
| Runtime | Delphi RTL (runtime library) statically linked into the .so |
| UI | FireMonkey (FMX) renders via OpenGL ES / Skia -- no Android native widgets |
Runtime Model¶
| Layer | Components |
|---|---|
| Java shell | FMXNativeActivity (extends NativeActivity), com.embarcadero.firemonkey.* classes |
| Native library | Single .so containing application code, Delphi RTL, FireMonkey framework |
| Rendering | FireMonkey draws to a SurfaceView via OpenGL ES or Skia backend |
| Networking | Indy (TIdHTTP, TIdTCPClient) or TNetHTTPClient compiled into the native binary |
The Java layer is minimal. FMXNativeActivity uses Android's NativeActivity mechanism to immediately hand control to the native .so, which initializes the Delphi RTL and FireMonkey renderer. Almost all logic -- including UI event handling, networking, and data storage -- runs in native code.
FireMonkey UI¶
FireMonkey does not use Android's View hierarchy. It paints everything to a GPU surface, meaning:
- Android Layout Inspector and
uiautomatorshow a single opaque surface - Accessibility services see minimal widget tree
- UI automation tools cannot interact with individual FMX controls
Identification¶
| Indicator | Location |
|---|---|
libFMXNativeActivity.so |
lib/<arch>/ -- default name for Delphi Android apps |
com.embarcadero.firemonkey.* |
DEX classes in the Java shell |
com.embarcadero.rtl.* |
DEX helper classes for RTL integration |
FMXNativeActivity |
Main activity in AndroidManifest.xml |
Large single .so (10-50+ MB) |
lib/<arch>/ -- contains entire app + RTL + FMX |
System., Vcl., Fmx., IdHTTP |
String literals in the native binary (Delphi unit names) |
Quick check:
Secondary check via strings in the native library:
Code Location¶
Single Native Binary¶
The entire application is in one .so file. Unlike frameworks that split logic across multiple libraries or script files, Delphi compiles everything -- application code, RTL, FireMonkey, and third-party libraries -- into a single monolithic binary.
Expect a large file (typically 10-50 MB or more) because the Delphi RTL and FireMonkey framework are statically linked.
RTTI in the Binary¶
Delphi embeds extensive Runtime Type Information (RTTI) in compiled binaries. This is one of the most valuable features for reverse engineering -- RTTI structures contain:
- Class names (fully qualified with unit prefix, e.g.,
Unit1.TForm1) - Method names (published methods)
- Property names, types, and accessor methods
- Virtual method tables with readable names
- Form resource data (DFM serialized as binary)
This metadata survives compilation and is present in release builds unless explicitly stripped (which is rare in practice).
Analysis Tools & Workflow¶
| Tool | Purpose |
|---|---|
| Ghidra | Primary disassembler -- load the .so and recover RTTI structures |
| IDA Pro | Commercial disassembler with ARM decompiler |
| IDR (Interactive Delphi Reconstructor) | Parses Delphi RTTI from executables -- primarily x86/Windows but RTTI format knowledge transfers |
| Delphi RTTI Ghidra Scripts | Ghidra scripts to parse Delphi RTTI structures from ARM binaries |
| jadx | Decompile Java shell (minimal but shows manifest and entry points) |
| Frida | Runtime hooking of native functions |
Recommended Workflow¶
- Unzip APK and confirm Delphi via
libFMXNativeActivity.soorcom.embarcadero.*classes - Extract the native
.sofromlib/<arch>/ - Run strings extraction to identify Delphi unit names, class names, and API endpoints
- Load into Ghidra and apply Delphi RTTI parsing scripts to recover class hierarchies
- Locate key classes by searching RTTI strings for network-related (
TIdHTTP,TNetHTTPClient), crypto (TCipher,THash), or form names (TLoginForm,TMainForm) - Map virtual method tables -- Delphi VMTs have a predictable layout with class name pointer, instance size, and method pointers
- Hook at runtime with Frida to intercept function calls, network traffic, and data processing
RTTI Recovery in Ghidra¶
Delphi VMT structures follow a documented layout. The class name is stored as a ShortString (length-prefixed Pascal string) at a known offset from the VMT pointer:
Look for patterns like TForm1, TMainForm, TDataModule1 -- these indicate developer-created classes. Cross-reference these names in Ghidra with the surrounding code to find business logic entry points.
DFM Form Resources¶
Delphi forms are serialized as DFM (Delphi Form Module) resources, often embedded in the binary. DFM data contains the complete UI layout: component names, properties, event handler assignments, and nested component hierarchies. Search for DFM signatures (TPF0 for binary DFM format) in the .so:
Hooking Strategy¶
Network Interception¶
Delphi apps commonly use Indy (TIdHTTP) or TNetHTTPClient for networking. These are compiled into the native binary, so hooks target the native .so:
var libApp = Process.findModuleByName("libFMXNativeActivity.so");
if (libApp) {
libApp.enumerateExports().forEach(function(exp) {
if (exp.name.indexOf("TIdHTTP") !== -1 && exp.name.indexOf("DoRequest") !== -1) {
Interceptor.attach(exp.address, {
onEnter: function(args) {
console.log("[Delphi HTTP] DoRequest called");
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n"));
},
onLeave: function(retval) {}
});
}
});
}
Symbol Enumeration¶
Delphi binaries often export or contain symbols with mangled Object Pascal names. Enumerate them to find targets:
var libApp = Process.findModuleByName("libFMXNativeActivity.so");
if (libApp) {
libApp.enumerateExports().forEach(function(exp) {
if (exp.type === "function") {
console.log("[Export] " + exp.name + " @ " + exp.address);
}
});
}
Function Tracing¶
For tracing specific Delphi class methods, use pattern-based hooking on mangled names:
var libApp = Process.findModuleByName("libFMXNativeActivity.so");
if (libApp) {
libApp.enumerateSymbols().forEach(function(sym) {
if (sym.name.indexOf("TMainForm") !== -1 || sym.name.indexOf("TLoginForm") !== -1) {
Interceptor.attach(sym.address, {
onEnter: function(args) {
console.log("[Delphi] " + sym.name + " called");
},
onLeave: function(retval) {}
});
}
});
}
SSL Pinning Bypass¶
Delphi apps implement SSL/TLS through Indy's TIdSSLIOHandlerSocketOpenSSL or the newer TNetHTTPClient with THTTPSecureProtocol. Both ultimately use OpenSSL (bundled or system).
Indy SSL Handler Hook¶
var libApp = Process.findModuleByName("libFMXNativeActivity.so");
if (libApp) {
libApp.enumerateExports().forEach(function(exp) {
if (exp.name.indexOf("TIdSSLIOHandler") !== -1 && exp.name.indexOf("Verify") !== -1) {
Interceptor.attach(exp.address, {
onEnter: function(args) {
console.log("[SSL] TIdSSLIOHandler verify intercepted");
},
onLeave: function(retval) {
retval.replace(ptr(1));
console.log("[SSL] Forced verify to return true");
}
});
}
});
}
OpenSSL Direct Bypass¶
Since Delphi bundles OpenSSL, target the verification callback directly:
var libssl = Process.findModuleByName("libssl.so");
if (libssl) {
var setVerify = libssl.findExportByName("SSL_CTX_set_verify");
if (setVerify) {
Interceptor.attach(setVerify, {
onEnter: function(args) {
args[1] = ptr(0);
args[2] = ptr(0);
console.log("[SSL] SSL_CTX_set_verify forced to SSL_VERIFY_NONE");
}
});
}
}
TNetHTTPClient Certificate Validation¶
For apps using the newer TNetHTTPClient, the OnValidateServerCertificate event controls pinning. Hook the validation callback:
var libApp = Process.findModuleByName("libFMXNativeActivity.so");
if (libApp) {
libApp.enumerateSymbols().forEach(function(sym) {
if (sym.name.indexOf("ValidateServerCertificate") !== -1 || sym.name.indexOf("OnValidateCert") !== -1) {
Interceptor.attach(sym.address, {
onEnter: function(args) {
console.log("[SSL] Certificate validation callback intercepted");
},
onLeave: function(retval) {
retval.replace(ptr(1));
}
});
}
});
}
Obfuscation & Anti-Analysis¶
Delphi Android apps rarely employ advanced obfuscation. The ecosystem lacks mature obfuscation tooling comparable to Java/Kotlin or JavaScript frameworks. RTTI preservation means class/method/property names are readable in release builds, developers rarely strip symbols, DFM form resources expose UI structure, and few commercial protectors target Delphi ARM binaries. Some developers apply generic ARM binary protectors or use app-level protectors from the Packers ecosystem, but these are uncommon.
RE Difficulty Assessment¶
| Aspect | Rating |
|---|---|
| Code format | Compiled native ARM (Object Pascal via LLVM) |
| Readability | Low for code, high for metadata (RTTI, DFM) |
| String extraction | Excellent -- RTTI preserves class/method/property names |
| Control flow recovery | Standard native RE, aided by RTTI-based function identification |
| Patching | Binary patching of the .so |
| Obfuscation ceiling | Low -- limited tooling for Delphi ARM obfuscation |
| Overall difficulty | Moderate-Hard (rank 10/28) |
The rich RTTI metadata in Delphi binaries is a double-edged sword for developers -- it significantly aids reverse engineering by providing a readable map of the application's class structure, method names, and properties. The main challenge is the size of the monolithic binary and the need for ARM native analysis skills. Once RTTI is parsed, locating and understanding specific functionality becomes substantially easier than with a typical stripped C/C++ binary.