Handle PE forwarded exports

Resolve: #307
This commit is contained in:
Romain Thomas 2019-12-10 06:33:48 +01:00 committed by rthomas
parent ab349aadc6
commit eeddc38393
9 changed files with 170 additions and 14 deletions

View File

@ -33,7 +33,20 @@ using setter_t = void (ExportEntry::*)(T);
template<>
void create<ExportEntry>(py::module& m) {
py::class_<ExportEntry, LIEF::Object>(m, "ExportEntry")
py::class_<ExportEntry, LIEF::Object> export_entry(m, "ExportEntry");
py::class_<ExportEntry::forward_information_t>(export_entry, "forward_information_t")
.def_readwrite("library", &ExportEntry::forward_information_t::library)
.def_readwrite("function", &ExportEntry::forward_information_t::function)
.def("__str__", [] (const ExportEntry::forward_information_t& info)
{
std::ostringstream stream;
stream << info;
return stream.str();
});
export_entry
.def(py::init<>())
.def_property("name",
@ -54,6 +67,15 @@ void create<ExportEntry>(py::module& m) {
static_cast<getter_t<bool>>(&ExportEntry::is_extern),
static_cast<setter_t<bool>>(&ExportEntry::is_extern))
.def_property_readonly("is_forwarded",
&ExportEntry::is_forwarded)
.def_property_readonly("forward_information",
&ExportEntry::forward_information)
.def_property_readonly("function_rva",
&ExportEntry::function_rva)
.def("__eq__", &ExportEntry::operator==)
.def("__ne__", &ExportEntry::operator!=)
.def("__hash__",

View File

@ -4,6 +4,7 @@ Changelog
0.11.0 - Not Released
---------------------
- Handle PE forwarded exports (`issues/307 <https://github.com/lief-project/LIEF/issues/307>`)
- Add ``PT_GNU_PROPERTY`` enum
0.10.1 - November 29, 2019

View File

@ -0,0 +1,9 @@
import lief
import sys
pe = lief.parse(sys.argv[1])
exports = pe.get_export()
for e in filter(lambda e: e.is_forwarded, exports.entries):
fwd = e.forward_information
print(f"{e.name:<35} -> {fwd.library}.{fwd.function}")

View File

@ -38,6 +38,16 @@ class LIEF_API ExportEntry : public Object {
friend class Builder;
friend class Parser;
public:
struct LIEF_API forward_information_t {
std::string library;
std::string function;
operator bool() const;
LIEF_API friend std::ostream& operator<<(std::ostream& os, const forward_information_t& info);
};
public:
ExportEntry(void);
ExportEntry(const ExportEntry&);
@ -48,6 +58,10 @@ class LIEF_API ExportEntry : public Object {
uint16_t ordinal(void) const;
uint32_t address(void) const;
bool is_extern(void) const;
bool is_forwarded(void) const;
forward_information_t forward_information(void) const;
uint32_t function_rva(void) const;
void name(const std::string& name);
void ordinal(uint16_t ordinal);
@ -63,10 +77,13 @@ class LIEF_API ExportEntry : public Object {
private:
std::string name_;
uint32_t function_rva_;
uint16_t ordinal_;
uint32_t address_;
bool is_extern_;
forward_information_t forward_info_;
};
}

View File

@ -31,6 +31,10 @@ ExportEntry::ExportEntry(void) :
is_extern_{false}
{}
ExportEntry::forward_information_t::operator bool() const {
return library.size() > 0 or function.size() > 0;
}
const std::string& ExportEntry::name(void) const {
return this->name_;
@ -48,6 +52,22 @@ bool ExportEntry::is_extern(void) const {
return this->is_extern_;
}
bool ExportEntry::is_forwarded(void) const {
return this->forward_info_;
}
ExportEntry::forward_information_t ExportEntry::forward_information(void) const {
if (not this->is_forwarded()) {
return {};
}
return this->forward_info_;
}
uint32_t ExportEntry::function_rva(void) const {
return this->function_rva_;
}
void ExportEntry::name(const std::string& name) {
this->name_ = name;
}
@ -78,14 +98,20 @@ bool ExportEntry::operator!=(const ExportEntry& rhs) const {
return not (*this == rhs);
}
std::ostream& operator<<(std::ostream& os, const ExportEntry::forward_information_t& info) {
os << info.library << "." << info.function;
return os;
}
std::ostream& operator<<(std::ostream& os, const ExportEntry& export_entry) {
os << std::hex;
os << std::left;
std::string name = export_entry.name();
if (name.size() > 20) {
name = name.substr(0, 17) + "...";
if (name.size() > 30) {
name = name.substr(0, 27) + "... ";
}
os << std::setw(23) << name;
os << std::setw(33) << name;
os << std::setw(5) << export_entry.ordinal();
if (not export_entry.is_extern()) {
@ -93,6 +119,10 @@ std::ostream& operator<<(std::ostream& os, const ExportEntry& export_entry) {
} else {
os << std::setw(10) << "[Extern]";
}
if (export_entry.is_forwarded()) {
os << " " << export_entry.forward_information();
}
return os;
}

View File

@ -766,19 +766,21 @@ void Parser::parse_exports(void) {
// If value is inside export directory => 'external' function
if (value >= std::get<0>(range) and value < std::get<1>(range)) {
uint32_t name_offset = this->binary_->rva_to_offset(value);
ExportEntry entry;
entry.name_ = this->stream_->peek_string_at(name_offset);
entry.address_ = 0;
entry.is_extern_ = true;
entry.ordinal_ = i + export_directory_table.OrdinalBase;
entry.name_ = this->stream_->peek_string_at(name_offset);
entry.address_ = 0;
entry.is_extern_ = true;
entry.ordinal_ = i + export_directory_table.OrdinalBase;
entry.function_rva_ = value;
export_object.entries_.push_back(std::move(entry));
} else {
ExportEntry entry;
entry.name_ = "";
entry.address_ = value;
entry.is_extern_ = false;
entry.ordinal_ = i + export_directory_table.OrdinalBase;
entry.name_ = "";
entry.address_ = value;
entry.is_extern_ = false;
entry.ordinal_ = i + export_directory_table.OrdinalBase;
entry.function_rva_ = value;
if (value == 0) {
@ -801,7 +803,29 @@ void Parser::parse_exports(void) {
std::string name = this->stream_->peek_string_at(name_offset);
ExportEntry& entry = export_object.entries_[ordinal_table[i]];
entry.name_ = name;
// Check if the entry is 'extern' and if the export name is already set
if (entry.is_extern_ and not entry.name_.empty()) {
std::string fwd_str = entry.name_;
std::string function = fwd_str;
std::string library;
// Split on '.'
const size_t dot_pos = fwd_str.find('.');
if (dot_pos != std::string::npos) {
library = fwd_str.substr(0, dot_pos);
function = fwd_str.substr(dot_pos + 1);
}
ExportEntry::forward_information_t finfo;
finfo.library = library;
finfo.function = function;
entry.forward_info_ = finfo;
}
entry.name_ = name;
if (name.size() > MAX_EXPORT_NAME_SIZE) {
corrupted_entries.insert(entry.ordinal_);

View File

@ -317,6 +317,14 @@ void JsonVisitor::visit(const ExportEntry& export_entry) {
this->node_["ordinal"] = export_entry.ordinal();
this->node_["address"] = export_entry.address();
this->node_["is_extern"] = export_entry.is_extern();
if (export_entry.is_forwarded()) {
const ExportEntry::forward_information_t& fwd_info = export_entry.forward_information();
this->node_["forward_information"] = {
{"library", fwd_info.library},
{"function", fwd_info.function},
};
}
}
void JsonVisitor::visit(const TLS& tls) {

View File

@ -68,6 +68,10 @@ if (PYTHON_TESTS_ENABLED)
${PYTHON_EXECUTABLE}
"${CMAKE_CURRENT_SOURCE_DIR}/test_loadconfig.py")
ADD_PYTHON_TEST(PE_PYTHON_forwarded_exports
${PYTHON_EXECUTABLE}
"${CMAKE_CURRENT_SOURCE_DIR}/test_forward_information.py")
endif()

View File

@ -0,0 +1,41 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import lief
import unittest
import logging
import json
from unittest import TestCase
from utils import get_sample
class TestForwardInfo(TestCase):
def setUp(self):
self.logger = logging.getLogger(__name__)
self.maxDiff = None
def test_basic(self):
path = get_sample('PE/PE32_x86_library_kernel32.dll')
sample = lief.parse(path)
exports = sample.get_export()
forwarded_exports = [exp for exp in exports.entries if exp.is_forwarded]
self.assertTrue(len(forwarded_exports) == 82)
# Test JSON Serialization
json_serialized = json.loads(lief.to_json(forwarded_exports[0]))
self.assertTrue("forward_information" in json_serialized)
self.assertTrue(json_serialized["forward_information"]["library"] == "NTDLL")
self.assertTrue(json_serialized["forward_information"]["function"] == "RtlInterlockedPushListSList")
if __name__ == '__main__':
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
root_logger.addHandler(ch)
unittest.main(verbosity=2)