dissect.cstruct 4.8.dev6__tar.gz → 4.8.dev8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/PKG-INFO +1 -1
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/utils.py +138 -30
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/PKG-INFO +1 -1
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_utils.py +132 -1
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/.git-blame-ignore-revs +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/.gitattributes +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/CHANGELOG.md +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/COPYRIGHT +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/LICENSE +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/MANIFEST.in +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/README.md +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/__init__.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/bitbuffer.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/compiler.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/cstruct.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/exceptions.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/expression.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/parser.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/tools/__init__.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/tools/stubgen.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/__init__.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/base.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/char.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/enum.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/flag.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/int.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/leb128.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/packed.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/pointer.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/structure.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/void.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/wchar.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/SOURCES.txt +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/entry_points.txt +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/requires.txt +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/top_level.txt +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/examples/disk.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/examples/mirai.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/examples/pe.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/examples/protobuf.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/examples/secdesc.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/pyproject.toml +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/setup.cfg +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/__init__.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/_data/testdef.txt +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/_docs/Makefile +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/_docs/__init__.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/_docs/conf.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/_docs/index.rst +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/conftest.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_align.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_annotations.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_basic.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_benchmark.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_bitbuffer.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_bitfield.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_compiler.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_ctypes.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_expression.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_parser.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_tools_stubgen.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_types_base.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_types_char.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_types_custom.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_types_enum.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_types_flag.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_types_int.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_types_leb128.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_types_packed.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_types_pointer.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_types_structure.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_types_union.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_types_void.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/test_types_wchar.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tests/utils.py +0 -0
- {dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 4.8.
|
|
3
|
+
Version: 4.8.dev8
|
|
4
4
|
Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import pprint
|
|
4
5
|
import string
|
|
5
6
|
import sys
|
|
@@ -13,15 +14,29 @@ if TYPE_CHECKING:
|
|
|
13
14
|
from collections.abc import Iterator
|
|
14
15
|
from typing import Literal
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
# Regular ANSI colors
|
|
18
|
+
COLOR_RED = "\033[0;31m"
|
|
19
|
+
COLOR_GREEN = "\033[0;32m"
|
|
20
|
+
COLOR_YELLOW = "\033[0;93m"
|
|
21
|
+
COLOR_BLUE = "\033[0;34m"
|
|
22
|
+
COLOR_PURPLE = "\033[0;35m"
|
|
23
|
+
COLOR_CYAN = "\033[0;36m"
|
|
24
|
+
COLOR_WHITE = "\033[0;37m"
|
|
25
|
+
COLOR_BLACK = "\033[0;30m"
|
|
26
|
+
COLOR_GREY = "\033[0;90m"
|
|
27
|
+
|
|
28
|
+
# Bold ANSI colors
|
|
29
|
+
COLOR_RED_BOLD = "\033[1;31m"
|
|
30
|
+
COLOR_GREEN_BOLD = "\033[1;32m"
|
|
31
|
+
COLOR_YELLOW_BOLD = "\033[1;33m"
|
|
32
|
+
COLOR_BLUE_BOLD = "\033[1;34m"
|
|
33
|
+
COLOR_PURPLE_BOLD = "\033[1;35m"
|
|
34
|
+
COLOR_CYAN_BOLD = "\033[1;36m"
|
|
35
|
+
COLOR_WHITE_BOLD = "\033[1;37m"
|
|
36
|
+
COLOR_BLACK_BOLD = "\033[1;30m"
|
|
37
|
+
COLOR_GREY_BOLD = "\033[1;90m"
|
|
38
|
+
|
|
39
|
+
# Background ANSI colors
|
|
25
40
|
COLOR_BG_RED = "\033[1;41m\033[1;37m"
|
|
26
41
|
COLOR_BG_GREEN = "\033[1;42m\033[1;37m"
|
|
27
42
|
COLOR_BG_YELLOW = "\033[1;43m\033[1;37m"
|
|
@@ -30,6 +45,10 @@ COLOR_BG_PURPLE = "\033[1;45m\033[1;37m"
|
|
|
30
45
|
COLOR_BG_CYAN = "\033[1;46m\033[1;37m"
|
|
31
46
|
COLOR_BG_WHITE = "\033[1;47m\033[1;30m"
|
|
32
47
|
|
|
48
|
+
# Reset ANSI codes
|
|
49
|
+
COLOR_CLEAR = "\033[0m"
|
|
50
|
+
COLOR_CLEAR_BOLD = "\033[1;0m"
|
|
51
|
+
|
|
33
52
|
PRINTABLE = string.digits + string.ascii_letters + string.punctuation + " "
|
|
34
53
|
|
|
35
54
|
ENDIANNESS_MAP: dict[str, Literal["big", "little"]] = {
|
|
@@ -44,20 +63,67 @@ ENDIANNESS_MAP: dict[str, Literal["big", "little"]] = {
|
|
|
44
63
|
Palette = list[tuple[int, str]]
|
|
45
64
|
|
|
46
65
|
|
|
47
|
-
def
|
|
66
|
+
def _human_colors() -> dict[str, str]:
|
|
67
|
+
"""Generates a dictionary of characters with a human-readable ANSI color they should be in a hexdump.
|
|
68
|
+
|
|
69
|
+
Coloring logic implementation derived from HexFriend and ImHex.
|
|
70
|
+
"""
|
|
71
|
+
# Make all characters not in any rules below light green
|
|
72
|
+
colors = {chr(char): COLOR_GREEN for char in range(256)}
|
|
73
|
+
|
|
74
|
+
# Make all ASCII extended characters yellow
|
|
75
|
+
for char in colors:
|
|
76
|
+
if ord(char) & 0x80 == 0:
|
|
77
|
+
colors[char] = COLOR_YELLOW
|
|
78
|
+
|
|
79
|
+
# Make null bytes grey
|
|
80
|
+
colors["\00"] = COLOR_GREY
|
|
81
|
+
|
|
82
|
+
# Make printable ASCII characters bold white (0x32-0x7E)
|
|
83
|
+
for char in PRINTABLE:
|
|
84
|
+
colors[char] = COLOR_WHITE_BOLD
|
|
85
|
+
|
|
86
|
+
# Make ASCII whitespace characters green bold (0x9, 0xA, 0xB, 0xC, 0xD, 0x20)
|
|
87
|
+
for char in ("\t", "\n", "\11", "\12", "\r", "\20"):
|
|
88
|
+
colors[char] = COLOR_GREEN_BOLD
|
|
89
|
+
|
|
90
|
+
return colors
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
HUMAN_COLORS = _human_colors()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _hexdump(
|
|
97
|
+
data: bytes,
|
|
98
|
+
*,
|
|
99
|
+
palette: Palette | None = None,
|
|
100
|
+
offset: int = 0,
|
|
101
|
+
prefix: str = "",
|
|
102
|
+
pretty: bool | None = False,
|
|
103
|
+
autoskip: bool = False,
|
|
104
|
+
) -> Iterator[str]:
|
|
48
105
|
"""Hexdump some data.
|
|
49
106
|
|
|
50
107
|
Args:
|
|
51
108
|
data: Bytes to hexdump.
|
|
109
|
+
palette: Colorize the hexdump using this color pattern.
|
|
52
110
|
offset: Byte offset of the hexdump.
|
|
53
111
|
prefix: Optional prefix.
|
|
54
|
-
|
|
112
|
+
pretty: Use pretty colors, mutual exclusive with palette.
|
|
113
|
+
autoskip: A single '*' replaces NUL-lines in the output.
|
|
55
114
|
"""
|
|
56
115
|
if palette:
|
|
57
116
|
palette = palette[::-1]
|
|
58
117
|
|
|
118
|
+
# only happy little accidents
|
|
119
|
+
if pretty and palette:
|
|
120
|
+
raise ValueError("Cannot use argument 'pretty' in combination with 'palette', please pick one")
|
|
121
|
+
|
|
59
122
|
remaining = 0
|
|
60
123
|
active = None
|
|
124
|
+
in_null_run = False
|
|
125
|
+
in_collapsed_null_run = False
|
|
126
|
+
last_offset = len(data) - 16
|
|
61
127
|
|
|
62
128
|
for i in range(0, len(data), 16):
|
|
63
129
|
values = ""
|
|
@@ -87,42 +153,80 @@ def _hexdump(data: bytes, palette: Palette | None = None, offset: int = 0, prefi
|
|
|
87
153
|
|
|
88
154
|
if active:
|
|
89
155
|
values += f"{ord(char):02x}"
|
|
90
|
-
chars.append(active + print_char +
|
|
156
|
+
chars.append(active + print_char + COLOR_CLEAR_BOLD)
|
|
91
157
|
else:
|
|
92
|
-
|
|
93
|
-
|
|
158
|
+
if pretty and (color := HUMAN_COLORS.get(char, "")):
|
|
159
|
+
values += f"{color}{ord(char):02x}{COLOR_CLEAR}"
|
|
160
|
+
chars.append(color + print_char + COLOR_CLEAR)
|
|
161
|
+
else:
|
|
162
|
+
values += f"{ord(char):02x}"
|
|
163
|
+
chars.append(print_char)
|
|
94
164
|
|
|
95
165
|
remaining -= 1
|
|
96
166
|
if remaining == 0:
|
|
97
167
|
active = None
|
|
98
168
|
|
|
99
169
|
if palette is not None:
|
|
100
|
-
values +=
|
|
170
|
+
values += COLOR_CLEAR_BOLD
|
|
101
171
|
|
|
102
172
|
if j == 15 and palette is not None:
|
|
103
|
-
values +=
|
|
173
|
+
values += COLOR_CLEAR_BOLD
|
|
104
174
|
|
|
105
175
|
values += " "
|
|
106
176
|
if j == 7:
|
|
107
177
|
values += " "
|
|
108
178
|
|
|
179
|
+
if autoskip and 0 < i < last_offset and data[i : i + 16] == b"\x00" * 16:
|
|
180
|
+
if in_null_run:
|
|
181
|
+
if not in_collapsed_null_run:
|
|
182
|
+
yield "*"
|
|
183
|
+
in_collapsed_null_run = True
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
# Keep the first interior NUL line visible, collapse from the second onwards.
|
|
187
|
+
in_null_run = True
|
|
188
|
+
else:
|
|
189
|
+
in_null_run = False
|
|
190
|
+
in_collapsed_null_run = False
|
|
191
|
+
|
|
109
192
|
chars = "".join(chars)
|
|
110
193
|
yield f"{prefix}{offset + i:08x} {values:48s} {chars}"
|
|
111
194
|
|
|
112
195
|
|
|
113
196
|
def hexdump(
|
|
114
|
-
data: bytes,
|
|
197
|
+
data: bytes,
|
|
198
|
+
*,
|
|
199
|
+
palette: Palette | None = None,
|
|
200
|
+
offset: int = 0,
|
|
201
|
+
prefix: str = "",
|
|
202
|
+
output: str = "print",
|
|
203
|
+
pretty: bool | None = None,
|
|
204
|
+
autoskip: bool = False,
|
|
115
205
|
) -> Iterator[str] | str | None:
|
|
116
206
|
"""Hexdump some data.
|
|
117
207
|
|
|
208
|
+
Uses colored ANSI output with output type "print" by default. Disable with ``pretty=False``
|
|
209
|
+
or set the environment variable ``NO_COLOR``.
|
|
210
|
+
|
|
118
211
|
Args:
|
|
119
212
|
data: Bytes to hexdump.
|
|
120
213
|
palette: Colorize the hexdump using this color pattern.
|
|
121
214
|
offset: Byte offset of the hexdump.
|
|
122
215
|
prefix: Optional prefix.
|
|
123
216
|
output: Output format, can be 'print', 'generator' or 'string'.
|
|
217
|
+
pretty: Use pretty colors for improved human readability.
|
|
218
|
+
autoskip: A single '*' replaces NUL-lines in the output.
|
|
124
219
|
"""
|
|
125
|
-
|
|
220
|
+
# Enable pretty colors by default if ...
|
|
221
|
+
if (
|
|
222
|
+
output == "print" # the output type is set to 'print'
|
|
223
|
+
and not palette # no palette is given (structdump only)
|
|
224
|
+
and pretty is not False # pretty was not explicitly set to False
|
|
225
|
+
and not os.environ.get("NO_COLOR") # and the environment allows colors
|
|
226
|
+
):
|
|
227
|
+
pretty = True
|
|
228
|
+
|
|
229
|
+
generator = _hexdump(data, palette=palette, offset=offset, prefix=prefix, pretty=pretty, autoskip=autoskip)
|
|
126
230
|
if output == "print":
|
|
127
231
|
print("\n".join(generator))
|
|
128
232
|
return None
|
|
@@ -139,16 +243,17 @@ def _dumpstruct(
|
|
|
139
243
|
offset: int,
|
|
140
244
|
color: bool,
|
|
141
245
|
output: str,
|
|
246
|
+
autoskip: bool,
|
|
142
247
|
) -> str | None:
|
|
143
248
|
palette = []
|
|
144
249
|
colors = [
|
|
145
|
-
(
|
|
146
|
-
(
|
|
147
|
-
(
|
|
148
|
-
(
|
|
149
|
-
(
|
|
150
|
-
(
|
|
151
|
-
(
|
|
250
|
+
(COLOR_RED_BOLD, COLOR_BG_RED),
|
|
251
|
+
(COLOR_GREEN_BOLD, COLOR_BG_GREEN),
|
|
252
|
+
(COLOR_YELLOW_BOLD, COLOR_BG_YELLOW),
|
|
253
|
+
(COLOR_BLUE_BOLD, COLOR_BG_BLUE),
|
|
254
|
+
(COLOR_PURPLE_BOLD, COLOR_BG_PURPLE),
|
|
255
|
+
(COLOR_CYAN_BOLD, COLOR_BG_CYAN),
|
|
256
|
+
(COLOR_WHITE_BOLD, COLOR_BG_WHITE),
|
|
152
257
|
]
|
|
153
258
|
ci = 0
|
|
154
259
|
out = [f"struct {structure.__class__.__name__}:"]
|
|
@@ -172,7 +277,7 @@ def _dumpstruct(
|
|
|
172
277
|
size = structure.__sizes__[field._name]
|
|
173
278
|
palette.append((size, background))
|
|
174
279
|
ci += 1
|
|
175
|
-
out.append(f"- {foreground}{field._name}{
|
|
280
|
+
out.append(f"- {foreground}{field._name}{COLOR_CLEAR_BOLD}: {value}")
|
|
176
281
|
else:
|
|
177
282
|
out.append(f"- {field._name}: {value}")
|
|
178
283
|
|
|
@@ -180,11 +285,11 @@ def _dumpstruct(
|
|
|
180
285
|
|
|
181
286
|
if output == "print":
|
|
182
287
|
print()
|
|
183
|
-
hexdump(data, palette, offset=offset)
|
|
288
|
+
hexdump(data, palette=palette, offset=offset, autoskip=autoskip)
|
|
184
289
|
print()
|
|
185
290
|
print(out)
|
|
186
291
|
elif output == "string":
|
|
187
|
-
return f"\n{hexdump(data, palette, offset=offset, output='string')}\n\n{out}"
|
|
292
|
+
return f"\n{hexdump(data, palette=palette, offset=offset, output='string', autoskip=autoskip)}\n\n{out}"
|
|
188
293
|
return None
|
|
189
294
|
|
|
190
295
|
|
|
@@ -193,6 +298,7 @@ def dumpstruct(
|
|
|
193
298
|
data: bytes | None = None,
|
|
194
299
|
offset: int = 0,
|
|
195
300
|
color: bool = True,
|
|
301
|
+
autoskip: bool = False,
|
|
196
302
|
output: str = "print",
|
|
197
303
|
) -> str | None:
|
|
198
304
|
"""Dump a structure or parsed structure instance.
|
|
@@ -203,15 +309,17 @@ def dumpstruct(
|
|
|
203
309
|
obj: Structure to dump.
|
|
204
310
|
data: Bytes to parse the Structure on, if obj is not a parsed Structure already.
|
|
205
311
|
offset: Byte offset of the hexdump.
|
|
312
|
+
color: Colorize the hexdump and structure output.
|
|
313
|
+
autoskip: A single '*' replaces NUL-lines in the output.
|
|
206
314
|
output: Output format, can be 'print' or 'string'.
|
|
207
315
|
"""
|
|
208
316
|
if output not in ("print", "string"):
|
|
209
317
|
raise ValueError(f"Invalid output argument: {output!r} (should be 'print' or 'string').")
|
|
210
318
|
|
|
211
319
|
if isinstance(obj, Structure):
|
|
212
|
-
return _dumpstruct(obj, obj.dumps(), offset, color, output)
|
|
320
|
+
return _dumpstruct(obj, obj.dumps(), offset, color, output, autoskip)
|
|
213
321
|
if issubclass(obj, Structure) and data is not None:
|
|
214
|
-
return _dumpstruct(obj(data), data, offset, color, output)
|
|
322
|
+
return _dumpstruct(obj(data), data, offset, color, output, autoskip)
|
|
215
323
|
raise ValueError("Invalid arguments")
|
|
216
324
|
|
|
217
325
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 4.8.
|
|
3
|
+
Version: 4.8.dev8
|
|
4
4
|
Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def test_hexdump(capsys: pytest.CaptureFixture) -> None:
|
|
17
|
-
utils.hexdump(b"\x00" * 16)
|
|
17
|
+
utils.hexdump(b"\x00" * 16, pretty=False)
|
|
18
18
|
captured = capsys.readouterr()
|
|
19
19
|
assert captured.out == "00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................\n"
|
|
20
20
|
|
|
@@ -30,6 +30,137 @@ def test_hexdump(capsys: pytest.CaptureFixture) -> None:
|
|
|
30
30
|
utils.hexdump("b\x00", output="str")
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
def test_hexdump_pretty(capsys: pytest.CaptureFixture) -> None:
|
|
34
|
+
"""Check if we can create a pretty hexdump."""
|
|
35
|
+
c = utils.COLOR_CLEAR
|
|
36
|
+
g = utils.COLOR_GREY
|
|
37
|
+
w = utils.COLOR_WHITE_BOLD
|
|
38
|
+
y = utils.COLOR_YELLOW
|
|
39
|
+
|
|
40
|
+
utils.hexdump((b"\x00" * 5) + b"\x01\x02\x03abc" + (b"\x00" * 5), pretty=True)
|
|
41
|
+
captured = capsys.readouterr()
|
|
42
|
+
assert (
|
|
43
|
+
captured.out
|
|
44
|
+
== "00000000 "
|
|
45
|
+
+ (f"{g}00{c} " * 5)
|
|
46
|
+
+ f"{y}01{c} {y}02{c} {y}03{c} {w}61{c} {w}62{c} {w}63{c} "
|
|
47
|
+
+ (f"{g}00{c} " * 5)
|
|
48
|
+
+ " "
|
|
49
|
+
+ (f"{g}.{c}" * 5)
|
|
50
|
+
+ f"{y}.{c}{y}.{c}{y}.{c}{w}a{c}{w}b{c}{w}c{c}"
|
|
51
|
+
+ (f"{g}.{c}" * 5)
|
|
52
|
+
+ "\n"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_hexdump_autoskip_collapses_middle_null_run() -> None:
|
|
57
|
+
"""Keep first interior NUL line, then collapse the rest of that run to '*'."""
|
|
58
|
+
# Layout: [A data line] [3 NUL lines] [B data line]
|
|
59
|
+
# Expected: [A line] [first NUL line] [*] [B line] = 4 lines
|
|
60
|
+
data = (b"A" * 16) + (b"\x00" * 48) + (b"B" * 16)
|
|
61
|
+
|
|
62
|
+
out = utils.hexdump(data, output="string", pretty=False, autoskip=True)
|
|
63
|
+
assert out is not None
|
|
64
|
+
|
|
65
|
+
lines = out.splitlines()
|
|
66
|
+
assert len(lines) == 4
|
|
67
|
+
assert lines[0].startswith("00000000") # A line
|
|
68
|
+
assert lines[1].startswith("00000010") # First NUL line is kept
|
|
69
|
+
assert lines[2] == "*" # Remaining NUL lines collapsed
|
|
70
|
+
assert lines[3].startswith("00000040") # B line
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_hexdump_autoskip_keeps_edge_null_lines() -> None:
|
|
74
|
+
"""Do not collapse first/last hexdump lines even when they are all NUL bytes."""
|
|
75
|
+
# Layout: [3 NUL lines]
|
|
76
|
+
# Expected: all lines are kept (only one interior line, so nothing is repeated there)
|
|
77
|
+
data = b"\x00" * 48
|
|
78
|
+
|
|
79
|
+
out = utils.hexdump(data, output="string", pretty=False, autoskip=True)
|
|
80
|
+
assert out is not None
|
|
81
|
+
|
|
82
|
+
lines = out.splitlines()
|
|
83
|
+
assert len(lines) == 3
|
|
84
|
+
assert lines[0].startswith("00000000") # First line kept (edge)
|
|
85
|
+
assert lines[1].startswith("00000010") # Single interior NUL line is kept
|
|
86
|
+
assert lines[2].startswith("00000020") # Last line kept (edge)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_hexdump_autoskip_separate_null_runs() -> None:
|
|
90
|
+
"""Emit one '*' per interior NUL run, after keeping each run's first interior NUL line."""
|
|
91
|
+
# Layout: [A data] [2 NUL lines] [B data] [2 NUL lines] [C data]
|
|
92
|
+
# Expected: [A line] [NUL line] [*] [B line] [NUL line] [*] [C line] = 7 lines
|
|
93
|
+
data = (b"A" * 16) + (b"\x00" * 32) + (b"B" * 16) + (b"\x00" * 32) + (b"C" * 16)
|
|
94
|
+
|
|
95
|
+
out = utils.hexdump(data, output="string", pretty=False, autoskip=True)
|
|
96
|
+
assert out is not None
|
|
97
|
+
|
|
98
|
+
lines = out.splitlines()
|
|
99
|
+
assert len(lines) == 7
|
|
100
|
+
assert lines[0].startswith("00000000") # A line
|
|
101
|
+
assert lines[1].startswith("00000010") # First NUL line of first run kept
|
|
102
|
+
assert lines[2] == "*" # Remaining NUL lines of first run collapsed
|
|
103
|
+
assert lines[3].startswith("00000030") # B line
|
|
104
|
+
assert lines[4].startswith("00000040") # First NUL line of second run kept
|
|
105
|
+
assert lines[5] == "*" # Remaining NUL lines of second run collapsed
|
|
106
|
+
assert lines[6].startswith("00000060") # C line
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_hexdump_autoskip_single_interior_null_line_is_not_collapsed() -> None:
|
|
110
|
+
"""Keep a single interior all-NUL line visible when autoskip is enabled."""
|
|
111
|
+
# Layout: [A data line] [1 NUL line] [B data line]
|
|
112
|
+
# Expected: [A line] [NUL line] [B line] = 3 lines (no repeated interior NUL line)
|
|
113
|
+
data = (b"A" * 16) + (b"\x00" * 16) + (b"B" * 16)
|
|
114
|
+
|
|
115
|
+
out = utils.hexdump(data, output="string", pretty=False, autoskip=True)
|
|
116
|
+
assert out is not None
|
|
117
|
+
|
|
118
|
+
lines = out.splitlines()
|
|
119
|
+
assert len(lines) == 3
|
|
120
|
+
assert lines[0].startswith("00000000") # A line
|
|
121
|
+
assert lines[1].startswith("00000010") # Single NUL line is kept
|
|
122
|
+
assert lines[2].startswith("00000020") # B line
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_hexdump_autoskip_false_does_not_collapse() -> None:
|
|
126
|
+
"""Keep all lines expanded and never emit '*' when autoskip is disabled."""
|
|
127
|
+
# Layout: [A data line] [3 NUL lines] [B data line]
|
|
128
|
+
# Expected: [A line] [NUL line] [NUL line] [NUL line] [B line] = 5 lines (no * when disabled)
|
|
129
|
+
data = (b"A" * 16) + (b"\x00" * 48) + (b"B" * 16)
|
|
130
|
+
|
|
131
|
+
out = utils.hexdump(data, output="string", pretty=False, autoskip=False)
|
|
132
|
+
assert out is not None
|
|
133
|
+
|
|
134
|
+
lines = out.splitlines()
|
|
135
|
+
assert len(lines) == 5
|
|
136
|
+
assert all(line != "*" for line in lines) # No * when autoskip disabled
|
|
137
|
+
assert lines[0].startswith("00000000") # A line
|
|
138
|
+
assert lines[1].startswith("00000010") # First NUL line (expanded)
|
|
139
|
+
assert lines[2].startswith("00000020") # Second NUL line (expanded)
|
|
140
|
+
assert lines[3].startswith("00000030") # Third NUL line (expanded)
|
|
141
|
+
assert lines[4].startswith("00000040") # B line
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_hexdump_pretty_print_conditions(capsys: pytest.CaptureFixture, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
145
|
+
"""Test if we respec the ``NO_COLOR`` environment variable and ``pretty=False`` argument."""
|
|
146
|
+
# Test regular print output behavior
|
|
147
|
+
utils.hexdump(b"\x00" * 16)
|
|
148
|
+
captured = capsys.readouterr()
|
|
149
|
+
assert captured.out.startswith("00000000 \x1b[0;90m00")
|
|
150
|
+
|
|
151
|
+
# Test explicit disable using NO_COLOR
|
|
152
|
+
with monkeypatch.context() as m:
|
|
153
|
+
m.setenv("NO_COLOR", "1")
|
|
154
|
+
utils.hexdump(b"\x00" * 16)
|
|
155
|
+
captured = capsys.readouterr()
|
|
156
|
+
assert captured.out.startswith("00000000 00")
|
|
157
|
+
|
|
158
|
+
# Test explicit disable using pretty=False
|
|
159
|
+
utils.hexdump(b"\x00" * 16, pretty=False)
|
|
160
|
+
captured = capsys.readouterr()
|
|
161
|
+
assert captured.out.startswith("00000000 00")
|
|
162
|
+
|
|
163
|
+
|
|
33
164
|
def test_dumpstruct(cs: cstruct, capsys: pytest.CaptureFixture, compiled: bool) -> None:
|
|
34
165
|
cdef = """
|
|
35
166
|
struct test {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{dissect_cstruct-4.8.dev6 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|