From 5e3e6b44069964aa5d3310fcdd1b11296d409fc4 Mon Sep 17 00:00:00 2001 From: Erik Schamper <1254028+Schamper@users.noreply.github.com> Date: Mon, 7 Oct 2024 12:59:51 +0200 Subject: [PATCH] Add option to swap last sub authority endianness to SID reading (#54) --- dissect/util/sid.py | 16 ++++++++--- tests/test_sid.py | 65 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/dissect/util/sid.py b/dissect/util/sid.py index 9fff04d..4587d2d 100644 --- a/dissect/util/sid.py +++ b/dissect/util/sid.py @@ -3,7 +3,7 @@ from typing import BinaryIO, Union -def read_sid(fh: Union[BinaryIO, bytes], endian: str = "<") -> str: +def read_sid(fh: Union[BinaryIO, bytes], endian: str = "<", swap_last: bool = False) -> str: """Read a Windows SID from bytes. Normally we'd do this with cstruct, but do it with just struct to keep dissect.util dependency-free. @@ -21,18 +21,26 @@ def read_sid(fh: Union[BinaryIO, bytes], endian: str = "<") -> str: Args: fh: A file-like object or bytes object to read the SID from. endian: Optional endianness for reading the sub authorities. + swap_list: Optional flag for swapping the endianess of the _last_ sub authority entry. """ if isinstance(fh, bytes): fh = io.BytesIO(fh) - revision, sub_authority_count, authority = struct.unpack("BB6s", fh.read(8)) + buf = fh.read(8) + revision = buf[0] + sub_authority_count = buf[1] + authority = int.from_bytes(buf[2:], "big") - sub_authorities = struct.unpack(f"{endian}{sub_authority_count}I", fh.read(sub_authority_count * 4)) + sub_authority_buf = bytearray(fh.read(sub_authority_count * 4)) + if sub_authority_count and swap_last: + sub_authority_buf[-4:] = sub_authority_buf[-4:][::-1] + + sub_authorities = struct.unpack(f"{endian}{sub_authority_count}I", sub_authority_buf) sid_elements = [ "S", f"{revision}", - f"{authority[-1]}", + f"{authority}", ] sid_elements.extend(map(str, sub_authorities)) readable_sid = "-".join(sid_elements) diff --git a/tests/test_sid.py b/tests/test_sid.py index c11e429..428dffd 100644 --- a/tests/test_sid.py +++ b/tests/test_sid.py @@ -1,19 +1,11 @@ +from __future__ import annotations + import io import pytest from dissect.util import sid -testdata = [ - (b"\x01\x00\x00\x00\x00\x00\x00\x00", "S-1-0"), - (b"\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00", "S-1-1-0"), - ( - 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", - ), - (io.BytesIO(b"\x01\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00"), "S-1-1-0"), -] - def id_fn(val): if isinstance(val, (str,)): @@ -22,6 +14,53 @@ def id_fn(val): return "" -@pytest.mark.parametrize("binary_sid, readable_sid", testdata, ids=id_fn) -def test_read_sid(binary_sid, readable_sid): - assert readable_sid == sid.read_sid(binary_sid) +@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, + ), + ], + ids=id_fn, +) +def test_read_sid(binary_sid: bytes | io.BinaryIO, endian: str, swap_last: bool, readable_sid: str) -> None: + assert readable_sid == sid.read_sid(binary_sid, endian, swap_last)