Skip to main content

rusthound_ce/enums/
sid.rs

1use std::error::Error;
2use log::{trace,error};
3use crate::enums::{secdesc::LdapSid, regex::IS_SID_RE1};
4
5/// Function to check if string is SID
6pub fn is_sid(input: &str) -> Result<bool, Box<dyn Error>> {
7    Ok(IS_SID_RE1.is_match(input))
8}
9
10/// Function to make SID String from ldap_sid struct
11pub fn sid_maker(sid: LdapSid, domain: &str) -> String {
12    trace!("sid_maker before: {:?}",&sid);
13
14    let sub = sid.sub_authority.iter().map(|x| x.to_string()).collect::<Vec<String>>().join("-");
15
16    let result = format!("S-{}-{}-{}", sid.revision, sid.identifier_authority.value[5], sub);
17
18    let final_sid = {
19        if result.len() <= 16 {
20            format!("{}-{}", domain.to_uppercase(), result.to_owned())
21        } else {
22            result
23        }
24    };
25
26    trace!("sid_maker value: {}",final_sid);
27    if final_sid.contains("S-0-0"){
28        error!("SID contains null bytes!\n[INPUT: {:?}]\n[OUTPUT: {}]", &sid, final_sid);
29    }
30
31    final_sid
32}
33
34/// Change SID value to correct format.
35pub fn objectsid_to_vec8(sid: &str) -> Vec<u8>
36{
37    sid.as_bytes().iter().map(|x| *x).collect::<Vec<u8>>()
38}
39
40/// Function to decode objectGUID binary to string value. 
41/// src: <https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/001eec5a-7f8b-4293-9e21-ca349392db40>
42/// Thanks to: <https://github.com/picketlink/picketlink/blob/master/modules/common/src/main/java/org/picketlink/common/util/LDAPUtil.java>
43pub fn _decode_guid(raw_guid: &[u8]) -> String
44{
45    // A byte-based String representation in the form of \[0]\[1]\[2]\[3]\[4]\[5]\[6]\[7]\[8]\[9]\[10]\[11]\[12]\[13]\[14]\[15]
46    // A string representing the decoded value in the form of [3][2][1][0]-[5][4]-[7][6]-[8][9]-[10][11][12][13][14][15].
47    let raw_guid = raw_guid.iter().map(|x| x & 0xFF).collect::<Vec<u8>>();
48    let rev = | x: &[u8] | -> Vec<u8> { x.iter().map(|i| *i).rev().collect::<Vec<u8>>()};
49
50    // Note slice syntax means up to the second number, but not including, so [0..4] is [0, 1, 2, 3] for example.
51    let str_guid = format!(
52        "{}-{}-{}-{}-{}",
53        &hex_push(&raw_guid[0..4]),
54        &hex_push(&rev(&raw_guid[4..6])),
55        &hex_push(&rev(&raw_guid[6..8])),
56        &hex_push(&raw_guid[8..10]),
57        &hex_push(&raw_guid[10..16]),
58    );
59
60    str_guid
61}
62
63/// Function to get a hexadecimal representation from bytes
64/// Thanks to: <https://newbedev.com/how-do-i-convert-a-string-to-hex-in-rust>
65pub fn hex_push(blob: &[u8]) -> String {
66    // For each char in blob, get the capitalised hexadecimal representation (:X) and collect that into a String
67    blob.iter().map(|x| format!("{:X}", x)).collect::<String>()
68}
69
70/// Function to get uuid from bin to string format
71pub fn bin_to_string(raw_guid: &[u8]) -> String
72{
73    // before: e2 49 30 00 aa 00 85 a2 11 d0 0d e6 bf 96 7a ba
74    //         0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15
75    // after: bf 96 7a ba - 0d e6 - 11 d0 - a2 85 - 00 aa 00 30 49 e2
76    //        12 13 14 15   10 11   8  9    7  6    5  4  3  2  1  0 
77
78    let raw_guid = raw_guid.iter().map(|x| x & 0xFF).collect::<Vec<u8>>();
79    let rev = | x: &[u8] | -> Vec<u8> { x.iter().map(|i| *i).collect::<Vec<u8>>()};
80
81    let str_guid = format!(
82        "{}-{}-{}-{}-{}",
83        &hex_push(&raw_guid[12..16]),
84        &hex_push(&raw_guid[10..12]),
85        &hex_push(&raw_guid[8..10]),
86        &hex_push(&rev(&raw_guid[6..8])),
87        &hex_push(&rev(&raw_guid[0..6]))
88    );
89
90    str_guid
91}
92
93/// Function to decode GUID from binary to string format with correct little-endian handling
94pub fn decode_guid_le(raw_guid: &[u8]) -> String {
95    // Correct GUID format with proper endianness
96    let str_guid = format!(
97        "{:02X}{:02X}{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
98        raw_guid[3], raw_guid[2], raw_guid[1], raw_guid[0], // Data1 (little-endian)
99        raw_guid[5], raw_guid[4],                           // Data2 (little-endian)
100        raw_guid[7], raw_guid[6],                           // Data3 (little-endian)
101        raw_guid[8], raw_guid[9],                           // Data4 (big-endian)
102        raw_guid[10], raw_guid[11], raw_guid[12], raw_guid[13], raw_guid[14], raw_guid[15] // Data5 (big-endian)
103    );
104
105    str_guid
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::enums::secdesc::{LdapSid, LdapSidIdentifiedAuthority};
112
113    fn make_sid(sub_authority: Vec<u32>) -> LdapSid {
114        LdapSid {
115            revision: 1,
116            sub_authority_count: sub_authority.len() as u8,
117            identifier_authority: LdapSidIdentifiedAuthority {
118                value: vec![0, 0, 0, 0, 0, 5],
119            },
120            sub_authority,
121        }
122    }
123
124    #[test]
125    fn sid_maker_keeps_long_sid_without_domain_prefix() {
126        let sid = make_sid(vec![21, 123456789, 1234567890, 500]);
127
128        assert_eq!(
129            sid_maker(sid, "domain.local"),
130            "S-1-5-21-123456789-1234567890-500"
131        );
132    }
133
134    #[test]
135    fn sid_maker_prefixes_short_sid_with_domain() {
136        let sid = make_sid(vec![500]);
137
138        assert_eq!(sid_maker(sid, "domain.local"), "DOMAIN.LOCAL-S-1-5-500");
139    }
140
141    #[test]
142    fn decode_guid_le_converts_little_endian_fields() {
143        let raw = [
144            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
145            0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
146        ];
147
148        assert_eq!(
149            decode_guid_le(&raw),
150            "33221100-5544-7766-8899-AABBCCDDEEFF"
151        );
152    }
153
154    #[test]
155    fn is_sid_accepts_valid_sid_and_rejects_invalid_input() {
156        assert!(is_sid("S-1-5-21-123456789-500").unwrap());
157        assert!(!is_sid("S-1-4-21-123456789").unwrap());
158        assert!(!is_sid("not-a-sid").unwrap());
159    }
160
161    #[test]
162    fn hex_push_returns_uppercase_hex_without_padding() {
163        assert_eq!(hex_push(&[0x0F, 0xA0]), "FA0");
164    }
165}