Skip to content

Commit 06e93ce

Browse files
committed
Initial commit
1 parent e312272 commit 06e93ce

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+8669
-2
lines changed

dissect/executable/pe/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from dissect.executable.pe.pe import PE
2+
3+
__all__ = [
4+
"PE",
5+
]

dissect/executable/pe/c_pe.py

Lines changed: 2231 additions & 0 deletions
Large diffs are not rendered by default.

dissect/executable/pe/c_pe.pyi

Lines changed: 2997 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from dissect.executable.pe.directory.base import DataDirectory
2+
from dissect.executable.pe.directory.basereloc import BaseRelocationDirectory
3+
from dissect.executable.pe.directory.bound_import import BoundImportDirectory
4+
from dissect.executable.pe.directory.com_descriptor import ComDescriptorDirectory
5+
from dissect.executable.pe.directory.debug import DebugDirectory
6+
from dissect.executable.pe.directory.delay_import import DelayImportDirectory
7+
from dissect.executable.pe.directory.exception import ExceptionDirectory
8+
from dissect.executable.pe.directory.export import ExportDirectory
9+
from dissect.executable.pe.directory.iat import IatDirectory
10+
from dissect.executable.pe.directory.imports import ImportDirectory, ImportFunction, ImportModule
11+
from dissect.executable.pe.directory.load_config import LoadConfigDirectory
12+
from dissect.executable.pe.directory.resource import (
13+
ResourceDataEntry,
14+
ResourceDirectory,
15+
ResourceDirectoryEntry,
16+
ResourceEntry,
17+
)
18+
from dissect.executable.pe.directory.security import SecurityDirectory
19+
from dissect.executable.pe.directory.tls import TlsDirectory
20+
21+
__all__ = [
22+
"BaseRelocationDirectory",
23+
"BoundImportDirectory",
24+
"ComDescriptorDirectory",
25+
"DataDirectory",
26+
"DebugDirectory",
27+
"DelayImportDirectory",
28+
"ExceptionDirectory",
29+
"ExportDirectory",
30+
"IatDirectory",
31+
"ImportDirectory",
32+
"ImportFunction",
33+
"ImportModule",
34+
"LoadConfigDirectory",
35+
"ResourceDataEntry",
36+
"ResourceDirectory",
37+
"ResourceDirectoryEntry",
38+
"ResourceEntry",
39+
"SecurityDirectory",
40+
"TlsDirectory",
41+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
if TYPE_CHECKING:
6+
from dissect.executable.pe.pe import PE
7+
8+
9+
class DataDirectory:
10+
"""Base class for PE data directories."""
11+
12+
def __init__(self, pe: PE, address: int, size: int):
13+
self.pe = pe
14+
self.address = address
15+
self.size = size
16+
17+
def __repr__(self) -> str:
18+
return f"<{self.__class__.__name__} address={self.address:#x} size={self.size}>"
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
from functools import cached_property
5+
from typing import TYPE_CHECKING
6+
7+
from dissect.executable.pe.c_pe import c_pe
8+
from dissect.executable.pe.directory.base import DataDirectory
9+
10+
if TYPE_CHECKING:
11+
from collections.abc import Iterator
12+
13+
14+
class BaseRelocationDirectory(DataDirectory):
15+
"""The base relocation directory of a PE file."""
16+
17+
def __repr__(self) -> str:
18+
return f"<BaseRelocationDirectory entries={len(self.entries)}>"
19+
20+
def __len__(self) -> int:
21+
return len(self.entries)
22+
23+
def __iter__(self) -> Iterator[BaseRelocation]:
24+
return iter(self.entries)
25+
26+
def __getitem__(self, idx: int) -> BaseRelocation:
27+
return self.entries[idx]
28+
29+
@cached_property
30+
def entries(self) -> list[BaseRelocation]:
31+
"""List of base relocation entries."""
32+
result = []
33+
34+
offset = self.address
35+
while offset < self.address + self.size:
36+
self.pe.vfh.seek(offset)
37+
38+
block = c_pe._IMAGE_BASE_RELOCATION(self.pe.vfh)
39+
if block.SizeOfBlock == 0:
40+
break
41+
42+
page_rva = block.VirtualAddress
43+
44+
num_entries = (block.SizeOfBlock - len(c_pe._IMAGE_BASE_RELOCATION)) // len(c_pe.USHORT)
45+
result.extend(
46+
BaseRelocation(c_pe.IMAGE_REL_BASED(entry >> 12), page_rva + (entry & 0xFFF))
47+
for entry in c_pe.USHORT[num_entries](self.pe.vfh)
48+
if (entry >> 12) != 0 # Skip IMAGE_REL_BASED_ABSOLUTE (0)
49+
)
50+
offset += block.SizeOfBlock
51+
offset += -offset & 3 # Align to 4 bytes
52+
53+
return result
54+
55+
56+
@dataclass
57+
class BaseRelocation:
58+
"""A single base relocation entry in the base relocation directory."""
59+
60+
type: c_pe.IMAGE_REL_BASED
61+
rva: int
62+
63+
def __repr__(self) -> str:
64+
return f"<BaseRelocation rva={self.rva:#x} type={self.type.name or self.type.value}>"
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
from __future__ import annotations
2+
3+
from functools import cached_property
4+
from typing import TYPE_CHECKING
5+
6+
from dissect.util.ts import from_unix
7+
8+
from dissect.executable.pe.c_pe import c_pe
9+
from dissect.executable.pe.directory.base import DataDirectory
10+
11+
if TYPE_CHECKING:
12+
import datetime
13+
14+
15+
class BoundImportDirectory(DataDirectory):
16+
"""The bound import directory of a PE file."""
17+
18+
def __repr__(self) -> str:
19+
return f"<BoundImportDirectory modules={len(self.modules)}>"
20+
21+
def __len__(self) -> int:
22+
return len(self.modules)
23+
24+
def __getitem__(self, idx: str | int) -> BoundImportModule:
25+
if isinstance(idx, int):
26+
return self.modules[idx]
27+
if isinstance(idx, str):
28+
if idx not in self._by_name:
29+
raise KeyError(f"Bound import module {idx!r} not found")
30+
return self._by_name[idx]
31+
raise TypeError(f"BoundImportDirectory indices must be str or int, not {type(idx).__name__}")
32+
33+
def __contains__(self, name: str) -> bool:
34+
if isinstance(name, str):
35+
return name in self._by_name
36+
return False
37+
38+
@cached_property
39+
def modules(self) -> list[BoundImportModule]:
40+
"""List of bound imported modules."""
41+
result = []
42+
43+
self.pe.vfh.seek(self.address)
44+
while self.pe.vfh.tell() < self.address + self.size:
45+
descriptor = c_pe.IMAGE_BOUND_IMPORT_DESCRIPTOR(self.pe.vfh)
46+
if not descriptor:
47+
break
48+
49+
forwarders = []
50+
for _ in range(descriptor.NumberOfModuleForwarderRefs):
51+
forwarder = c_pe.IMAGE_BOUND_FORWARDER_REF(self.pe.vfh)
52+
if not forwarder:
53+
break
54+
55+
forwarders.append(BoundImportForwardReference(self, forwarder))
56+
57+
result.append(BoundImportModule(self, descriptor, forwarders))
58+
59+
return result
60+
61+
@cached_property
62+
def _by_name(self) -> dict[str, BoundImportModule]:
63+
"""A mapping of module names to their :class:`DelayImportModule`."""
64+
return {module.name: module for module in self.modules}
65+
66+
67+
class BoundImportModule:
68+
"""A module bound imported by a PE file, containing its functions."""
69+
70+
def __init__(
71+
self,
72+
directory: BoundImportDirectory,
73+
descriptor: c_pe.IMAGE_BOUND_IMPORT_DESCRIPTOR,
74+
forwarders: list[BoundImportForwardReference],
75+
):
76+
self.directory = directory
77+
self.descriptor = descriptor
78+
self.forwarders = forwarders
79+
80+
@property
81+
def timestamp(self) -> datetime.datetime | None:
82+
"""The timestamp of this bound import module, or ``None`` if the PE file is compiled as reproducible."""
83+
if self.directory.pe.is_reproducible():
84+
return None
85+
return from_unix(self.descriptor.TimeDateStamp)
86+
87+
@property
88+
def name(self) -> str:
89+
self.directory.pe.vfh.seek(self.directory.address + self.descriptor.OffsetModuleName)
90+
return c_pe.CHAR[None](self.directory.pe.vfh).decode()
91+
92+
93+
class BoundImportForwardReference:
94+
"""A forward reference in a bound import module."""
95+
96+
def __init__(
97+
self,
98+
directory: BoundImportDirectory,
99+
descriptor: c_pe.IMAGE_BOUND_FORWARDER_REF,
100+
):
101+
self.directory = directory
102+
self.descriptor = descriptor
103+
104+
@property
105+
def timestamp(self) -> datetime.datetime | None:
106+
"""The timestamp of this bound import module, or ``None`` if the PE file is compiled as reproducible."""
107+
if self.directory.pe.is_reproducible():
108+
return None
109+
return from_unix(self.descriptor.TimeDateStamp)
110+
111+
@property
112+
def name(self) -> str:
113+
self.directory.pe.vfh.seek(self.directory.address + self.descriptor.OffsetModuleName)
114+
return c_pe.CHAR[None](self.directory.pe.vfh).decode()
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from __future__ import annotations
2+
3+
from functools import cached_property
4+
from typing import BinaryIO
5+
6+
from dissect.util.stream import RangeStream
7+
8+
from dissect.executable.pe.c_pe import c_pe
9+
from dissect.executable.pe.directory.base import DataDirectory
10+
11+
12+
class ComDescriptorDirectory(DataDirectory):
13+
"""The COM descriptor directory of a PE file.
14+
15+
References:
16+
- https://www.codeproject.com/Articles/12585/The-NET-File-Format
17+
"""
18+
19+
@cached_property
20+
def descriptor(self) -> c_pe.IMAGE_COR20_HEADER:
21+
"""The CLR 2.0 header descriptor."""
22+
self.pe.vfh.seek(self.address)
23+
return c_pe.IMAGE_COR20_HEADER(self.pe.vfh)
24+
25+
@cached_property
26+
def metadata(self) -> ComMetadata:
27+
"""The COM metadata directory."""
28+
return ComMetadata(self.pe, self.descriptor.MetaData.VirtualAddress, self.descriptor.MetaData.Size)
29+
30+
31+
class ComMetadata(DataDirectory):
32+
"""The COM metadata directory of the COM descriptor."""
33+
34+
@cached_property
35+
def metadata(self) -> c_pe.IMAGE_COR20_METADATA:
36+
"""The CLR 2.0 metadata descriptor."""
37+
self.pe.vfh.seek(self.address)
38+
return c_pe.IMAGE_COR20_METADATA(self.pe.vfh)
39+
40+
@property
41+
def version(self) -> str:
42+
"""The version as defined in the metadata."""
43+
return self.metadata.Version.decode().strip("\x00")
44+
45+
@cached_property
46+
def streams(self) -> list[ComStream]:
47+
"""A list of streams defined in the metadata."""
48+
result = []
49+
50+
offset = self.address + len(self.metadata)
51+
for _ in range(self.metadata.NumberOfStreams):
52+
self.pe.vfh.seek(offset)
53+
header = c_pe.IMAGE_COR20_STREAM_HEADER(self.pe.vfh)
54+
55+
result.append(ComStream(self, header.Offset, header.Size, header.Name.decode()))
56+
57+
offset += len(header)
58+
offset += -offset & 3 # Align to 4 bytes
59+
60+
return result
61+
62+
63+
class ComStream:
64+
"""A stream in the COM metadata."""
65+
66+
def __init__(self, metadata: ComMetadata, offset: int, size: int, name: str):
67+
self.metadata = metadata
68+
self.offset = offset
69+
self.size = size
70+
self.name = name
71+
72+
def __repr__(self) -> str:
73+
return f"<ComStream offset={self.offset:#x} size={self.size:#x} name={self.name!r}>"
74+
75+
@property
76+
def data(self) -> bytes:
77+
"""The data of the stream."""
78+
self.metadata.pe.vfh.seek(self.metadata.address + self.offset)
79+
return self.metadata.pe.vfh.read(self.size)
80+
81+
def open(self) -> BinaryIO:
82+
"""Open the stream for reading."""
83+
return RangeStream(self.metadata.pe.vfh, self.metadata.address + self.offset, self.size)

0 commit comments

Comments
 (0)