PE¶
Modifications
Introduction¶
PE binaries can be parsed using the function.
import lief
# Using filepath
pe: lief.PE.Binary | None = lief.PE.parse(r"C:\Users\test.exe")
# Using a Path from pathlib
pe: lief.PE.Binary | None = lief.PE.parse(pathlib.Path(r"C:\Users\test.exe"))
# Using an io object
with open(r"C:\Users\test.exe", "rb") as f:
pe: lief.PE.Binary | None = lief.PE.parse(f)
#include <LIEF/PE.hpp>
// Using a file path as a std::string
std::unique_ptr<LIEF::PE::Binary> pe = LIEF::PE::Parser::parse("some.exe");
// Using a vector
std::vector<uint8_t> my_raw_pe;
pe = LIEF::PE::Parser::parse(my_raw_pe);
let pe: lief::pe::Binary = lief::pe::Binary::parse("/bin/ls").unwrap();
Note
In Python, you can also use the generic lief.parse(), which returns a lief.PE.Binary object.
With the parsed PE binary, you can use the API to inspect or modify the binary itself.
pe: lief.PE.Binary
print(pe.rich_header)
print(pe.authentihash_md5.hex(":"))
for section in pe.sections:
print(section.name, len(section.content))
std::unique_ptr<LIEF::PE::Binary> pe;
if (const LIEF::PE::RichHeader* rich = pe->rich_header()) {
std::cout << *rich << '\n';
}
for (const LIEF::PE::Section& section : pe->sections()) {
std::cout << section.name() << section.content().size() << '\n';
}
let pe: &lief::pe::Binary = some_pe;
println!("{:?}", pe.rich_header().expect("Missing Rich header"));
for section in pe.sections() {
println!("{} {}", section.name(), section.content().len());
}
After modifying a object, you can use to write the changes back to a raw PE file.
pe: lief.PE.Binary
section = lief.PE.Section(".hello")
section.content = [0xCC] * 0x100
pe.add_section(section)
pe.write("new.exe")
std::unique_ptr<LIEF::PE::Binary> pe;
LIEF::PE::Section section(".hello");
section.content(std::vector<uint8_t>(0x100, 0xCC));
pe->add_section(section);
pe->write("new.exe");
let mut pe = lief::pe::Binary::parse("some.exe").unwrap();
let mut section = lief::pe::Section::new_with_name(".hello");
section.set_content(&[0xCC; 0x100]);
pe.add_section(section);
pe.write("new.exe");
See also
Dump Analysis¶
LIEF has the support to process PE memory dump with . This function translates the file offsets referenced by the PE structures into their location inside the dump, using the base address passed as the second parameter:
# 0x7ffd21b80000 is the (absolute) address at which the dump was mapped
pe = lief.PE.parse_from_dump("module.dump", 0x7FFD21B80000)
assert isinstance(pe, lief.PE.Binary)
for imp in pe.imports:
print(imp.name)
auto pe = LIEF::PE::Parser::parse_from_dump("module.dump", 0x7ffd21b80000);
for (const LIEF::PE::Import& imp : pe->imports()) {
std::cout << imp.name() << '\n';
}
let pe = lief::pe::Binary::parse_from_dump("module.dump", 0x7ffd_21b8_0000).unwrap();
for imp in pe.imports() {
println!("{}", imp.name());
}
Note
The second parameter must be the (absolute) virtual address at which the dump was mapped. It is used to convert the RVAs found in the PE structures back into an offset within the dump.
Producing a dump with the runtime API¶
Such a dump can be produced from a live process thanks to the LIEF runtime and, more precisely, the Module API. captures the memory of a loaded module (from its imagebase over its virtual size):
# Find the module to dump in the current process
mod = lief.runtime.module_from_name("target.dll")
assert isinstance(mod, lief.runtime.windows.Module)
# Dump the module's memory into a file (the raw bytes are also returned) ...
data: bytes = mod.dump("module.dump")
# ... and parse it back using the same imagebase:
pe = lief.PE.parse_from_dump(data, mod.imagebase)
// Find the module to dump in the current process
auto mod = LIEF::runtime::module_from_name("target.dll");
// Dump the module's memory into a file (the raw bytes are also returned)
std::vector<uint8_t> data = mod->dump("module.dump");
auto pe = LIEF::PE::Parser::parse_from_dump("module.dump", mod->imagebase());
use lief::runtime::Module;
let module = lief::runtime::module_from_name("target.dll").unwrap();
// Dump the module's memory into a file (the raw bytes are also returned)
let data = module.dump_to_file("module.dump");
let pe = lief::pe::Binary::parse_from_dump("module.dump", module.imagebase()).unwrap();
Advanced Parsing/Writing¶
Modifications
can take an extra parameter to specify parts of the PE format to ignore during parsing.
Warning
Generally, requires a complete initial parsing of the PE file.
Similarly, can take an extra parameter to include or ignore parts of the PE binary during the build process.
parser_config = lief.PE.ParserConfig()
parser_config.parse_signature = False
pe = lief.PE.parse("some.exe", parser_config)
assert isinstance(pe, lief.PE.Binary)
builder_config = lief.PE.Builder.config_t()
builder_config.imports = True
pe.write("new.exe", builder_config)
LIEF::PE::ParserConfig parser_config;
parser_config.parse_signature = false;
auto pe = LIEF::PE::Parser::parse("some.exe", parser_config);
LIEF::PE::Builder::config_t builder_config;
builder_config.imports = true;
pe->write("new.exe", builder_config);
let mut parser_config = lief::pe::parser_config::Config::default();
parser_config.parse_signature = false;
let mut pe = lief::pe::parse_with_config("some.exe", &parser_config).unwrap();
let mut config = lief::pe::builder::Config::default();
config.imports = true;
pe.write_with_config("new.exe", config);
You can also use to get the new PE binary as a buffer of bytes:
Note
This API can also take an extra parameter.
pe: lief.PE.Binary
new_pe: bytes = pe.write_to_bytes()
std::unique_ptr<LIEF::PE::Binary> pe;
std::ostringstream os;
pe->write(os);
std::string buffer = os.str();
const auto* start = reinterpret_cast<const uint8_t*>(buffer.data());
size_t size = buffer.size();
let pe: &mut lief::pe::Binary = some_pe;
let bytes: Vec<u8> = pe.write_to_bytes();
PDB Support¶
Using LIEF Extended, you can access PDB debug information () using the function.
For more details regarding PDB support, please refer to the PDB section.
Authenticode¶
LIEF supports PE Authenticode by providing an API for inspecting and verifying PE executable signatures.
PE Authenticode signatures can be accessed by iterating over . The function can be used to verify that a PE binary is correctly signed.
Note
Typically, a signed PE executable contains a single signature, but the format allows for multiple signatures. Consequently, returns an iterator rather than a single signature object.
pe = lief.PE.parse("signed.exe")
assert isinstance(pe, lief.PE.Binary)
for signature in pe.signatures:
for crt in signature.certificates:
print(crt)
assert pe.verify_signature() == lief.PE.Signature.VERIFICATION_FLAGS.OK
auto pe = LIEF::PE::Parser::parse("signed.exe");
for (const LIEF::PE::Signature& sig : pe->signatures()) {
for (const LIEF::PE::x509& crt : sig.certificates()) {
std::cout << crt << '\n';
}
}
std::cout << (pe->verify_signature() ==
LIEF::PE::Signature::VERIFICATION_FLAGS::OK)
<< '\n';
if let Some(lief::Binary::PE(pe)) = lief::Binary::parse("signed.exe") {
for sig in pe.signatures() {
for crt in sig.certificates() {
println!("{:?}", crt);
}
}
assert!(
pe.verify_signature(lief::pe::signature::VerificationChecks::DEFAULT)
== lief::pe::signature::VerificationFlags::OK
);
}
You can find additional details about Authenticode support in this tutorial: PE Authenticode