Node.js bindings for LIEF - the Library to Instrument Executable Formats.
Parse and manipulate binary executables (ELF, PE, Mach-O, etc.) from JavaScript/TypeScript with full access to LIEF's comprehensive API.
- Format Support: ELF (Linux), PE (Windows), Mach-O (macOS/iOS)
- Binary Parsing: Automatically detect and parse binary format
- Symbol & Relocation Access: Read symbols, relocations, and section information
- Binary Modification: Patch addresses, modify sections, extend segments, and more
- Format-Agnostic API: Work with the generic Binary interface
- Format-Specific APIs: Access format-specific details (MachO segments, code signatures, etc.)
- TypeScript Support: Full type definitions included
- Prebuilt Binaries: Fast installation with prebuilt binaries for common platforms
pnpm install node-liefFor most platforms, prebuilt binaries will be automatically installed. If no prebuilt binary is available, the package will compile from source automatically.
For prebuilt binaries (recommended):
- Node.js 14+
For building from source:
- Node.js 14+
- CMake 3.15+
- Ninja build system
- C++17 compatible compiler (GCC 7+, Clang 5+, MSVC 2017+)
- Git (for cloning LIEF submodule)
const LIEF = require('node-lief');
// Parse a binary file (auto-detects format)
const binary = LIEF.parse('/bin/ls');
console.log(`Format: ${binary.format}`); // 'ELF', 'PE', 'MachO', or 'UNKNOWN'
console.log(`Entrypoint: 0x${binary.entrypoint.toString(16)}`);
console.log(`PIE: ${binary.isPie}`);
console.log(`NX: ${binary.hasNx}`);
// Access sections
const sections = binary.sections();
sections.forEach(section => {
console.log(`Section: ${section.name} @ 0x${section.virtualAddress.toString(16)}`);
});
// Access symbols
const symbols = binary.symbols();
symbols.forEach(symbol => {
console.log(`Symbol: ${symbol.name} @ 0x${symbol.value.toString(16)}`);
});
// Get a specific symbol by name
const mainSymbol = binary.getSymbol('main');
if (mainSymbol) {
console.log(`main @ 0x${mainSymbol.value.toString(16)}`);
}
// Patch an address with new bytes
binary.patchAddress(0x1000n, [0x90, 0x90, 0x90]); // NOP sled
// Write modified binary
binary.write('./output');import * as LIEF from 'node-lief';
// Parse with auto-detection
const binary = LIEF.parse('/bin/ls');
// Type-safe access to properties
const entrypoint: bigint = binary.entrypoint;
const isPie: boolean = binary.isPie;
const format: 'ELF' | 'PE' | 'MachO' | 'UNKNOWN' = binary.format;
// Format-specific operations
if (binary.format === 'ELF') {
// ELF-specific code here
const elfBinary = binary as LIEF.ELF.Binary;
}
// Work with sections, symbols, and relocations
const sections: LIEF.Abstract.Section[] = binary.sections();
const symbols: LIEF.Abstract.Symbol[] = binary.symbols();
const relocations: LIEF.Abstract.Relocation[] = binary.relocations();
// Modify and write
binary.patchAddress(0x1000n, Buffer.from([0x90, 0x90]));
binary.write('./output');Parse a binary file and return format-specific binary object. Automatically detects the format and returns the appropriate type.
Returns: ELF.Binary | PE.Binary | MachO.Binary | Abstract.Binary
Parse a MachO file and return a FatBinary object (which may contain single or multiple architectures).
Returns: MachO.FatBinary
Generic binary interface that works across all formats.
format: string- Binary format ('ELF', 'PE', 'MachO', 'UNKNOWN')entrypoint: bigint- Entry point addressisPie: boolean- Whether binary is position-independenthasNx: boolean- Whether binary has NX protectionheader: Header- Binary header information
sections(): Section[]- Get all sectionssymbols(): Symbol[]- Get all symbolsrelocations(): Relocation[]- Get all relocationssegments(): Segment[]- Get all segments (empty for Abstract, implemented for MachO)getSymbol(name: string): Symbol | null- Get a specific symbol by namepatchAddress(address: number | bigint, patch: Buffer | number[]): void- Patch bytes at addresswrite(outputPath: string): void- Write the binary to disk
Represents a section in a binary.
name: string- Section nametype: string- Section typevirtualAddress: bigint- Virtual addresssize: bigint- Section size (read/write)virtualSize: bigint- Virtual size (read/write)fileOffset: bigint- Offset in fileoffset: bigint- File offset (alias)content: number[] | Buffer- Section content (read/write)
Represents a symbol in a binary.
name: string- Symbol namevalue: bigint- Symbol value/addresssize: bigint- Symbol size
Represents a relocation in a binary.
address: bigint- Relocation addresssize: number- Relocation size
ELF-specific binary class with all Abstract.Binary methods.
PE (Windows) specific binary class with all Abstract.Binary methods.
MachO-specific binary class with additional methods:
hasCodeSignature: boolean- Whether the binary has a code signaturegetSegment(name: string): Segment | null- Get a segment by nameremoveSignature(): void- Remove code signatureextendSegment(segment: Segment, size: bigint | number): boolean- Extend a segment
Represents a MachO segment.
name: string- Segment nametype: string- Segment typevirtualAddress: bigint- Virtual addressvirtualSize: bigint- Virtual sizefileOffset: bigint- File offsetfileSize: bigint- File sizesections(): Section[]- Get sections in this segmentgetSection(name: string): Section | null- Get a section by name
Universal/Fat binary containing multiple architectures.
size(): number- Number of architecturesat(index: number): Binary | null- Get binary at indextake(index: number): Binary | null- Get and take ownership of binary at index
LIEF.logging.disable()- Disable LIEF logging outputLIEF.logging.enable()- Enable LIEF logging output
The package includes LIEF as a git submodule and uses a two-stage build process:
# Clone with submodules
git clone --recursive https://github.com/Piebald-AI/node-lief.git
cd node-lief
# Install dependencies
pnpm install
# Build (this runs both stages automatically)
pnpm build-
Stage 1: Build LIEF library (
pnpm build:lief)- Runs
scripts/build-lief.sh - Uses CMake + Ninja to build LIEF C++ library
- Produces
lief-build/libLIEF.a(orLIEF.libon Windows) - Configured for minimal build (disables Python/Rust APIs, OAT, DEX, etc.)
- Runs
-
Stage 2: Build Node addon (
node-gyp rebuild)- Links against the pre-built LIEF static library
- Compiles C++ sources in
src/directory - Produces
build/Release/node_lief.node
# Full build (LIEF + addon)
pnpm build
# Clean build artifacts
pnpm clean
# Build only LIEF library
pnpm build:lief
# Create prebuilt binaries for distribution
pnpm prebuildify- CMake 3.15+ with Ninja generator
- C++17 compiler with RTTI support enabled
- Git for LIEF submodule
- node-gyp for native addon compilation
The build is configured in:
binding.gyp- node-gyp configurationscripts/build-lief.sh- LIEF CMake configuration
- Node.js 14+ (BigInt support required)
- Node.js 16+ (recommended)
- All current LTS versions
- Linux (x86-64, ARM64)
- macOS (Intel x86-64, Apple Silicon ARM64) - requires macOS 13.0+
- Windows (x86-64)
Prebuilt binaries are provided for common platforms via prebuildify. For unsupported platforms, the package will automatically compile from source.
The Node.js bindings are compiled to native code with minimal overhead:
- Binary parsing: Near C++ native performance
- Symbol/section enumeration: Fast iteration via N-API
- Memory efficient: Smart pointers ensure proper cleanup
- No serialization overhead: Direct access to LIEF's C++ objects
For production use cases:
- Parsing typical executables: < 100ms
- Large binaries (100MB+): A few seconds
- Comparable performance to LIEF's Python bindings
MachO files can contain multiple architectures. Use MachO.parse() to handle them:
const LIEF = require('node-lief');
// Parse as Fat binary
const fat = LIEF.MachO.parse('./universal-binary');
console.log(`Architectures: ${fat.size()}`);
// Access individual architectures
for (let i = 0; i < fat.size(); i++) {
const binary = fat.at(i);
console.log(`Arch ${i}: ${binary.format}`);
// Work with binary...
}const binary = LIEF.parse('./binary');
const sections = binary.sections();
// Find and modify a section
const textSection = sections.find(s => s.name === '.text' || s.name === '__text');
if (textSection) {
// Read content
const content = textSection.content;
// Modify content (as array or Buffer)
const newContent = Buffer.from([0x90, 0x90, 0x90]);
textSection.content = newContent;
textSection.size = BigInt(newContent.length);
// Write modified binary
binary.write('./modified-binary');
}const binary = LIEF.MachO.parse('./signed-macho').at(0);
if (binary.hasCodeSignature) {
console.log('Removing code signature...');
binary.removeSignature();
binary.write('./unsigned-macho');
}const LIEF = require('node-lief');
try {
const binary = LIEF.parse('./binary');
// Perform operations
binary.patchAddress(0x1000n, [0x90, 0x90]);
binary.write('./output');
} catch (error) {
console.error('Error:', error.message);
// Handle parse errors, invalid addresses, I/O errors, etc.
}This is an actively developed project. Current limitations:
- Format coverage: ELF, PE, and MachO are well-supported; OAT, DEX, VDEX, and ART are not included in the build
- API coverage: Core functionality implemented; some advanced LIEF features not yet exposed
- Debug info: DWARF and PDB parsing not yet implemented
- Async operations: Currently synchronous only; async API planned
- Streaming: No support for streaming large files; entire binary loaded into memory
Contributions welcome! See CLAUDE.md for development guidance.
Contributions are welcome! This project is actively developed and there are many opportunities to expand functionality.
- Format-specific APIs: Expose more ELF, PE, and MachO-specific features
- Debug information: DWARF and PDB parsing support
- Async operations: Add async/await API for I/O operations
- Documentation: Usage examples, tutorials, API docs
- Performance: Optimize hot paths, add benchmarks
# Clone with submodules
git clone --recursive https://github.com/Piebald-AI/node-lief.git
cd node-lief
# Install and build
pnpm install
pnpm build
# Make changes to src/...
# Rebuild
pnpm buildSee CLAUDE.md for detailed development guidelines and architecture documentation.
node-lief/
├── src/
│ ├── init.cpp # Module initialization
│ ├── abstract/ # Format-agnostic API
│ │ ├── binary.{h,cpp}
│ │ ├── section.{h,cpp}
│ │ ├── segment.{h,cpp}
│ │ └── symbol.{h,cpp}
│ ├── elf/ # ELF-specific
│ │ └── binary.{h,cpp}
│ ├── pe/ # PE-specific
│ │ └── binary.{h,cpp}
│ └── macho/ # MachO-specific
│ ├── binary.{h,cpp}
│ ├── fat_binary.{h,cpp}
│ └── parse.cpp
├── lib/
│ ├── index.js # Entry point
│ └── index.d.ts # TypeScript definitions
├── scripts/
│ └── build-lief.sh # LIEF build script
├── binding.gyp # node-gyp configuration
└── LIEF/ # Git submodule
See CLAUDE.md for detailed instructions on:
- Adding new methods to existing classes
- Creating new wrapper classes
- Working with LIEF types
- Memory management patterns
- Error handling conventions
Apache License 2.0 (same as LIEF)
- LIEF Project - The underlying C++ library
- LIEF Documentation - Comprehensive LIEF docs
- LIEF GitHub - LIEF source code
- node-addon-api - N-API wrapper used by this project
- Issues: Report bugs and request features on GitHub Issues
- Documentation: See CLAUDE.md, USAGE.md, and ARCHITECTURE.md
- Examples: Check USAGE.md and QUICK_START.md for usage examples
- LIEF Help: Refer to LIEF documentation for underlying functionality
This project is built on top of LIEF by Quarkslab. LIEF is a comprehensive library for parsing and manipulating executable formats, and this package aims to make that functionality easily accessible to the Node.js ecosystem.