Skip to content

Commit ab5e6d1

Browse files
joost-jSchamper
andauthored
Add write_sid function (#111)
Co-authored-by: Schamper <1254028+Schamper@users.noreply.github.com>
1 parent 94da877 commit ab5e6d1

File tree

2 files changed

+55
-1
lines changed

2 files changed

+55
-1
lines changed

dissect/util/sid.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,34 @@ def read_sid(fh: BinaryIO | bytes, endian: str = "<", swap_last: bool = False) -
4848
]
4949
sid_elements.extend(map(str, sub_authorities))
5050
return "-".join(sid_elements)
51+
52+
53+
def write_sid(sid: str, endian: str = "<", swap_last: bool = False) -> bytes:
54+
"""Write a Windows SID string to bytes.
55+
56+
Args:
57+
sid: SID in the form ``S-Revision-Authority-SubAuth1-...``.
58+
endian: Optional endianness for reading the sub authorities.
59+
swap_last: Optional flag for swapping the endianess of the _last_ sub authority entry.
60+
"""
61+
if not sid:
62+
return b""
63+
64+
parts = sid.split("-")
65+
if len(parts) < 3 or parts[0].upper() != "S":
66+
raise ValueError("Invalid SID string format: insufficient parts")
67+
68+
revision = int(parts[1]).to_bytes(1, "little")
69+
authority = int(parts[2]).to_bytes(6, "big")
70+
sub_authorities = [int(x) for x in parts[3:]]
71+
72+
header = revision + len(sub_authorities).to_bytes(1, "little") + authority
73+
74+
if not sub_authorities:
75+
return header
76+
77+
sub_bytes = bytearray(struct.pack(f"{endian}{len(sub_authorities)}I", *sub_authorities))
78+
if swap_last:
79+
sub_bytes[-4:] = sub_bytes[-4:][::-1]
80+
81+
return header + bytes(sub_bytes)

tests/test_sid.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,13 @@ def id_fn(val: bytes | str) -> str:
8484
],
8585
ids=id_fn,
8686
)
87-
def test_read_sid(binary_sid: bytes | BinaryIO, endian: str, swap_last: bool, readable_sid: str) -> None:
87+
def test_read_write_sid(binary_sid: bytes | BinaryIO, endian: str, swap_last: bool, readable_sid: str) -> None:
8888
assert readable_sid == sid.read_sid(binary_sid, endian, swap_last)
8989

90+
if isinstance(binary_sid, io.BytesIO):
91+
binary_sid = binary_sid.getvalue()
92+
assert binary_sid == sid.write_sid(readable_sid, endian, swap_last)
93+
9094

9195
@pytest.mark.benchmark
9296
@pytest.mark.parametrize(
@@ -105,3 +109,22 @@ def test_read_sid(binary_sid: bytes | BinaryIO, endian: str, swap_last: bool, re
105109
)
106110
def test_read_sid_benchmark(benchmark: BenchmarkFixture, binary_sid: bytes, swap_last: bool) -> None:
107111
benchmark(sid.read_sid, binary_sid, "<", swap_last)
112+
113+
114+
@pytest.mark.benchmark
115+
@pytest.mark.parametrize(
116+
("readable_sid", "swap_last"),
117+
[
118+
(
119+
"S-1-5-21-123456789-268435456-500",
120+
False,
121+
),
122+
(
123+
"S-1-5-21-123456789-268435456-500",
124+
True,
125+
),
126+
],
127+
ids=lambda x: x if isinstance(x, str) else str(x),
128+
)
129+
def test_write_sid_benchmark(benchmark: BenchmarkFixture, readable_sid: str, swap_last: bool) -> None:
130+
benchmark(sid.write_sid, readable_sid, "<", swap_last)

0 commit comments

Comments
 (0)