Skip to main content

rusthound_ce/modules/localgroup/
types.rs

1//! Internal types for the local-group module.
2
3/// One BUILTIN alias to read: its RID, display name, and BloodHound edge.
4#[derive(Clone, Copy, Debug)]
5pub struct Alias {
6    pub rid: u32,
7    pub name: &'static str,
8    pub edge: &'static str,
9}
10
11/// The four aliases SharpHound reads, in its own order.
12/// ref: SharpHoundCommon v4.8.0 LocalGroupRids
13pub const ALIASES: &[Alias] = &[
14    Alias { rid: 544, name: "Administrators", edge: "AdminTo" },
15    Alias { rid: 555, name: "Remote Desktop Users", edge: "CanRDP" },
16    Alias { rid: 562, name: "Distributed COM Users", edge: "ExecuteDCOM" },
17    Alias { rid: 580, name: "Remote Management Users", edge: "CanPSRemote" },
18];
19
20/// One alias read on one host.
21pub struct AliasFinding {
22    /// The BloodHound ObjectIdentifier of the group.
23    pub group_sid: String,
24    /// The alias display name, used to build the LocalGroup `Name` field.
25    pub group_name: &'static str,
26    pub members: Vec<String>,
27    pub collected: bool,
28    pub failure: Option<String>,
29}
30
31/// Everything learned about one host, folded into its Computer afterwards.
32pub struct HostFindings {
33    pub computer_sid: String,
34    /// FQDN, used to build "<GROUP>@<HOST>" names on a member host.
35    pub host: String,
36    /// True on a DC, where BloodHound must not get a duplicate group node.
37    pub is_dc: bool,
38    pub aliases: Vec<AliasFinding>,
39    pub errors: Vec<String>,
40}
41
42/// The group's `Name` field, which tells BloodHound whether to (re)create the
43/// LocalGroup node. On a DC the BUILTIN alias is the domain group already in the
44/// graph, so the sentinel suppresses a duplicate; on a member host it is
45/// "<GROUP NAME>@<HOST>", matching SharpHound.
46pub const IGNORED_NAME: &str = "IGNOREME";
47
48pub fn group_display_name(group_name: &str, host: &str, is_dc: bool) -> String {
49    if is_dc {
50        IGNORED_NAME.to_string()
51    } else {
52        format!("{}@{}", group_name, host).to_uppercase()
53    }
54}
55
56/// The group's ObjectIdentifier, in SharpHound's two forms.
57pub fn group_object_id(rid: u32, domain: &str, is_dc: bool, computer_sid: &str) -> String {
58    if is_dc {
59        format!("{}-S-1-5-32-{rid}", domain.to_uppercase())
60    } else {
61        format!("{computer_sid}-{rid}")
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn group_display_name_member_vs_dc() {
71        // Member host: "<GROUP>@<HOST>", uppercased, matching SharpHound.
72        assert_eq!(
73            group_display_name("Remote Desktop Users", "BRAAVOS.ESSOS.LOCAL", false),
74            "REMOTE DESKTOP USERS@BRAAVOS.ESSOS.LOCAL"
75        );
76        // DC: the sentinel that stops BloodHound duplicating the domain group.
77        assert_eq!(
78            group_display_name("Remote Desktop Users", "MEEREEN.ESSOS.LOCAL", true),
79            "IGNOREME"
80        );
81    }
82
83}