Rust static-analysis library for compiled Delphi, C++Builder, and Free Pascal / Lazarus binaries. Identifies the toolchain, recovers full class hierarchies and per-class RTTI, parses embedded form streams, and surfaces every blob and cross-reference the runtime metadata makes reachable - without executing the binary.
use undelphi::DelphiBinary;
let bytes = std::fs::read("my_app.exe")?;
if let Ok(bin) = DelphiBinary::parse(&bytes) {
if let Some(info) = bin.compiler() {
println!("{:?} {} on {:?}/{:?}", info.compiler, info.raw, info.os, info.arch);
}
println!("{} classes, {} forms", bin.classes().len(), bin.forms().len());
}| Signal | Confidence | Recovers |
|---|---|---|
Embarcadero build-string (Embarcadero Delphi for Win64 compiler version 36.0 …) |
High | exact compiler version → marketing release name (e.g. Delphi 12 Athens), target OS, target arch |
FPC build-string (FPC 3.2.2 [2021/05/15] for i386 - Win32) |
High | FPC version, target OS, target arch |
DVCLAL resource |
High | SKU (Personal / Professional / Enterprise) |
PACKAGEINFO resource |
High | unit list + required-package list |
SOFTWARE\Borland\Delphi\RTL registry path |
Medium | pre-XE2 Delphi (era only) |
Namespaced unit names (Vcl.Controls, System.SysUtils, …) |
Medium | XE2-or-later Delphi (era only) |
TPF0 magic-byte count |
Medium | non-zero ⇒ binary is form-bearing |
| Validated VMT scan | Medium | non-empty ⇒ structurally Delphi/FPC even if every string was stripped |
- VMT shape-test scanner for both Delphi and FPC layouts on 32-bit and 64-bit, across Windows PE / macOS Mach-O / Linux ELF.
- Parent-chain resolution with FPC
PClass-style indirection fallback. Detects external-parent classes (vmtParentpointing into a dependency package's VMTs) so BPL runtime packages report cleanly asroots=0, external=Ninstead of a sea of orphans. - Indexed lookups by class name and by VMT virtual address.
- Class-tree rendering, ancestry walks (cycle-safe), child enumeration, max-depth, root/orphan/external counts.
For every discovered class:
- Published methods - name + code VA, plus the modern-Delphi extended trailer.
- Published fields - name, instance-relative offset, resolved
PPTypeInfofor the field type. Both Delphi legacy and modern field- table layouts plus FPC'sTVmtFieldTable. - Published properties - full
TPropInfo: PropType pointer, get/set/ stored access (Delphi field/virtual/static or FPCptField/ptStatic/ptVirtual/ptConst), Index, Default, NameIndex. - Implemented interfaces - IID GUID (canonical formatting), vtable
VA, instance offset, getter VA. FPC Corba interfaces additionally
surface the
IIDStrname. - Virtual-method-pointer table - one entry per slot, automatically bounded by the next class's VMT base.
- Init (managed-fields) table - every offset the runtime ref-count- manages (strings, interfaces, dynamic arrays, managed records).
- Dynamic-dispatch table -
dynamicandmessageslots with their IDs and handler VAs.
Full structural decode for every type kind we've seen in the wild:
tkClass · tkEnumeration · tkInteger · tkChar · tkWChar ·
tkFloat · tkSet · tkClassRef · tkDynArray · tkInterface ·
tkRecord · tkMethod · tkProcedure · tkLString · tkUString ·
tkWString.
The decode_type_detail dispatcher follows a property's or field's
PPTypeInfo indirection and returns a TypeDetail enum so callers can
match on Kind without pre-dispatching. Delphi and FPC byte values are
both honoured (FPC reorders the TTypeKind enum - tkClass is 7 in
Delphi, 15 in FPC; both verified against the runtime sources).
- Per-member visibility flags (private / protected / public / published) for properties beyond the classic published-only set.
- Attribute-table decoder with constructor-argument extraction (string / integer / raw bytes).
Full TPF0 and TPF1 binary form-stream parser. Sources walked:
- PE
RT_RCDATAresources (Delphi / C++Builder / FPC Windows). - FPC internal-resources tree (
fpc.resources/.fpc.resources- Mach-O, ELF, and FPC PE builds withoutFPC_HAS_WINLIKERESOURCES).
Recursive component tree, every documented TValueType (Null, List,
Int8/16/32/64, QWord, Single, Double, Currency, Date, Extended, String,
Ident, LString, WString, UString, Utf8String, Binary, Set, Collection,
Nil, True, False), filer flags (ffInherited, ffChildPos, ffInline),
and the awkward Collection-with-vaList-wrapper grammar (verified
against FPC writer.inc and reproduced under regression test).
A symbolic render module pretty-prints raw values against their
declared RTTI types (e.g. Align = alClient instead of Align = 2,
BorderIcons = [biSystemMenu, biMaximize] instead of BorderIcons = 7).
bin.blobs() walks every parsed form, surfaces every vaBinary leaf,
and classifies it by magic bytes:
ICO · CUR · BMP · PNG · JPEG · GIF · WebP · TIFF · ZIP · GZIP · 7Z · RAR · PDF · PE · ELF · Mach-O · WAV · MP3 · WMF/EMF · RIFF · ShortString.
Each EmbeddedBlob carries the originating form resource, the dotted
component path, the property name, the leading-byte classification, and
the raw byte slice (borrowed from the input). Useful for malware triage:
it drops a triage-ready inventory of every dropped icon, embedded
script, packed payload, or stowaway PE in the binary's form streams.
use undelphi::DelphiBinary;
let bin = DelphiBinary::parse(&bytes).unwrap();
for blob in bin.blobs() {
println!("{:>6} bytes {:?} {}.{}",
blob.data.len(), blob.kind, blob.path, blob.property_name());
}Built on top of the already-extracted metadata, no extra binary scanning:
- Interface implementors -
BTreeMap<GUID, [class names]>. - DFM class instantiations -
BTreeMap<TButton, [forms that use it]>. - Event-handler bindings - every
OnClick/OnCreate/ etc. in every form, with the resolved code VA when the handler can be linked to a published method (walks the class's ancestry chain). - Per-unit aggregate stats - class count, total fields / methods / properties / interfaces, total instance bytes per declaring unit.
- External-parent report - list of locally-declared classes that inherit from a dependency package's exported VMTs.
Byte-by-byte reconstruction of an instance for a given class - fuses the
published-field table, init-table managed-field offsets, and declared
InstanceSize. Marks named fields, gaps (private/non-published members
or padding), and managed-only entries (ref-counted fields the class
keeps private).
- Imports (
bin.imports(), delay-loaded included) with each Borland package symbol demangled:@$xp$14System@Integeris the typeinfo ofSystem.Integer,@Classes@TPersistent@the VMT ofClasses.TPersistent, and Win64's Itanium names (_ZTVN6System7Classes11TPersistentE) read the same way, generic instances included. bin.import_at(va)names the import behind aPPTypeInfo,vmtParentor field-class cell that points out of the module, andbin.required_packages()lists the.bpls an executable needs (itsPACKAGEINFOdoes not).- Exports (
bin.exports()) of a runtime package, the compiler's own answer key for every class VMT, typeinfo and routine it defines.
- Unit initialization table (
bin.unit_init_table()): every unit'sinitialization/finalizationprocedure in startup order, Delphi 2010+ class constructor / destructor pairs, and the 2010+ type table that lists every RTTI type by declaring unit. - Resource strings (
bin.resource_strings()): theRT_STRINGtable joined with eachresourcestring'sTResStringRec. - Interface method RTTI (
bin.interface_rtti(..)): the methods of an interface compiled with method info (IInvokable), with calling conventions, parameters and result types.
bin.structure_extents() gives the byte range of every structure the
crate reads - VMT headers, virtual-method arrays, method / field /
dynamic / interface / automation tables, interface vtables, RTTI records,
string constants - so a disassembler can keep them out of the code it
decodes.
The dcu module decodes the compiler's own record of a unit: every
declaration of its interface and implementation, the layout of every type
(records and classes with every field and offset, including private ones
and types without RTTI), every routine with its parameters, result and
calling convention, its uses lists and imports, and its code and RTTI with
their fixups. The format follows Hmelnov's DCU32INT for Delphi 2 - 10.3,
extended with the records Delphi 10.4 - 13 add; every unit of the Delphi 6,
10 Seattle and 13 libraries decodes.
dcu::Unit::parsereads a.dcu,dcu::package::Package::parsethe units inside a.dcp, anddcu::library::Libraryopens a compiler's library directory and resolves types across units.dcu::mangle::Manglergenerates a routine's export name (Borland on Win32, Itanium on Win64), matching 98 - 99 % of a runtime package's exports.bin.locate(&library)places a program's DCU routines, typed constants and variables in the linked binary and names the slots of its unit initialization table.
The kb module reads and writes the knowledge bases of IDR (the
Interactive Delphi Reconstructor), which describe the units of a Delphi
release's library: constants, types with their fields, properties and
methods, variables, resource strings, and every routine with its prototype
and its code, relocated bytes masked, so it can be recognised in a binary
(Dump::matches). KnowledgeBase::parse reads a file without copying and
looks records up by name or unit; Contents::to_bytes writes one, and
every published file (Delphi 2 - XE6 Win32, XE2 - XE6 Win64) reads and
writes back unchanged. kb::build makes a knowledge base from a release's
DCUs the way IDR's own were made: built from the Delphi 6 library, its
records match kb6's wherever the input units are the same.
| Toolchain | Versions | Containers |
|---|---|---|
| Embarcadero / Borland Delphi | Delphi 2 → Delphi 13 Florence (dcc v9 → v37) |
PE32 / PE32+, Mach-O, ELF |
| Embarcadero C++Builder | parsed identically to Delphi (same VCL/FMX RTTI) | PE32 / PE32+ |
| Free Pascal / Lazarus | FPC 1.0 → FPC 3.2+ (incl. trunk-shaped 3.3) | PE32 / PE32+, Mach-O, ELF |
| Arch | x86, x86_64, ARM, AArch64 | (per the build-string and / or container header) |
On a 25 MB Delphi 12 Athens PE (heidisql.exe - 2 661 classes, 42 form
resources, hundreds of thousands of VA dereferences), full parse +
forms() + every xref::* view runs in ~100 ms in release mode on a
contemporary laptop.
-
examples/dump.rs- exhaustive metadata dumper for a single binary. Print every fact this crate knows how to extract:cargo run --release --example dump -- path/to/binary.exe
-
examples/dcu_dump.rs- a decoded unit's header, uses, type table and declarations. -
examples/dcu_check.rs- decodes every.dcuand.dcpunder a directory and reports the failures. -
examples/dcu_facts.rs- a unit's types, fields and routines in a line format comparable with DCU32INT's. -
examples/mangle_check.rs- generated export names checked against a set of runtime packages. -
examples/verify_exports.rs-classes()andtypes()checked against runtime packages' exports. -
examples/kb_dump.rs- an IDR knowledge base's units, or every record of a given name. -
examples/kb_build.rs- an IDR knowledge base built from a directory of DCUs. -
examples/kb_diff.rs- two knowledge bases compared record by record.
Copyright 2026 ATRAPS LLC. Licensed under the
Apache License, Version 2.0. See also NOTICE.