mirror of
https://github.com/rescatux/chntpw
synced 2026-06-21 14:07:44 +00:00
1792 lines
47 KiB
C
1792 lines
47 KiB
C
/*
|
|
* libsam.c - SAM database functions, user and group editing
|
|
*
|
|
* Functions to edit SAM database, like adding and removing
|
|
* users to groups, list users and groups
|
|
* list user data and reset passwords
|
|
* low level SID handling functions
|
|
|
|
*
|
|
* 2013-aug: Cleaned up a bit for release, still some debug/strange things left
|
|
* 2013-aug: actually having functions doing listings in library is not good, bu
|
|
* have to do with that for now.
|
|
* 2013-apr-may: Functions for password reset, more group stuff etc
|
|
* 2012-oct: Split off from functions in chntpw.c
|
|
* 2012-jun-oct: Made routines for group handling (add/remove user from group etc)
|
|
*
|
|
* See HISTORY.txt for more detailed info on history.
|
|
*
|
|
*****
|
|
*
|
|
* Copyright (c) 1997-2013 Petter Nordahl-Hagen.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; version 2 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* See file GPL.txt for the full license.
|
|
*
|
|
*****
|
|
*
|
|
* Some information and ideas taken from pwdump by Jeremy Allison.
|
|
* More info from NTCrack by Jonathan Wilkins.
|
|
*
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "ntreg.h"
|
|
#include "sam.h"
|
|
|
|
extern int gverbose; /* Ehm.. must get rid of this some day */
|
|
|
|
/* Strings for account bits fields */
|
|
|
|
char *acb_fields[16] = {
|
|
"Disabled" ,
|
|
"Homedir req." ,
|
|
"Passwd not req." ,
|
|
"Temp. duplicate" ,
|
|
"Normal account" ,
|
|
"NMS account" ,
|
|
"Domain trust act." ,
|
|
"Wks trust act." ,
|
|
"Srv trust act" ,
|
|
"Pwd don't expire" ,
|
|
"Auto lockout" ,
|
|
"(unknown 0x08)" ,
|
|
"(unknown 0x10)" ,
|
|
"(unknown 0x20)" ,
|
|
"(unknown 0x40)" ,
|
|
"(unknown 0x80)" ,
|
|
};
|
|
|
|
/* Number of paths we find group info under, needed by some later routines */
|
|
#define SAM_NUM_GROUPPATHS 2
|
|
|
|
/* Paths for group ID list*/
|
|
|
|
static char *SAM_GRPCPATHS[] = {
|
|
"\\SAM\\Domains\\Builtin\\Aliases",
|
|
"\\SAM\\Domains\\Account\\Aliases",
|
|
"" };
|
|
|
|
/* Paths for C (group data) value under group ID %08X */
|
|
static char *SAM_GRPCPATHID[] = {
|
|
"\\SAM\\Domains\\Builtin\\Aliases\\%08X\\C",
|
|
"\\SAM\\Domains\\Account\\Aliases\\%08X\\C",
|
|
"" };
|
|
|
|
/* Paths for users lists of group memberships, machine SID %s , user RID %08x
|
|
* each key contains one default value (no name) with TYPE indicating number of group IDs it contains
|
|
* value contents is then an array of 32 bit group IDs
|
|
*/
|
|
|
|
static char *SAM_GRPMEMBERSPATH[] = {
|
|
"\\SAM\\Domains\\Builtin\\Aliases\\Members\\%s\\%08X",
|
|
"\\SAM\\Domains\\Account\\Aliases\\Members\\%s\\%08X",
|
|
"" };
|
|
|
|
static char *SAM_GRPSIDPATH[] = {
|
|
"\\SAM\\Domains\\Builtin\\Aliases\\Members\\%s",
|
|
"\\SAM\\Domains\\Account\\Aliases\\Members\\%s",
|
|
"" };
|
|
|
|
|
|
|
|
/* Check if hive is SAM, and if it is, extract some
|
|
* global policy information from it, like lockout counts etc
|
|
* show = 1 means also print some more info
|
|
* Returns the number of allowed logins before lockout (locklimit)
|
|
* or -1 if error (not SAM, key not found etc)
|
|
*/
|
|
|
|
|
|
int sam_get_lockoutinfo(struct hive *hdesc, int show)
|
|
{
|
|
struct accountdb_F *f;
|
|
struct keyval *v;
|
|
|
|
if (hdesc->type == HTYPE_SAM) {
|
|
|
|
/* Get accoundb F value */
|
|
v = get_val2buf(hdesc, NULL, 0, ACCOUNTDB_F_PATH, REG_BINARY, TPF_VK);
|
|
if (!v) {
|
|
fprintf(stderr,"WARNING: Login counts data not found in SAM\n");
|
|
return (-1);
|
|
}
|
|
|
|
f = (struct accountdb_F *)&v->data;
|
|
|
|
if (show) {
|
|
printf("\n* SAM policy limits:\n");
|
|
printf("Failed logins before lockout is: %d\n",f->locklimit);
|
|
printf("Minimum password length : %d\n",f->minpwlen);
|
|
printf("Password history count : %d\n",f->minpwlen);
|
|
}
|
|
|
|
return(f->locklimit);
|
|
|
|
}
|
|
|
|
return(-1); /* Not SAM */
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Try to decode and possibly change account lockout etc
|
|
* This is \SAM\Domains\Account\Users\<RID>\F
|
|
* It's size seems to always be 0x50.
|
|
* Params: RID - user ID, mode - 0 silent, 1 print info, 2 edit.
|
|
* Returns: ACB bits with high bit set if lockout count is >0
|
|
*/
|
|
|
|
short sam_handle_accountbits(struct hive *hdesc, int rid, int mode)
|
|
{
|
|
|
|
struct user_F *f;
|
|
char s[200];
|
|
struct keyval *v;
|
|
unsigned short acb;
|
|
int b;
|
|
int max_sam_lock;
|
|
|
|
if (hdesc->type != HTYPE_SAM) return(0);
|
|
|
|
/* Get users F value */
|
|
snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\F",rid);
|
|
v = get_val2buf(hdesc, NULL, 0, s, REG_BINARY, TPF_VK_EXACT);
|
|
if (!v) {
|
|
printf("Cannot find value <%s>\n",s);
|
|
return(0);
|
|
}
|
|
|
|
if (v->len < 0x48) {
|
|
printf("handle_F: F value is 0x%x bytes, need >= 0x48, unable to check account flags!\n",v->len);
|
|
FREE(v);
|
|
return(0);
|
|
}
|
|
|
|
max_sam_lock = sam_get_lockoutinfo(hdesc, 0);
|
|
|
|
f = (struct user_F *)&v->data;
|
|
acb = f->ACB_bits;
|
|
|
|
if (mode == 1) {
|
|
printf("Account bits: 0x%04x =\n",acb);
|
|
|
|
|
|
for (b=0; b < 15; b++) {
|
|
printf("[%s] %-15.15s | ",
|
|
(acb & (1<<b)) ? "X" : " ", acb_fields[b] );
|
|
if (b%3 == 2) printf("\n");
|
|
}
|
|
|
|
printf("\nFailed login count: %u, while max tries is: %u\n",f->failedcnt,max_sam_lock);
|
|
printf("Total login count: %u\n",f->logins);
|
|
}
|
|
|
|
if (mode == 2) { /* MODE = 2, reset to default sane sets of bits and null failed login counter */
|
|
acb |= ACB_PWNOEXP;
|
|
acb &= ~ACB_DISABLED;
|
|
acb &= ~ACB_AUTOLOCK;
|
|
f->ACB_bits = acb;
|
|
f->failedcnt = 0;
|
|
put_buf2val(hdesc, v, 0, s, REG_BINARY,TPF_VK_EXACT); /* TODO: Check error return */
|
|
printf("Unlocked!\n");
|
|
}
|
|
return (acb | ( (f->failedcnt > 0 && f->failedcnt >= max_sam_lock)<<15 ) | (acb & ACB_AUTOLOCK)<<15 | (acb & ACB_DISABLED)<<15);
|
|
}
|
|
|
|
|
|
/***** SID data handling routines **********/
|
|
|
|
|
|
/* Get machines SID as binary (raw data)
|
|
* str = pointer to buffer, first 20 bytes will be filled in
|
|
* returns true if found, else 0
|
|
*/
|
|
|
|
int sam_get_machine_sid(struct hive *hdesc, char *sidbuf)
|
|
{
|
|
|
|
struct accountdb_V *v;
|
|
struct keyval *kv;
|
|
uint32_t ofs;
|
|
uint32_t len;
|
|
|
|
/* Get accoundb V value */
|
|
kv = get_val2buf(hdesc, NULL, 0, ACCOUNTDB_V_PATH, REG_BINARY, TPF_VK);
|
|
if (!kv) {
|
|
fprintf(stderr,"sam_get_machine_sid: Machine SID not found in SAM\n");
|
|
return(0);
|
|
}
|
|
|
|
// hexdump(&(kv->data), 0, kv->len,1);
|
|
|
|
v = (struct accountdb_V *)&kv->data;
|
|
ofs = v->sid_ofs;
|
|
len = v->sid_len + 4;
|
|
ofs += 0x40;
|
|
|
|
if (len != SID_BIN_LEN) {
|
|
fprintf(stderr,"sam_get_machine_sid: WARNING: SID found, but it has len=%d instead of expected %d bytes\n",len,SID_BIN_LEN);
|
|
}
|
|
|
|
// printf("get_machine_sid: adjusted ofs = %x, len = %x (%d)\n",ofs,len,len);
|
|
|
|
|
|
memcpy(sidbuf, (char *)v+ofs, len);
|
|
|
|
// hexdump(sidbuf, 0, len, 1);
|
|
|
|
return(1);
|
|
}
|
|
|
|
/* Make string out of SID, in S-1-5 authority (NT authority)
|
|
* like S-1-5-21-516312364-151943033-2698651
|
|
* Will allocate return string (which can be of variable lenght)
|
|
* NOTE: caller must free it
|
|
* sidbuf = the SID binary data structure with it's type+counter first
|
|
*
|
|
* returns str:
|
|
* 6 chars athority prefix (S-1-5-)
|
|
* 4 * 10 digits (the 4 32 bit groups)
|
|
* 3 for the - between the groups
|
|
* 1 for null termination
|
|
* 50 chars
|
|
*/
|
|
char *sam_sid_to_string(struct sid_binary *sidbuf)
|
|
{
|
|
|
|
int cnt, i;
|
|
char *str = NULL;
|
|
|
|
// hexdump(sidbuf, 0, 24, 1);
|
|
|
|
|
|
if (sidbuf->revision != 1) {
|
|
fprintf(stderr,"sam_sid_to_string: DEBUG: first byte unexpected: %d\n",sidbuf->revision);
|
|
}
|
|
|
|
cnt = sidbuf->sections;
|
|
|
|
// printf("sid_to_string: DEBUG: sections = %d\n",cnt);
|
|
|
|
str = str_dup("S-");
|
|
str = str_catf(str, "%u-%u", sidbuf->revision, sidbuf->authority);
|
|
|
|
for (i = 0; i < cnt; i++) {
|
|
str = str_catf(str,"-%u",sidbuf->array[i]);
|
|
}
|
|
|
|
// printf("sid_to_string: returning <%s>\n",str);
|
|
|
|
|
|
return(str);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Stuff SID binary list into more easily handled arrays
|
|
* sidbuf = binary list buffer (not changed, may point into value structure)
|
|
* size = number of bytes of raw data
|
|
* returns pointer to array, terminated with NULL pointer.
|
|
* Keeps full binary data from each SID
|
|
* All array space is allocated, call sam_free_sid_array() to free it.
|
|
*/
|
|
|
|
struct sid_array *sam_make_sid_array(struct sid_binary *sidbuf, int size)
|
|
{
|
|
|
|
int num = 0;
|
|
int sidlen;
|
|
struct sid_binary *sb;
|
|
struct sid_array *array;
|
|
|
|
CREATE(array, struct sid_array, 1);
|
|
array[0].len = 0;
|
|
array[0].sidptr = NULL;
|
|
|
|
while (size > 0) {
|
|
|
|
sidlen = sidbuf->sections * 4 + 8;
|
|
|
|
// printf("make_sid_array: sidlen = %d\n",sidlen);
|
|
|
|
ALLOC(sb, 1, sidlen);
|
|
memcpy(sb, sidbuf, sidlen);
|
|
array[num].len = sidlen;
|
|
array[num].sidptr = sb;
|
|
sidbuf = (void *)sidbuf + sidlen;
|
|
size -= sidlen;
|
|
num++;
|
|
|
|
array = realloc(array, (num + 1) * sizeof(struct sid_array));
|
|
array[num].len = 0;
|
|
array[num].sidptr = NULL;
|
|
|
|
}
|
|
|
|
|
|
return(array);
|
|
|
|
}
|
|
|
|
/* Free the sid array (from the function above) */
|
|
|
|
void sam_free_sid_array(struct sid_array *array)
|
|
{
|
|
|
|
int num = 0;
|
|
|
|
while (array[num].sidptr) {
|
|
free(array[num].sidptr);
|
|
num++;
|
|
}
|
|
|
|
free(array);
|
|
}
|
|
|
|
/* Compare two SIDs, and return like strcmp */
|
|
int sam_sid_cmp(struct sid_binary *s1, struct sid_binary *s2)
|
|
{
|
|
int p;
|
|
|
|
if (!s1 && !s2) return(0);
|
|
if (!s1) return(-1);
|
|
if (!s2) return(1);
|
|
|
|
if (s1->sections < s2->sections) return(-1); /* s1 has shorter len, always smaller */
|
|
if (s1->sections > s2->sections) return(1); /* s1 has longer len, always larger */
|
|
/* Run compare since same length */
|
|
for (p = 0; p < s1->sections; p++) {
|
|
if (s1->array[p] < s2->array[p]) return (-1);
|
|
if (s1->array[p] > s2->array[p]) return (1);
|
|
}
|
|
/* At end. Thus equal */
|
|
return(0);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/************** GROUP DATA HANDLING ROUTINES ****************/
|
|
|
|
|
|
/* Get C value of a group ID, searching botg bult-in and user defined
|
|
* hdesc - hive
|
|
* grp - group ID
|
|
* returns pointer to value buffer or NULL if not found
|
|
*/
|
|
|
|
struct keyval *sam_get_grpC(struct hive *hdesc, int grp)
|
|
{
|
|
struct keyval *c = NULL;
|
|
int n = 0;
|
|
char g[200];
|
|
|
|
/* Try built-in groups first (administrators, user, guests etc) */
|
|
while (*SAM_GRPCPATHID[n] && !c) {
|
|
snprintf(g, 180, SAM_GRPCPATHID[n], grp);
|
|
c = get_val2buf(hdesc, NULL, 0, g, 0, TPF_VK_EXACT);
|
|
n++;
|
|
}
|
|
|
|
return(c);
|
|
|
|
}
|
|
|
|
|
|
/* Get list of group members for a group
|
|
* Will get the SID list (as binary) into a buffer that will be allocated
|
|
* according to the neccessary size (based on member count)
|
|
* NOTE: Caller must free the buffer when not needed any more
|
|
* grp = group ID
|
|
* sidarray = pointer to pointer to sid array which will be allocated
|
|
* Returns number of members in the group
|
|
*/
|
|
|
|
int sam_get_grp_members_sid(struct hive *hdesc, int grp, struct sid_array **sarray)
|
|
{
|
|
// char groupname[128];
|
|
|
|
struct sid_array *marray;
|
|
struct keyval *c = NULL;
|
|
struct group_C *cd;
|
|
// int grpnamoffs, grpnamlen;
|
|
int mofs, mlen;
|
|
|
|
c = sam_get_grpC(hdesc, grp);
|
|
if (c) {
|
|
cd = (struct group_C *)&c->data;
|
|
|
|
// grpnamoffs = cd->grpname_ofs + 0x34;
|
|
// grpnamlen = cd->grpname_len;
|
|
|
|
// cheap_uni2ascii((char *)cd + grpnamoffs, groupname, grpnamlen);
|
|
|
|
// printf("get_grp_members_sid: group %x named %s has %d members\n",grp,groupname,cd->grp_members);
|
|
|
|
mofs = cd->members_ofs;
|
|
mlen = cd->members_len;
|
|
|
|
// printf("get_grp_members_sid: mofs = %x, mlen = %x (%d)\n", mofs,mlen,mlen);
|
|
// printf("get_grp_members_sid: ajusted: mofs = %x, mlen = %x (%d)\n", mofs + 0x34 ,mlen,mlen);
|
|
|
|
// hexdump(&c->data, 0, c->len, 1);
|
|
// hexdump(&cd->data[mofs], 0, mlen, 1);
|
|
|
|
marray = sam_make_sid_array((struct sid_binary *)&cd->data[mofs], mlen);
|
|
|
|
*sarray = marray;
|
|
// sam_free_sid_array(marray);
|
|
|
|
free(c);
|
|
|
|
} else {
|
|
printf("Group info for %x not found!\n",grp);
|
|
*sarray = NULL;
|
|
return(0);
|
|
}
|
|
|
|
return(cd->grp_members);
|
|
|
|
}
|
|
|
|
/* Put list of group members back into group C structure
|
|
* grp = group ID
|
|
* sidarray = pointer to sid array
|
|
* Returns true if success
|
|
*/
|
|
|
|
int sam_put_grp_members_sid(struct hive *hdesc, int grp, struct sid_array *sarray)
|
|
{
|
|
char g[200];
|
|
char groupname[128];
|
|
|
|
struct keyval *c = NULL;
|
|
struct group_C *cd;
|
|
int grpnamoffs, grpnamlen;
|
|
int mofs, mlen;
|
|
int sidlen = 0;
|
|
void *sidptr;
|
|
int i, n;
|
|
char *str;
|
|
|
|
/* Try built-in groups first (administrators, user, guests etc) */
|
|
n = 0;
|
|
while (*SAM_GRPCPATHID[n] && !c) {
|
|
snprintf(g, 180, SAM_GRPCPATHID[n], grp);
|
|
c = get_val2buf(hdesc, NULL, 0, g, 0, TPF_VK_EXACT);
|
|
n++;
|
|
}
|
|
|
|
if (c) {
|
|
cd = (struct group_C *)&c->data;
|
|
|
|
grpnamoffs = cd->grpname_ofs + 0x34;
|
|
grpnamlen = cd->grpname_len;
|
|
|
|
cheap_uni2ascii((char *)cd + grpnamoffs, groupname, grpnamlen);
|
|
|
|
if (gverbose) printf("put_grp_members_sid: group %x named %s has %d members\n",grp,groupname,cd->grp_members);
|
|
|
|
mofs = cd->members_ofs;
|
|
mlen = cd->members_len;
|
|
|
|
if (gverbose) printf("put_grp_members_sid: ajusted: mofs = %x, mlen = %x (%d)\n", mofs + 0x34 ,mlen,mlen);
|
|
|
|
if (gverbose) hexdump(&c->data, 0, c->len, 1);
|
|
|
|
/* Get total size of new SID data */
|
|
|
|
for (i = 0; sarray[i].sidptr; i++) sidlen += sarray[i].len;
|
|
|
|
if (gverbose) printf("put_grp_members_sid: new count : %d, new sidlen: %x\n",i,sidlen);
|
|
|
|
/* Resize buffer with C structure */
|
|
c = realloc(c, 4 + mofs + sidlen + 0x34); /* offset of SIDs + sids lenght + pointer list at start */
|
|
c->len = 0x34 + mofs + sidlen;
|
|
|
|
cd = (struct group_C *)&c->data;
|
|
mofs = cd->members_ofs;
|
|
sidptr = &cd->data[mofs];
|
|
|
|
for (i = 0; sarray[i].sidptr; i++) {
|
|
if (gverbose) printf(" copying : %d len %x, at %x\n",i,sarray[i].len, sidptr);
|
|
str = sam_sid_to_string(sarray[i].sidptr);
|
|
if (gverbose) printf(" Member # %d = <%s>\n", i, str);
|
|
FREE(str);
|
|
memcpy(sidptr, sarray[i].sidptr, sarray[i].len);
|
|
sidptr += sarray[i].len;
|
|
}
|
|
|
|
cd->members_len = sidlen; /* Update member count in C struct */
|
|
cd->grp_members = i;
|
|
|
|
if (gverbose) hexdump(&c->data, 0, c->len, 1);
|
|
|
|
if (!put_buf2val(hdesc, c, 0, g, 0, TPF_VK_EXACT)) {
|
|
fprintf(stderr,"put_grp_members_sid: could not write back group info in value %s\n",g);
|
|
free(c);
|
|
return(0);
|
|
}
|
|
|
|
|
|
free(c);
|
|
|
|
} else {
|
|
printf("Group info for %x not found!\n",grp);
|
|
return(0);
|
|
}
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get group IDs a user is member of
|
|
* rid = user ID
|
|
* returns: since value data is just an array of grp ids (4 bytes each),
|
|
* just return the keyval structure (size + data)
|
|
* caller must free() keyval
|
|
*/
|
|
|
|
struct keyval *sam_get_user_grpids(struct hive *hdesc, int rid)
|
|
{
|
|
char s[200];
|
|
struct sid_binary sid;
|
|
char *sidstr;
|
|
|
|
int nk = 0;
|
|
struct keyval *m = NULL;
|
|
struct keyval *result = NULL;
|
|
struct keyval *newresult = NULL;
|
|
int count = 0;
|
|
int size;
|
|
int n;
|
|
|
|
if (!rid || (hdesc->type != HTYPE_SAM)) return(NULL);
|
|
|
|
if (!sam_get_machine_sid(hdesc, (char *)&sid)) {
|
|
fprintf(stderr,"sam_get_user_grpids: Could not find machine SID\n");
|
|
return(0);
|
|
}
|
|
|
|
sidstr = sam_sid_to_string(&sid);
|
|
|
|
/* Get member list for user on this machine */
|
|
|
|
n = 0; /* Look up user RID under computer SID under builtin and account path */
|
|
while (*SAM_GRPMEMBERSPATH[n]) {
|
|
|
|
snprintf(s, 180, SAM_GRPMEMBERSPATH[n], sidstr, rid);
|
|
|
|
if (gverbose) printf("sam_get_user_grpids: member path: %s\n",s);
|
|
|
|
nk = trav_path(hdesc, 0, s, 0);
|
|
|
|
if (nk) { /* Found a key */
|
|
|
|
/* Now, the TYPE field is the number of groups the user is member of */
|
|
/* Don't we just love the inconsistent use of fields!! */
|
|
nk += 4;
|
|
count = get_val_type(hdesc,nk,"@",TPF_VK_EXACT);
|
|
if (count == -1) {
|
|
printf("sam_get_user_grpids: Cannot find default value <%s\\@>\n",s);
|
|
n++;
|
|
continue;
|
|
}
|
|
|
|
// printf("sam_get_user_grpids: User is member of %d groups:\n",count);
|
|
|
|
/* This is the data size */
|
|
size = get_val_len(hdesc,nk,"@",TPF_VK_EXACT);
|
|
|
|
/* It should be 4 bytes for each group */
|
|
// if (gverbose) printf("Data size %d bytes.\n",size);
|
|
if (size != count * 4) {
|
|
printf("sam_get_user_grpids: DEBUG: Size is not 4 * count! May not matter anyway. Continuing..\n");
|
|
}
|
|
|
|
m = get_val2buf(hdesc, NULL, nk, "@", 0, TPF_VK_EXACT);
|
|
if (!m) {
|
|
printf("sam_get_user_grpids: Could not get value data! Giving up.\n");
|
|
FREE(sidstr);
|
|
return(NULL);
|
|
}
|
|
|
|
/* At this point we have a value containing member list from this part of the tree */
|
|
/* Just append this one to the earlier ones */
|
|
|
|
newresult = reg_valcat(result, m);
|
|
FREE(m);
|
|
FREE(result);
|
|
result = newresult;
|
|
|
|
}
|
|
|
|
n++;
|
|
}
|
|
|
|
FREE(sidstr);
|
|
|
|
if (!result) {
|
|
/* This probably means user is not in any group. Seems to be the case
|
|
for a couple of XPs built in support / guest users. So just return */
|
|
if (gverbose) printf("sam_get_user_grpids: Cannot find RID under computer SID <%s>\n",s);
|
|
return(NULL);
|
|
}
|
|
|
|
// printf(" sam_get_user_grpids done\n");
|
|
return(result);
|
|
}
|
|
|
|
/* Put/set group IDs a user is member of
|
|
* rid = user ID
|
|
* val = keyval structure of data, actual value data is a list
|
|
* of ints, one per group
|
|
* returns true if successful setting the value
|
|
*/
|
|
|
|
int sam_put_user_grpids(struct hive *hdesc, int rid, struct keyval *val)
|
|
{
|
|
char s[200];
|
|
char news[200];
|
|
char ks[12];
|
|
struct nk_key *newkey = NULL;
|
|
struct sid_binary sid;
|
|
char *sidstr;
|
|
|
|
int n, grp, pnum;
|
|
int newcount = 0;
|
|
int nk = 0;
|
|
int count = 0;
|
|
struct keyvala *v;
|
|
struct keyvala *new;
|
|
struct keyvala entry;
|
|
|
|
/* Pointers to value lists for each group path in use */
|
|
struct keyval *p[SAM_NUM_GROUPPATHS];
|
|
|
|
if (!rid || (hdesc->type != HTYPE_SAM)) return(0);
|
|
|
|
if (!val) return(0);
|
|
|
|
#if 0
|
|
if (!val->len) {
|
|
printf("sam_put_user_grpids: zero list len\n");
|
|
// return(0);
|
|
}
|
|
#endif
|
|
|
|
v = (struct keyvala *)val;
|
|
|
|
if (!sam_get_machine_sid(hdesc, (char *)&sid)) {
|
|
fprintf(stderr,"sam_put_user_grpids: Could not find machine SID\n");
|
|
return(0);
|
|
}
|
|
sidstr = sam_sid_to_string(&sid);
|
|
|
|
for (n = 0; n < SAM_NUM_GROUPPATHS; n++) {
|
|
ALLOC(p[n], sizeof(struct keyvala), 1);
|
|
p[n]->len = 0;
|
|
p[n]->data = 0;
|
|
|
|
}
|
|
|
|
/* Split value list into relevant stuff for each path */
|
|
for (n = 0; n < val->len >> 2; n++) {
|
|
grp = v->data[n];
|
|
for (pnum = 0; pnum < SAM_NUM_GROUPPATHS; pnum++) {
|
|
snprintf(s, 180, SAM_GRPCPATHID[pnum], grp);
|
|
// printf("sam_put_user_grpids: split path: %s\n",s);
|
|
nk = trav_path(hdesc, 0, s, TPF_VK_EXACT); /* Check if group is in path?? */
|
|
if (nk) { /* Yup, it is here */
|
|
entry.data[0] = grp;
|
|
entry.len = 4;
|
|
// printf("sam_put_user_grpids: path match for grp ID: %x\n", entry.data[1]);
|
|
new = (struct keyvala *)reg_valcat( p[pnum], (struct keyval *)&entry);
|
|
FREE( p[pnum] );
|
|
p[pnum] = (struct keyval *)new;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Now put the lists into the correct place */
|
|
|
|
for (n = 0; n < SAM_NUM_GROUPPATHS; n++) {
|
|
|
|
/* Get member list for user on this machine */
|
|
snprintf(s,180,SAM_GRPMEMBERSPATH[n] ,sidstr, rid);
|
|
// printf("sam_put_user_grpids: putting for path: %s\n",s);
|
|
|
|
newcount = p[n]->len >> 2;
|
|
|
|
// printf("--- list for that path has len: %d\n",p[n]->len);
|
|
for (pnum = 0; pnum < p[n]->len >> 2; pnum++) {
|
|
new = (struct keyvala *)p[n];
|
|
// printf("%d : %x\n", pnum, new->data[pnum]);
|
|
}
|
|
|
|
|
|
/* Find users member list under this path */
|
|
|
|
nk = trav_path(hdesc, 0, s, 0);
|
|
if (!nk) {
|
|
/* User is not in any group in this path, see if we need to create key */
|
|
|
|
if (gverbose) printf("sam_put_user_grpids: Cannot find path <%s>\n",s);
|
|
if (!newcount) continue; /* Nothing to put there anyway, so just try next path */
|
|
|
|
snprintf(news,180,SAM_GRPSIDPATH[n] ,sidstr);
|
|
// snprintf(ks, 180, "%08X", rid);
|
|
|
|
// printf("sam_put_user_grpids: creating key <%s> on path <%s>\n",ks,news);
|
|
|
|
nk = trav_path(hdesc, 0, news, 0);
|
|
|
|
newkey = add_key(hdesc, nk+4, ks);
|
|
if (!newkey) {
|
|
fprintf(stderr,"sam_put_user_grpids: ERROR: creating group list key for RID <%08x> under path <%s>\n",rid,news);
|
|
abort();
|
|
}
|
|
|
|
nk = trav_path(hdesc, 0, s, 0);
|
|
|
|
if (!add_value(hdesc, nk+4, "@", 0)) {
|
|
fprintf(stderr,"sam_put_user_grpids: ERROR: creating group list default value for RID <%08x> under path <%s>\n",rid,news);
|
|
abort();
|
|
}
|
|
}
|
|
|
|
nk += 4;
|
|
|
|
/* Now, the TYPE field is the number of groups the user is member of */
|
|
|
|
count = get_val_type(hdesc, nk,"@", TPF_VK_EXACT);
|
|
if (count == -1) {
|
|
printf("sam_put_user_grpids: Cannot find value <%s\\@>\n",s);
|
|
return(1);
|
|
}
|
|
|
|
if (gverbose) printf("sam_put_user_grpids: User was member of %d groups:\n",count);
|
|
|
|
/* This is the data size */
|
|
/* It should be 4 bytes for each group */
|
|
|
|
|
|
if (gverbose) printf("Data size %d bytes.\n",p[n]->len);
|
|
if (p[n]->len != newcount << 2) {
|
|
printf("set_user_grpids: DEBUG: Size is not 4 * count! May not matter anyway. Continuing..\n");
|
|
}
|
|
|
|
if (gverbose) printf("sam_put_user_grpids: User is NOW member of %d groups:\n",newcount);
|
|
|
|
if (newcount == 0) { /* Seems windows removes the key and default subvalue when user not in any group */
|
|
|
|
// printf("sam_put_user_grpids: removing user reference for path %s\n",s);
|
|
del_value(hdesc, nk, "@", TPF_VK_EXACT);
|
|
nk = trav_path(hdesc, nk, "..", 0);
|
|
snprintf(s,180, "%08X", rid);
|
|
del_key(hdesc, nk + 4, s);
|
|
|
|
} else { /* Stuff back list into default value */
|
|
|
|
set_val_type(hdesc, nk, "@", TPF_VK_EXACT, newcount);
|
|
if (!put_buf2val(hdesc, p[n], nk, "@", 0, TPF_VK_EXACT) ) {
|
|
printf("sam_put_user_grpids: Could not set reg value data!\n");
|
|
return(0);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
} /* for path loop */
|
|
|
|
FREE(sidstr);
|
|
|
|
for (n = 0; n < SAM_NUM_GROUPPATHS; n++) {
|
|
FREE(p[n]);
|
|
}
|
|
|
|
|
|
printf("sam_put_user_grpids: success exit\n");
|
|
return(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/********* GROUP / USER MANIPULATION ROUTINES **************/
|
|
|
|
|
|
/* Add user to a group
|
|
* rid = user RID
|
|
* grp = group ID
|
|
* return true if success
|
|
*/
|
|
|
|
int sam_add_user_to_grp(struct hive *hdesc, int rid, int grp)
|
|
{
|
|
struct keyvala *usrgrplist, *newusrgrplist;
|
|
struct sid_array *sarray, *narray;
|
|
struct sid_binary *usid;
|
|
struct sid_binary msid;
|
|
int members, newmembers;
|
|
char *str;
|
|
int ugcnt;
|
|
int o,n,hit,c;
|
|
unsigned int *og, *ng;
|
|
char s[200];
|
|
|
|
|
|
if (!rid || !grp || (hdesc->type !=HTYPE_SAM) ) return(0);
|
|
|
|
snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\V",rid);
|
|
if (!trav_path(hdesc, 0, s, TPF_VK_EXACT)) {
|
|
fprintf(stderr,"sam_add_user_to_grp: user # %x not found!\n",rid);
|
|
return(0);
|
|
}
|
|
|
|
/* Build user SID (add RID to machine SID) */
|
|
|
|
if (!sam_get_machine_sid(hdesc, (char *)&msid)) {
|
|
fprintf(stderr,"sam_add_user_to_grp: Could not find machine SID\n");
|
|
return(0);
|
|
}
|
|
|
|
/* well, and hope that machine SID is always same size here too */
|
|
ALLOC(usid, sizeof(struct sid_binary) +4, 1);
|
|
|
|
memcpy(usid, &msid, sizeof(struct sid_binary));
|
|
|
|
usid->array[4] = rid; /* Tack RID on at end */
|
|
usid->sections = 5;
|
|
|
|
str = sam_sid_to_string(usid);
|
|
|
|
if (gverbose) printf("add_user_to_grp: user SID is <%s>\n", str);
|
|
|
|
free(str);
|
|
|
|
/* With all of the above functions, it should now just be to get
|
|
* the list of groups the user account has listed under it
|
|
* and the list of users the group has listed under it
|
|
*/
|
|
|
|
usrgrplist = (struct keyvala *)sam_get_user_grpids(hdesc, rid);
|
|
|
|
if (!usrgrplist) {
|
|
printf("sam_add_user_to_grp: user # %x WAS IN NO GROUPS!\n",rid);
|
|
/* So make new blank list for it */
|
|
ALLOC(usrgrplist, sizeof(struct keyvala), 1);
|
|
usrgrplist->len = 0;
|
|
usrgrplist->data[0] = 0;
|
|
}
|
|
|
|
|
|
members = sam_get_grp_members_sid(hdesc, grp, &sarray);
|
|
|
|
if (!sarray) {
|
|
printf("sam_add_user_to_grp: group # %x not found!\n",grp);
|
|
FREE(usrgrplist);
|
|
return(0);
|
|
}
|
|
|
|
|
|
/* Add the group to the users list of groups it is member of */
|
|
|
|
ugcnt = usrgrplist->len >> 2; /* Count of groups already on user */
|
|
|
|
/* Allocate new larger usrgrplist for one more entry */
|
|
|
|
ALLOC(newusrgrplist, usrgrplist->len + 4 + 4, 1);
|
|
bzero(newusrgrplist, usrgrplist->len + 4 + 4); /* for sanity.. */
|
|
newusrgrplist->len = usrgrplist->len + 4;
|
|
|
|
og = (unsigned int *)&usrgrplist->data;
|
|
ng = (unsigned int *)&newusrgrplist->data;
|
|
|
|
if (gverbose) printf("usrgrplist-len = %d\n", usrgrplist->len);
|
|
|
|
|
|
#if 0 /* If list should be sorted, but seems windows does not do that? */
|
|
|
|
/* Copy over users group list, adding in where needed */
|
|
|
|
hit = 0;
|
|
for (o = 0, n = 0; o < ugcnt; o++, n++) {
|
|
printf(":: %d %d : %x\n",o,n,og[o]);
|
|
if (og[o] == grp) { /* Was already in there, so just don't increase size.. */
|
|
newusrgrplist->len = usrgrplist->len;
|
|
hit = 1;
|
|
}
|
|
if (og[o] > grp && !hit) {
|
|
ng[n++] = grp; /* Next is higher, so insert out rid */
|
|
hit = 1;
|
|
printf(" -- insert\n");
|
|
}
|
|
ng[n] = og[o];
|
|
}
|
|
printf("n = %d\n",n);
|
|
if (!hit) ng[n] = grp; /* Insert at end if we run down */
|
|
|
|
#endif
|
|
|
|
/* Copy over users group list, checking if already there */
|
|
|
|
hit = 0;
|
|
for (o = 0; o < ugcnt; o++) {
|
|
if (gverbose) printf(":: %d : %x\n",o,og[o]);
|
|
if (og[o] == grp) { /* Was already in there, so just don't increase size.. */
|
|
newusrgrplist->len = usrgrplist->len;
|
|
hit = 1;
|
|
if (gverbose) printf(" -- match\n");
|
|
}
|
|
ng[o] = og[o];
|
|
}
|
|
if (gverbose) printf(" - end of list at o = %d\n",o);
|
|
|
|
if (!hit) ng[o] = grp; /* Just stuff new group in at end if not already in list */
|
|
|
|
if (gverbose) {
|
|
for (o = 0; o < (newusrgrplist->len >> 2); o++) {
|
|
printf("grp index %d = %08x\n", o, ng[o]);
|
|
}
|
|
}
|
|
/* And then we add the user SID into the groups list of members */
|
|
|
|
if (gverbose) {
|
|
printf("add_user_to_grp: grp memberlist BEFORE:\n");
|
|
|
|
for (o = 0; sarray[o].sidptr; o++) {
|
|
str = sam_sid_to_string(sarray[o].sidptr);
|
|
printf(" Member # %d = <%s>\n", o, str);
|
|
FREE(str);
|
|
}
|
|
}
|
|
|
|
newmembers = members + 1;
|
|
ALLOC(narray, sizeof(struct sid_array) * (newmembers + 2), 1); /* Add one entry size */
|
|
|
|
if (gverbose) printf("members = %d\n", members);
|
|
|
|
hit = 0;
|
|
for (o = 0, n = 0; o <= members; o++, n++) {
|
|
c = sam_sid_cmp(sarray[o].sidptr, usid); /* Compare slot with new SID */
|
|
if (gverbose) printf("sam_sid_cmp returns %d\n",c);
|
|
if (c == 0) {
|
|
newmembers--; /* Already there, don't change anything */
|
|
hit = 1;
|
|
}
|
|
if (!hit && ((c > 0) || !sarray[o].sidptr)) { /* Next is higher, insert new SID */
|
|
if (gverbose) printf(" -- add\n");
|
|
narray[n].len = usid->sections * 4 + 8; /* Hmm */
|
|
narray[n].sidptr = usid;
|
|
n++;
|
|
hit = 1;
|
|
}
|
|
narray[n].len = sarray[o].len;
|
|
narray[n].sidptr = sarray[o].sidptr;
|
|
}
|
|
|
|
if (gverbose) {
|
|
printf("add_user_to_grp: grp memberlist AFTER:\n");
|
|
|
|
|
|
for (o = 0; narray[o].sidptr; o++) {
|
|
str = sam_sid_to_string(narray[o].sidptr);
|
|
printf(" Member # %d = <%s>\n", o, str);
|
|
FREE(str);
|
|
}
|
|
}
|
|
|
|
/* Write new lists back to registry */
|
|
if (!sam_put_user_grpids(hdesc, rid, (struct keyval *)newusrgrplist)) {
|
|
fprintf(stderr, "add_user_to_grp: failed storing users group list\n");
|
|
} else if (!sam_put_grp_members_sid(hdesc, grp, narray)) {
|
|
fprintf(stderr,"add_user_to_grp: failed storing groups user list\n");
|
|
sam_put_user_grpids(hdesc, rid, (struct keyval *)usrgrplist); /* Try to roll back */
|
|
}
|
|
|
|
FREE(usrgrplist);
|
|
FREE(newusrgrplist);
|
|
sam_free_sid_array(narray);
|
|
FREE(sarray); /* Pointers was copied to narray, and freed above, just free the array here */
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
/* Remove user from a group
|
|
* rid = user RID
|
|
* grp = group ID
|
|
* return true if success
|
|
*/
|
|
|
|
int sam_remove_user_from_grp(struct hive *hdesc, int rid, int grp)
|
|
{
|
|
struct keyvala *usrgrplist, *newusrgrplist;
|
|
struct sid_array *sarray, *narray;
|
|
struct sid_binary *usid;
|
|
struct sid_binary msid;
|
|
int members, newmembers;
|
|
char *str;
|
|
int ugcnt;
|
|
int o,n,hit,c;
|
|
unsigned int *og, *ng;
|
|
|
|
|
|
|
|
if (!rid || !grp || (hdesc->type != HTYPE_SAM)) return(0);
|
|
|
|
/* Build user SID (add RID to machine SID) */
|
|
|
|
if (!sam_get_machine_sid(hdesc, (char *)&msid)) {
|
|
fprintf(stderr,"sam_remove_user_from_grp: Could not find machine SID\n");
|
|
return(0);
|
|
}
|
|
|
|
/* well, and hope that machine SID is always same size here too */
|
|
ALLOC(usid, sizeof(struct sid_binary) +4, 1);
|
|
|
|
memcpy(usid, &msid, sizeof(struct sid_binary));
|
|
|
|
usid->array[4] = rid; /* Tack RID on at end */
|
|
usid->sections = 5;
|
|
|
|
if (gverbose) {
|
|
str = sam_sid_to_string(usid);
|
|
printf("remove_user_from_grp: user SID is <%s>\n", str);
|
|
free(str);
|
|
}
|
|
|
|
/* With all of the above functions, it should now just be to get
|
|
* the list of groups the user account has listed under it
|
|
* and the list of users the group has listed under it
|
|
*/
|
|
|
|
usrgrplist = (struct keyvala *)sam_get_user_grpids(hdesc, rid);
|
|
|
|
if (!usrgrplist) {
|
|
printf("remove_user_from_grp: user # %x not found!\n",rid);
|
|
return(0);
|
|
}
|
|
|
|
|
|
members = sam_get_grp_members_sid(hdesc, grp, &sarray);
|
|
|
|
if (!sarray) {
|
|
printf("remove_user_from_grp: group # %x not found!\n",grp);
|
|
FREE(usrgrplist);
|
|
return(0);
|
|
}
|
|
|
|
|
|
/* Add the group to the users list of groups it is member of */
|
|
|
|
ugcnt = usrgrplist->len >> 2; /* Count of groups already on user */
|
|
|
|
/* Allocate same size usrgrplist, since we don't know if we are in there and need to be removed */
|
|
|
|
ALLOC(newusrgrplist, usrgrplist->len + 4, 1);
|
|
bzero(newusrgrplist, usrgrplist->len + 4); /* for sanity.. */
|
|
newusrgrplist->len = usrgrplist->len;
|
|
|
|
og = (unsigned int *)&usrgrplist->data;
|
|
ng = (unsigned int *)&newusrgrplist->data;
|
|
|
|
if (gverbose) printf("usrgrplist-len = %d\n", usrgrplist->len);
|
|
|
|
|
|
/* Copy over users group list, if relevant group found, don't copy it over */
|
|
|
|
hit = 0;
|
|
for (o = 0; o < ugcnt; o++) {
|
|
if (gverbose) printf(":: %d : %x\n",o,og[o]);
|
|
if (og[o] == grp) { /* Group found */
|
|
hit = 1;
|
|
if (gverbose) printf(" -- match\n");
|
|
} else {
|
|
ng[o-hit] = og[o];
|
|
}
|
|
}
|
|
if (gverbose) printf(" - end of list at o = %d\n",o);
|
|
if (hit) {
|
|
newusrgrplist->len -= 4; /* Decrease size if found */
|
|
} else {
|
|
fprintf(stderr, "remove_user_from_grp: NOTE: group not in users list of groups, may mean user not member at all. Safe. Continuing.\n");
|
|
}
|
|
|
|
|
|
if (gverbose) {
|
|
for (o = 0; o < (newusrgrplist->len >> 2); o++) {
|
|
printf("grp index %d = %08x\n", o, ng[o]);
|
|
}
|
|
|
|
/* Remove the user SID from the groups list of members */
|
|
|
|
printf("remove_user_from_grp: grp memberlist BEFORE:\n");
|
|
|
|
for (o = 0; sarray[o].sidptr; o++) {
|
|
str = sam_sid_to_string(sarray[o].sidptr);
|
|
printf(" Member # %d = <%s>\n", o, str);
|
|
FREE(str);
|
|
}
|
|
}
|
|
|
|
newmembers = members;
|
|
ALLOC(narray, sizeof(struct sid_array) * (newmembers + 2), 1);
|
|
|
|
if (gverbose) printf("members = %d\n", members);
|
|
|
|
|
|
hit = 0;
|
|
for (o = 0, n = 0; o <= members; o++, n++) {
|
|
c = sam_sid_cmp(sarray[o].sidptr, usid); /* Compare slot with new SID */
|
|
if (gverbose) printf("sid_cmp returns %d\n",c);
|
|
if (c == 0) {
|
|
newmembers--; /* Found, skip copy and decrease list size */
|
|
hit = 1;
|
|
n--;
|
|
} else {
|
|
narray[n].len = sarray[o].len; /* Copy entry */
|
|
narray[n].sidptr = sarray[o].sidptr;
|
|
}
|
|
}
|
|
if (!hit) fprintf(stderr, "remove_user_from_grp: NOTE: user not in groups list of users, may mean user was not member at all. Does not matter, continuing.\n");
|
|
|
|
if (gverbose) {
|
|
printf("remove_user_from_grp: grp memberlist AFTER:\n");
|
|
for (o = 0; narray[o].sidptr; o++) {
|
|
str = sam_sid_to_string(narray[o].sidptr);
|
|
printf(" Member # %d = <%s>\n", o, str);
|
|
FREE(str);
|
|
}
|
|
}
|
|
|
|
/* Write new lists back to registry */
|
|
if (!sam_put_user_grpids(hdesc, rid, (struct keyval *)newusrgrplist)) {
|
|
fprintf(stderr, "remove_user_from_grp: failed storing users group list\n");
|
|
} else if (!sam_put_grp_members_sid(hdesc, grp, narray)) {
|
|
fprintf(stderr,"remvoe_user_from_grp: failed storing groups user list\n");
|
|
sam_put_user_grpids(hdesc, rid, (struct keyval *)usrgrplist); /* Try to roll back */
|
|
}
|
|
|
|
FREE(usrgrplist);
|
|
FREE(newusrgrplist);
|
|
sam_free_sid_array(narray);
|
|
FREE(sarray); /* Pointers was copied to narray, and freed above, just free the array here */
|
|
|
|
return(1);
|
|
|
|
}
|
|
|
|
|
|
/* TODO: So.. having listing functions in library.. should better be handled by tools.. */
|
|
|
|
/* List users membership or check if admin (is in admin group)
|
|
* rid - users rid
|
|
* check - if 1 just check if admin, do not list
|
|
* returns true if user is admin
|
|
*/
|
|
|
|
int sam_list_user_groups(struct hive *hdesc, int rid, int check)
|
|
{
|
|
char groupname[128];
|
|
struct keyval *m = NULL, *c = NULL;
|
|
struct group_C *cd;
|
|
unsigned int *grps;
|
|
int count = 0, isadmin = 0;
|
|
int i, grp, grpnamoffs, grpnamlen;
|
|
|
|
if (!rid || (hdesc->type != HTYPE_SAM) ) return(0);
|
|
|
|
m = sam_get_user_grpids(hdesc, rid);
|
|
|
|
if (!m) return(0);
|
|
|
|
grps = (unsigned int *)&m->data;
|
|
count = m->len >> 2;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
grp = grps[i];
|
|
if (!check) printf("%08x ",grp);
|
|
|
|
if (grp == 0x220) isadmin = 1;
|
|
|
|
if (!check) {
|
|
c = sam_get_grpC(hdesc, grp);
|
|
if (c) {
|
|
cd = (struct group_C *)&c->data;
|
|
grpnamoffs = cd->grpname_ofs + 0x34;
|
|
grpnamlen = cd->grpname_len;
|
|
|
|
cheap_uni2ascii((char *)cd + grpnamoffs, groupname, grpnamlen);
|
|
|
|
printf("= %s (which has %d members)\n",groupname,cd->grp_members);
|
|
|
|
// get_grp_members_sid(grp, &sidbuf);
|
|
|
|
} else {
|
|
printf("Group info for %x not found!\n",grp);
|
|
}
|
|
}
|
|
}
|
|
|
|
free(m);
|
|
|
|
return(isadmin);
|
|
}
|
|
|
|
|
|
|
|
|
|
/* List users in SAM file
|
|
* readable - 1 = list in human readable form, 0 = colon-separated, 2 = quiet, no ouput (find admin)
|
|
* return logic:
|
|
* If no users / error: 0
|
|
* If only 0x1f4 (built-in adminsitrator) is admin (or no one at all in admin group), return 0x1f4
|
|
* Else return lowest numbered user that is in admin group
|
|
*
|
|
* Fields ouput in parsable listing (all numbers in hex)
|
|
* rid:username:isadmin:acb:hashlen
|
|
* rid = User RID
|
|
* isadmin = (boolean flag) 1 user is admin, 0 is not
|
|
* acb = ACB account bits
|
|
* hashlen = lenght of password hash, 14 is normal if has passwd, 4 if blank
|
|
*/
|
|
|
|
char SAMdaunPATH[] = "\\SAM\\Domains\\Account\\Users\\Names\\";
|
|
|
|
int sam_list_users(struct hive *hdesc, int readable)
|
|
{
|
|
char s[200];
|
|
struct keyval *v;
|
|
int nkofs /* ,vkofs */ ;
|
|
int rid;
|
|
int count = 0, countri = 0;
|
|
int ntpw_len;
|
|
|
|
int admrid = 0x1f4;
|
|
int isadm;
|
|
|
|
unsigned short acb;
|
|
|
|
struct user_V *vpwd;
|
|
struct ex_data ex;
|
|
|
|
if (hdesc->type != HTYPE_SAM) return(0);
|
|
|
|
nkofs = trav_path(hdesc, 0, SAMdaunPATH, 0);
|
|
if (!nkofs) {
|
|
printf("sam_list_users: Cannot find usernames in registry! (is this a SAM-hive?)\n");
|
|
return(0);
|
|
}
|
|
|
|
if (readable == 1) printf("| RID -|---------- Username ------------| Admin? |- Lock? --|\n");
|
|
|
|
while ((ex_next_n(hdesc, nkofs+4, &count, &countri, &ex) > 0)) {
|
|
|
|
/* Extract the value out of the username-key, value is RID */
|
|
snprintf(s,180,"%s%s\\@",SAMdaunPATH, ex.name);
|
|
rid = get_dword(hdesc, 0, s, TPF_VK_EXACT|TPF_VK_SHORT);
|
|
|
|
/* Now that we have the RID, build the path to, and get the V-value */
|
|
snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\V",rid);
|
|
v = get_val2buf(hdesc, NULL, 0, s, REG_BINARY, TPF_VK_EXACT);
|
|
if (!v) {
|
|
printf("Cannot find value <%s>\n",s);
|
|
return(1);
|
|
}
|
|
|
|
if (v->len < 0xcc) {
|
|
printf("sam_list_users: Value <%s> is too short (only %d bytes) to be a SAM user V-struct!\n",
|
|
s, v->len);
|
|
} else {
|
|
|
|
vpwd = (struct user_V *)&(v->data);
|
|
ntpw_len = vpwd->ntpw_len;
|
|
|
|
acb = sam_handle_accountbits(hdesc, rid, 0);
|
|
isadm = sam_list_user_groups(hdesc, rid, 1);
|
|
|
|
if (isadm && rid != 0x1f4) { /* Found an non-built-in administrator */
|
|
if (admrid == 0x1f4) admrid = rid; /* Prefer anything over built-in one */
|
|
if (rid < admrid) admrid = rid;
|
|
}
|
|
|
|
if (readable == 1) {
|
|
printf("| %04x | %-30.30s | %-6s | %-8s |\n",
|
|
rid, ex.name, ( isadm ? "ADMIN" : "") , ( acb & 0x8000 ? "dis/lock" : (ntpw_len < 16) ? "*BLANK*" : "") );
|
|
} else if (readable == 0) {
|
|
printf("%04x:%s:%d:%x:%x\n",
|
|
rid, ex.name, isadm , acb, ntpw_len );
|
|
}
|
|
|
|
|
|
// change_pw( (char *)&v->data , rid, v->len, (*automode == 'l') ? 2 : 1);
|
|
|
|
}
|
|
FREE(v);
|
|
FREE(ex.name);
|
|
}
|
|
return(admrid);
|
|
}
|
|
|
|
|
|
|
|
/* Get username when we have a RID
|
|
* hdesc - hive
|
|
* rid - just that.. :)
|
|
* returns allocated string with username, caller must free it
|
|
* or NULL if RID not found in local databse
|
|
*/
|
|
|
|
char *sam_get_username(struct hive *hdesc, int rid)
|
|
{
|
|
char s[200];
|
|
char *username = NULL;
|
|
int username_offset,username_len;
|
|
struct user_V *v;
|
|
struct keyval *value;
|
|
int vlen;
|
|
char *vp;
|
|
|
|
snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\V",rid);
|
|
value = get_val2buf(hdesc, NULL, 0, s, REG_BINARY, TPF_VK_EXACT);
|
|
if (!value) {
|
|
printf(" sam_get_username: ERROR: User with RID 0x%x not found, path <%s>\n",rid,s);
|
|
return(NULL);
|
|
}
|
|
|
|
vlen = value->len;
|
|
if (vlen < 0xcc) {
|
|
printf(" sam_get_username: Value <%s> is too short (only %d bytes) to be a SAM user V-struct!\n",
|
|
s, vlen);
|
|
FREE(value);
|
|
return(NULL);
|
|
}
|
|
|
|
v = (struct user_V *)&value->data;
|
|
vp = (char *)&value->data;
|
|
|
|
username_offset = v->username_ofs;
|
|
username_len = v->username_len;
|
|
|
|
if(username_len <= 0 || username_len > vlen ||
|
|
username_offset <= 0 || username_offset >= vlen)
|
|
{
|
|
printf(" sam_get_username: Not a legal V struct? (negative struct lengths)\n");
|
|
FREE(value);
|
|
return(0);
|
|
}
|
|
|
|
/* Offsets in top of struct is relative to end of pointers, adjust */
|
|
username_offset += 0xCC;
|
|
|
|
ALLOC(username, 2, (username_len >> 1) + 4);
|
|
*username = 0;
|
|
cheap_uni2ascii(vp + username_offset,username,username_len);
|
|
|
|
if (gverbose) {
|
|
printf("RID : %04d [%04x]\n",rid,rid);
|
|
printf("Username: %s\n",username);
|
|
}
|
|
|
|
FREE(value);
|
|
return(username);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get username from SID:
|
|
* Local database if matching machine SID
|
|
* Well known SIDs if in list
|
|
* Else probably domain SID, don't know how to find more info
|
|
*
|
|
* sid = sid to extract RID from
|
|
*
|
|
* returns a string (which must be free()d) or NULL if not able to resolve
|
|
*/
|
|
|
|
char *sam_get_username_from_sid(struct hive *hdesc, struct sid_binary *sid)
|
|
{
|
|
int rid;
|
|
char *str;
|
|
int i;
|
|
struct sid_binary msid;
|
|
|
|
struct known_sidentry {
|
|
int val;
|
|
char *name;
|
|
};
|
|
|
|
const struct known_sidentry ntauthority_table[] = {
|
|
{ 4, "INTERACTIVE" },
|
|
{ 11, "Authenticated Users" },
|
|
{ 17, "IUSR" },
|
|
{ 20, "NETWORK SERVICE" },
|
|
{ 0, "" }
|
|
};
|
|
|
|
|
|
if (!sid) return(NULL);
|
|
if(sid->sections < 1) return(NULL);
|
|
|
|
rid = sid->array[sid->sections-1];
|
|
|
|
if (sid->sections != 5) {
|
|
// fprintf(stderr," WARNING: sam_get_rid_from_sid: Strange size SID, sections = %d, not 5, got rid = %d\n",sid->sections, rid);
|
|
|
|
if (sid->sections == 1) {
|
|
if (sid->authority == 5) { /* We only handle S-1-5 (NTAUTHORITY) known names yet */
|
|
str = str_dup("NT AUTHORITY\\");
|
|
for (i = 0; ntauthority_table[i].val; i++) {
|
|
if (rid == ntauthority_table[i].val) {
|
|
str = str_cat(str, ntauthority_table[i].name);
|
|
return(str);
|
|
}
|
|
|
|
}
|
|
|
|
} else { /* Not NT AUTHORITY */
|
|
return(sam_sid_to_string(sid));
|
|
}
|
|
} /* sections */
|
|
return(sam_sid_to_string(sid));
|
|
}
|
|
|
|
|
|
if (sam_get_machine_sid(hdesc, (char *)&msid)) {
|
|
|
|
sid->sections--; /* Don't compare RID part */
|
|
if (!sam_sid_cmp(sid, &msid)) {
|
|
sid->sections++;
|
|
return(sam_get_username(hdesc, rid)); /* Match, find and return local username */
|
|
} else {
|
|
sid->sections++;
|
|
return(sam_sid_to_string(sid)); /* No match with local machine SID, so just return SID string */
|
|
}
|
|
|
|
}
|
|
|
|
/* If we get here we don't have a machine SID, so, well, try to get a local name anyway */
|
|
return(sam_get_username(hdesc, rid));
|
|
|
|
}
|
|
|
|
|
|
/* List groups in SAM
|
|
* hdesc - the hive
|
|
* listmembers - true = list groups members also, else just group id/name etc
|
|
* human = human readable form (true) or parsable (false)
|
|
*
|
|
* Format of group list:
|
|
* grpid:grpname:membercount
|
|
* Format of membership list:
|
|
* grpid:grpname:index:rid:username:usersid
|
|
* where index is just the ordinal number in the groups membership list (starts at 0)
|
|
* grpid and rid is in hex
|
|
*/
|
|
|
|
void sam_list_groups(struct hive *hdesc, int listmembers, int human) {
|
|
|
|
struct ex_data ex;
|
|
struct sid_array *sids = NULL;
|
|
int nkofs;
|
|
unsigned int grp;
|
|
int count,countri;
|
|
struct keyval *c = NULL;
|
|
struct group_C *cd;
|
|
int grpnamoffs, grpnamlen, i;
|
|
char groupname[200];
|
|
char *str;
|
|
char *username;
|
|
int pnum = 0;
|
|
|
|
if (hdesc->type != HTYPE_SAM) return;
|
|
|
|
while (*SAM_GRPCPATHS[pnum]) {
|
|
|
|
// printf(" -- grp C list path: %s\n",SAM_GRPCPATHS[pnum]);
|
|
|
|
nkofs = trav_path(hdesc, 0, SAM_GRPCPATHS[pnum], 0);
|
|
if (!nkofs) {
|
|
printf(" list_groups: Cannot find group list in registry! (is this a SAM-hive?)\n");
|
|
return;
|
|
}
|
|
|
|
/* Pick up all subkeys here, they are local groups */
|
|
count = 0;
|
|
countri = 0;
|
|
while ((ex_next_n(hdesc, nkofs+4, &count, &countri, &ex) > 0)) {
|
|
|
|
// printf("Group ID %s\n",ex.name);
|
|
sscanf(ex.name,"%x",&grp);
|
|
|
|
/* Groups keys have a C value, get it and pick up the name etc */
|
|
/* Some other keys also exists (Members, Names at least), but we skip them */
|
|
|
|
c = get_val2buf(hdesc, NULL, ex.nkoffs+4, "C", 0, TPF_VK_EXACT);
|
|
if (c) {
|
|
cd = (struct group_C *)&c->data;
|
|
grpnamoffs = cd->grpname_ofs + 0x34;
|
|
grpnamlen = cd->grpname_len;
|
|
|
|
cheap_uni2ascii((char *)cd + grpnamoffs, groupname, grpnamlen);
|
|
|
|
if (human) printf("=== Group #%4x : %s\n",grp,groupname);
|
|
else if (!listmembers) printf("%x:%s:%d\n",grp,groupname,cd->grp_members);
|
|
|
|
if (listmembers) {
|
|
sam_get_grp_members_sid(hdesc, grp, &sids);
|
|
|
|
for (i = 0; sids[i].sidptr; i++) {
|
|
str = sam_sid_to_string(sids[i].sidptr);
|
|
username = sam_get_username_from_sid(hdesc, sids[i].sidptr);
|
|
if (human) printf(" %3d | %04x | %-31s | <%s>\n", i, sids[i].sidptr->array[sids[i].sidptr->sections-1], username, str);
|
|
else printf("%x:%s:%d:%x:%s:%s\n", grp, groupname, i, sids[i].sidptr->array[sids[i].sidptr->sections-1], username, str);
|
|
|
|
FREE(username);
|
|
FREE(str);
|
|
|
|
}
|
|
sam_free_sid_array(sids);
|
|
}
|
|
} /* if c */
|
|
|
|
}
|
|
|
|
pnum++;
|
|
} /* path loop */
|
|
}
|
|
|
|
/* Get groupname when we have a group ID
|
|
* hdesc - hive
|
|
* grpid - just that.. :)
|
|
* returns allocated string with username, caller must free it
|
|
* or NULL if RID not found in local databse
|
|
*/
|
|
|
|
char *sam_get_groupname(struct hive *hdesc, int grpid)
|
|
{
|
|
|
|
struct keyval *value = NULL;
|
|
struct group_C *cd;
|
|
int grpnamoffs;
|
|
int grpnamlen;
|
|
char *groupname = NULL;
|
|
|
|
|
|
value = sam_get_grpC(hdesc, grpid);
|
|
if (!value) {
|
|
printf(" sam_get_groupname: ERROR: Group ID 0x%x not found\n",grpid);
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
/* Offsets in top of struct is relative to end of pointers, adjust */
|
|
|
|
cd = (struct group_C *)&value->data;
|
|
grpnamoffs = cd->grpname_ofs + 0x34;
|
|
grpnamlen = cd->grpname_len;
|
|
|
|
ALLOC(groupname, 2, (grpnamlen >> 1) + 4);
|
|
*groupname = 0;
|
|
cheap_uni2ascii((char *)cd + grpnamoffs, groupname, grpnamlen);
|
|
|
|
// printf("==== Group #%4x : %s\n",grpid,groupname);
|
|
|
|
FREE(value);
|
|
return(groupname);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Reset users password
|
|
* hdesc - the HIVE :)
|
|
* rid - the users RID
|
|
*
|
|
* Returns: 0 = OK, 1 = error (for use in exit())
|
|
*/
|
|
int sam_reset_pw(struct hive *hdesc, int rid)
|
|
{
|
|
|
|
char *vp;
|
|
static char username[128],fullname[128];
|
|
int username_offset,username_len;
|
|
int fullname_offset,fullname_len;
|
|
int ntpw_len,lmpw_len,ntpw_offs,lmpw_offs;
|
|
int vlen;
|
|
struct user_V *v;
|
|
struct keyval *value;
|
|
char s[200];
|
|
|
|
if (!hdesc || !rid) return(1);
|
|
|
|
/* Now that we have the RID, build the path to, and get the V-value */
|
|
snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\V",rid);
|
|
value = get_val2buf(hdesc, NULL, 0, s, REG_BINARY, TPF_VK_EXACT);
|
|
if (!value) {
|
|
printf(" sam_reset_pw: ERROR: User with RID 0x%x not found, path <%s>\n",rid,s);
|
|
return(1);
|
|
}
|
|
|
|
vlen = value->len;
|
|
if (vlen < 0xcc) {
|
|
printf(" sam_reset_pw: Value <%s> is too short (only %d bytes) to be a SAM user V-struct!\n",
|
|
s, vlen);
|
|
return(1);
|
|
}
|
|
|
|
v = (struct user_V *)&value->data;
|
|
vp = (char *)&value->data;
|
|
|
|
username_offset = v->username_ofs;
|
|
username_len = v->username_len;
|
|
fullname_offset = v->fullname_ofs;
|
|
fullname_len = v->fullname_len;
|
|
lmpw_offs = v->lmpw_ofs;
|
|
lmpw_len = v->lmpw_len;
|
|
ntpw_offs = v->ntpw_ofs;
|
|
ntpw_len = v->ntpw_len;
|
|
|
|
if (gverbose) {
|
|
printf(" lmpw_offs: 0x%x, lmpw_len: %d (0x%x)\n",lmpw_offs,lmpw_len,lmpw_len);
|
|
printf(" ntpw_offs: 0x%x, ntpw_len: %d (0x%x)\n",ntpw_offs,ntpw_len,ntpw_len);
|
|
}
|
|
|
|
*username = 0;
|
|
*fullname = 0;
|
|
|
|
if(username_len <= 0 || username_len > vlen ||
|
|
username_offset <= 0 || username_offset >= vlen ||
|
|
fullname_len < 0 || fullname_len > vlen ||
|
|
lmpw_offs < 0 || lmpw_offs >= vlen)
|
|
{
|
|
printf(" sam_reset_pw: Not a legal V struct? (negative struct lengths)\n");
|
|
FREE(value);
|
|
return(0);
|
|
}
|
|
|
|
/* Offsets in top of struct is relative to end of pointers, adjust */
|
|
username_offset += 0xCC;
|
|
fullname_offset += 0xCC;
|
|
ntpw_offs += 0xCC;
|
|
lmpw_offs += 0xCC;
|
|
|
|
cheap_uni2ascii(vp + username_offset,username,username_len);
|
|
cheap_uni2ascii(vp + fullname_offset,fullname,fullname_len);
|
|
|
|
if (gverbose) {
|
|
printf("RID : %04d [%04x]\n",rid,rid);
|
|
printf("Username: %s\n",username);
|
|
printf("fullname: %s\n",fullname);
|
|
}
|
|
|
|
/* Setting hash lengths to zero seems to make NT think it is blank
|
|
* However, we probably leak about 40 bytes since I am to lazy to adjust the rest
|
|
* of the V structure.
|
|
*/
|
|
v->ntpw_len = 0;
|
|
v->lmpw_len = 0;
|
|
|
|
if (!(put_buf2val(hdesc, value, 0, s, REG_BINARY, TPF_VK_EXACT))) {
|
|
printf(" reset_pw: Failed to write updated <%s> to registry! Password change not completed!\n",s);
|
|
FREE(value);
|
|
return(1);
|
|
}
|
|
|
|
if (gverbose) printf(" reset_pw: Password cleared for user %s\n",username);
|
|
FREE(value);
|
|
return(0);
|
|
|
|
}
|
|
|
|
|
|
/* Reset password of ALL admin users
|
|
* hdesc - hive
|
|
* list - if true, list some info about users processed
|
|
*/
|
|
|
|
int sam_reset_all_pw(struct hive *hdesc, int list)
|
|
{
|
|
char s[200];
|
|
struct keyval *v;
|
|
int nkofs;
|
|
int rid;
|
|
int isadm;
|
|
int count = 0;
|
|
int countri = 0;
|
|
int fail = 0;
|
|
|
|
struct ex_data ex;
|
|
|
|
if (hdesc->type != HTYPE_SAM) return(0);
|
|
|
|
nkofs = trav_path(hdesc, 0, SAMdaunPATH, 0);
|
|
if (!nkofs) {
|
|
printf("sam_reset_all_pw: Cannot find usernames in registry! (is this a SAM-hive?)\n");
|
|
return(1);
|
|
}
|
|
|
|
while ((ex_next_n(hdesc, nkofs+4, &count, &countri, &ex) > 0)) {
|
|
|
|
/* Extract the value out of the username-key, value is RID */
|
|
snprintf(s,180,"%s%s\\@",SAMdaunPATH, ex.name);
|
|
rid = get_dword(hdesc, 0, s, TPF_VK_EXACT|TPF_VK_SHORT);
|
|
|
|
/* Now that we have the RID, build the path to, and get the V-value */
|
|
snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\V",rid);
|
|
v = get_val2buf(hdesc, NULL, 0, s, REG_BINARY, TPF_VK_EXACT);
|
|
if (!v) {
|
|
printf("sam_reset_all_pw: Cannot find value <%s>\n",s);
|
|
return(1);
|
|
}
|
|
|
|
if (v->len < 0xcc) {
|
|
printf("sam_reset_all_pw: Value <%s> is too short (only %d bytes) to be a SAM user V-struct!\n",
|
|
s, v->len);
|
|
} else {
|
|
|
|
isadm = sam_list_user_groups(hdesc, rid, 1);
|
|
|
|
if (isadm) {
|
|
if (list) printf("Reset user :%04x:%s\n", rid, ex.name );
|
|
fail |= sam_reset_pw(hdesc, rid);
|
|
}
|
|
|
|
|
|
}
|
|
FREE(v);
|
|
FREE(ex.name);
|
|
}
|
|
return(fail);
|
|
}
|