5.3 KiB
Reflektor
Reflektor is a Go library and CLI for loading shared libraries from bytes and invoking exported functions.
It exposes a stable root package (reflektor) so other projects can import it directly, while platform-specific loading is handled behind memmod.
Platform Support
| OS | Architectures | Shared Library Format | Status | Loader Notes |
|---|---|---|---|---|
| Windows | 386, amd64, arm64 |
PE (.dll) |
Supported | In-memory PE loader |
| Darwin | amd64, arm64 |
Mach-O (.dylib, bundle) |
Supported | Dyld4 root-image loader with system dependencies registered through public dyld; supports no-cgo builds and avoids temp-file legacy NS APIs. |
| Linux | 386, amd64, arm64 |
ELF (.so) |
Supported | Pure Go in-memory ELF loader (maps PT_LOAD segments, applies relocations, resolves externals from runtime modules/dlsym); no memfd, no /dev/shm, no temp-file disk writes. |
| Other | - | - | Unsupported | Returns an explicit unsupported-platform error. |
Public API
Import path:
import "github.com/sliverarmory/reflektor"
Example:
payload := []byte{}
lib, err := reflektor.LoadLibrary(payload)
if err != nil {
return err
}
defer lib.Close()
if err := lib.CallExport("StartW"); err != nil {
return err
}
You can also load from a path:
lib, err := reflektor.LoadLibraryFile("./payload.dylib")
To read and map a library's non-system dependencies through Reflektor as well, use recursive mode:
lib, err := reflektor.LoadLibraryFileRecursive("./payload.dylib")
The byte-oriented equivalent resolves relative dependency names from the current working directory:
lib, err := reflektor.LoadLibraryRecursive(payload)
Both recursive APIs read the complete custom dependency graph before the root
export is invoked. LoadLibraryFileRecursive is preferred for libraries that
use origin-relative names such as $ORIGIN, @loader_path, or @rpath.
CLI
The CLI is in reflektor/cli and uses Cobra.
Build:
go build -o reflektor ./cli
Usage:
./reflektor <shared-library-path> [--call-export StartW]
--call-export defaults to StartW.
Behavior Notes
CallExportis designed for zero-argument exports.- Reflektor normalizes common symbol naming differences where possible (for example underscore-prefixed forms).
- The root
reflektor.Libraryinterface is intentionally small:CallExport()andClose(). - Recursive mode maps file-backed application dependencies from their bytes and resolves imports within the in-memory graph. Platform runtime libraries remain delegated to the native loader: Darwin shared-cache libraries, Windows System32/API-set libraries, and Linux libraries in trusted system roots. Those libraries require OS-managed TLS, symbol versioning, loader registration, and other facilities that cannot be reproduced by simply reading a file—and some Darwin shared-cache images do not exist as standalone readable files.
- Linux custom images reject general ELF TLS, IFUNC/IRELATIVE, and RELR with explicit errors; those features remain available through the system-library carveout. Windows custom dependency cycles and delay-load import tables are also rejected explicitly. Darwin and Linux graph cycles are deduplicated.
- After the first export call, Darwin recursive mappings remain process-resident because dyld retains their loader records. Reusing a Darwin install-name in a later load follows dyld's first-loaded identity semantics.
LoadLibraryandLoadLibraryFileretain their original behavior and API.
Test Data And Validation
C test shared libraries are generated from:
reflektor/testdata/c/basic.creflektor/testdata/c/recursive_leaf.creflektor/testdata/c/recursive_middle.creflektor/testdata/c/recursive_root.c
The recursive C fixture is a transitive root -> middle -> leaf graph. Its test
checks that the graph is absent from Linux /proc/self/maps or the Windows
loader module registry, then renames the dependency directory before calling
StartW. On Darwin the rename happens before the lazy dyld transaction. These
checks prove the custom dependencies came from bytes captured by Reflektor.
The Rust HTTPS fixture is built from reflektor/testdata/rust. It exports StartW, performs a bounded GET https://example.com/ through libcurl on Darwin/Linux or WinHTTP on Windows, and records ok:200 after receiving a non-empty successful response. The fixture is dependency-free Rust (no_std) so it does not require unsupported thread-local runtime state from the in-memory loaders.
Build test shared libraries for the full matrix:
./testdata/build_c_shared_libs.sh
Run tests:
go test ./...
The Rust fixture test requires Cargo with Rust 1.94.0 and outbound HTTPS access. Linux also requires the libcurl development package so the fixture can link against the system TLS client.
Linux cross-arch Docker harness:
reflektor/testdata/docker/linux-memmod.Dockerfilereflektor/testdata/docker/run-linux-memmod-matrix.sh
Repository Layout
reflektor/reflektor.go: root importable package (reflektor).reflektor/memmod: OS-specific loader backends.reflektor/cli: CLI entrypoint.reflektor/testdata: portable shared-library fixtures and build/test harnesses.