From 52a20264b7a066b3664eb19dc2dc2c972a0ac7d6 Mon Sep 17 00:00:00 2001 From: joost-j <2032793+joost-j@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:07:56 +0100 Subject: [PATCH 1/3] Add write_sid function --- dissect/util/sid.py | 30 +++++++++++++++++ tests/test_sid.py | 79 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/dissect/util/sid.py b/dissect/util/sid.py index 0d32538..b1e2a2b 100644 --- a/dissect/util/sid.py +++ b/dissect/util/sid.py @@ -48,3 +48,33 @@ def read_sid(fh: BinaryIO | bytes, endian: str = "<", swap_last: bool = False) - ] sid_elements.extend(map(str, sub_authorities)) return "-".join(sid_elements) + + +def write_sid(sid_str: str, endian: str = "<", swap_last: bool = False) -> bytes: + """Write a Windows SID string to its binary representation. + + Args: + sid_str: SID in the form "S-Revision-Authority-SubAuth1-...". + endian: Optional endianness for reading the sub authorities. + swap_last: Optional flag for swapping the endianess of the _last_ sub authority entry. + """ + if not sid_str: + return b"" + + parts = sid_str.split("-") + if len(parts) < 3 or parts[0].upper() != "S": + raise ValueError("Invalid SID string") + + revision = int(parts[1]).to_bytes(1, "little") + authority = int(parts[2]).to_bytes(6, "big") + sub_authorities = [int(x) for x in parts[3:]] if len(parts) > 3 else [] + + header = revision + len(sub_authorities).to_bytes(1, "little") + authority + + if not sub_authorities: + return header + + sub_bytes = bytearray(struct.pack(f"{endian}{len(sub_authorities)}I", *sub_authorities)) + if swap_last: + sub_bytes[-4:] = sub_bytes[-4:][::-1] + return header + bytes(sub_bytes) diff --git a/tests/test_sid.py b/tests/test_sid.py index a1a05e3..6e105ed 100644 --- a/tests/test_sid.py +++ b/tests/test_sid.py @@ -105,3 +105,82 @@ def test_read_sid(binary_sid: bytes | BinaryIO, endian: str, swap_last: bool, re ) def test_read_sid_benchmark(benchmark: BenchmarkFixture, binary_sid: bytes, swap_last: bool) -> None: benchmark(sid.read_sid, binary_sid, "<", swap_last) + + +@pytest.mark.parametrize( + ("binary_sid", "readable_sid", "endian", "swap_last"), + [ + ( + b"\x01\x00\x00\x00\x00\x00\x00\x00", + "S-1-0", + "<", + False, + ), + ( + b"\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00", + "S-1-1-0", + "<", + False, + ), + ( + b"\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\x15\xcd\x5b\x07\x00\x00\x00\x10\xf4\x01\x00\x00", + "S-1-5-21-123456789-268435456-500", + "<", + False, + ), + ( + io.BytesIO(b"\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00"), + "S-1-1-0", + "<", + False, + ), + ( + b"\x01\x04\x00\x00\x00\x00\x00\x05\x00\x00\x00\x15\x07\x5b\xcd\x15\x10\x00\x00\x00\x00\x00\x01\xf4", + "S-1-5-21-123456789-268435456-500", + ">", + False, + ), + ( + b"\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\x15\xcd\x5b\x07\x00\x00\x00\x10\x00\x00\x01\xf4", + "S-1-5-21-123456789-268435456-500", + "<", + True, + ), + ( + b"\x01\x04\x00\x00\x00\x00\x00\x05\x00\x00\x00\x15\x07\x5b\xcd\x15\x10\x00\x00\x00\xf4\x01\x00\x00", + "S-1-5-21-123456789-268435456-500", + ">", + True, + ), + ( + b"", + "", + "<", + False, + ), + ], + ids=id_fn, +) +def test_write_sid(readable_sid: str, endian: str, swap_last: bool, binary_sid: bytes) -> None: + if isinstance(binary_sid, io.BytesIO): + binary_sid = binary_sid.getvalue() + assert binary_sid == sid.write_sid(readable_sid, endian, swap_last) + + +@pytest.mark.benchmark +@pytest.mark.parametrize( + ("readable_sid", "swap_last"), + [ + ( + "S-1-5-21-123456789-268435456-500", + False, + ), + ( + "S-1-5-21-123456789-268435456-500", + True, + ), + ], + ids=lambda x: x if isinstance(x, str) else str(x), +) +def test_write_sid_benchmark(benchmark: BenchmarkFixture, readable_sid: str, swap_last: bool) -> None: + benchmark(sid.write_sid, readable_sid, "<", swap_last) From 9a2ce1f329f8424578c2276c4351c040583e3210 Mon Sep 17 00:00:00 2001 From: joost-j <2032793+joost-j@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:16:55 +0100 Subject: [PATCH 2/3] Return empty bytes instead of raising ValueError --- dissect/util/sid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/util/sid.py b/dissect/util/sid.py index b1e2a2b..f5d3d1b 100644 --- a/dissect/util/sid.py +++ b/dissect/util/sid.py @@ -63,7 +63,7 @@ def write_sid(sid_str: str, endian: str = "<", swap_last: bool = False) -> bytes parts = sid_str.split("-") if len(parts) < 3 or parts[0].upper() != "S": - raise ValueError("Invalid SID string") + return b"" revision = int(parts[1]).to_bytes(1, "little") authority = int(parts[2]).to_bytes(6, "big") From a7a47328a39c81a9e9b0a890459f5295abd41c5a Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:52:49 +0100 Subject: [PATCH 3/3] Changes --- dissect/util/sid.py | 15 ++++++----- tests/test_sid.py | 66 ++++----------------------------------------- 2 files changed, 13 insertions(+), 68 deletions(-) diff --git a/dissect/util/sid.py b/dissect/util/sid.py index f5d3d1b..ac4327e 100644 --- a/dissect/util/sid.py +++ b/dissect/util/sid.py @@ -50,24 +50,24 @@ def read_sid(fh: BinaryIO | bytes, endian: str = "<", swap_last: bool = False) - return "-".join(sid_elements) -def write_sid(sid_str: str, endian: str = "<", swap_last: bool = False) -> bytes: - """Write a Windows SID string to its binary representation. +def write_sid(sid: str, endian: str = "<", swap_last: bool = False) -> bytes: + """Write a Windows SID string to bytes. Args: - sid_str: SID in the form "S-Revision-Authority-SubAuth1-...". + sid: SID in the form ``S-Revision-Authority-SubAuth1-...``. endian: Optional endianness for reading the sub authorities. swap_last: Optional flag for swapping the endianess of the _last_ sub authority entry. """ - if not sid_str: + if not sid: return b"" - parts = sid_str.split("-") + parts = sid.split("-") if len(parts) < 3 or parts[0].upper() != "S": - return b"" + raise ValueError("Invalid SID string format: insufficient parts") revision = int(parts[1]).to_bytes(1, "little") authority = int(parts[2]).to_bytes(6, "big") - sub_authorities = [int(x) for x in parts[3:]] if len(parts) > 3 else [] + sub_authorities = [int(x) for x in parts[3:]] header = revision + len(sub_authorities).to_bytes(1, "little") + authority @@ -77,4 +77,5 @@ def write_sid(sid_str: str, endian: str = "<", swap_last: bool = False) -> bytes sub_bytes = bytearray(struct.pack(f"{endian}{len(sub_authorities)}I", *sub_authorities)) if swap_last: sub_bytes[-4:] = sub_bytes[-4:][::-1] + return header + bytes(sub_bytes) diff --git a/tests/test_sid.py b/tests/test_sid.py index 6e105ed..bea0beb 100644 --- a/tests/test_sid.py +++ b/tests/test_sid.py @@ -84,9 +84,13 @@ def id_fn(val: bytes | str) -> str: ], ids=id_fn, ) -def test_read_sid(binary_sid: bytes | BinaryIO, endian: str, swap_last: bool, readable_sid: str) -> None: +def test_read_write_sid(binary_sid: bytes | BinaryIO, endian: str, swap_last: bool, readable_sid: str) -> None: assert readable_sid == sid.read_sid(binary_sid, endian, swap_last) + if isinstance(binary_sid, io.BytesIO): + binary_sid = binary_sid.getvalue() + assert binary_sid == sid.write_sid(readable_sid, endian, swap_last) + @pytest.mark.benchmark @pytest.mark.parametrize( @@ -107,66 +111,6 @@ def test_read_sid_benchmark(benchmark: BenchmarkFixture, binary_sid: bytes, swap benchmark(sid.read_sid, binary_sid, "<", swap_last) -@pytest.mark.parametrize( - ("binary_sid", "readable_sid", "endian", "swap_last"), - [ - ( - b"\x01\x00\x00\x00\x00\x00\x00\x00", - "S-1-0", - "<", - False, - ), - ( - b"\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00", - "S-1-1-0", - "<", - False, - ), - ( - b"\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\x15\xcd\x5b\x07\x00\x00\x00\x10\xf4\x01\x00\x00", - "S-1-5-21-123456789-268435456-500", - "<", - False, - ), - ( - io.BytesIO(b"\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00"), - "S-1-1-0", - "<", - False, - ), - ( - b"\x01\x04\x00\x00\x00\x00\x00\x05\x00\x00\x00\x15\x07\x5b\xcd\x15\x10\x00\x00\x00\x00\x00\x01\xf4", - "S-1-5-21-123456789-268435456-500", - ">", - False, - ), - ( - b"\x01\x04\x00\x00\x00\x00\x00\x05\x15\x00\x00\x00\x15\xcd\x5b\x07\x00\x00\x00\x10\x00\x00\x01\xf4", - "S-1-5-21-123456789-268435456-500", - "<", - True, - ), - ( - b"\x01\x04\x00\x00\x00\x00\x00\x05\x00\x00\x00\x15\x07\x5b\xcd\x15\x10\x00\x00\x00\xf4\x01\x00\x00", - "S-1-5-21-123456789-268435456-500", - ">", - True, - ), - ( - b"", - "", - "<", - False, - ), - ], - ids=id_fn, -) -def test_write_sid(readable_sid: str, endian: str, swap_last: bool, binary_sid: bytes) -> None: - if isinstance(binary_sid, io.BytesIO): - binary_sid = binary_sid.getvalue() - assert binary_sid == sid.write_sid(readable_sid, endian, swap_last) - - @pytest.mark.benchmark @pytest.mark.parametrize( ("readable_sid", "swap_last"),