-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathdelay_import.py
More file actions
176 lines (139 loc) · 7.01 KB
/
Copy pathdelay_import.py
File metadata and controls
176 lines (139 loc) · 7.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
from __future__ import annotations
from functools import cached_property
from typing import TYPE_CHECKING
from dissect.util.ts import from_unix
from dissect.executable.pe.c_pe import c_pe
from dissect.executable.pe.directory.base import DataDirectory
if TYPE_CHECKING:
import datetime
from collections.abc import Iterator
from dissect.executable.pe.pe import PE
class DelayImportDirectory(DataDirectory):
"""The delay import directory of a PE file."""
def __repr__(self) -> str:
return f"<DelayImportDirectory modules={len(self.modules)}>"
def __len__(self) -> int:
return len(self.modules)
def __getitem__(self, idx: str | int) -> DelayImportModule:
if isinstance(idx, int):
return self.modules[idx]
if isinstance(idx, str):
if idx not in self._by_name:
raise KeyError(f"Delay import module {idx!r} not found")
return self._by_name[idx]
raise TypeError(f"DelayImportDirectory indices must be str or int, not {type(idx).__name__}")
def __contains__(self, name: str) -> bool:
if isinstance(name, str):
return name in self._by_name
return False
@cached_property
def modules(self) -> list[DelayImportModule]:
"""List of delay imported modules."""
self.pe.vfh.seek(self.address)
return [
DelayImportModule(self.pe, descriptor) for descriptor in c_pe.IMAGE_DELAYLOAD_DESCRIPTOR[None](self.pe.vfh)
]
@cached_property
def _by_name(self) -> dict[str, DelayImportModule]:
"""A mapping of module names to their :class:`DelayImportModule`."""
return {module.name: module for module in self.modules}
class DelayImportModule:
"""A module delay imported by a PE file, containing its functions."""
def __init__(self, pe: PE, descriptor: c_pe.IMAGE_DELAYLOAD_DESCRIPTOR):
self.pe = pe
self.descriptor = descriptor
def __repr__(self) -> str:
return f"<DelayImportModule name={self.name!r} functions={len(self.functions)}>"
def __iter__(self) -> Iterator[DelayImportFunction]:
return iter(self.functions)
def __getitem__(self, idx: str | int) -> DelayImportFunction:
if isinstance(idx, int):
return self._by_ordinal[idx]
if isinstance(idx, str):
if idx not in self._by_name:
raise KeyError(f"Delay import function {idx!r} not found in module {self.name!r}")
return self._by_name[idx]
raise TypeError(f"DelayImportModule indices must be str or int, not {type(idx).__name__}")
def __contains__(self, idx: str | int) -> bool:
if isinstance(idx, int):
return idx in self._by_ordinal
if isinstance(idx, str):
return idx in self._by_name
return False
@property
def timestamp(self) -> datetime.datetime | None:
"""The timestamp of the target DLL of this delay import module, or ``None`` if the PE file is
compiled as reproducible.
Will be ``0`` (so epoch) if it's not bound yet.
"""
if self.pe.is_reproducible():
return None
return from_unix(self.descriptor.TimeDateStamp)
@cached_property
def name(self) -> str:
"""The name of the delay import module."""
self.pe.vfh.seek(self.descriptor.DllNameRVA)
return c_pe.CHAR[None](self.pe.vfh).decode()
@cached_property
def functions(self) -> list[DelayImportFunction]:
"""List of delay imported functions from this module."""
ctype = c_pe.IMAGE_THUNK_DATA64 if self.pe.is_64bit() else c_pe.IMAGE_THUNK_DATA32
self.pe.vfh.seek(self.descriptor.ImportNameTableRVA)
name_table = ctype[None](self.pe.vfh)
self.pe.vfh.seek(self.descriptor.ImportAddressTableRVA)
address_table = ctype[None](self.pe.vfh)
bound_table = []
if self.descriptor.BoundImportAddressTableRVA:
self.pe.vfh.seek(self.descriptor.BoundImportAddressTableRVA)
bound_table = ctype[None](self.pe.vfh)
# If we are not bound (e.g. PE file on disk instead of in memory), create an empty table
bound_table = bound_table or [None] * len(name_table)
unload_table = []
if self.descriptor.UnloadInformationTableRVA:
self.pe.vfh.seek(self.descriptor.UnloadInformationTableRVA)
unload_table = ctype[None](self.pe.vfh)
# If we are not unloading (e.g. PE file on disk instead of in memory), create an empty table
unload_table = unload_table or [None] * len(name_table)
return [
DelayImportFunction(self, name_thunk, address_thunk, bound_thunk, unload_thunk)
for name_thunk, address_thunk, bound_thunk, unload_thunk in zip(
name_table, address_table, bound_table, unload_table, strict=False
)
]
@cached_property
def _by_name(self) -> dict[str, DelayImportFunction]:
"""A mapping of imported function names to their :class:`DelayImportFunction`."""
return {func.name: func for func in self.functions if func.name is not None}
@cached_property
def _by_ordinal(self) -> dict[int, DelayImportFunction]:
"""A mapping of function ordinals to their :class:`DelayImportFunction`."""
return {func.ordinal: func for func in self.functions}
class DelayImportFunction:
"""A function delay imported from a module."""
def __init__(
self,
module: DelayImportModule,
name_thunk: c_pe.IMAGE_THUNK_DATA32 | c_pe.IMAGE_THUNK_DATA64,
address_thunk: c_pe.IMAGE_THUNK_DATA32 | c_pe.IMAGE_THUNK_DATA64,
bound_thunk: c_pe.IMAGE_THUNK_DATA32 | c_pe.IMAGE_THUNK_DATA64 | None = None,
unload_thunk: c_pe.IMAGE_THUNK_DATA32 | c_pe.IMAGE_THUNK_DATA64 | None = None,
):
self.module = module
self.name_thunk = name_thunk
self.address_thunk = address_thunk
if name_thunk.u1.Ordinal & (c_pe.IMAGE_ORDINAL_FLAG64 if module.pe.is_64bit() else c_pe.IMAGE_ORDINAL_FLAG32):
self.ordinal = name_thunk.u1.Ordinal & 0xFFFF
self.name = None
else:
self.module.pe.vfh.seek(name_thunk.u1.AddressOfData & (c_pe.IMAGE_ORDINAL_FLAG32 - 1))
import_by_name = c_pe.IMAGE_IMPORT_BY_NAME(self.module.pe.vfh)
self.ordinal = import_by_name.Hint
self.name = import_by_name.Name.decode()
self.address = address_thunk.u1.Function
self.bound_address = bound_thunk.u1.Function if bound_thunk else None
self.unload_address = unload_thunk.u1.Function if unload_thunk else None
def __repr__(self) -> str:
address = hex(self.address) if self.address is not None else "None"
bound_address = hex(self.bound_address) if self.bound_address is not None else "None"
unload_address = hex(self.unload_address) if self.unload_address is not None else "None"
return f"<DelayImportFunction name={self.name!r} ordinal={self.ordinal} address={address} bound_address={bound_address} unload_address={unload_address}>" # noqa: E501