Skip to content

Commit e312272

Browse files
authored
Minor ELF improvements (#29)
1 parent 4ff3e9f commit e312272

16 files changed

Lines changed: 72 additions & 66 deletions

dissect/executable/elf/elf.py

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727
class ELF:
2828
def __init__(self, fh: BinaryIO):
2929
self.fh = fh
30-
offset = fh.tell()
30+
31+
fh.seek(0)
3132
self.e_ident = fh.read(0x10)
32-
fh.seek(offset)
3333

3434
if self.e_ident[:4] != c_common_elf.ELFMAG:
35-
raise InvalidSignatureError("Invalid header magic")
35+
raise InvalidSignatureError(
36+
f"Invalid ELF header magic, expected {c_common_elf.ELFMAG!r}, got {self.e_ident[:4]!r}"
37+
)
3638

3739
c_elf_version = c_elf_32
3840
if self.e_ident[c_common_elf.EI_CLASS] == c_common_elf.ELFCLASS64:
@@ -43,20 +45,22 @@ def __init__(self, fh: BinaryIO):
4345
is_little = self.e_ident[c_common_elf.EI_DATA] == c_common_elf.ELFDATA2LSB
4446
self.c_elf.endian = "<" if is_little else ">"
4547

48+
fh.seek(0)
4649
self.header = self.c_elf.Ehdr(fh)
50+
4751
self.segments = SegmentTable.from_elf(self)
48-
self.section_table = SectionTable.from_elf(self)
49-
self.symbol_tables: list[SymbolTable] = self.section_table.by_type([SHT.SYMTAB, SHT.DYNSYM])
52+
self.sections = SectionTable.from_elf(self)
53+
self.symbol_tables: list[SymbolTable] = self.sections.by_type([SHT.SYMTAB, SHT.DYNSYM])
5054

5155
def __repr__(self) -> str:
5256
return str(self.header)
5357

5458
def dump(self) -> bytes:
5559
output_data = [
5660
self.segments.dump_table(),
57-
self.section_table.dump_table(),
61+
self.sections.dump_table(),
5862
*self.segments.dump_data(),
59-
*self.section_table.dump_data(),
63+
*self.sections.dump_data(),
6064
]
6165
output_data.sort(key=itemgetter(0))
6266

@@ -87,12 +91,12 @@ def dynamic(self) -> bool:
8791

8892

8993
class Table(Generic[T]):
90-
def __init__(self, entries: int) -> None:
91-
self.entries = entries
92-
self.items: list[T] = [None] * entries
94+
def __init__(self, num: int) -> None:
95+
self.num = num
96+
self.items: list[T] = [None] * num
9397

9498
def __iter__(self) -> Iterator[T]:
95-
for idx in range(self.entries):
99+
for idx in range(self.num):
96100
yield self[idx]
97101

98102
def __getitem__(self, idx: int) -> T:
@@ -125,7 +129,7 @@ def __init__(self, fh: BinaryIO, idx: int | None = None, c_elf: cstruct = c_elf_
125129

126130
def __repr__(self) -> str:
127131
return (
128-
f"<{self.__class__.__name__} idx={self.idx} name={self.name} type={self.type}"
132+
f"<{self.__class__.__name__} idx={self.idx} name={self.name!r} type={self.type}"
129133
f" offset=0x{self.offset:x} size=0x{self.size:x}>"
130134
)
131135

@@ -138,16 +142,12 @@ def _set_link(self, table: SectionTable) -> None:
138142
self._link = table[self.header.sh_link]
139143

140144
@classmethod
141-
def from_section_table(cls, section_table: SectionTable, idx: int) -> Section:
142-
result = cls(section_table.fh, idx=idx, c_elf=section_table.c_elf)
143-
result._set_link(section_table)
144-
145-
string_table = section_table.string_table
146-
if isinstance(result, StringTable):
147-
string_table = result
145+
def from_section_table(cls, table: SectionTable, idx: int) -> Section:
146+
result = cls(table.fh, idx=idx, c_elf=table.c_elf)
147+
result._set_link(table)
148148

149-
if string_table:
150-
result._set_name(string_table)
149+
if sh_strtab := (result if idx == table._sh_strtab_idx else table._sh_strtab):
150+
result._set_name(sh_strtab)
151151

152152
return result
153153

@@ -163,7 +163,7 @@ def link(self) -> Section | None:
163163
return self._link
164164

165165
@cached_property
166-
def contents(self) -> bytes:
166+
def data(self) -> bytes:
167167
self.fh.seek(self.offset)
168168
return self.fh.read(self.size)
169169

@@ -173,20 +173,22 @@ def __init__(
173173
self,
174174
fh: BinaryIO,
175175
offset: int,
176-
entries: int,
176+
num: int,
177177
size: int,
178-
string_index: int | None = None,
178+
sh_strtab_idx: int | None = None,
179179
c_elf: cstruct = c_elf_64,
180180
):
181-
super().__init__(entries)
181+
super().__init__(num)
182182
self.fh = fh
183183
self.offset = offset
184184
self.size = size
185-
self.string_table = None
186185
self.c_elf = c_elf
187186

188-
if string_index:
189-
self.string_table: StringTable = self[string_index]
187+
self._sh_strtab_idx = sh_strtab_idx
188+
self._sh_strtab = None
189+
190+
if sh_strtab_idx:
191+
self._sh_strtab: StringTable = self[sh_strtab_idx]
190192

191193
def __repr__(self) -> str:
192194
return f"<SectionTable offset=0x{self.offset:x} size=0x{self.size:x}>"
@@ -206,11 +208,14 @@ def _create_item(self, idx: int) -> Section:
206208

207209
@classmethod
208210
def from_elf(cls, elf: ELF) -> SectionTable:
209-
offset = elf.header.e_shoff
210-
entries = elf.header.e_shnum
211-
size = elf.header.e_shentsize
212-
other_index = elf.header.e_shstrndx
213-
return cls(elf.fh, offset, entries, size, other_index, elf.c_elf)
211+
return cls(
212+
elf.fh,
213+
elf.header.e_shoff,
214+
elf.header.e_shnum,
215+
elf.header.e_shentsize,
216+
elf.header.e_shstrndx,
217+
elf.c_elf,
218+
)
214219

215220
def by_type(self, section_types: list[int] | int) -> list[Section]:
216221
types = section_types
@@ -223,14 +228,14 @@ def related_sections(self, segment: Segment) -> list[Section]:
223228
return self.find(lambda x: x.is_related(segment))
224229

225230
def by_name(self, name: str) -> list[Section]:
226-
return self.find(lambda x: x.name in name)
231+
return self.find(lambda x: name == x.name)
227232

228233
def dump_table(self) -> tuple[int, bytes]:
229234
buf = bytearray()
230235
return self.offset, buf.join([x.header.dumps() for x in self])
231236

232237
def dump_data(self) -> list[tuple[int, bytes]]:
233-
return [(x.offset, x.contents) for x in self]
238+
return [(x.offset, x.data) for x in self]
234239

235240

236241
class Segment:
@@ -268,7 +273,7 @@ def is_related(self, section: Section) -> bool:
268273
return self.offset <= section.offset < self.end
269274

270275
@property
271-
def contents(self) -> bytes:
276+
def data(self) -> bytes:
272277
if not self._data:
273278
self.fh.seek(self.offset)
274279
self._data = self.fh.read(self.size)
@@ -320,7 +325,7 @@ def by_type(self, segment_types: list[int] | int) -> list[Segment]:
320325
return self.find(lambda x: x.type in types)
321326

322327
def dump_data(self) -> list[tuple[int, bytearray]]:
323-
return [(x.offset, x.contents) for x in self]
328+
return [(x.offset, x.data) for x in self]
324329

325330
def dump_table(self) -> tuple[int, bytearray]:
326331
buf = bytearray()
@@ -337,9 +342,9 @@ def __getitem__(self, offset: int) -> str:
337342
return self._get_string(offset)
338343

339344
def _get_string(self, index: int) -> str:
340-
if index > len(self.contents) or index == SHN.UNDEF:
345+
if index > self.size or index == SHN.UNDEF:
341346
return None
342-
return self.c_elf.char[None](self.contents[index:]).decode("utf8")
347+
return self.c_elf.char[None](self.data[index:]).decode("utf8")
343348

344349

345350
class Symbol:
@@ -372,7 +377,7 @@ def _set_name(self, table: StringTable) -> None:
372377
@classmethod
373378
def from_symbol_table(cls, table: SymbolTable, idx: int) -> Symbol:
374379
offset = idx * table.entry_size
375-
data = table.contents[offset : offset + table.entry_size]
380+
data = table.data[offset : offset + table.entry_size]
376381
output = cls(io.BytesIO(data), idx, table.c_elf)
377382
output._set_name(table.link)
378383
return output

pyproject.toml

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ name = "dissect.executable"
77
description = "A Dissect module implementing a parsers for various executable formats such as PE, ELF and Macho-O"
88
readme = "README.md"
99
requires-python = "~=3.9"
10-
license.text = "Affero General Public License v3"
10+
license = "AGPL-3.0-or-later"
11+
license-files = ["LICENSE", "COPYRIGHT"]
1112
authors = [
1213
{name = "Dissect Team", email = "dissect@fox-it.com"}
1314
]
@@ -16,7 +17,6 @@ classifiers = [
1617
"Environment :: Console",
1718
"Intended Audience :: Developers",
1819
"Intended Audience :: Information Technology",
19-
"License :: OSI Approved",
2020
"Operating System :: OS Independent",
2121
"Programming Language :: Python :: 3",
2222
"Topic :: Internet :: Log Analysis",
@@ -43,7 +43,7 @@ dev = [
4343

4444
[tool.ruff]
4545
line-length = 120
46-
required-version = ">=0.9.0"
46+
required-version = ">=0.12.0"
4747

4848
[tool.ruff.format]
4949
docstring-code-format = true
@@ -86,15 +86,12 @@ select = [
8686
ignore = ["E203", "B904", "UP024", "ANN002", "ANN003", "ANN204", "ANN401", "SIM105", "TRY003"]
8787

8888
[tool.ruff.lint.per-file-ignores]
89-
"tests/docs/**" = ["INP001"]
89+
"tests/_docs/**" = ["INP001"]
9090

9191
[tool.ruff.lint.isort]
9292
known-first-party = ["dissect.executable"]
9393
known-third-party = ["dissect"]
9494

95-
[tool.setuptools]
96-
license-files = ["LICENSE", "COPYRIGHT"]
97-
9895
[tool.setuptools.packages.find]
9996
include = ["dissect.*"]
10097

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
project = "dissect.executable"
2+
13
extensions = [
24
"autoapi.extension",
35
"sphinx.ext.autodoc",
File renamed without changes.

tests/_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from pathlib import Path
2+
3+
4+
def absolute_path(path: str) -> Path:
5+
return (Path(__file__).parent / path).resolve()

tests/elf/__init__.py

Whitespace-only changes.
Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,22 @@
66
import pytest
77

88
from dissect.executable import ELF
9-
10-
from .util import data_file
9+
from tests._utils import absolute_path
1110

1211
if TYPE_CHECKING:
1312
from pathlib import Path
1413

1514

1615
@pytest.mark.parametrize(
17-
"file_name",
18-
["hello_world.out", "hello_world.stripped.out"],
16+
"path",
17+
[
18+
"_data/elf/hello_world.out",
19+
"_data/elf/hello_world.stripped.out",
20+
],
1921
)
20-
def test_dump(tmp_path: Path, file_name: str) -> None:
22+
def test_dump(path: str, tmp_path: Path) -> None:
2123
output_path = tmp_path / "output"
22-
input_path = data_file(file_name)
24+
input_path = absolute_path(path)
2325

2426
with input_path.open("rb") as input_file:
2527
elf_file = ELF(input_file)

0 commit comments

Comments
 (0)