LIEF provides extensive support for modifying the PE export table, enabling you to add, remove, or modify export entries, or create an entire export table for a PE binary.
This could be used for code lifting or fuzzing.
pe: lief.PE.Binary
exp = pe.get_export()
assert isinstance(exp, lief.PE.Export)
# Remove an entry
exp.remove_entry("my_exported_name")
# Add a new export
exp.add_entry("fuzz_me", 0x10010)
config = lief.PE.Builder.config_t()
config.exports = True
config.export_section = ".myedata" # optional
pe.write("out.dll", config)
std::unique_ptr<LIEF::PE::Binary> pe;
LIEF::PE::Export* exp = pe->get_export();
// Remove an entry
exp->remove_entry("my_exported_name");
// Add a new export
exp->add_entry("fuzz_me", 0x10010);
LIEF::PE::Builder::config_t config;
config.exports = true;
config.export_section = ".myedata";
pe->write("out.dll", config);
let pe: &mut lief::pe::Binary = some_pe;
let mut exp: lief::pe::Export = pe.export().unwrap();
// Remove an entry
exp.remove_entry_by_name("my_exported_name");
// Add a new export
exp.add_entry_by_name("fuzz_me", 0x10010);
let mut config = lief::pe::builder::Config::default();
config.exports = true;
config.export_section = ".myedata".to_string();
pe.write_with_config("out.dll", config);
This section introduces the API for creating an export table. We’ll explore a scenario where we want to convert a PE executable into a DLL.
Note
The process of converting an executable to a library is also detailed for ELF binaries in the tutorial: 08 - Transforming an ELF executable into a library.
First, we must update the PE headers to ensure they are compliant with the DLL format:
pe: lief.PE.Binary
pe.header.add_characteristic(lief.PE.Header.CHARACTERISTICS.DLL)
pe.optional_header.addressof_entrypoint = 0
std::unique_ptr<LIEF::PE::Binary> pe;
pe->header().add_characteristic(LIEF::PE::Header::CHARACTERISTICS::DLL);
pe->optional_header().addressof_entrypoint(0);
let pe: &mut lief::pe::Binary = some_pe;
pe.header()
.add_characteristic(lief::pe::headers::Characteristics::DLL);
pe.optional_header().set_addressof_entrypoint(0);
Then, we can start creating and populating a new export table:
pe: lief.PE.Binary
exp = lief.PE.Export(
"lib_exe2dll.dll",
[
lief.PE.ExportEntry("cbk1", 0x0001000),
lief.PE.ExportEntry("cbk2", 0x0001010),
],
)
pe.set_export(exp)
config = lief.PE.Builder.config_t()
config.exports = True
pe.write("lib_exe2dll.dll")
std::unique_ptr<LIEF::PE::Binary> pe;
LIEF::PE::Export exp("lib_exe2dll.dll",
{
LIEF::PE::ExportEntry("cbk1", 0x0001000),
LIEF::PE::ExportEntry("cbk2", 0x0001010),
});
pe->set_export(exp);
LIEF::PE::Builder::config_t config;
config.exports = true;
pe->write("lib_exe2dll.dll", config);
let pe: &mut lief::pe::Binary = some_pe;
let mut exp = lief::pe::Export::new();
exp.set_name("lib_exe2dll.dll");
exp.add_entry_by_name("cbk1", 0x0001000);
exp.add_entry_by_name("cbk2", 0x0001010);
pe.set_export(&exp);
let mut config = lief::pe::builder::Config::default();
config.exports = true;
pe.write_with_config("lib_exe2dll.dll", config);
Limitations
This binary-to-library example assumes that the original executable was compiled to be position-independent, meaning it contains relocations.
Within a Python environment, we can verify that lib_exe2dll.dll can be loaded as a DLL and that we can call cbk1 and cbk2:
import ctypes
lib = ctypes.windll.LoadLibrary("lib_exe2dll.dll")
assert lib.cbk1() >= 0
assert lib.cbk2() >= 0