diff --git a/HISTORY.txt b/HISTORY.txt index 39a50c9..b47ec34 100644 --- a/HISTORY.txt +++ b/HISTORY.txt @@ -1,7 +1,7 @@ The Offline NT Password Editor -(c) 1997-2011 Petter Nordahl-Hagen +(c) 1997-2014 Petter Nordahl-Hagen See README for general info, copyright & credits. See INSTALL for compile/installation instructions. @@ -9,6 +9,46 @@ See INSTALL for compile/installation instructions. Revision history (tools only, see website for info on bootdisk revisions) +* v 1.00 140201 (beta releases to some people dated 1308xx) + +samlib: +new library, contains functions for user and group +manipulation and low level SID functions. Main features: +- add user to local group +- remove user from local group +- reset user password +- list users and groups, with memberships etc +- SID handling routines (binary to/from string etc) required by + group/user edit functions +- updates both group and user correctly, thus finally user "promotion" works! + +regedit library: +- Enter buil-in buffer debugger only if in trace mode, else return error or abort() +- Fixed critical bug in del_value which could + thrash the hive when removing value in bottom of key. +- And a pointer not reinitialized when buffer reallocated in some cases, fixed. + Thanks to Jacky To for reporting those two. +- Some minor adjustments for compiler. A few more utility functions. + +sampasswd: +new command line utility +- Can be used to reset users passwords from scripts (non interactive) +- Also a few list options, also can supply parsable output + +samusrgrp: +new command line utility +- edit group memberships (non interactive) +- some list functions, parsable + +chntpw: +- Now dedicated to interactive edits +- Now supports full local group membership edits, finally working promotion! +- A lot of users complained about user select being difficult to exit + (not understanding to use ! to quit), so after user edit, return + is to main menu or program exit (if not -i option) +- User edit (reset, promote etc) now loops instead until q for quit + + * v 0.99.6 110511 regedit library (chntpw and reged uses it): diff --git a/INSTALL.txt b/INSTALL.txt index d2639c9..0877250 100644 --- a/INSTALL.txt +++ b/INSTALL.txt @@ -1,13 +1,13 @@ The Offline NT Password & Registry Editor -(c) 1997-2011 Petter Nordahl-Hagen +(c) 1997-2014 Petter Nordahl-Hagen This file is meant for developers. General usage is via the provided boot CD images, but -static linux builds of the tools are also available -in the source zip file. +static 32 bit linux builds of the tools are also available +in the "static" zip file available on my website. See REAMDE for general instructions and license info, HISTORY.txt for version info. @@ -16,7 +16,12 @@ If you want to build yourself, it should compile with most gcc compilers. However, I am not particulary good with types, so there may be some warnings. -You may have to install the openssl-devel package from you +Unlike earlier, crypto stuff is not compiled in by default any more, +but that only disables function to set new specific password, +which does not seem to work well on newer Windows anyway. + +However, you can enable it in the Makefile, if so +you may have to install the openssl-devel package from you linux distro to compile/link, since I need the static variant. (change the makefile to link dynamic if you wish) I link statically with libcrypto.a from OpenSSL. @@ -26,15 +31,23 @@ Thanks to Denis Ducamp for modifying chntpw to use OpenSSL. Thanks to Mike Doty, via Alon Bar-Lev for 64 bit build compatible patch. (from http://bugs.gentoo.org/show_bug.cgi?id=185411) +which I may or may not have broken again i 2013... Look into Makefile, you may need to change things to fit your env. -(also if you need 64 bit) +also if you need 64 bit. +Warning: I mostly build on 32-bit and may not have tested on 64 bit +in every version! (32-bit binaries run fine on 64-bit systems anyway, +and it does not matter if Windows it edits registry from is 32- or 64-bit) If make goes well, you should have: - chntpw - Password tool, dynamic with libc, static with OpenSSL + chntpw - interactive password tool chntpw.static - Completely static passord tool reged - Registry edit tool, dynamic linked with libc. No crypto. - reged.static - Registry too, statically linked. + reged.static - Registry tool, statically linked. + sampasswd - user list and password reset commandline util + sampasswd.static - same but statically linked + samusrgrp - group list and group membership tool + samusrgrp.static - static version of same cpnt - Simple copy utility, does not truncate file on write Now deprecated, was at least needed earlier for NTFS writes. @@ -42,7 +55,4 @@ Also, the floppies and CDs are build under a different environment for small libc size (uClibc), see website for details. (build scripts not currently available..) -So, note that the binary then contains cryptographic code -which may be illegal for you to export. - Good luck. diff --git a/MANUAL.txt b/MANUAL.txt new file mode 100644 index 0000000..5302dd8 --- /dev/null +++ b/MANUAL.txt @@ -0,0 +1,513 @@ + +The Offline Windows Password Editor +(c) 1997-2014 Petter Nordahl-Hagen + +Command line utilities + +All these programs need one (or more) registry files to work on. +These are the binary registry files, called "hives" that usually are +located on a Windows machine under \WINDOWS\System32\config +where the most important ones are: +SAM - HKEY_LOCAL_MACHINE\SAM, Security Accounts Manager + contains local user / group info, user passwords are here +SYSTEM - HKEY_LOCAL_MACHINE\SYSTEM: System settings / configuration + (services, drivers, hardware info, some boot config) +SOFTWARE - HKEY_LOCAL_MACHINE\SOFTARE: Config and info of installed + software and a lot of higher level windows config + +Note that these programs (and the registry library they use) does not +join all these files in the same tree like windows does. +For example, a path like +HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control +is not valid, it is instead just +ControSet001\Control +(after selecting the hive if more than one is loaded) +But except if you use the registry edit or export/import functions you +do not need think about this as for other things the tools handle +this. + + +chntpw: Program for interactively resetting passwords and group +memberships. +My boot CD runs this with options -i -L SAM + + chntpw: change password of a user in a Windows SAM file, + or invoke registry editor. Should handle both 32 and 64 bit windows and + all version from NT3.x to Win8 + chntpw [OPTIONS] [systemfile] [securityfile] [otherreghive] [...] + -h This message + -u Username or RID (0x3e9 for example) to interactively edit + -l list all users in SAM file and exit + -i Interactive Menu system + -f Interactively edit first admin user + -e Registry editor. Now with full write support! + -d Enter buffer debugger instead (hex editor), + -v Be a little more verbose (for debuging) + -L For scripts, write names of changed files to /tmp/changed + -N No allocation mode. Only same length overwrites possible (very safe mode) + -E No expand mode, do not expand hive file (safe mode) + + + + -u Username or RID (0x3e9 for example) to interactively edit + +Invoke the interactive edit menu on specified user. +Specifying a user name will most likely fail if user has international +character, so better to use user ID (RID), for example + chnptw -u 0x3e9 SAM +to edit user with hexadecimal RID 3e9 + + + -l list all users in SAM file and exit + +Just that, list users in human readable form, with some info about if +user is admin and if password is set. + + -i Interactive Menu system + +Invokes the menu system. Menu items will vary a bit depending on what +registry hives are loaded. + + -f Interactively edit first admin user + +Select first admin user for edit. This is user with lowest RID that +also is member of administators group, or built-in user 0x1f4 if not +others possible. + + -e Registry editor. Now with full write support! + +Enter the registry editor. It is a small command system. ? for help +there. See other documentation for more on regedits. + + -d Enter buffer debugger instead (hex editor), + +Command line type hex editor, mostly for debugging purposes. ? for help. + + -v Be a little more verbose (for debuging) + +Lots of debug output during most operations (especially hive loading) + + -L For scripts, write names of changed files to /tmp/changed + +If any of the other functions changes the registry, the changed files +are listed here. Can be used by wrapper scripts to know what to save. +My boot CD uses it. + + -N No allocation mode. Only same length overwrites possible (very safe mode) + +Safe mode. Will only allow changes in registry that overwrites old +values with same length data. Password reset only changes 2 bytes, and +does not change value lenght, so password reset will still work in +this safe mode. If something tries to violate this safe mode, a lot of +error messages (some of the rather obscure) may occur. + + -E No expand mode, do not expand hive file (safe mode) + +Safe mode. Does not allow expanding the size of the file, but will +allow adding keys/values as long as there is free space in the file +already. (most files contains some free space) +If expansion is needed but not allowed by this option, +a lot of obscure error messages may occur, and file should not be saved. + +------------------------------------------------------------------------- + +reged: Registry editor and export and import of .reg files. + + reged version 0.1 110511, (c) Petter N Hagen + + Modes: + -x + Xport. Where for example is HKEY_LOCAL_MACHINE\SOFTWARE + is key to dump (recursively), \ or \\ means all keys in hive + Only one .reg and one hive file supported at the same time + -I + Import from .reg file. Where for example is HKEY_LOCAL_MACHINE\SOFTWARE + Only one .reg and one hive file supported at the same time + -e ... + Interactive edit one or more of registry files + + Options: + -L : Log changed filenames to /tmp/changed, also auto-saves + -C : Auto-save (commit) changed hives without asking + -N : No allocate mode, only allow edit of existing values with same size + -E : No expand mode, do not expand hive file (safe mode) + -t : Debug trace of allocated blocks + -v : Some more verbose messages + + +Modes: +-x + Xport. Where for example is HKEY_LOCAL_MACHINE\SOFTWARE + is key to dump (recursively), \ or \\ means all keys in hive + Only one .reg and one hive file supported at the same time + +Exports everything below the specified of the hive to .reg file. +Since the program does not care about what part of the registry +Windows thinks it is, you must specify the prefix if the .reg file +needs to be used in Windows. +Prefix is like +HKEY_LOCAL_MACHINE\SOFTWARE for software hive file +HKEY_LOCAL_MACHINE\SYSTEM for system hive file +and so on, look in regedit.exe in windows for details. + +To dump the whole hive, use \ or \\ or . as the key. +Examples: + + reged -x system HKEY_LOCAL_MACHINE\\SYSTEM ControlSet001 output.reg + +should export everything below ControlSet001 key from registry hive +file named system into .reg file named output.reg, using +HKEY_LOCAL_MACHINE\SYSTEM in front of every key name in the .reg file. +(in most shells \\ is needed to ignore the meaning \ has to the shell) + + reged -x system HKEY_LOCAL_MACHINE\\SYSTEM ControlSet001\\Enum output.reg + +same, but start from ControlSet001\Enum + + reged -x system HKEY_LOCAL_MACHINE\\SYSTEM \\ output.reg + +export everything in the system file + + + + -I + Import from .reg file. Where for example is HKEY_LOCAL_MACHINE\SOFTWARE + Only one .reg and one hive file supported at the same time + +Reverse of -x, this reads from a .reg file and puts it into the hive +file, just like regedit.exe foobar.reg will do in windows. +The is removed from the start of each key name, if you +specify this wrong, the result may not be what you expected. +KNOWN PROBLEM: This routine is slow, very slow indeed on binary values (has +hex numbers in .reg file). May take over 5 minutes to import a file +the size of a normal XP software-hive .reg export. +Problems / unusual things in the .reg file may cause crash or +unexpected data or some times even an error messsage! :) + +KNOWN PROBLEMS: May bork on some wide character (UTF16) .reg files. +(crash or does not do exactly what you would expect) + + -e ... + Interactive edit one or more of registry files + +Enter the registry editor. It is a small command system. ? for help +there. See other documentation for more on regedits. +If both -I and -e given, editor will be entered after import, but +before save, so you can check things if you need. + + + Options: + -L : Log changed filenames to /tmp/changed, also auto-saves + -C : Auto-save (commit) changed hives without asking + +Usually this command prompts for save, since import may have messed up. + +-N : No allocate mode, only allow edit of existing values with same size +-E : No expand mode, do not expand hive file (safe mode) + +same as on chntpw + +-t : Debug trace of allocated blocks + +lot of hive load debug info. very much. allocator debug. + +-v : Some more verbose messages + +a bit debug messages + +** In interactive registry editor: + name of a key (not a path), ex: MyKey + name of a value (not a path), ex: MyCoolValueName + may be a path, ex: foobar\baz\gazonk +as usual with paths, \ starts from the root key, without it is relative to +current key + value, may have path. ex: \foo\bar\MyValue +is value named MyValue in key bar in key foo +cd .. goes back a directory + + +List subkeys and values in current key + dir + ls + +Change current key: + cd + cd .. # goes back a key + +Show a value, tries to show according to type, else hexdump + type + cat + +Hexdump value data (no matter what type it is) + hex + +Edit a value: + ed +and it will show old value and ask for new + +Make a new key (cannot be a path) + nk + +Delete a key, key must be empty, cannot be a path + dk + +Delete a value, cannot be a path + dv + +Delete all values in current key (no questions asked!) + delallv + +Delete recursively key and all keys under it (no questions asked) +May take some time if large tree + rdel + +Make a new value of selected type. It will be empty, use 'ed' afterwards. + nv +Where type is a hex number, from this list: + 0 : REG_NONE + 1 : REG_SZ + 2 : REG_EXPAND_SZ + 3 : REG_BINARY + 4 : REG_DWORD + 5 : REG_DWORD_BIG_ENDIAN + 6 : REG_LINK + 7 : REG_MULTI_SZ + 8 : REG_RESOUCE_LIST + 9 : REG_FULL_RES_DESC + a : REG_RES_REQ + b : REG_QWORD +it will not prevent you from using other numbers + +Show key class data, in hexdump (class data has no special type) + ck + +Export to .reg file, same as -x option above + ek + + + + + +------------------------------ + +samusrgrp +is a command line tool to add users to groups or remove users from +groups. Users and groups must be local (cannot be domain / AD). +It can also list the groups with their members in several forms, the +output can be used in scripts of course. + +Listing groups will also list domain users that are members of the +group (if any), but it will not be able to look up the name, so it +will be listed as a SID only. + + samusrgrp version 0.2 130501, (c) Petter N Hagen + ./samusrgrp [-a|-r] -u -g + Add or remove a (local) user to/from a group + Mode: + -a = add user to group + -r = remove user from group + -l = list groups + -L = list groups and also their members + -s = Print machine SID + +For add or remove, you must also specify a bit more info: + + Parameters: + can be given as a username or a RID in hex with 0x in front + is the group number, in hex with 0x in front + Example: + -a -u theboss -g 0x220 -> add user named 'theboss' group hex 220 (administrators) + -a -u 0x3ea -g 0x221 -> add user with RID (hex) 3ea group hex 221 (users) + -r -u 0x3ff -g 0x220 -> remove user RID 0x3ff from grp 0x220 + Note that usernames with international characters usually fails to be found + so please use RID number instead + If success, there will be no output, and exit code is 0 + Also, success if user already in (or not in if -r) the group + + Options: + -H : Human readable output, else parsable + -N : No allocate mode, only allow edit of existing values with same size + -E : No expand mode, do not expand hive file (safe mode) + -t : Debug trace of allocated blocks + -v : Some more verbose messages/debug + + Multi call binary, if program is named: + samusrtogrp -- Assume -a mode: Add a user into a group + samusrfromgrp -- Assume -r mode: Remove user from a group + +Example of group listing: + + + samusrgrp -L SAM + +will list groups with member users, for example: + +222:Guests:0:1f4:Administrator:S-1-5-21-596911364-3511430013-269812654-500 +222:Guests:1:1f5:Guest:S-1-5-21-596911364-3511430013-269812654-501 +223:Power Users:0:1f4:Administrator:S-1-5-21-596911364-3511430013-269812654-500 +223:Power Users:1:1f5:Guest:S-1-5-21-596911364-3511430013-269812654-501 +223:Power Users:2:3e9:foo:S-1-5-21-596911364-3511430013-269812654-1001 + +Fields are: +Group ID (hex) +Group name +Member number (starts at 0) +Members RID (hex) +Members name (if available, else SID) +Members SID + +So in this example, the Guests group have 2 members: Administrator and +Guest. +At the time of writing this, it WILL NOT LIST EMPTY GROUPS (no +members). I plan to change this, empty groups on one line with -1 in +member number field, and rest of user fields empty. + + samusrgrp -l SAM + +will list groups (no members) + +220:Administrators:2 +221:Users:2 +222:Guests:1 +223:Power Users:0 + +Fields are: + +Group ID (hex) +Group name +Number of members + +this listing will show all groups, also empty groups. + + +------------------------------------------------- + +sampasswd +Password reset from command line (scriptable) +Or list users in SAM file in a few different formats. + + + + ./sampasswd [-r|-l] [-H] -u + Reset password or list users in SAM database + Mode: + -r = reset users password + -l = list users in sam + +One mode must be selected + + Parameters: + can be given as a username or a RID in hex with 0x in front + Example: + -r -u theboss -> resets password of user named 'theboss' if found + -r -u 0x3ea -> resets password for user with RID 0x3ea (hex) + -r -a -> Reset password of all users in administrators group (0x220) + -r -f -> Reset password of admin user with lowest RID + not counting built-in admin (0x1f4) unless it is the only admin + Note that usernames with international characters usually fails to be found + so please use RID number instead + If success, there will be no output, and exit code is 0 + + Options: + -H : For list: Human readable listing (default is parsable table) + -H : For reset: Will output confirmation message if success + -N : No allocate mode, only allow edit of existing values with same size + -E : No expand mode, do not expand hive file (safe mode) + -t : Debug trace of allocated blocks + -v : Some more verbose messages/debug + +List examples: + + sampasswd -l xpreg/SAM + +01f4:Administrator:1:210:14 +01f5:Guest:0:8215:4 +03e8:HelpAssistant:0:8211:14 +03e9:foo:1:210:14 + +Fields are: + +User RID (hex) +User name +Is user admin? (1 = yes, 0 = no) (member of group ID 0x220) +Account flags, ACB (hex). See sam.h file. +Password hash length. 14 = normal password. 0 or 4 = probably blank. + +The bulk reset (-r -a) will also output parsable result: + + + sampasswd -H -r -a xpreg/SAM + +Reset user :01f4:Administrator +Reset user :03e9:pnh + +which of course is :RID:username + + +Explanation on this: + -r -f -> Reset password of admin user with lowest RID + not counting built-in admin (0x1f4) unless it is the only admin + +All windows from NT3.1 up has a system created administrator account +with RID (user ID) 0x1f4 (500 decimal) + +Before Windows XP the installer asked for a password for this account, +and then it was used to login first after installation. +(Built-in guest account was also created by the installer, but it has +always been disabled by default) + +On Windows XP and newer systems, the installer also creates this +account, but locks it down, it generally cannot be logged in. It is +also not shown on the welcome screen (unless all other users are +deleted or disabled) +The installer instead asks for a user to create during install. That +user is a normal non-hardwired user (RID > 0x3e8, 1000 decimal), +and it is added to the built-in administrators group (group # 0x220). + +It is users in the built-in group 0x220 that generally has full +administrator rights to the machine. + +(XP installer can create several users, but only first gets group +0x220) + +More users can of course be added from the control panel, and they can +be put into the 0x220 group if neccessary. From the "simplified" +control panel dialog this is what happens if user is selected to be able to +have full (or admin) access to the machine. If user is set to "normal" +or something like that, it is not in the 0x220 group. + +From the "Users and Groups" part of the administrative tools (not +available on some home versions of windows) the group assignments and +other user info can be changed in more detail of course. +From there, users in a domain (if machine is in domain) can also be +added to the local 0x220 group, the domain user full access to that +local machine even if the user is nothing special in the domain. + +Anyway.. +On XP and newer, it is therefore not the hardwired 0x1f4 account that is +used for admim. On home machines it is most often the first regular one (since +most people do not change any user stuff after the installer) or it +could be any other user in the list. + +So this reset function picks the first it finds over 0x3e8 (1000) +that is also in the 0x220 group. It will most likely work for 98% of +home user machines :) Unless there are no users in the 0x220 group, +then it picks the 0x1f4 hard-wired user (since it may be Windows +2000??????). + +This may of course be wrong if someone managed to remove all accounts +above 1000 from the 0x220 group. But then they have "samusrgrp" to add +someone back :) + + +Explanation on -a -r: + +The -r -a option will reset all users in the 0x220 group. Also user +0x1f4, which maybe is bad.. will consider changing this... + + + + + + + diff --git a/Makefile b/Makefile index 3e267b7..6b4531e 100644 --- a/Makefile +++ b/Makefile @@ -22,15 +22,17 @@ OSSLLIB=$(OSSLPATH)/lib # This is to link with whatever we have, SSL crypto lib we put in static -LIBS=-L$(OSSLLIB) $(OSSLLIB)/libcrypto.a +#LIBS=-L$(OSSLLIB) $(OSSLLIB)/libcrypto.a +LIBS=-L$(OSSLLIB) -all: chntpw chntpw.static cpnt reged reged.static -chntpw: chntpw.o ntreg.o edlib.o - $(CC) $(CFLAGS) -o chntpw chntpw.o ntreg.o edlib.o $(LIBS) +all: chntpw chntpw.static cpnt reged reged.static samusrgrp samusrgrp.static sampasswd sampasswd.static -chntpw.static: chntpw.o ntreg.o edlib.o - $(CC) -static $(CFLAGS) -o chntpw.static chntpw.o ntreg.o edlib.o $(LIBS) +chntpw: chntpw.o ntreg.o edlib.o libsam.o + $(CC) $(CFLAGS) -o chntpw chntpw.o ntreg.o edlib.o libsam.o $(LIBS) + +chntpw.static: chntpw.o ntreg.o edlib.o libsam.o + $(CC) -static $(CFLAGS) -o chntpw.static chntpw.o ntreg.o edlib.o libsam.o $(LIBS) cpnt: cpnt.o $(CC) $(CFLAGS) -o cpnt cpnt.o $(LIBS) @@ -41,6 +43,19 @@ reged: reged.o ntreg.o edlib.o reged.static: reged.o ntreg.o edlib.o $(CC) -static $(CFLAGS) -o reged.static reged.o ntreg.o edlib.o +samusrgrp.static: samusrgrp.o ntreg.o libsam.o + $(CC) -static $(CFLAGS) -o samusrgrp.static samusrgrp.o ntreg.o libsam.o + +samusrgrp: samusrgrp.o ntreg.o libsam.o + $(CC) $(CFLAGS) -o samusrgrp samusrgrp.o ntreg.o libsam.o + +sampasswd: sampasswd.o ntreg.o libsam.o + $(CC) $(CFLAGS) -o sampasswd sampasswd.o ntreg.o libsam.o + +sampasswd.static: sampasswd.o ntreg.o libsam.o + $(CC) -static $(CFLAGS) -o sampasswd.static sampasswd.o ntreg.o libsam.o + + #ts: ts.o ntreg.o # $(CC) $(CFLAGS) -nostdlib -o ts ts.o ntreg.o $(LIBS) @@ -51,5 +66,5 @@ reged.static: reged.o ntreg.o edlib.o $(CC) -c $(CFLAGS) $< clean: - rm -f *.o chntpw chntpw.static cpnt reged reged.static *~ + rm -f *.o chntpw chntpw.static cpnt reged reged.static samusrgrp samusrgrp.static sampasswd sampasswd.static *~ diff --git a/README.txt b/README.txt index 126b8de..0c4e7f0 100644 --- a/README.txt +++ b/README.txt @@ -1,20 +1,26 @@ -The Offline NT Password Editor +The Offline Windows Password Editor -(c) 1997-2011 Petter Nordahl-Hagen +(c) 1997-2014 Petter Nordahl-Hagen This is free software, licensed under the following: -"ntreg" (the registry library) is licensed under the GNU Lesser Public -License. See LGPL.txt. +"ntreg" (the registry library) and +"libsam" (SAM manipulation library, user, groups etc) +is licensed under the GNU Lesser Public License. See LGPL.txt. -"chntpw" (the password reset / registry editor frontend) is licensed -under the GNU General Public License, see GPL.txt. +"chntpw" (the password reset / registry editor frontend) +"reged" (registry editor, export and import tool) +"sampasswd" (password reset command line program) +"samusrgrp" (user and group command line program) +is licensed under the GNU General Public License, see GPL.txt. -"reged" (registry editor /export tool) is licensed -under the GNU General Public License, see GPL.txt. -See INSTALL.txt for compile/installation instructions. +For manual to the different commands, see MANUAL.txt +Also, all have some help built in, just use the -h option. + +See INSTALL.txt for compile instructions. + Where to get more info: ----------------------- @@ -22,16 +28,17 @@ Where to get more info: http://pogostick.net/~pnh/ntpasswd/ At that site there's a floppy and a bootable CD that use chntpw to -access the NT/2k/XP/Vista-system it is booted on to edit password etc. +access the NT/2k/XP/Vista/Win7/Win8 system it is booted on to edit password etc. The instructions below are for the standalone program itself, not the floppy. What does chntpw do? -------------------- This little program will enable you to view some information and -change user passwords in a Windows (NT/XP/Vista/win7) etc SAM userdatabase file. +change user passwords, change user/group memberships +in a Windows (NT/XP/Vista/win7/win8) etc SAM userdatabase file. You do not need to know the old passwords. -However, you need to get at the file some way or another yourself. +However, you need to get at the registry files some way or another yourself. In addition it contains a simple registry editor with full write support, and hex-editor which enables you to fiddle around with bits&bytes in the file as you wish yourself. @@ -43,6 +50,15 @@ Also have registry import or export the registry hive (binary) files. Also has an editor, but still rudimentary text based command line type thing. +And by popular request +Even have programs that can be used in scripts! +----------------------------------------------- +"sampasswd" can be used in scripts to get lists +of users or reset passwords automatically +"samusrgrp" can be used in scripts to list or change +memberships in groups automatically. + + Why? ---- @@ -52,101 +68,43 @@ I just _must_ have some stuff out of half a year later..) On most unix-based boxes you just boot the thingy off some kind of rescue bootmedia (cd/floppy etc), and simply edit the password file. -On Windows NT however, as far as I know, there is no way except reinstalling +On Windows however, as far as I know, there is no way except reinstalling the userdatabase, losing all users except admin. (ok, some companies let you pay lotsa $$$$$ for some rescue service..) +(ok, from Windows Vista or something you can make a password reset +file, but you have to remember to do that BEFORE you forget your password...) How? ---- Currently, this thing only runs under linux, but it may just happen to compile on other platforms, too. -(there are dos-versions available, look for links on my webpage) -So, to set a new adminpassword on your NT installation you either: + +So, to set a new adminpassword on your Windows installation you either: + 1) Take the harddrive and mount it on a linux-box -2) Use a linux-bootdisk or CD - one is available at: http://pogostick.net/~pnh/ntpasswd/ -ie. you do it offline, with the NT system down. + +or + +2) Boot a "live" linux CD with full GUI (many available: Ubuntu, + Knoppix and more. Search for them) + +In both those cases, use the "chntpw.static" program found in the +"static" zip file on my website. +or + +3) Use my linux boot CD (or USB) at: http://pogostick.net/~pnh/ntpasswd/ Usage: ------ -This is usage of the "chntpw" program binary only. -For info on the bootdisk, see the web site. -Some of the output format has changed a little since the docs were -first written. +For manual to the different commands, see MANUAL.txt +Also, all have some help built in, just use the -h option. - chntpw version 0.99.2 040105, (c) Petter N Hagen - chntpw: change password of a user in a NT SAM file, or invoke registry editor. - chntpw [OPTIONS] [systemfile] [securityfile] [otherreghive] [...] - -h This message - -u Username to change, Administrator is default - -l list all users in SAM file - -i Interactive. List users (as -l) then ask for username to change - -e Registry editor. Now with full write support! - -d Enter buffer debugger instead (hex editor), - -t Trace. Show hexdump of structs/segments. (deprecated debug function) - -v Be a little more verbose (for debuging) - -L Write names of changed files to /tmp/changed - -N No allocation mode. Only (old style) same length overwrites possible +Some old tech babble on how the password is stored +-------------------------------------------------- +(still mostly valid, but should be moved somewhere else than this file) -Normal usage is: - -> chntpw sam system security - - open registry hives 'sam' and 'system' and change administrator account. - Verions dated later from Feb 1999 and later also supports - and will find the admin account, even if the name has been changed, - or the name has been localized (different languageversion of NT - use different admin-names) - -The -u option: -Specifies user to change: - -> chntpw -u jabbathehutt mysam - - Prompt for password for 'jabbathehutt', if found (otherwise do nothing) - -Or you may give RID number in hex: -> chntpw -u 0x1f4 mysam - - Will edit administrator. - -Names does not support multibyte (unicode) characters like -some russian and asian locales. Give RID in hex to edit users -with such names. Must start with 0x. Ex: 0x2fa - -The -l option: - Will list all users in the sam-file. - -The -i option: - Go into the interactive menu system. - -The -d option: - This will load the file, and then immediately enter the - buffer debugger. - This is a simple hex-editor with only a few commands, - enter ? at the . prompt to se a short command overview. - 'q' exits without saving, 's' exit and saves. - -The -e option: - Will enter the registry editor. - You can navigate the registry like a filesystem at the command-line prompt: - See regedit.txt file for more info. - -The -t option: - This is a debug function (extended -l) to show how it traces the chain - of structs in the file. This also includes a raw interpretation - of the different registry structures + a hex dump. - -The -L option: - Drops the filenames of the changed hives in /tmp/changed - Used by the bootdisk scripts. - -The -N option: - Will fall back to old edit mode, disable the block allocations - and only support overwrite-same-size. Used to ensure safety - in testing period. - -How does it work: ------------------ A struct, called the V value of a key in the NT registry was suddenly somewhat documented through the pwdump utility diff --git a/chntpw-presplit.c b/chntpw-presplit.c new file mode 100644 index 0000000..824fed5 --- /dev/null +++ b/chntpw-presplit.c @@ -0,0 +1,2312 @@ +/* + * chntpw.c - Offline Password Edit Utility for Windows SAM database + * + * This program uses the "ntreg" library to load and access the registry, + * it's main purpose is to reset password based information. + * It can also call the registry editor etc + + * 2011-apr: Command line options added for hive expansion safe mode + * 2010-jun: Syskey not visible in menu, but is selectable (2) + * 2010-apr: Interactive menu adapts to show most relevant + * selections based on what is loaded + * 2008-mar: Minor other tweaks + * 2008-mar: Interactive reg ed moved out of this file, into edlib.c + * 2008-mar: 64 bit compatible patch by Mike Doty, via Alon Bar-Lev + * http://bugs.gentoo.org/show_bug.cgi?id=185411 + * 2007-sep: Group handling extended, promotion now public + * 2007-sep: User edit menu, some changes to user info edit + * 2007-apr-may: Get and display users group memberships + * 2007-apr: GNU license. Some bugfixes. Cleaned up some output. + * 2004-aug: More stuff in regedit. Stringinput bugfixes. + * 2004-jan: Changed some of the verbose/debug stuff + * 2003-jan: Changed to use more of struct based V + some small stuff + * 2003-jan: Support in ntreg for adding keys etc. Editor updated. + * 2002-dec: New option: Specify user using RID + * 2002-dec: New option: blank the pass (zero hash lengths). + * 2001-jul: extra blank password logic (when NT or LANMAN hash missing) + * 2001-jan: patched & changed to use OpenSSL. Thanks to Denis Ducamp + * 2000-jun: changing passwords regardless of syskey. + * 2000-jun: syskey disable works on NT4. Not properly on NT5. + * 2000-jan: Attempt to detect and disable syskey + * 1999-feb: Now able to browse registry hives. (write support to come) + * See HISTORY.txt for more detailed info on history. + * + ***** + * + * Copyright (c) 1997-2012 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. + * + ***** + * + * Information and ideas taken from pwdump by Jeremy Allison. + * + * More info from NTCrack by Jonathan Wilkins. + * + */ + +/* TODO: This is getting ugly. Most likely best to split up into different programs + * and put commun stuff in library + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Define DOCRYPTO in makefile to include cryptostuff to be able to change passwords to + * a new one. + * Changing passwords is seems not to be working reliably on XP and newer anyway. + * When not defined, only reset (nulling) of passwords available. + */ + +#ifdef DOCRYPTO +#include +#include +#endif + +#define uchar u_char +#define MD4Init MD4_Init +#define MD4Update MD4_Update +#define MD4Final MD4_Final + +#include "ntreg.h" +#include "sam.h" + +const char chntpw_version[] = "chntpw version 0.99.6 110511 , (c) Petter N Hagen"; + +extern char *val_types[REG_MAX+1]; + +/* Global verbosity */ +int gverbose = 0; + + +#define MAX_HIVES 10 + +/* Array of loaded hives */ +struct hive *hive[MAX_HIVES+1]; +int no_hives = 0; + +/* Icky icky... globals used to refer to hives, will be + * set when loading, so that hives can be loaded in any order + */ + +int H_SAM = -1; +int H_SYS = -1; +int H_SEC = -1; +int H_SOF = -1; + +int syskeyreset = 0; +int dirty = 0; +int max_sam_lock = 0; + +/* + * of user with RID 500, because silly MS decided + * to localize the bloody admin-username!! AAAGHH! + */ +char admuser[129]="Administrator"; + +/* ============================================================== */ + + +#ifdef DOCRYPTO + +/* Crypto-stuff & support for what we'll do in the V-value */ + +/* Zero out string for lanman passwd, then uppercase + * the supplied password and put it in here */ + +void make_lanmpw(char *p, char *lm, int len) +{ + int i; + + for (i=0; i < 15; i++) lm[i] = 0; + for (i=0; i < len; i++) lm[i] = toupper(p[i]); +} + +/* + * Convert a 7 byte array into an 8 byte des key with odd parity. + */ + +void str_to_key(unsigned char *str,unsigned char *key) +{ + int i; + + key[0] = str[0]>>1; + key[1] = ((str[0]&0x01)<<6) | (str[1]>>2); + key[2] = ((str[1]&0x03)<<5) | (str[2]>>3); + key[3] = ((str[2]&0x07)<<4) | (str[3]>>4); + key[4] = ((str[3]&0x0F)<<3) | (str[4]>>5); + key[5] = ((str[4]&0x1F)<<2) | (str[5]>>6); + key[6] = ((str[5]&0x3F)<<1) | (str[6]>>7); + key[7] = str[6]&0x7F; + for (i=0;i<8;i++) { + key[i] = (key[i]<<1); + } + DES_set_odd_parity((des_cblock *)key); +} + +/* + * Function to convert the RID to the first decrypt key. + */ + +void sid_to_key1(uint32_t sid,unsigned char deskey[8]) +{ + unsigned char s[7]; + + s[0] = (unsigned char)(sid & 0xFF); + s[1] = (unsigned char)((sid>>8) & 0xFF); + s[2] = (unsigned char)((sid>>16) & 0xFF); + s[3] = (unsigned char)((sid>>24) & 0xFF); + s[4] = s[0]; + s[5] = s[1]; + s[6] = s[2]; + + str_to_key(s,deskey); +} + +/* + * Function to convert the RID to the second decrypt key. + */ + +void sid_to_key2(uint32_t sid,unsigned char deskey[8]) +{ + unsigned char s[7]; + + s[0] = (unsigned char)((sid>>24) & 0xFF); + s[1] = (unsigned char)(sid & 0xFF); + s[2] = (unsigned char)((sid>>8) & 0xFF); + s[3] = (unsigned char)((sid>>16) & 0xFF); + s[4] = s[0]; + s[5] = s[1]; + s[6] = s[2]; + + str_to_key(s,deskey); +} + +/* DES encrypt, for LANMAN */ + +void E1(uchar *k, uchar *d, uchar *out) +{ + des_key_schedule ks; + des_cblock deskey; + + str_to_key(k,(uchar *)deskey); +#ifdef __FreeBSD__ + des_set_key(&deskey,ks); +#else /* __FreeBsd__ */ + des_set_key((des_cblock *)deskey,ks); +#endif /* __FreeBsd__ */ + des_ecb_encrypt((des_cblock *)d,(des_cblock *)out, ks, DES_ENCRYPT); +} + +#endif /* DOCRYPTO */ + + +/* 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 get_machine_sid(char *sidbuf) +{ + + struct accountdb_V *v; + struct keyval *kv; + uint32_t ofs; + uint32_t len; + + if (H_SAM >= 0) { + + /* Get accoundb V value */ + kv = get_val2buf(hive[H_SAM], NULL, 0, ACCOUNTDB_V_PATH, REG_BINARY, TPF_VK); + if (!kv) { + fprintf(stderr,"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,"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); + } + return(0); +} + +/* 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 + * + * str = string buffer to fill, be sure to have at least space: + * 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 *sid_to_string(struct sid_binary *sidbuf) +{ + + int cnt, i; + int *array; + char *str = NULL; + + // hexdump(sidbuf, 0, 24, 1); + + + array = (int *)&sidbuf->array; + + if (sidbuf->unknown0 != 1) { + fprintf(stderr,"sid_to_string: DEBUG: first byte unexpected: %d\n",sidbuf->unknown0); + } + + cnt = sidbuf->sections; + + // printf("sid_to_string: DEBUG: sections = %d\n",cnt); + + str = str_dup("S-1-5"); + + for (i = 0; i < cnt; i++) { + str = str_catf(str,"-%u",sidbuf->array[i]); + } + + // printf("sid_to_string: returning <%s>\n",str); + + + return(str); +} + + + + +/* Check if hive is SAM, and if it is, extract some + * global policy information from it, like lockout counts etc + */ + +void check_get_samdata(int show) +{ + struct accountdb_F *f; + struct keyval *v; + + if (H_SAM >= 0) { + + /* Get accoundb F value */ + v = get_val2buf(hive[H_SAM], NULL, 0, ACCOUNTDB_F_PATH, REG_BINARY, TPF_VK); + if (!v) { + fprintf(stderr,"WARNING: Login counts data not found in SAM\n"); + return; + } + + f = (struct accountdb_F *)&v->data; + max_sam_lock = f->locklimit; + + if (show) { + printf("\n* SAM policy limits:\n"); + printf("Failed logins before lockout is: %d\n",max_sam_lock); + printf("Minimum password length : %d\n",f->minpwlen); + printf("Password history count : %d\n",f->minpwlen); + } + } +} + + +/* Try to decode and possibly change account lockout etc + * This is \SAM\Domains\Account\Users\\F + * It's size seems to always be 0x50. + * Params: RID - user ID, mode - 0 silent, 1 silent, 2 edit. + * Returns: ACB bits with high bit set if lockout count is >0 + */ + +short handle_F(int rid, int mode) +{ + + struct user_F *f; + char s[200]; + struct keyval *v; + unsigned short acb; + int b; + + if (H_SAM < 0) return(0); + + /* Get users F value */ + snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\F",rid); + v = get_val2buf(hive[H_SAM], 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); + } + + 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<failedcnt,max_sam_lock); + printf("Total login count: %u\n",f->logins); + } + + if (mode == 2) { + acb |= ACB_PWNOEXP; + acb &= ~ACB_DISABLED; + acb &= ~ACB_AUTOLOCK; + f->ACB_bits = acb; + f->failedcnt = 0; + put_buf2val(hive[H_SAM], v, 0, s, REG_BINARY,TPF_VK_EXACT); + printf("Unlocked!\n"); + } + return (acb | ( (f->failedcnt > 0 && f->failedcnt >= max_sam_lock)<<15 ) | (acb & ACB_AUTOLOCK)<<15 | (acb & ACB_DISABLED)<<15); +} + + +/* 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 free_sid_array() to free it. + */ + +struct sid_array *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 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 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); +} + + + +/* 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 get_grp_members_sid(int grp, struct sid_array **sarray) +{ + char g[200]; + // char groupname[128]; + + struct sid_array *marray; + struct keyval *c = NULL; + struct group_C *cd; + // int grpnamoffs, grpnamlen; + int mofs, mlen; + + snprintf(g,180,"\\SAM\\Domains\\Builtin\\Aliases\\%08X\\C",grp); + c = get_val2buf(hive[H_SAM], NULL, 0, g, 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); + + // 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 = make_sid_array((struct sid_binary *)&cd->data[mofs], mlen); + + *sarray = marray; + // 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 put_grp_members_sid(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; + char *str; + + snprintf(g,180,"\\SAM\\Domains\\Builtin\\Aliases\\%08X\\C",grp); + c = get_val2buf(hive[H_SAM], NULL, 0, g, 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 (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 = 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(hive[H_SAM], 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); + +} + + +/* List groups, optionally with members */ + +void list_groups(int listmembers) { + + struct ex_data ex; + struct sid_array *sids = NULL; + int nkofs; + unsigned int grp; + int count = 0, countri = 0; + struct keyval *c = NULL; + struct group_C *cd; + int grpnamoffs, grpnamlen, i; + char groupname[200]; + char *str; + + + if (H_SAM < 0) return; + + + nkofs = trav_path(hive[H_SAM], 0,"\\SAM\\Domains\\Builtin\\Aliases",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 */ + while ((ex_next_n(hive[H_SAM], 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(hive[H_SAM], 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); + + printf("Group #%x named <%s> has %d members\n",grp,groupname,cd->grp_members); + + if (listmembers) { + get_grp_members_sid(grp, &sids); + + for (i = 0; sids[i].sidptr; i++) { + str = sid_to_string(sids[i].sidptr); + printf(" Member # %d = <%s>\n", i, str); + FREE(str); + } + free_sid_array(sids); + } + } + + } + +} + +/* 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 *get_user_grpids(int rid) +{ + char s[200]; + struct sid_binary sid; + char *sidstr; + + int nk = 0; + struct keyval *m = NULL; + int count = 0; + int size; + + if (!rid || (H_SAM < 0)) return(NULL); + + if (!get_machine_sid((char *)&sid)) { + fprintf(stderr,"get_user_grpids: Could not find machine SID\n"); + return(0); + } + + sidstr = sid_to_string(&sid); + + /* Get member list for user on this machine */ + snprintf(s,180,"\\SAM\\Domains\\Builtin\\Aliases\\Members\\%s\\%08X",sidstr,rid); + + free(sidstr); + + /* 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 = trav_path(hive[H_SAM], 0, s, 0); + if (!nk) { + /* 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("get_user_grpids: Cannot find RID under computer SID <%s>\n",s); + return(NULL); + } + nk += 4; + count = get_val_type(hive[H_SAM],nk,"@",TPF_VK_EXACT); + if (count == -1) { + printf("get_user_grpids: Cannot find value <%s\\@>\n",s); + return(NULL); + } + + // printf("get_user_grpids: User is member of %d groups:\n",count); + + /* This is the data size */ + size = get_val_len(hive[H_SAM],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("get_user_grpids: DEBUG: Size is not 4 * count! May not matter anyway. Continuing..\n"); + } + + m = get_val2buf(hive[H_SAM], NULL, nk, "@", 0, TPF_VK_EXACT); + if (!m) { + printf("get_user_grpids: Could not get value data! Giving up.\n"); + return(NULL); + } + + return(m); +} + +/* 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 put_user_grpids(int rid, struct keyval *val) +{ + char s[200]; + struct sid_binary sid; + char *sidstr; + + int newcount = 0; + int nk = 0; + int count = 0; + + if (!rid || (H_SAM < 0)) return(0); + + if (!val || !val->len) return(0); + + if (!get_machine_sid((char *)&sid)) { + fprintf(stderr,"put_user_grpids: Could not find machine SID\n"); + return(0); + } + + sidstr = sid_to_string(&sid); + + /* Get member list for user on this machine */ + snprintf(s,180,"\\SAM\\Domains\\Builtin\\Aliases\\Members\\%s\\%08X",sidstr,rid); + + free(sidstr); + + /* Now, the TYPE field is the number of groups the user is member of */ + + nk = trav_path(hive[H_SAM], 0, s, 0); + if (!nk) { + /* 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("put_user_grpids: Cannot find RID under computer SID <%s>\n",s); + return(0); + } + + nk += 4; + + count = get_val_type(hive[H_SAM],nk,"@",TPF_VK_EXACT); + if (count == -1) { + printf("put_user_grpids: Cannot find value <%s\\@>\n",s); + return(1); + } + + if (gverbose) printf("put_user_grpids: User was member of %d groups:\n",count); + + /* This is the data size */ + /* It should be 4 bytes for each group */ + + newcount = val->len >> 2; + + if (gverbose) printf("Data size %d bytes.\n",val->len); + if (val->len != newcount << 2) { + printf("set_user_grpids: DEBUG: Size is not 4 * count! May not matter anyway. Continuing..\n"); + } + + if (gverbose) printf("put_user_grpids: User is NOW member of %d groups:\n",newcount); + + set_val_type(hive[H_SAM],nk,"@",TPF_VK_EXACT,newcount); + + if (!put_buf2val(hive[H_SAM], val, nk, "@", 0, TPF_VK_EXACT) ) { + printf("put_user_grpids: Could not set reg value data!\n"); + return(0); + } + + return(1); +} + + + + + +/* 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 list_user_groups(int rid, int check) +{ + char g[200]; + + 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 || (H_SAM < 0)) return(0); + + m = get_user_grpids(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) { + snprintf(g,180,"\\SAM\\Domains\\Builtin\\Aliases\\%08X\\C",grp); + c = get_val2buf(hive[H_SAM], NULL, 0, g, 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); + + 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); +} + + + + +/* Add user to a group + * rid = user RID + * grp = group ID + * return true if success + */ + +int add_user_to_grp(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 || (H_SAM < 0)) return(0); + + /* Build user SID (add RID to machine SID) */ + + if (!get_machine_sid((char *)&msid)) { + fprintf(stderr,"get_user_grpids: 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 = 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 *)get_user_grpids(rid); + + if (!usrgrplist) { + printf("add_user_to_grp: user # %x not found!\n",rid); + return(0); + } + + + members = get_grp_members_sid(grp, &sarray); + + if (!sarray) { + printf("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 = 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 = sid_cmp(sarray[o].sidptr, usid); /* Compare slot with new SID */ + if (gverbose) printf("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 = sid_to_string(narray[o].sidptr); + printf(" Member # %d = <%s>\n", o, str); + FREE(str); + } + } + + /* Write new lists back to registry */ + if (!put_user_grpids(rid, (struct keyval *)newusrgrplist)) { + fprintf(stderr, "add_user_to_grp: failed storing users group list\n"); + } else if (!put_grp_members_sid(grp, narray)) { + fprintf(stderr,"add_user_to_grp: failed storing groups user list\n"); + put_user_grpids(rid, (struct keyval *)usrgrplist); /* Try to roll back */ + } + + FREE(usrgrplist); + FREE(newusrgrplist); + 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 remove_user_from_grp(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 || (H_SAM < 0)) return(0); + + /* Build user SID (add RID to machine SID) */ + + if (!get_machine_sid((char *)&msid)) { + fprintf(stderr,"get_user_grpids: 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 = 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 *)get_user_grpids(rid); + + if (!usrgrplist) { + printf("remove_user_from_grp: user # %x not found!\n",rid); + return(0); + } + + + members = get_grp_members_sid(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 = 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 = 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 = sid_to_string(narray[o].sidptr); + printf(" Member # %d = <%s>\n", o, str); + FREE(str); + } + } + + /* Write new lists back to registry */ + if (!put_user_grpids(rid, (struct keyval *)newusrgrplist)) { + fprintf(stderr, "remove_user_from_grp: failed storing users group list\n"); + } else if (!put_grp_members_sid(grp, narray)) { + fprintf(stderr,"remvoe_user_from_grp: failed storing groups user list\n"); + put_user_grpids(rid, (struct keyval *)usrgrplist); /* Try to roll back */ + } + + FREE(usrgrplist); + FREE(newusrgrplist); + free_sid_array(narray); + FREE(sarray); /* Pointers was copied to narray, and freed above, just free the array here */ + + return(1); + +} + + + + +/* Promote user into administrators group (group ID 0x220) + * And remove from all others... + * rid - users rid + * no returns yet + */ + +void promote_user(int rid) +{ + + char yn[5]; + + if (!rid || (H_SAM < 0)) return; + + printf("\n=== PROMOTE USER\n\n"); + printf("Will add the user to the administrator group (0x220)\n" + "and to the users group (0x221). That should usually be\n" + "what is needed to log in and get administrator rights.\n" + "Also, remove the user from the guest group (0x222), since\n" + "it may forbid logins.\n\n"); + printf("(To add or remove user from other groups, please other menu selections)\n\n"); + printf("Note: You may get some errors if the user is already member of some\n" + "of these groups, but that is no problem.\n\n"); + + fmyinput("Do it? (y/n) [n] : ", yn, 3); + + if (*yn == 'y') { + + printf("* Adding to 0x220 (Administrators) ...\n"); + add_user_to_grp(rid, 0x220); + printf("* Adding to 0x221 (Users) ...\n"); + add_user_to_grp(rid, 0x221); + + printf("* Removing from 0x222 (Guests) ...\n"); + remove_user_from_grp(rid, 0x222); + + printf("\nPromotion DONE!\n"); + + } else { + printf("Nothing done, going back..\n"); + } + +} + + +void interactive_remusrgrp(int rid) +{ + char inp[20]; + int grp, l; + + printf("\n=== REMOVE USER FROM A GROUP\n"); + + list_user_groups(rid,0); + + printf("\nPlease enter group number (for example 220), or 0 to go back\n"); + l = fmyinput("Group number? : ",inp,16); + sscanf(inp, "%x", &grp); + + if (!grp) { + printf("Going back..\n"); + return; + } + + printf("Removing user from group 0x%x (%d)\n",grp,grp); + printf("Error messages if the user was not member of the group are harmless\n\n"); + + remove_user_from_grp(rid, grp); + + printf("\nFinished removing user from group\n\n"); + +} + + +void interactive_addusrgrp(int rid) +{ + char inp[20]; + int grp, l; + + printf("\n == ADD USER TO A GROUP\n"); + + list_groups(0); + + printf("\nPlease enter group number (for example 220), or 0 to go back\n"); + l = fmyinput("Group number? : ",inp,16); + sscanf(inp, "%x", &grp); + + if (!grp) { + printf("Going back..\n"); + return; + } + + printf("Adding user to group 0x%x (%d)\n",grp,grp); + printf("Error messages if the user was already member of the group are harmless\n\n"); + + add_user_to_grp(rid, grp); + + printf("\nFinished adding user to group\n\n"); + + +} + + +/* Decode the V-struct, and change the password + * vofs - offset into SAM buffer, start of V struct + * rid - the users RID, required for the DES decrypt stage + * + * Some of this is ripped & modified from pwdump by Jeremy Allison + * + */ +char *change_pw(char *buf, int rid, int vlen, int stat) +{ + + int pl; + char *vp; + static char username[128],fullname[128]; + char comment[128], homedir[128], newp[20]; + int username_offset,username_len; + int fullname_offset,fullname_len; + int comment_offset,comment_len; + int homedir_offset,homedir_len; + int ntpw_len,lmpw_len,ntpw_offs,lmpw_offs; + int dontchange = 0; + unsigned short acb; + struct user_V *v; + +#ifdef DOCRYPT + int i; + char md4[32],lanman[32]; + char newunipw[34], despw[20], newlanpw[16], newlandes[20]; + des_key_schedule ks1, ks2; + des_cblock deskey1, deskey2; + MD4_CTX context; + unsigned char digest[16]; + uchar x1[] = {0x4B,0x47,0x53,0x21,0x40,0x23,0x24,0x25}; +#endif + + + v = (struct user_V *)buf; + vp = buf; + + username_offset = v->username_ofs; + username_len = v->username_len; + fullname_offset = v->fullname_ofs; + fullname_len = v->fullname_len; + comment_offset = v->comment_ofs; + comment_len = v->comment_len; + homedir_offset = v->homedir_ofs; + homedir_len = v->homedir_len; + lmpw_offs = v->lmpw_ofs; + lmpw_len = v->lmpw_len; + ntpw_offs = v->ntpw_ofs; + ntpw_len = v->ntpw_len; + + if (!rid) { + printf("No RID given. Unable to change passwords..\n"); + return(0); + } + + 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; + *comment = 0; + *homedir = 0; + + if(username_len <= 0 || username_len > vlen || + username_offset <= 0 || username_offset >= vlen || + comment_len < 0 || comment_len > vlen || + fullname_len < 0 || fullname_len > vlen || + homedir_offset < 0 || homedir_offset >= vlen || + comment_offset < 0 || comment_offset >= vlen || + lmpw_offs < 0 || lmpw_offs >= vlen) + { + if (stat != 1) printf("change_pw: Not a legal V struct? (negative struct lengths)\n"); + return(NULL); + } + + /* Offsets in top of struct is relative to end of pointers, adjust */ + username_offset += 0xCC; + fullname_offset += 0xCC; + comment_offset += 0xCC; + homedir_offset += 0xCC; + ntpw_offs += 0xCC; + lmpw_offs += 0xCC; + + cheap_uni2ascii(vp + username_offset,username,username_len); + cheap_uni2ascii(vp + fullname_offset,fullname,fullname_len); + cheap_uni2ascii(vp + comment_offset,comment,comment_len); + cheap_uni2ascii(vp + homedir_offset,homedir,homedir_len); + +#if 0 + /* Reset hash-lengths to 16 if syskey has been reset */ + if (syskeyreset && ntpw_len > 16 && !stat) { + ntpw_len = 16; + lmpw_len = 16; + ntpw_offs -= 4; + (unsigned int)*(vp+0xa8) = ntpw_offs - 0xcc; + *(vp + 0xa0) = 16; + *(vp + 0xac) = 16; + } +#endif + + printf("\nRID : %04d [%04x]\n",rid,rid); + printf("Username: %s\n",username); + printf("fullname: %s\n",fullname); + printf("comment : %s\n",comment); + printf("homedir : %s\n\n",homedir); + + list_user_groups(rid,0); + printf("\n"); + + acb = handle_F(rid,1); + + if (lmpw_len < 16 && gverbose) { + printf("** LANMAN password not set. User MAY have a blank password.\n** Usually safe to continue. Normal in Vista\n"); + } + + if (ntpw_len < 16) { + printf("** No NT MD4 hash found. This user probably has a BLANK password!\n"); + if (lmpw_len < 16) { + printf("** No LANMAN hash found either. Try login with no password!\n"); + dontchange = 1; + } else { + printf("** LANMAN password IS however set. Will now install new password as NT pass instead.\n"); + printf("** NOTE: Continue at own risk!\n"); + ntpw_offs = lmpw_offs; + *(vp+0xa8) = ntpw_offs - 0xcc; + ntpw_len = 16; + lmpw_len = 0; + } + } + + if (gverbose) { + hexprnt("Crypted NT pw: ",(unsigned char *)(vp+ntpw_offs),16); + hexprnt("Crypted LM pw: ",(unsigned char *)(vp+lmpw_offs),16); + } + +#ifdef DOCRYPTO + /* Get the two decrpt keys. */ + sid_to_key1(rid,(unsigned char *)deskey1); + des_set_key((des_cblock *)deskey1,ks1); + sid_to_key2(rid,(unsigned char *)deskey2); + des_set_key((des_cblock *)deskey2,ks2); + + /* Decrypt the NT md4 password hash as two 8 byte blocks. */ + des_ecb_encrypt((des_cblock *)(vp+ntpw_offs ), + (des_cblock *)md4, ks1, DES_DECRYPT); + des_ecb_encrypt((des_cblock *)(vp+ntpw_offs + 8), + (des_cblock *)&md4[8], ks2, DES_DECRYPT); + + /* Decrypt the lanman password hash as two 8 byte blocks. */ + des_ecb_encrypt((des_cblock *)(vp+lmpw_offs), + (des_cblock *)lanman, ks1, DES_DECRYPT); + des_ecb_encrypt((des_cblock *)(vp+lmpw_offs + 8), + (des_cblock *)&lanman[8], ks2, DES_DECRYPT); + + if (gverbose) { + hexprnt("MD4 hash : ",(unsigned char *)md4,16); + hexprnt("LANMAN hash : ",(unsigned char *)lanman,16); + } +#endif /* DOCRYPTO */ + + + printf("\n- - - - User Edit Menu:\n"); + printf(" 1 - Clear (blank) user password\n"); + printf("%s2 - Unlock and enable user account%s\n", (acb & 0x8000) ? " " : "(", + (acb & 0x8000) ? " [probably locked now]" : ") [seems unlocked already]"); + printf(" 3 - Promote user (make user an administrator)\n"); + printf(" 4 - Add user to a group\n"); + printf(" 5 - Remove user from a group\n"); +#ifdef DOCRYPTO + printf(" 9 - Edit (set new) user password (careful with this on XP or Vista)\n"); +#endif + printf(" q - Quit editing user, back to user select\n"); + + pl = fmyinput("Select: [q] > ",newp,16); + + if ( (pl < 1) || (*newp == 'q') || (*newp == 'Q')) return(0); + + + if (*newp == '2') { + acb = handle_F(rid,2); + return(username); + } + + if (*newp == '3') { + promote_user(rid); + return(username); + } + + if (*newp == '4') { + interactive_addusrgrp(rid); + return(username); + } + + if (*newp == '5') { + interactive_remusrgrp(rid); + return(username); + } + + +#ifdef DOCRYPT + if (*newp == '9') { /* Set new password */ + + if (dontchange) { + printf("Sorry, unable to edit since password seems blank already (thus no space for it)\n"); + return(0); + } + + pl = fmyinput("New Password: ",newp,16); + + if (pl < 1) { + printf("No change.\n"); + return(0); + } + + cheap_ascii2uni(newp,newunipw,pl); + + make_lanmpw(newp,newlanpw,pl); + + /* printf("Ucase Lanman: %s\n",newlanpw); */ + + MD4Init (&context); + MD4Update (&context, newunipw, pl<<1); + MD4Final (digest, &context); + + if (gverbose) hexprnt("\nNEW MD4 hash : ",digest,16); + + E1((uchar *)newlanpw, x1, (uchar *)lanman); + E1((uchar *)newlanpw+7, x1, (uchar *)lanman+8); + + if (gverbose) hexprnt("NEW LANMAN hash : ",(unsigned char *)lanman,16); + + /* Encrypt the NT md4 password hash as two 8 byte blocks. */ + des_ecb_encrypt((des_cblock *)digest, + (des_cblock *)despw, ks1, DES_ENCRYPT); + des_ecb_encrypt((des_cblock *)(digest+8), + (des_cblock *)&despw[8], ks2, DES_ENCRYPT); + + des_ecb_encrypt((des_cblock *)lanman, + (des_cblock *)newlandes, ks1, DES_ENCRYPT); + des_ecb_encrypt((des_cblock *)(lanman+8), + (des_cblock *)&newlandes[8], ks2, DES_ENCRYPT); + + if (gverbose) { + hexprnt("NEW DES crypt : ",(unsigned char *)despw,16); + hexprnt("NEW LANMAN crypt: ",(unsigned char *)newlandes,16); + } + + /* Reset hash length to 16 if syskey enabled, this will cause + * a conversion to syskey-hashes upon next boot */ + if (syskeyreset && ntpw_len > 16) { + ntpw_len = 16; + lmpw_len = 16; + ntpw_offs -= 4; + *(vp+0xa8) = (unsigned int)(ntpw_offs - 0xcc); + *(vp + 0xa0) = 16; + *(vp + 0xac) = 16; + } + + for (i = 0; i < 16; i++) { + *(vp+ntpw_offs+i) = (unsigned char)despw[i]; + if (lmpw_len >= 16) *(vp+lmpw_offs+i) = (unsigned char)newlandes[i]; + } + + printf("Password changed!\n"); + + + } /* new password */ +#endif /* DOCRYPT */ + + if (pl == 1 && *newp == '1') { + /* Setting hash lengths to zero seems to make NT think it is blank + * However, since we cant cut the previous hash bytes out of the V value + * due to missing resize-support of values, it may leak about 40 bytes + * each time we do this. + */ + v->ntpw_len = 0; + v->lmpw_len = 0; + + printf("Password cleared!\n"); + } + +#if 0 + hexprnt("Pw in buffer: ",(vp+ntpw_offs),16); + hexprnt("Lm in buffer: ",(vp+lmpw_offs),16); +#endif + dirty = 1; + return(username); +} + + +/* Registry edit wrapper */ + +void mainloop(void) +{ + regedit_interactive(hive, no_hives); +} + + +/* Iterate over users in SAM file, and do things with it + * automode - if null, just list, else handle auto change + * f = reset first user that is in admin group + * a = reset all users in admin group + * 0x1f4 (built-in administrator account) will only be reset + * if no other users are found to be admin group + */ + +int list_users(int readable) +{ + char s[200]; + struct keyval *v; + int nkofs /* ,vkofs */ ; + int rid; + int count = 0, countri = 0; + int ntpw_len; + + unsigned short acb; + + struct user_V *vpwd; + struct ex_data ex; + + if (H_SAM < 0) return(1); + nkofs = trav_path(hive[H_SAM], 0,"\\SAM\\Domains\\Account\\Users\\Names\\",0); + if (!nkofs) { + printf("list_users: Cannot find usernames in registry! (is this a SAM-hive?)\n"); + return(1); + } + + if (readable) printf("| RID -|---------- Username ------------| Admin? |- Lock? --|\n"); + + while ((ex_next_n(hive[H_SAM], nkofs+4, &count, &countri, &ex) > 0)) { + + /* Extract the value out of the username-key, value is RID */ + snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\Names\\%s\\@",ex.name); + rid = get_dword(hive[H_SAM], 0, s, TPF_VK_EXACT|TPF_VK_SHORT); + if (rid == 500) strncpy(admuser,ex.name,128); /* Copy out admin-name */ + + /* 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(hive[H_SAM], NULL, 0, s, REG_BINARY, TPF_VK_EXACT); + if (!v) { + printf("Cannot find value <%s>\n",s); + return(1); + } + + if (v->len < 0xcc) { + printf("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 = handle_F(rid,0); + if (readable) { + printf("| %04x | %-30.30s | %-6s | %-8s |\n", + rid, ex.name, (list_user_groups(rid,1) ? "ADMIN" : "") , ( acb & 0x8000 ? "dis/lock" : (ntpw_len < 16) ? "*BLANK*" : "") ); + } else { + printf("%04x:%s:%d:%x:%x\n", + rid, ex.name, list_user_groups(rid,1) , acb, ntpw_len ); + } + + + // change_pw( (char *)&v->data , rid, v->len, (*automode == 'l') ? 2 : 1); + + } + FREE(v); + FREE(ex.name); + } + return(0); +} + + +/* Find a username in the SAM registry, then get it's V-value, + * and feed it to the password changer. + */ + +void find_n_change(char *username) +{ + char s[200]; + struct keyval *v; + int rid = 0; + + if ((H_SAM < 0) || (!username)) return; + if (*username == '0' && *(username+1) == 'x') sscanf(username,"%i",&rid); + + if (!rid) { /* Look up username */ + /* Extract the unnamed value out of the username-key, value is RID */ + snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\Names\\%s\\@",username); + rid = get_dword(hive[H_SAM],0,s, TPF_VK_EXACT|TPF_VK_SHORT); + if (rid == -1) { + printf("Cannot find value <%s>\n",s); + return; + } + } + + /* + printf("Username: %s, RID = %d (0x%0x)\n",username,rid,rid); + */ + + /* 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(hive[H_SAM], NULL, 0, s, REG_BINARY, TPF_VK_EXACT); + if (!v) { + printf("Cannot find value <%s>\n",s); + return; + } + + if (v->len < 0xcc) { + printf("Value <%s> is too short (only %d bytes) to be a SAM user V-struct!\n", + s, v->len); + } else { + change_pw( (char *)&v->data , rid, v->len, 0); + if (dirty) { + if (!(put_buf2val(hive[H_SAM], v, 0, s, REG_BINARY, TPF_VK_EXACT))) { + printf("Failed to write updated <%s> to registry! Password change not completed!\n",s); + } + } + } + FREE(v); +} + +/* Check for presence of syskey and possibly disable it if + * user wants it. + * This is tricky, and extremely undocumented! + * See docs for more info on what's going on when syskey is installed + */ + +#undef LSADATA + +void handle_syskey(void) +{ + + /* This is \SAM\Domains\Account\F */ + struct samkeyf { + char unknown[0x50]; /* 0x0000 - Unknown. May be machine SID */ + char unknown2[0x14]; + char syskeymode; /* 0x0064 - Type/mode of syskey in use */ + char syskeyflags1[0xb]; /* 0x0065 - More flags/settings */ + char syskeyobf[0x30]; /* 0x0070 - This may very well be the obfuscated syskey */ + }; /* There may be more, usually 8 null-bytes? */ + + /* Security\Policy\SecretEncryptionKey\@, only on NT5 */ + /* Probably contains some keyinfo for syskey. Second DWORD seems to be syskeymode */ + struct secpoldata { + int unknown1; /* Some kind of flag? usually 1 */ + int syskeymode; /* Is this what we're looking for? */ + int unknown2; /* Usually 0? */ + char keydata[0x40]; /* Some kind of scrambled keydata? */ + }; + +#ifdef LSADATA + /* SYSTEM\CurrentControlSet\Control\Lsa\Data, only on NT5?? */ + /* Probably contains some keyinfo for syskey. Byte 0x34 seems to be mode */ + struct lsadata { + char keydata[0x34]; /* Key information */ + int syskeymode; /* Is this what we're looking for? */ + }; +#endif + + /* void *fdata; */ + struct samkeyf *ff = NULL; + struct secpoldata *sf = NULL; + /* struct lsadata *ld = NULL; */ + int /* len, */ i,secboot, samfmode, secmode /* , ldmode */ ; + struct keyval *samf, *secpol /* , *lsad */ ; + char *syskeytypes[4] = { "off", "key-in-registry", "enter-passphrase", "key-on-floppy" }; + char yn[5]; + + printf("\n---------------------> SYSKEY CHECK <-----------------------\n"); + + + if (H_SAM < 0) { + printf("ERROR: SAM hive not loaded!\n"); + return; + } + samf = get_val2buf(hive[H_SAM], NULL, 0, "\\SAM\\Domains\\Account\\F", REG_BINARY, TPF_VK_EXACT); + + if (samf && samf->len > 0x70 ) { + ff = (struct samkeyf *)&samf->data; + samfmode = ff->syskeymode; + } else { + samfmode = -1; + } + + secboot = -1; + if (H_SYS >= 0) { + secboot = get_dword(hive[H_SYS], 0, "\\ControlSet001\\Control\\Lsa\\SecureBoot", TPF_VK_EXACT ); + } + + secmode = -1; + if (H_SEC >=0) { + secpol = get_val2buf(hive[H_SEC], NULL, 0, "\\Policy\\PolSecretEncryptionKey\\@", REG_NONE, TPF_VK_EXACT); + if (secpol) { /* Will not be found in NT 4, take care of that */ + sf = (struct secpoldata *)&secpol->data; + secmode = sf->syskeymode; + } + } + +#ifdef LSADATA + lsad = get_val2buf(hive[H_SYS], NULL, 0, "\\ControlSet001\\Control\\Lsa\\Data\\Pattern", REG_BINARY, TPF_VK_EXACT); + + if (lsad && lsad->len >= 0x38) { + ld = (struct lsadata *)&lsad->data; + ldmode = ld->syskeymode; + } else { + ldmode = -1; + } +#endif + + printf("SYSTEM SecureBoot : %d -> %s\n", secboot, + (secboot < 0 || secboot > 3) ? "Not Set (not installed, good!)" : syskeytypes[secboot]); + printf("SAM Account\\F : %d -> %s\n", samfmode, + (samfmode < 0 || samfmode > 3) ? "Not Set" : syskeytypes[samfmode]); + printf("SECURITY PolSecretEncryptionKey: %d -> %s\n", secmode, + (secmode < 0 || secmode > 3) ? "Not Set (OK if this is NT4)" : syskeytypes[secmode]); + +#ifdef LSADATA + printf("SYSTEM LsaData : %d -> %s\n\n", ldmode, + (ldmode < 0 || ldmode > 3) ? "Not Set (strange?)" : syskeytypes[ldmode]); +#endif + + if (secboot != samfmode && secboot != -1) { + printf("WARNING: Mismatch in syskey settings in SAM and SYSTEM!\n"); + printf("WARNING: It may be dangerous to continue (however, resetting syskey\n"); + printf(" may very well fix the problem)\n"); + } + + if (secboot > 0 || samfmode > 0) { + printf("\n***************** SYSKEY IS ENABLED! **************\n"); + printf("This installation very likely has the syskey passwordhash-obfuscator installed\n"); + printf("It's currently in mode = %d, %s-mode\n",secboot, + (secboot < 0 || secboot > 3) ? "Unknown" : syskeytypes[secboot]); + + if (no_hives < 2) { + printf("\nSYSTEM (and possibly SECURITY) hives not loaded, unable to disable syskey!\n"); + printf("Please start the program with at least SAM & SYSTEM-hive filenames as arguments!\n\n"); + return; + } + printf("SYSKEY is on! However, DO NOT DISABLE IT UNLESS YOU HAVE TO!\n"); + printf("This program can change passwords even if syskey is on, however\n"); + printf("if you have lost the key-floppy or passphrase you can turn it off,\n"); + printf("but please read the docs first!!!\n"); + printf("\n** IF YOU DON'T KNOW WHAT SYSKEY IS YOU DO NOT NEED TO SWITCH IT OFF!**\n"); + printf("NOTE: On WINDOWS 2000 and XP it will not be possible\n"); + printf("to turn it on again! (and other problems may also show..)\n\n"); + printf("NOTE: Disabling syskey will invalidate ALL\n"); + printf("passwords, requiring them to be reset. You should at least reset the\n"); + printf("administrator password using this program, then the rest ought to be\n"); + printf("done from NT.\n"); + printf("\nEXTREME WARNING: Do not try this on Vista or Win 7, it will go into endless re-boots\n\n"); + + fmyinput("\nDo you really wish to disable SYSKEY? (y/n) [n] ",yn,2); + if (*yn == 'y') { + /* Reset SAM syskey infostruct, fill with zeroes */ + if (ff) { + ff->syskeymode = 0; + + for (i = 0; i < 0x3b; i++) { + ff->syskeyflags1[i] = 0; + } + + put_buf2val(hive[H_SAM], samf, 0, "\\SAM\\Domains\\Account\\F", REG_BINARY, TPF_VK_EXACT); + + } + /* Reset SECURITY infostruct (if any) */ + if (sf) { + memset(sf, 0, secpol->len); + sf->syskeymode = 0; + + put_buf2val(hive[H_SEC], secpol, 0, "\\Policy\\PolSecretEncryptionKey\\@", REG_BINARY, TPF_VK_EXACT); + + } + +#if LSADATA + if (ld) { + + ld->syskeymode = 0; + + put_buf2val(hive[H_SYS], lsad, 0, "\\ControlSet001\\Control\\Lsa\\Data\\Pattern", REG_BINARY, TPF_VK_EXACT); + + } +#endif + + /* And SYSTEM SecureBoot parameter */ + + put_dword(hive[H_SYS], 0, "\\ControlSet001\\Control\\Lsa\\SecureBoot", TPF_VK_EXACT, 0); + + dirty = 1; + syskeyreset = 1; + printf("Updating passwordhash-lengths..\n"); + list_users(1); + printf("* SYSKEY RESET!\nNow please set new administrator password!\n"); + } else { + + syskeyreset = 1; + } + } else { + printf("Syskey not installed!\n"); + return; + } + +} + + +/* Interactive user edit */ +void useredit(void) +{ + char iwho[100]; + int il; + + printf("\n\n===== chntpw Edit User Info & Passwords ====\n\n"); + + if (H_SAM < 0) { + printf("ERROR: SAM registry file (which contains user data) is not loaded!\n\n"); + return; + } + + + list_users(1); + + while (1) { + printf("\nSelect: ! - quit, . - list users, 0x - User with RID (hex)\n"); + printf("or simply enter the username to change: [%s] ",admuser); + il = fmyinput("",iwho,32); + if (il == 1 && *iwho == '.') { printf("\n"); list_users(1); continue; } + if (il == 1 && *iwho == '!') return; + if (il == 0) strcpy(iwho,admuser); + find_n_change(iwho); + } + +} + + +void recoveryconsole() +{ + + int cmd = 0; + int sec = 0; + static char *scpath = "\\Microsoft\\Windows NT\\CurrentVersion\\Setup\\RecoveryConsole\\SetCommand"; + static char *slpath = "\\Microsoft\\Windows NT\\CurrentVersion\\Setup\\RecoveryConsole\\SecurityLevel"; + char yn[5]; + + if (H_SOF < 0) { + printf("\nSOFTWARE-hive not loaded, and there's where RecoveryConsole settings are..\n"); + return; + } + + cmd = get_dword(hive[H_SOF],0,scpath,TPF_VK_EXACT); + sec = get_dword(hive[H_SOF],0,slpath,TPF_VK_EXACT); + + if (cmd == -1 && sec == -1) { + printf("\nDid not find registry entries for RecoveryConsole.\n(RecoveryConsole is only in Windows 2000 and XP)\n"); + return; + } + + printf("\nRecoveryConsole:\n- Extended SET command is \t%s\n", cmd>0 ? "ENABLED (1)" : "DISABLED (0)"); + printf("- Administrator password login: %s\n", sec>0 ? "SKIPPED (1)" : "ENFORCED (0)"); + + fmyinput("\nDo you want to change it? (y/n) [n] ",yn,2); + if (*yn == 'y') { + cmd ^= 1; + sec ^= 1; + if (!put_dword(hive[0], 0, scpath, TPF_VK_EXACT, cmd)) printf("Update of SET level failed registry edit\n"); + if (!put_dword(hive[0], 0, slpath, TPF_VK_EXACT, sec)) printf("Update of login level failed registry edit\n"); + printf("Done!\n"); + } + +} + + +/* Interactive menu system */ + +void interactive(void) +{ + int il; + char inbuf[20]; + + while(1) { + printf("\n\n<>========<> chntpw Main Interactive Menu <>========<>\n\n" + "Loaded hives:"); + for (il = 0; il < no_hives; il++) { + printf(" <%s>",hive[il]->filename); + } + + printf("\n\n"); + + /* Make menu selection depending on what is loaded + but it is still possible to select even if not shown */ + + if (H_SAM >= 0) { + printf(" 1 - Edit user data and passwords\n"); + printf(" 2 - List groups\n"); + } + if (H_SOF >= 0) { + printf(" 3 - RecoveryConsole settings\n"); + printf(" 4 - Show product key (DigitalProductID)\n"); + } +#if 0 + if (H_SAM >= 0 && H_SYS >= 0 && H_SEC >= 0) { + printf(" 8 - Syskey status & change\n"); + } +#endif + + printf(" - - -\n" + " 9 - Registry editor, now with full write support!\n" + " q - Quit (you will be asked if there is something to save)\n" + "\n\n"); + + il = fmyinput("What to do? [1] -> ", inbuf, 10); + + if (!il) useredit(); + if (il) { + switch(inbuf[0]) { + case '1': useredit(); break; + case '2': list_groups(0); break; + case '3': recoveryconsole(); break; + case '4': cat_dpi(hive[H_SOF],0,"\\Microsoft\\Windows NT\\CurrentVersion\\DigitalProductId"); break; + case '8': handle_syskey(); break; + case '9': mainloop(); break; + case 'q': return; break; + } + } + } +} + + +int cmd_usrgrp(char *user, char *grp, int what) +{ + int numgrp; + int rid = 0; + char s[200]; + + numgrp = strtol(grp, NULL, 0); + + printf("numgrp = %d (0x%x)\n", numgrp, numgrp); + + if ((H_SAM < 0) || (!user)) return(1); + if (*user == '0' && *(user+1) == 'x') sscanf(user,"%i",&rid); + + if (!rid) { /* Look up username */ + /* Extract the unnamed value out of the username-key, value is RID */ + snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\Names\\%s\\@",user); + rid = get_dword(hive[H_SAM],0,s, TPF_VK_EXACT|TPF_VK_SHORT); + if (rid == -1) { + printf("User <%s> not found\n",user); + return(1); + } + } + + printf("Username: %s, RID = %d (0x%0x)\n",user,rid,rid); + + switch (what) { + case 0: return(add_user_to_grp(rid, numgrp)); break; + case 1: return(remove_user_from_grp(rid, numgrp)); break; + } + + + return(0); + +} + + +void do_automode(char *automode, char *who, char *grp) +{ + struct sid_binary sid; + char *sidstr; + +#if 1 + printf("DEBUG: do_automode start\n"); + printf("automode = %s\n",automode); + printf("who = %s\n",who); + printf("grp = %s\n",grp); +#endif + + switch (*automode) { + case 'l': list_users(0); break; + case 's': if(get_machine_sid((char *)&sid)) { sidstr = sid_to_string(&sid); puts(sidstr); FREE(sidstr);} break; + case 'g': list_groups(1); break; + case 'a': cmd_usrgrp(who, grp, 0); break; /* Add user to group */ + case 'r': cmd_usrgrp(who, grp, 1); break; /* Remove user from group */ + + } + + + // printf("DEBUG: do_automode end\n"); + +} + + +void usage(void) { + printf("chntpw: change password of a user in a Windows SAM file,\n" + "or invoke registry editor. Should handle both 32 and 64 bit windows and\n" + "all version from NT3.x to Win7\n" + "chntpw [OPTIONS] [systemfile] [securityfile] [otherreghive] [...]\n" + " -h This message\n" + " -u Username or RID to change, Administrator is default\n" + " -l list all users in SAM file\n" + " -i Interactive Menu system\n" + " -e Registry editor. Now with full write support!\n" + " -d Enter buffer debugger instead (hex editor), \n" + " -v Be a little more verbose (for debuging)\n" + " -L For scripts, write names of changed files to /tmp/changed\n" + " -N No allocation mode. Only same length overwrites possible (very safe mode)\n" + " -E No expand mode, do not expand hive file (safe mode)\n" + " -A Auto / Noninteractive. Do stuff without asking\n" + " -A F reset on first admin user (lowest RID)\n" + " -A A reset all admins\n" + " -A l list all users, output it parseable\n" + " -A g list groups\n" + " -A s show machine SID\n" + " -A a -u -u Add user to group\n" + " -A r -u -g Remove user from group\n" + + "\nUsernames can be given as name or RID (in hex with 0x first)\n" + " group IDs must be given as number, can be hex with 0x first\n" + "Example: chntpw -A a -u 0x3aa -g 1000 # Adds user with RID hex 0x3aa to group decimal 1000\n" + "\nSee readme file on how to get to the registry files, and what they are.\n" + "Source/binary freely distributable under GPL v2 license. See README for details.\n" + "NOTE: This program is somewhat hackish! You are on your own!\n" + ); +} + + +int main(int argc, char **argv) +{ + + int dodebug = 0, list = 0, inter = 0,edit = 0,il,d = 0, dd = 0, logchange = 0; + int mode = HMODE_INFO; + extern int /* opterr, */ optind; + extern char* optarg; + char *filename,c; + char *who = "Administrator"; + char *grp = NULL; + char iwho[100]; + char *automode = ""; + FILE *ch; /* Write out names of touched files to this */ + + char *options = "A:LENidehlvu:g:"; + + while((c=getopt(argc,argv,options)) > 0) { + switch(c) { + case 'd': dodebug = 1; break; + case 'e': edit = 1; break; + case 'L': logchange = 1; break; + case 'N': mode |= HMODE_NOALLOC; break; + case 'E': mode |= HMODE_NOEXPAND; break; + case 'l': list = 1; break; + case 'v': mode |= HMODE_VERBOSE; gverbose = 1; break; + case 'i': inter = 1; break; + case 'u': who = optarg; break; + case 'g': grp = optarg; break; + case 'A': automode = optarg; mode &= ~HMODE_INFO; break; + case 'h': usage(); exit(0); break; + default: usage(); exit(1); break; + } + } + + if (!*automode) printf("%s\n",chntpw_version); + + filename=argv[optind]; + if (!filename || !*filename) { + usage(); exit(1); + } + do { + if (!(hive[no_hives] = openHive(filename, + HMODE_RW|mode))) { + fprintf(stderr,"%s: Unable to open/read a hive, exiting..\n",argv[0]); + exit(1); + } + switch(hive[no_hives]->type) { + case HTYPE_SAM: H_SAM = no_hives; break; + case HTYPE_SOFTWARE: H_SOF = no_hives; break; + case HTYPE_SYSTEM: H_SYS = no_hives; break; + case HTYPE_SECURITY: H_SEC = no_hives; break; + } + no_hives++; + filename = argv[optind+no_hives]; + } while (filename && *filename && no_hives < MAX_HIVES); + +#if 0 + printf("user = %s\n",who); + printf("automode = %s\n",automode); +#endif + + if (dodebug) { + debugit(hive[0]->buffer,hive[0]->size); + } else if (*automode) { + check_get_samdata(0); + do_automode(automode, who, grp); + + } else if (inter) { + check_get_samdata(1); + interactive(); + } else if (edit) { + check_get_samdata(1); + mainloop(); + } else if (list) { + check_get_samdata(1); + list_users(1); + } else if (who) { + check_get_samdata(1); + find_n_change(who); + } + + + + + + if (list != 1) { + if (!*automode) printf("\nHives that have changed:\n # Name\n"); + for (il = 0; il < no_hives; il++) { + if (hive[il]->state & HMODE_DIRTY) { + if (!logchange && !*automode) printf("%2d <%s>",il,hive[il]->filename); + if (hive[il]->state & HMODE_DIDEXPAND) printf(" WARNING: File was expanded! Expermental! Use at own risk!\n"); + if (!*automode) printf("\n"); + + d = 1; + } + } + if (d) { + /* Only prompt user if logging of changed files has not been set */ + /* Thus we assume confirmations are done externally if they ask for a list of changes */ + if (!logchange && !*automode) fmyinput("Write hive files? (y/n) [n] : ",iwho,3); + if (*iwho == 'y' || logchange || *automode) { + if (logchange) { + ch = fopen("/tmp/changed","w"); + } + for (il = 0; il < no_hives; il++) { + if (hive[il]->state & HMODE_DIRTY) { + if (!*automode) printf("%2d <%s> - ",il,hive[il]->filename); + if (!writeHive(hive[il])) { + if (!*automode) printf("OK"); + if (hive[il]->state & HMODE_DIDEXPAND) printf(" WARNING: File was expanded! Expermental! Use at own risk!\n"); + if (!*automode) printf("\n"); + if (logchange) fprintf(ch,"%s ",hive[il]->filename); + dd = 2; + } + } + } + if (logchange) { + fprintf(ch,"\n"); + fclose(ch); + } + } else { + printf("Not written!\n\n"); + } + } else { + if (!*automode) printf("None!\n\n"); + } + } /* list only check */ + return(dd); +} diff --git a/chntpw.c b/chntpw.c index 06e7ac1..64296ee 100644 --- a/chntpw.c +++ b/chntpw.c @@ -2,9 +2,16 @@ * chntpw.c - Offline Password Edit Utility for Windows SAM database * * This program uses the "ntreg" library to load and access the registry, + * the "libsam" library for user / group handling * it's main purpose is to reset password based information. * It can also call the registry editor etc + * 2013-may: Added group add/remove in user edit (using new functions + * in sam library) + * 2013-apr: Changed around a bit on some features, chntpw is now + * mainly used for interactive edits. + * For automatic/scripted functions, use new programs: + * sampasswd and samusrgrp ! * 2011-apr: Command line options added for hive expansion safe mode * 2010-jun: Syskey not visible in menu, but is selectable (2) * 2010-apr: Interactive menu adapts to show most relevant @@ -33,7 +40,7 @@ * ***** * - * Copyright (c) 1997-2011 Petter Nordahl-Hagen. + * Copyright (c) 1997-2014 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 @@ -65,8 +72,17 @@ #include #include +/* Define DOCRYPTO in makefile to include cryptostuff to be able to change passwords to + * a new one. + * Changing passwords is seems not to be working reliably on XP and newer anyway. + * When not defined, only reset (nulling) of passwords available. + */ + +#ifdef DOCRYPTO #include #include +#endif + #define uchar u_char #define MD4Init MD4_Init #define MD4Update MD4_Update @@ -75,7 +91,7 @@ #include "ntreg.h" #include "sam.h" -const char chntpw_version[] = "chntpw version 0.99.6 110511 , (c) Petter N Hagen"; +const char chntpw_version[] = "chntpw version 1.00 140201, (c) Petter N Hagen"; extern char *val_types[REG_MAX+1]; @@ -102,14 +118,11 @@ int syskeyreset = 0; int dirty = 0; int max_sam_lock = 0; -/* - * of user with RID 500, because silly MS decided - * to localize the bloody admin-username!! AAAGHH! - */ -char admuser[129]="Administrator"; - /* ============================================================== */ + +#ifdef DOCRYPTO + /* Crypto-stuff & support for what we'll do in the V-value */ /* Zero out string for lanman passwd, then uppercase @@ -199,284 +212,108 @@ void E1(uchar *k, uchar *d, uchar *out) des_ecb_encrypt((des_cblock *)d,(des_cblock *)out, ks, DES_ENCRYPT); } - -/* Check if hive is SAM, and if it is, extract some - * global policy information from it, like lockout counts etc - */ - -void check_get_samdata(void) -{ - struct accountdb_F *f; - struct keyval *v; - - if (H_SAM >= 0) { - - /* Get users F value */ - v = get_val2buf(hive[H_SAM], NULL, 0, ACCOUNTDB_F_PATH, REG_BINARY, TPF_VK); - if (!v) { - printf("Login counts data not found in SAM\n"); - return; - } - printf("\n* SAM policy limits:\n"); - - f = (struct accountdb_F *)&v->data; - max_sam_lock = f->locklimit; - - printf("Failed logins before lockout is: %d\n",max_sam_lock); - printf("Minimum password length : %d\n",f->minpwlen); - printf("Password history count : %d\n",f->minpwlen); - - } -} - - -/* Try to decode and possibly change account lockout etc - * This is \SAM\Domains\Account\Users\\F - * It's size seems to always be 0x50. - * Params: RID - user ID, mode - 0 silent, 1 silent, 2 edit. - * Returns: ACB bits with high bit set if lockout count is >0 - */ - -short handle_F(int rid, int mode) -{ - - struct user_F *f; - char s[200]; - struct keyval *v; - unsigned short acb; - int b; - - if (H_SAM < 0) return(0); - - /* Get users F value */ - snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\F",rid); - v = get_val2buf(hive[H_SAM], 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); - } - - 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<failedcnt,max_sam_lock); - printf("Total login count: %u\n",f->logins); - } - - if (mode == 2) { - acb |= ACB_PWNOEXP; - acb &= ~ACB_DISABLED; - acb &= ~ACB_AUTOLOCK; - f->ACB_bits = acb; - f->failedcnt = 0; - put_buf2val(hive[H_SAM], v, 0, s, REG_BINARY,TPF_VK_EXACT); - printf("Unlocked!\n"); - } - return (acb | ( (f->failedcnt > 0 && f->failedcnt >= max_sam_lock)<<15 ) | (acb & ACB_AUTOLOCK)<<15 | (acb & ACB_DISABLED)<<15); -} - - -/* 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 list_user_groups(int rid, int check) -{ - char s[200]; - char g[200]; - char groupname[128]; - int nk = 0; - struct keyval *m = NULL, *c = NULL; - struct group_C *cd; - unsigned int *grps; - int count = 0, isadmin = 0; - int i, size, grp, grpnamoffs, grpnamlen; - - if (!rid || (H_SAM < 0)) return(0); - - - /* Get member list for user. Go for the first full SID, it's usually local computer I hope */ - snprintf(s,180,"\\SAM\\Domains\\Builtin\\Aliases\\Members\\S-1-5-21-\\%08X",rid); - /* 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 = trav_path(hive[H_SAM], 0, s, 0); - if (!nk) { - /* 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("list_user_groups(): Cannot find RID under computer SID <%s>\n",s); - return(0); - } - nk += 4; - count = get_val_type(hive[H_SAM],nk,"@",TPF_VK_EXACT); - if (count == -1) { - printf("list_user_groups(): Cannot find value <%s\\@>\n",s); - return(0); - } - - if (!check) printf("User is member of %d groups:\n",count); - - /* This is the data size */ - size = get_val_len(hive[H_SAM],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("list_user_groups(): DEBUG: Size is not 4 * count! May not matter anyway. Continuing..\n"); - } - - m = get_val2buf(hive[H_SAM], NULL, nk, "@", 0, TPF_VK_EXACT); - if (!m) { - printf("list_user_groups(): Could not get value data! Giving up.\n"); - return(0); - } - - grps = (unsigned int *)&m->data; - for (i = 0; i < count; i++) { - grp = grps[i]; - if (!check) printf("%08x ",grp); - - if (grp == 0x220) isadmin = 1; - - if (!check) { - snprintf(g,180,"\\SAM\\Domains\\Builtin\\Aliases\\%08X\\C",grp); - c = get_val2buf(hive[H_SAM], NULL, 0, g, 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); - - printf("= %s (which has %d members)\n",groupname,cd->grp_members); - - } else { - printf("Group info for %x not found!\n",grp); - } - } - } - return(isadmin); -} +#endif /* DOCRYPTO */ /* Promote user into administrators group (group ID 0x220) - * And remove from all others... * rid - users rid * no returns yet - * THIS IS VERY HACKISH YET */ void promote_user(int rid) { - char s[200]; - char g[200]; - int nk = 0; - struct keyval *m = NULL, *c = NULL; - struct keyval admember = { 4, 0x220 }; - unsigned int *grps, *gcnts; - int count = 0; - int i, size, grp; + + char yn[5]; if (!rid || (H_SAM < 0)) return; - - /* Get member list for user. Go for the first full SID, it's usually local computer I hope */ - snprintf(s,180,"\\SAM\\Domains\\Builtin\\Aliases\\Members\\S-1-5-21-\\%08X",rid); - /* 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 = trav_path(hive[H_SAM], 0, s, 0); - if (!nk) { - printf("Cannot find path <%s>\n",s); - return; + printf("\n=== PROMOTE USER\n\n"); + printf("Will add the user to the administrator group (0x220)\n" + "and to the users group (0x221). That should usually be\n" + "what is needed to log in and get administrator rights.\n" + "Also, remove the user from the guest group (0x222), since\n" + "it may forbid logins.\n\n"); + printf("(To add or remove user from other groups, please other menu selections)\n\n"); + printf("Note: You may get some errors if the user is already member of some\n" + "of these groups, but that is no problem.\n\n"); + + fmyinput("Do it? (y/n) [n] : ", yn, 3); + + if (*yn == 'y') { + + printf("* Adding to 0x220 (Administrators) ...\n"); + sam_add_user_to_grp(hive[H_SAM], rid, 0x220); + printf("* Adding to 0x221 (Users) ...\n"); + sam_add_user_to_grp(hive[H_SAM], rid, 0x221); + + printf("* Removing from 0x222 (Guests) ...\n"); + sam_remove_user_from_grp(hive[H_SAM], rid, 0x222); + + printf("\nPromotion DONE!\n"); + + } else { + printf("Nothing done, going back..\n"); } - nk += 4; - count = get_val_type(hive[H_SAM],nk,"@",TPF_VK); - if (count == -1) { - printf("Cannot find value <%s\\@>\n",s); - return; - } - printf("User is member of %d groups.\n",count); - - /* This is the data size */ - size = get_val_len(hive[H_SAM],nk,"@",TPF_VK); - - /* It should be 4 bytes for each group */ - if (size != count * 4) { - printf("DEBUG: Data size %d bytes.\n",size); - printf("DEBUG: Size is not 4 * count! May not matter anyway. Continuing..\n"); - } - - m = get_val2buf(hive[H_SAM], NULL, nk, "@", 0, TPF_VK); - if (!m) { - printf("Could not get value data! Giving up.\n"); - return; - } - - printf("User was member of groups: "); - grps = (unsigned int *)&m->data; - for (i = 0; i < count; i++) { - grp = grps[i]; - printf("%08x ",grp); - switch (grp) { - case 0x220: printf("=Administrators, "); break; - case 0x221: printf("=Users, "); break; - case 0x222: printf("=Guests, "); break; - default: printf(", "); break; - } - snprintf(g,180,"\\SAM\\Domains\\Builtin\\Aliases\\%08X\\C",grp); - c = get_val2buf(hive[H_SAM], NULL, 0, g, 0, TPF_VK); - if (c) { - gcnts = (unsigned int *)&c->data; - gcnts[0xc]--; - /* Decrease members counter */ - put_buf2val(hive[H_SAM], c, 0, g, 0, TPF_VK); - } else { - printf("Group info for %x not found!\n",grp); - } - } -#if 1 - printf("\nDeleting user memberships\n"); - - del_value(hive[H_SAM], nk, "@", TPF_VK); - - printf("Adding into only administrators:\n"); - - if (!add_value(hive[H_SAM], nk, "@", 1)) { /* Type is # of groups, here 1 */ - printf("Failed to add @ value to key\n"); - } -#endif - put_buf2val(hive[H_SAM], &admember, nk, "@", 0, TPF_VK); - - /* Now bumb up administrator groups count */ - c = get_val2buf(hive[H_SAM], NULL, 0, "\\SAM\\Domains\\Builtin\\Aliases\\00000220\\C", 0, TPF_VK); - if (!c) printf("Group info for 220 (adm) not found!\n"); - gcnts = (unsigned int *)&c->data; - gcnts[0xc]++; - put_buf2val(hive[H_SAM], c, 0, "\\SAM\\Domains\\Builtin\\Aliases\\00000220\\C", 0, TPF_VK); - - printf("Promotion DONE!\n"); - + } +void interactive_remusrgrp(int rid) +{ + char inp[20]; + int grp; + + printf("\n=== REMOVE USER FROM A GROUP\n"); + + sam_list_user_groups(hive[H_SAM], rid,0); + + printf("\nPlease enter group number (for example 220), or 0 to go back\n"); + fmyinput("Group number? : ",inp,16); + sscanf(inp, "%x", &grp); + + if (!grp) { + printf("Going back..\n"); + return; + } + + printf("Removing user from group 0x%x (%d)\n",grp,grp); + printf("Error messages if the user was not member of the group are harmless\n\n"); + + sam_remove_user_from_grp(hive[H_SAM], rid, grp); + + printf("\nFinished removing user from group\n\n"); + +} + + +void interactive_addusrgrp(int rid) +{ + char inp[20]; + int grp; + + printf("\n == ADD USER TO A GROUP\n"); + + sam_list_groups(hive[H_SAM], 0, 1); + + printf("\nPlease enter group number (for example 220), or 0 to go back\n"); + fmyinput("Group number? : ",inp,16); + sscanf(inp, "%x", &grp); + + if (!grp) { + printf("Going back..\n"); + return; + } + + printf("Adding user to group 0x%x (%d)\n",grp,grp); + printf("Error messages if the user was already member of the group are harmless\n\n"); + + sam_add_user_to_grp(hive[H_SAM], rid, grp); + + printf("\nFinished adding user to group\n\n"); + + +} /* Decode the V-struct, and change the password @@ -489,27 +326,32 @@ void promote_user(int rid) char *change_pw(char *buf, int rid, int vlen, int stat) { - uchar x1[] = {0x4B,0x47,0x53,0x21,0x40,0x23,0x24,0x25}; - char yn[4]; int pl; char *vp; static char username[128],fullname[128]; - char comment[128],homedir[128],md4[32],lanman[32]; - char newunipw[34], newp[20], despw[20], newlanpw[16], newlandes[20]; + char comment[128], homedir[128], newp[20]; int username_offset,username_len; int fullname_offset,fullname_len; int comment_offset,comment_len; int homedir_offset,homedir_len; - int ntpw_len,lmpw_len,ntpw_offs,lmpw_offs,i; - int dontchange = 0; + int ntpw_len,lmpw_len,ntpw_offs,lmpw_offs; + unsigned short acb; struct user_V *v; +#ifdef DOCRYPT + int dontchange = 0; + int i; + char md4[32],lanman[32]; + char newunipw[34], despw[20], newlanpw[16], newlandes[20]; des_key_schedule ks1, ks2; des_cblock deskey1, deskey2; - MD4_CTX context; unsigned char digest[16]; - unsigned short acb; + uchar x1[] = {0x4B,0x47,0x53,0x21,0x40,0x23,0x24,0x25}; +#endif + + + while (1) { /* Loop until quit input */ v = (struct user_V *)buf; vp = buf; @@ -579,23 +421,17 @@ char *change_pw(char *buf, int rid, int vlen, int stat) } #endif - if (stat) { - acb = handle_F(rid,0); - printf("| %04x | %-30.30s | %-6s | %-8s |\n", - rid, username, (list_user_groups(rid,1) ? "ADMIN" : "") , ( acb & 0x8000 ? "dis/lock" : (ntpw_len < 16) ? "*BLANK*" : "") ); - return(username); - } - + printf("================= USER EDIT ====================\n"); printf("\nRID : %04d [%04x]\n",rid,rid); printf("Username: %s\n",username); printf("fullname: %s\n",fullname); printf("comment : %s\n",comment); printf("homedir : %s\n\n",homedir); - list_user_groups(rid,0); + sam_list_user_groups(hive[H_SAM], rid,0); printf("\n"); - acb = handle_F(rid,1); + acb = sam_handle_accountbits(hive[H_SAM], rid,1); if (lmpw_len < 16 && gverbose) { printf("** LANMAN password not set. User MAY have a blank password.\n** Usually safe to continue. Normal in Vista\n"); @@ -604,8 +440,10 @@ char *change_pw(char *buf, int rid, int vlen, int stat) if (ntpw_len < 16) { printf("** No NT MD4 hash found. This user probably has a BLANK password!\n"); if (lmpw_len < 16) { - printf("** No LANMAN hash found either. Sorry, cannot change. Try login with no password!\n"); + printf("** No LANMAN hash found either. Try login with no password!\n"); +#ifdef DOCRYPTO dontchange = 1; +#endif } else { printf("** LANMAN password IS however set. Will now install new password as NT pass instead.\n"); printf("** NOTE: Continue at own risk!\n"); @@ -621,6 +459,7 @@ char *change_pw(char *buf, int rid, int vlen, int stat) hexprnt("Crypted LM pw: ",(unsigned char *)(vp+lmpw_offs),16); } +#ifdef DOCRYPTO /* Get the two decrpt keys. */ sid_to_key1(rid,(unsigned char *)deskey1); des_set_key((des_cblock *)deskey1,ks1); @@ -643,37 +482,49 @@ char *change_pw(char *buf, int rid, int vlen, int stat) hexprnt("MD4 hash : ",(unsigned char *)md4,16); hexprnt("LANMAN hash : ",(unsigned char *)lanman,16); } +#endif /* DOCRYPTO */ + printf("\n- - - - User Edit Menu:\n"); - printf(" 1 - Clear (blank) user password\n" - " 2 - Edit (set new) user password (careful with this on XP or Vista)\n" - " 3 - Promote user (make user an administrator)\n"); - printf("%s4 - Unlock and enable user account%s\n", (acb & 0x8000) ? " " : "(", + printf(" 1 - Clear (blank) user password\n"); + printf("%s2 - Unlock and enable user account%s\n", (acb & 0x8000) ? " " : "(", (acb & 0x8000) ? " [probably locked now]" : ") [seems unlocked already]"); + printf(" 3 - Promote user (make user an administrator)\n"); + printf(" 4 - Add user to a group\n"); + printf(" 5 - Remove user from a group\n"); +#ifdef DOCRYPTO + printf(" 9 - Edit (set new) user password (careful with this on XP or Vista)\n"); +#endif printf(" q - Quit editing user, back to user select\n"); pl = fmyinput("Select: [q] > ",newp,16); if ( (pl < 1) || (*newp == 'q') || (*newp == 'Q')) return(0); + + if (*newp == '2') { + acb = sam_handle_accountbits(hive[H_SAM], rid,2); + // return(username); + } + if (*newp == '3') { - printf("NOTE: This function is still experimental, and in some cases it\n" - " may result in stangeness when editing user/group in windows.\n" - " Also, users (like Guest often is) may still be prevented\n" - " from login via security/group policies which is not changed.\n"); - fmyinput("Do you still want to promote the user? (y/n) [n] ",yn,2); - if (*yn == 'y' || *yn == 'Y') { - promote_user(rid); - } - return(username); + promote_user(rid); + // return(username); } if (*newp == '4') { - acb = handle_F(rid,2); - return(username); + interactive_addusrgrp(rid); + // return(username); } - if (*newp == '2') { + if (*newp == '5') { + interactive_remusrgrp(rid); + // return(username); + } + + +#ifdef DOCRYPT + if (*newp == '9') { /* Set new password */ if (dontchange) { printf("Sorry, unable to edit since password seems blank already (thus no space for it)\n"); @@ -740,8 +591,9 @@ char *change_pw(char *buf, int rid, int vlen, int stat) } /* new password */ +#endif /* DOCRYPT */ - else if (pl == 1 && *newp == '1') { + if (pl == 1 && *newp == '1') { /* Setting hash lengths to zero seems to make NT think it is blank * However, since we cant cut the previous hash bytes out of the V value * due to missing resize-support of values, it may leak about 40 bytes @@ -749,6 +601,7 @@ char *change_pw(char *buf, int rid, int vlen, int stat) */ v->ntpw_len = 0; v->lmpw_len = 0; + dirty = 1; printf("Password cleared!\n"); } @@ -757,69 +610,12 @@ char *change_pw(char *buf, int rid, int vlen, int stat) hexprnt("Pw in buffer: ",(vp+ntpw_offs),16); hexprnt("Lm in buffer: ",(vp+lmpw_offs),16); #endif - dirty = 1; + } // Forever... + return(username); } -/* Registry edit wrapper */ - -void mainloop(void) -{ - regedit_interactive(hive, no_hives); -} - - -/* List users in SAM file - * pageit - hmm.. forgot this one for this release.. - */ - -int list_users(int pageit) -{ - char s[200]; - struct keyval *v; - int nkofs /* ,vkofs */ ; - int rid; - int count = 0, countri = 0; - struct ex_data ex; - - if (H_SAM < 0) return(1); - nkofs = trav_path(hive[H_SAM], 0,"\\SAM\\Domains\\Account\\Users\\Names\\",0); - if (!nkofs) { - printf("list_users: Cannot find usernames in registry! (is this a SAM-hive?)\n"); - return(1); - } - - printf("| RID -|---------- Username ------------| Admin? |- Lock? --|\n"); - - while ((ex_next_n(hive[H_SAM], nkofs+4, &count, &countri, &ex) > 0)) { - - /* Extract the value out of the username-key, value is RID */ - snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\Names\\%s\\@",ex.name); - rid = get_dword(hive[H_SAM], 0, s, TPF_VK_EXACT|TPF_VK_SHORT); - if (rid == 500) strncpy(admuser,ex.name,128); /* Copy out admin-name */ - - /* 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(hive[H_SAM], NULL, 0, s, REG_BINARY, TPF_VK_EXACT); - if (!v) { - printf("Cannot find value <%s>\n",s); - return(1); - } - - if (v->len < 0xcc) { - printf("list_users: Value <%s> is too short (only %d bytes) to be a SAM user V-struct!\n", - s, v->len); - } else { - change_pw( (char *)&v->data , rid, v->len, 1); - } - FREE(v); - FREE(ex.name); - } - return(0); -} - - /* Find a username in the SAM registry, then get it's V-value, * and feed it to the password changer. */ @@ -838,7 +634,7 @@ void find_n_change(char *username) snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\Names\\%s\\@",username); rid = get_dword(hive[H_SAM],0,s, TPF_VK_EXACT|TPF_VK_SHORT); if (rid == -1) { - printf("Cannot find value <%s>\n",s); + printf("Cannot find user, path is <%s>\n",s); return; } } @@ -851,7 +647,7 @@ void find_n_change(char *username) snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\%08X\\V",rid); v = get_val2buf(hive[H_SAM], NULL, 0, s, REG_BINARY, TPF_VK_EXACT); if (!v) { - printf("Cannot find value <%s>\n",s); + printf("Cannot find users V value <%s>\n",s); return; } @@ -1038,7 +834,7 @@ void handle_syskey(void) dirty = 1; syskeyreset = 1; printf("Updating passwordhash-lengths..\n"); - list_users(1); + sam_list_users(hive[H_SAM], 1); printf("* SYSKEY RESET!\nNow please set new administrator password!\n"); } else { @@ -1056,7 +852,8 @@ void handle_syskey(void) void useredit(void) { char iwho[100]; - int il; + int il, admrid; + int rid = 0; printf("\n\n===== chntpw Edit User Info & Passwords ====\n\n"); @@ -1066,18 +863,24 @@ void useredit(void) } - list_users(1); - - while (1) { - printf("\nSelect: ! - quit, . - list users, 0x - User with RID (hex)\n"); - printf("or simply enter the username to change: [%s] ",admuser); - il = fmyinput("",iwho,32); - if (il == 1 && *iwho == '.') { printf("\n"); list_users(1); continue; } - if (il == 1 && *iwho == '!') return; - if (il == 0) strcpy(iwho,admuser); - find_n_change(iwho); - } + admrid = sam_list_users(hive[H_SAM], 1); + + printf("\nPlease enter user number (RID) or 0 to exit: [%x] ", admrid); + + il = fmyinput("",iwho,32); + + if (il == 0) { + sprintf(iwho,"0x%x",admrid); + rid = admrid; + } else { + sscanf(iwho, "%x", &rid); + sprintf(iwho,"0x%x",rid); + } + if (!rid) return; + + find_n_change(iwho); + } @@ -1118,6 +921,23 @@ void recoveryconsole() } + +void listgroups(void) +{ + char yn[8]; + int il; + int members = 0; + + il = fmyinput("Also list group members? [n] ", yn, 2); + + if (il && (yn[0] == 'y' || yn[0] == 'Y')) members = 1; + + sam_list_groups(hive[H_SAM], members, 1); + +} + + + /* Interactive menu system */ void interactive(void) @@ -1137,17 +957,19 @@ void interactive(void) /* Make menu selection depending on what is loaded but it is still possible to select even if not shown */ - if (H_SAM >= 0) printf(" 1 - Edit user data and passwords\n"); - -#if 0 - if (H_SAM >= 0 && H_SYS >= 0 && H_SEC >= 0) { - printf(" 2 - Syskey status & change\n"); + if (H_SAM >= 0) { + printf(" 1 - Edit user data and passwords\n"); + printf(" 2 - List groups\n"); } -#endif if (H_SOF >= 0) { printf(" 3 - RecoveryConsole settings\n"); printf(" 4 - Show product key (DigitalProductID)\n"); } +#if 0 + if (H_SAM >= 0 && H_SYS >= 0 && H_SEC >= 0) { + printf(" 8 - Syskey status & change\n"); + } +#endif printf(" - - -\n" " 9 - Registry editor, now with full write support!\n" @@ -1160,10 +982,11 @@ void interactive(void) if (il) { switch(inbuf[0]) { case '1': useredit(); break; - case '2': handle_syskey(); break; + case '2': listgroups(); break; case '3': recoveryconsole(); break; case '4': cat_dpi(hive[H_SOF],0,"\\Microsoft\\Windows NT\\CurrentVersion\\DigitalProductId"); break; - case '9': mainloop(); break; + case '8': handle_syskey(); break; + case '9': regedit_interactive(hive, no_hives); break; case 'q': return; break; } } @@ -1171,131 +994,141 @@ void interactive(void) } - void usage(void) { printf("chntpw: change password of a user in a Windows SAM file,\n" "or invoke registry editor. Should handle both 32 and 64 bit windows and\n" - "all version from NT3.x to Win7\n" + "all version from NT3.x to Win8.1\n" "chntpw [OPTIONS] [systemfile] [securityfile] [otherreghive] [...]\n" " -h This message\n" - " -u Username to change, Administrator is default\n" - " -l list all users in SAM file\n" - " -i Interactive. List users (as -l) then ask for username to change\n" + " -u Username or RID (0x3e9 for example) to interactively edit\n" + " -l list all users in SAM file and exit\n" + " -i Interactive Menu system\n" + // " -f Interactively edit first admin user\n" " -e Registry editor. Now with full write support!\n" " -d Enter buffer debugger instead (hex editor), \n" " -v Be a little more verbose (for debuging)\n" " -L For scripts, write names of changed files to /tmp/changed\n" " -N No allocation mode. Only same length overwrites possible (very safe mode)\n" " -E No expand mode, do not expand hive file (safe mode)\n" - "See readme file on how to get to the registry files, and what they are.\n" + + "\nUsernames can be given as name or RID (in hex with 0x first)\n" + "\nSee readme file on how to get to the registry files, and what they are.\n" "Source/binary freely distributable under GPL v2 license. See README for details.\n" "NOTE: This program is somewhat hackish! You are on your own!\n" ); } + int main(int argc, char **argv) { + + int dodebug = 0, list = 0, inter = 0,edit = 0,il,d = 0, dd = 0, logchange = 0; + int mode = HMODE_INFO; + extern int /* opterr, */ optind; + extern char* optarg; + char *filename,c; + char *who = "Administrator"; + char iwho[100]; + FILE *ch; /* Write out names of touched files to this */ - int dodebug = 0, list = 2, inter = 0,edit = 0,il,d = 0, dd = 0, logchange = 0, mode = 0; - extern int /* opterr, */ optind; - extern char* optarg; - char *filename,c; - char *who = "Administrator"; - char iwho[100]; - FILE *ch; /* Write out names of touched files to this */ - - char *options = "LENidehlvu:"; - - printf("%s\n",chntpw_version); - while((c=getopt(argc,argv,options)) > 0) { - switch(c) { - case 'd': dodebug = 1; break; - case 'e': edit = 1; break; - case 'L': logchange = 1; break; - case 'N': mode |= HMODE_NOALLOC; break; - case 'E': mode |= HMODE_NOEXPAND; break; - case 'l': list = 1; who = 0; break; - case 'v': mode |= HMODE_VERBOSE; gverbose = 1; break; - case 'i': list = 2; who = 0; inter = 1; break; - case 'u': who = optarg; list = 2; break; - case 'h': usage(); exit(0); break; - default: usage(); exit(1); break; + char *options = "LENidehflvu:"; + + while((c=getopt(argc,argv,options)) > 0) { + switch(c) { + case 'd': dodebug = 1; break; + case 'e': edit = 1; break; + case 'L': logchange = 1; break; + case 'N': mode |= HMODE_NOALLOC; break; + case 'E': mode |= HMODE_NOEXPAND; break; + case 'l': list = 1; break; + case 'v': mode |= HMODE_VERBOSE; gverbose = 1; break; + case 'i': inter = 1; break; + case 'u': who = optarg; break; + case 'h': usage(); exit(0); break; + default: usage(); exit(1); break; + } + } + + printf("%s\n",chntpw_version); + + filename=argv[optind]; + if (!filename || !*filename) { + usage(); exit(1); + } + do { + if (!(hive[no_hives] = openHive(filename, + HMODE_RW|mode))) { + fprintf(stderr,"%s: Unable to open/read a hive, exiting..\n",argv[0]); + exit(1); + } + switch(hive[no_hives]->type) { + case HTYPE_SAM: H_SAM = no_hives; break; + case HTYPE_SOFTWARE: H_SOF = no_hives; break; + case HTYPE_SYSTEM: H_SYS = no_hives; break; + case HTYPE_SECURITY: H_SEC = no_hives; break; + } + no_hives++; + filename = argv[optind+no_hives]; + } while (filename && *filename && no_hives < MAX_HIVES); + + + if (dodebug) { + debugit(hive[0]->buffer,hive[0]->size); + } else { + if (H_SAM != -1) max_sam_lock = sam_get_lockoutinfo(hive[H_SAM], 0); + if (inter) { + interactive(); + } else if (edit) { + regedit_interactive(hive, no_hives); + } else if (list) { + sam_list_users(hive[H_SAM], 1); + } else if (who) { + find_n_change(who); + } + } + + + if (list != 1) { + printf("\nHives that have changed:\n # Name\n"); + for (il = 0; il < no_hives; il++) { + if (hive[il]->state & HMODE_DIRTY) { + if (!logchange) printf("%2d <%s>",il,hive[il]->filename); + if (hive[il]->state & HMODE_DIDEXPAND) printf(" WARNING: File was expanded! Expermental! Use at own risk!\n"); + printf("\n"); + + d = 1; } - } - filename=argv[optind]; - if (!filename || !*filename) { - usage(); exit(1); - } - do { - if (!(hive[no_hives] = openHive(filename, - HMODE_RW|mode))) { - printf("Unable to open/read a hive, exiting..\n"); - exit(1); - } - switch(hive[no_hives]->type) { - case HTYPE_SAM: H_SAM = no_hives; break; - case HTYPE_SOFTWARE: H_SOF = no_hives; break; - case HTYPE_SYSTEM: H_SYS = no_hives; break; - case HTYPE_SECURITY: H_SEC = no_hives; break; - } - no_hives++; - filename = argv[optind+no_hives]; - } while (filename && *filename && no_hives < MAX_HIVES); - - if (dodebug) debugit(hive[0]->buffer,hive[0]->size); - else { - - check_get_samdata(); - if (list && !edit && !inter) { - if ( list_users(1) ) edit = 1; - } - if (edit) mainloop(); - else if (who) { handle_syskey(); find_n_change(who); } - - if (inter) interactive(); - } - - if (list != 1) { - printf("\nHives that have changed:\n # Name\n"); - for (il = 0; il < no_hives; il++) { - if (hive[il]->state & HMODE_DIRTY) { - if (!logchange) printf("%2d <%s>",il,hive[il]->filename); - if (hive[il]->state & HMODE_DIDEXPAND) printf(" WARNING: File was expanded! Expermental! Use at own risk!\n"); - printf("\n"); - - d = 1; - } - } - if (d) { - /* Only prompt user if logging of changed files has not been set */ - /* Thus we assume confirmations are done externally if they ask for a list of changes */ - if (!logchange) fmyinput("Write hive files? (y/n) [n] : ",iwho,3); - if (*iwho == 'y' || logchange) { - if (logchange) { - ch = fopen("/tmp/changed","w"); - } - for (il = 0; il < no_hives; il++) { - if (hive[il]->state & HMODE_DIRTY) { - printf("%2d <%s> - ",il,hive[il]->filename); - if (!writeHive(hive[il])) { - printf("OK"); - if (hive[il]->state & HMODE_DIDEXPAND) printf(" WARNING: File was expanded! Expermental! Use at own risk!\n"); - printf("\n"); - if (logchange) fprintf(ch,"%s ",hive[il]->filename); - dd = 2; - } - } - } - if (logchange) { - fprintf(ch,"\n"); - fclose(ch); - } - } else { - printf("Not written!\n\n"); - } - } else { - printf("None!\n\n"); - } - } /* list only check */ - return(dd); + } + if (d) { + /* Only prompt user if logging of changed files has not been set */ + /* Thus we assume confirmations are done externally if they ask for a list of changes */ + if (!logchange) fmyinput("Write hive files? (y/n) [n] : ",iwho,3); + if (*iwho == 'y' || logchange) { + if (logchange) { + ch = fopen("/tmp/changed","w"); + } + for (il = 0; il < no_hives; il++) { + if (hive[il]->state & HMODE_DIRTY) { + printf("%2d <%s> - ",il,hive[il]->filename); + if (!writeHive(hive[il])) { + printf("OK"); + if (hive[il]->state & HMODE_DIDEXPAND) printf(" WARNING: File was expanded! Expermental! Use at own risk!\n"); + printf("\n"); + if (logchange) fprintf(ch,"%s ",hive[il]->filename); + dd = 2; + } + } + } + if (logchange) { + fprintf(ch,"\n"); + fclose(ch); + } + } else { + printf("Not written!\n\n"); + } + } else { + printf("None!\n\n"); + } + } /* list only check */ + return(dd); } diff --git a/edlib.c b/edlib.c index 7ef4318..2920857 100644 --- a/edlib.c +++ b/edlib.c @@ -4,6 +4,8 @@ * Point of this is so that interactive registry editor * can be accessed from several other programs * + * 2013-aug: Some minor bugfixes and adjustments + * Thanks to David Collett for catching and fixing bug in REG_MULTI_SZ editing. * 2010-jun: New function from Aleksander Wojdyga: dpi, decode product ID * Mostly used on \Microsoft\Windows NT\CurrentVersion\DigitalProductId * Now as command in registry editor, but may be moved to chnpw menu later. @@ -23,7 +25,7 @@ * ***** * - * Copyright (c) 1997-2011 Petter Nordahl-Hagen. + * Copyright (c) 1997-2014 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 @@ -48,7 +50,7 @@ #include "ntreg.h" -const char edlib_version[] = "edlib version 0.1 110511, (c) Petter N Hagen"; +const char edlib_version[] = "edlib version 0.1 140201, (c) Petter N Hagen"; #define ALLOC_DEBUG 0 /* Reg allocation debug hooks */ #define ADDBIN_DEBUG 0 /* Reg expansion debug hooks */ @@ -221,8 +223,8 @@ void cat_vk(struct hive *hdesc, int nkofs, char *path, int dohex) data = (void *)&(kv->data); - printf("Value <%s> of type %s, data length %d [0x%x]\n", path, - (type < REG_MAX ? val_types[type] : "(unknown)"), len, len); + printf("Value <%s> of type %s (%x), data length %d [0x%x]\n", path, + (type < REG_MAX ? val_types[type] : "(unknown)"), type, len, len); if (dohex) type = REG_BINARY; switch (type) { @@ -277,8 +279,8 @@ void edit_val(struct hive *h, int nkofs, char *path) } len = kv->len; - printf("EDIT: <%s> of type %s with length %d [0x%x]\n", path, - (type < REG_MAX ? val_types[type] : "(unknown)"), + printf("EDIT: <%s> of type %s (%x) with length %d [0x%x]\n", path, + (type < REG_MAX ? val_types[type] : "(unknown)"), type, len, len); switch(type) { @@ -364,15 +366,19 @@ void edit_val(struct hive *h, int nkofs, char *path) if (strcmp("--Q", inbuf)) { /* We didn't bail out */ if (newstring) newstring = realloc(newstring, in+1); else newstring = malloc(in+1); + + if (type == REG_MULTI_SZ) { - in++; *(newstring+in) = '\0'; /* Must add null termination */ + in++; } + ALLOC(newkv,1,(in<<1)+sizeof(int)); + newkv->len = in<<1; - printf("newkv->len: %d\n",newkv->len); + VERBF(h,"newkv->len: %d\n",newkv->len); cheap_ascii2uni(newstring, (char *)&(newkv->data), in); - + d = 1; FREE(kv); @@ -456,7 +462,7 @@ void regedit_interactive(struct hive *hive[], int no_hives) { struct hive *hdesc; int cdofs, newofs; - struct nk_key *cdkey; + // struct nk_key *cdkey; char inbuf[100], *bp, *file, *prefix; char path[1000]; int l, vkofs, nh, i; @@ -474,7 +480,7 @@ void regedit_interactive(struct hive *hive[], int no_hives) printf("Simple registry editor. ? for help.\n"); while (1) { - cdkey = (struct nk_key *)(hdesc->buffer + cdofs); + // cdkey = (struct nk_key *)(hdesc->buffer + cdofs); *path = 0; get_abs_path(hdesc,cdofs+4, path, 50); diff --git a/libsam.c b/libsam.c new file mode 100644 index 0000000..2c06c11 --- /dev/null +++ b/libsam.c @@ -0,0 +1,1791 @@ +/* + * 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 +#include +#include +#include + +#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\\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<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); +} diff --git a/ntreg.c b/ntreg.c index dd70c57..4bab001 100644 --- a/ntreg.c +++ b/ntreg.c @@ -1,6 +1,18 @@ /* * ntreg.c - Windows (NT and up) Registry Hive access library + * should be able to handle most basic functions: + * iterate, add&delete keys and values, read stuff, change stuff etc + * no rename of keys or values yet.. + * also contains some minor utility functions (string handling etc) for now * + * 2014-jan: openhive() now more compatible with build on non-unix? + * 2013-aug: Enter buil-in buffer debugger only if in trace mode, else return error or abort() + * 2013-may-aug: Fixed critical bug in del_value which could + * thrash the hive when removing value in bottom of key. + * And a pointer not reinitialized when buffer reallocated in some cases, fixed. + * Thanks to Jacky To for reporting those two. + * Some minor adjustments for compiler. A few more utility functions. + * 2012-oct: Added set_val_type. some minor changes. * 2011-may: Seems like large values >16k or something like that is split * into several blocks (db), have tried to implement that. * Vista seems to accept it. Not tested on others yet. @@ -43,7 +55,7 @@ ***** * * NTREG - Window registry file reader / writer library - * Copyright (c) 1997-2010 Petter Nordahl-Hagen. + * Copyright (c) 1997-2014 Petter Nordahl-Hagen. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -68,6 +80,7 @@ #include #include #include +#include #include "ntreg.h" @@ -77,7 +90,7 @@ #define ZEROFILL 1 /* Fill blocks with zeroes when allocating and deallocating */ #define ZEROFILLONLOAD 0 /* Fill blocks marked as unused/deallocated with zeroes on load. FOR DEBUG */ -const char ntreg_version[] = "ntreg lib routines, v0.95 110511, (c) Petter N Hagen"; +const char ntreg_version[] = "ntreg lib routines, v0.95 140201, (c) Petter N Hagen"; const char *val_types[REG_MAX+1] = { "REG_NONE", "REG_SZ", "REG_EXPAND_SZ", "REG_BINARY", "REG_DWORD", /* 0 - 4 */ @@ -162,14 +175,38 @@ char *str_dup( const char *str ) { char *str_new; - if (!str) - return 0 ; + if (!str) return(0); CREATE( str_new, char, strlen(str) + 1 ); strcpy( str_new, str ); return str_new; } +char *str_cat(char *str, char *add) +{ + if (!add) return(str); + str = (char *) realloc(str, strlen(str) + strlen(add) + 3); + strcat (str, add); + return (str); +} + +char *str_catf(char *str, const char *format, ... ) +{ + va_list ap; + char add[640] ; + + va_start(ap, format) ; + vsprintf(add, format, ap) ; + va_end(ap) ; + + str = (char *) realloc(str, strlen(str) + strlen(add) + 3); + strcat (str, add ); + return(str); +} + + + + /* Copy non-terminated string to buffer we allocate and null terminate it * Uses length only, does not check for nulls */ @@ -440,6 +477,44 @@ int debugit(char *buf, int sz) } +/* Utility function to copy and append two values into a new one + * Will allocate new buffer, but not touch the input ones + */ + +struct keyval *reg_valcat(struct keyval *a, struct keyval *b) +{ + int newsize = 0; + int asize = 0; + int bsize = 0; + struct keyval *result = NULL; + + if (!a && !b) return(NULL); + + if (a) asize = a->len; + if (b) bsize = b->len; + + newsize = asize + bsize; + + // printf("asize = %d, bsize = %d, newsize = %d\n",asize,bsize,newsize); + + ALLOC(result, sizeof(struct keyval) + newsize, 1); + + if (asize) memcpy(&result->data, &a->data, asize); + if (bsize) memcpy(&result->data + asize / sizeof(int), &b->data, bsize); + result->len = newsize; + + //printf("reg_valcat done\n"); + + return(result); + +} + + + + + + + /* ========================================================================= */ /* The following routines are mostly for debugging, I used it @@ -690,7 +765,7 @@ int parse_block(struct hive *hdesc, int vofs,int verbose) #endif if (seglen == 0) { printf("parse_block: FATAL! Zero data block size! (not registry or corrupt file?)\n"); - debugit(hdesc->buffer,hdesc->size); + if (verbose) debugit(hdesc->buffer,hdesc->size); return(0); } @@ -819,8 +894,8 @@ int find_free_blk(struct hive *hdesc, int pofs, int size) printf("find_free_blk: at exact end of hbin, do not care..\n"); return(0); } - abort(); - debugit(hdesc->buffer,hdesc->size); + if (hdesc->state & HMODE_TRACE) debugit(hdesc->buffer,hdesc->size); + else abort(); return(0); } @@ -1108,7 +1183,7 @@ int free_block(struct hive *hdesc, int blk) printf("free_block: trying to free already free block!\n"); #ifdef DOCORE printf("blk = %x\n",blk); - debugit(hdesc->buffer,hdesc->size); + if (hdesc->state & HMODE_TRACE) debugit(hdesc->buffer,hdesc->size); abort(); #endif return(0); @@ -1148,7 +1223,7 @@ int free_block(struct hive *hdesc, int blk) printf("free_block: ran off end of page!?!? Error in chains?\n"); #ifdef DOCORE printf("vofs = %x, pofs = %x, blk = %x\n",vofs,pofs,blk); - debugit(hdesc->buffer,hdesc->size); + if (hdesc->state & HMODE_TRACE) debugit(hdesc->buffer,hdesc->size); abort(); #endif return(0); @@ -1701,9 +1776,9 @@ void nk_ls(struct hive *hdesc, char *path, int vofs, int type) VERBF(hdesc,"ls of node at offset 0x%0x\n",nkofs); if (key->id != 0x6b6e) { - printf("Error: Not a 'nk' node!\n"); + printf("Error: Not a 'nk' node at offset %x!\n",nkofs); - debugit(hdesc->buffer,hdesc->size); + if (hdesc->state & HMODE_TRACE) debugit(hdesc->buffer,hdesc->size); } @@ -1721,13 +1796,13 @@ void nk_ls(struct hive *hdesc, char *path, int vofs, int type) } count = 0; if (key->no_values) { - printf(" size type value name [value if type DWORD]\n"); + printf(" size type value name [value if type DWORD]\n"); while ((ex_next_v(hdesc, nkofs, &count, &vex) > 0)) { - if (hdesc->state & HMODE_VERBOSE) printf("[%6x] %6d %-16s <%s>", vex.vkoffs - 4, vex.size, + if (hdesc->state & HMODE_VERBOSE) printf("[%6x] %6d %x %-16s <%s>", vex.vkoffs - 4, vex.size, vex.type, (vex.type < REG_MAX ? val_types[vex.type] : "(unknown)"), vex.name); else - printf("%6d %-16s <%s>", vex.size, - (vex.type < REG_MAX ? val_types[vex.type] : "(unknown)"), vex.name); + printf("%6d %x %-16s <%s>", vex.size, vex.type, + (vex.type < REG_MAX ? val_types[vex.type] : "(unknown)"), vex.name); if (vex.type == REG_DWORD) printf(" %*d [0x%x]",25-(int)strlen(vex.name),vex.val , vex.val); printf("\n"); @@ -1752,6 +1827,27 @@ int get_val_type(struct hive *hdesc, int vofs, char *path, int exact) return(vkkey->val_type); } +/* Set/change the type of a value. Strangely we need this in some situation in SAM + * and delete + add value is a bit overkill */ + +int set_val_type(struct hive *hdesc, int vofs, char *path, int exact, int type) +{ + struct vk_key *vkkey; + int vkofs; + + vkofs = trav_path(hdesc, vofs,path,exact | TPF_VK); + if (!vkofs) { + return -1; + } + vkofs +=4; + vkkey = (struct vk_key *)(hdesc->buffer + vkofs); + + vkkey->val_type = type; + + return(vkkey->val_type); +} + + /* Get len of a value, given current key + path */ int get_val_len(struct hive *hdesc, int vofs, char *path, int exact) @@ -1930,7 +2026,7 @@ int fill_block(struct hive *hdesc, int ofs, void *data, int size) /* if (blksize < size || ( (ofs & 0xfffff000) != ((ofs+size) & 0xfffff000) )) { */ if (blksize < size) { printf("fill_block: ERROR: block to small for data: ofs = %x, size = %x, blksize = %x\n",ofs,size,blksize); - debugit(hdesc->buffer,hdesc->size); + if (hdesc->state & HMODE_TRACE) debugit(hdesc->buffer,hdesc->size); abort(); } @@ -2282,7 +2378,7 @@ int del_value(struct hive *hdesc, int nkofs, char *name, int exact) /* Now copy over, omitting deleted entry */ newlistkey = (int32_t *)(hdesc->buffer + newlistofs + 4); - for (n = 0, o = 0; o < nk->no_values+1; o++, n++) { + for (n = 0, o = 0; n < nk->no_values; o++, n++) { if (o == slot) o++; newlistkey[n] = tmplist[o]; } @@ -2609,7 +2705,7 @@ int del_key(struct hive *hdesc, int nkofs, char *name) if (key->id != 0x6b6e) { - printf("add_key: current ptr not nk\n"); + printf("del_key: current ptr not nk\n"); return(1); } @@ -2747,6 +2843,13 @@ int del_key(struct hive *hdesc, int nkofs, char *name) /* Allocate space for our new lf list and copy it into reg */ if ( no_keys && (newlf || newli) ) { newlfofs = alloc_block(hdesc, nkofs, 8 + (newlf ? 8 : 4) * no_keys); + + /* alloc_block may invalidate pointers if hive expanded. Recalculate this one. + * Thanks to Jacky To for reporting it here, and suggesting a fix + * (better would of course be for me to redesign stuff :) + */ + if (delnkofs) delnk = (struct nk_key *)(delnkofs + hdesc->buffer + 0x1004); + #ifdef DKDEBUG printf("del_key: alloc_block for index returns: %x\n",newlfofs); #endif @@ -2775,7 +2878,7 @@ int del_key(struct hive *hdesc, int nkofs, char *name) if (newlfofs < 0xfff) { printf("del_key: ERROR: newlfofs = %x\n",newlfofs); #if DOCORE - debugit(hdesc->buffer,hdesc->size); + if (hdesc->state & HMODE_TRACE) debugit(hdesc->buffer,hdesc->size); abort(); #endif } @@ -2878,10 +2981,10 @@ void rdel_keys(struct hive *hdesc, char *path, int vofs) */ if (key->id != 0x6b6e) { - printf("Error: Not a 'nk' node!\n"); + printf("rdel_keys: ERROR: Not a 'nk' node!\n"); - debugit(hdesc->buffer,hdesc->size); - + if (hdesc->state & HMODE_TRACE) debugit(hdesc->buffer,hdesc->size); + return; } #if 0 @@ -3770,7 +3873,7 @@ void import_reg(struct hive *hdesc, char *filename, char *prefix) char *value = NULL; int wide = 0; int c,wl; - void *valbuf; + // void *valbuf; char *valname = NULL; char *plainname = NULL; char *valstr, *key, *walstr = NULL; @@ -3951,7 +4054,7 @@ void import_reg(struct hive *hdesc, char *filename, char *prefix) } value = dequote(value); valstr = str_dup(value); - valbuf = NULL; + // valbuf = NULL; //printf("import_reg: val name = %s\n",valname); //printf("import_reg: val str = %s\n",valstr); @@ -4018,7 +4121,8 @@ int32_t calc_regfsum(struct hive *hdesc) -/* Write the hive back to disk (only if dirty & not readonly */ +/* Write the hive back to disk (only if dirty & not readonly) */ + int writeHive(struct hive *hdesc) { int len; @@ -4065,6 +4169,7 @@ struct hive *openHive(char *filename, int mode) uint32_t pofs; int32_t checksum; char *c; + int rt; struct hbin_page *p; struct regf_header *hdr; struct nk_key *nk; @@ -4072,6 +4177,9 @@ struct hive *openHive(char *filename, int mode) int verbose = (mode & HMODE_VERBOSE); int trace = (mode & HMODE_TRACE) ? 1 : 0; + int info = (mode & HMODE_INFO); + + if (!filename || !*filename) return(NULL); CREATE(hdesc,struct hive,1); @@ -4085,6 +4193,12 @@ struct hive *openHive(char *filename, int mode) } else { fmode = O_RDWR; } + + /* Some non-unix platforms may need this. Thanks to Dan Schmidt */ +#ifdef O_BINARY + fmode |= O_BINARY; +#endif + hdesc->filedesc = open(hdesc->filename,fmode); if (hdesc->filedesc < 0) { fprintf(stderr,"openHive(%s) failed: %s, trying read-only\n",hdesc->filename,strerror(errno)); @@ -4106,14 +4220,23 @@ struct hive *openHive(char *filename, int mode) hdesc->size = sbuf.st_size; hdesc->state = mode | HMODE_OPEN; - /* fprintf(stderr,"hiveOpen(%s) successful\n",hdesc->filename); */ - + /* Read the whole file */ ALLOC(hdesc->buffer,1,hdesc->size); - r = read(hdesc->filedesc,hdesc->buffer,hdesc->size); - if (r < hdesc->size) { + rt = 0; + do { /* On some platforms read may not block, and read in chunks. handle that */ + r = read(hdesc->filedesc, hdesc->buffer + rt, hdesc->size - rt); + rt += r; + } while ( !errno && (rt < hdesc->size) ); + + if (errno) { + perror("openHive(): read error: "); + closeHive(hdesc); + return(NULL); + } + if (rt < hdesc->size) { fprintf(stderr,"Could not read file, got %d bytes while expecting %d\n", r, hdesc->size); closeHive(hdesc); @@ -4141,13 +4264,13 @@ struct hive *openHive(char *filename, int mode) fprintf(stderr,"openHive(%s): WARNING: REGF header checksum mismatch! calc: 0x%08x != file: 0x%08x\n",filename,checksum,hdr->checksum); } - - - printf("Hive <%s> name (from header): <",filename); - for (c = hdr->name; *c && (c < hdr->name + 64); c += 2) putchar(*c); - hdesc->rootofs = hdr->ofs_rootkey + 0x1000; - printf(">\nROOT KEY at offset: 0x%06x * ",hdesc->rootofs); + + if (info) { + printf("Hive <%s> name (from header): <",filename); + for (c = hdr->name; *c && (c < hdr->name + 64); c += 2) putchar(*c); + printf(">\nROOT KEY at offset: 0x%06x * ",hdesc->rootofs); + } /* Cache the roots subkey index type (li,lf,lh) so we can use the correct * one when creating the first subkey in a key */ @@ -4157,7 +4280,7 @@ struct hive *openHive(char *filename, int mode) rikey = (struct ri_key *)(hdesc->buffer + nk->ofs_lf + 0x1004); hdesc->nkindextype = rikey->id; if (hdesc->nkindextype == 0x6972) { /* Gee, big root, must check indirectly */ - printf("openHive: DEBUG: BIG ROOT!\n"); + fprintf(stderr,"openHive: DEBUG: BIG ROOT!\n"); rikey = (struct ri_key *)(hdesc->buffer + rikey->hash[0].ofs_li + 0x1004); hdesc->nkindextype = rikey->id; } @@ -4167,7 +4290,7 @@ struct hive *openHive(char *filename, int mode) hdesc->nkindextype = 0x666c; } - printf("Subkey indexing type is: %04x <%c%c>\n", + if (info) printf("Subkey indexing type is: %04x <%c%c>\n", hdesc->nkindextype, hdesc->nkindextype & 0xff, hdesc->nkindextype >> 8); @@ -4183,7 +4306,7 @@ struct hive *openHive(char *filename, int mode) #endif p = (struct hbin_page *)(hdesc->buffer + pofs); if (p->id != 0x6E696268) { - printf("Page at 0x%x is not 'hbin', assuming file contains garbage at end\n",pofs); + if (info) printf("Page at 0x%x is not 'hbin', assuming file contains garbage at end\n",pofs); break; } hdesc->pages++; @@ -4218,10 +4341,11 @@ struct hive *openHive(char *filename, int mode) printf("hdr->unknown4 (version?) : 0x%x\n",hdr->unknown4); } - printf("File size %d [%x] bytes, containing %d pages (+ 1 headerpage)\n",hdesc->size,hdesc->size, hdesc->pages); - printf("Used for data: %d/%d blocks/bytes, unused: %d/%d blocks/bytes.\n\n", - hdesc->useblk,hdesc->usetot,hdesc->unuseblk,hdesc->unusetot); - + if (info || verbose) { + printf("File size %d [%x] bytes, containing %d pages (+ 1 headerpage)\n",hdesc->size,hdesc->size, hdesc->pages); + printf("Used for data: %d/%d blocks/bytes, unused: %d/%d blocks/bytes.\n\n", + hdesc->useblk,hdesc->usetot,hdesc->unuseblk,hdesc->unusetot); + } /* So, let's guess what kind of hive this is, based on keys in its root */ diff --git a/ntreg.h b/ntreg.h index 1012828..4b46106 100644 --- a/ntreg.h +++ b/ntreg.h @@ -6,7 +6,7 @@ ***** * * NTREG - Window registry file reader / writer library - * Copyright (c) 1997-2011 Petter Nordahl-Hagen. + * Copyright (c) 1997-2013 Petter Nordahl-Hagen. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -318,6 +318,7 @@ struct keyvala { #define HMODE_DIDEXPAND 0x20 /* File has been expanded */ #define HMODE_VERBOSE 0x1000 #define HMODE_TRACE 0x2000 +#define HMODE_INFO 0x4000 /* Show some info on open and close */ /* Suggested type of hive loaded, guessed by library, but not used by it */ #define HTYPE_UNKNOWN 0 @@ -386,6 +387,8 @@ struct hive { /******* Function prototypes **********/ char *str_dup( const char *str ); +char *str_cat(char *str, char *add); +char *str_catf(char *str, const char *format, ... ); int fmyinput(char *prmpt, char *ibuf, int maxlen); void hexprnt(char *s, unsigned char *bytes, int len); void hexdump(char *hbuf, int start, int stop, int ascii); @@ -397,12 +400,16 @@ void skipspace(char **c); int gethex(char **c); int gethexorstr(char **c, char *wb); int debugit(char *buf, int sz); +struct keyval *reg_valcat(struct keyval *a, struct keyval *b); + + int parse_block(struct hive *hdesc, int vofs,int verbose); int ex_next_n(struct hive *hdesc, int nkofs, int *count, int *countri, struct ex_data *sptr); int ex_next_v(struct hive *hdesc, int nkofs, int *count, struct vex_data *sptr); int get_abs_path(struct hive *hdesc, int nkofs, char *path, int maxlen); int trav_path(struct hive *hdesc, int vofs, char *path, int type); int get_val_type(struct hive *hdesc, int vofs, char *path, int exact); +int set_val_type(struct hive *hdesc, int vofs, char *path, int exact, int type); int get_val_len(struct hive *hdesc, int vofs, char *path, int exact); void *get_val_data(struct hive *hdesc, int vofs, char *path, int val_type, int exact); struct keyval *get_val2buf(struct hive *hdesc, struct keyval *kv, diff --git a/reged.c b/reged.c index b0d4a05..1e8d806 100644 --- a/reged.c +++ b/reged.c @@ -13,7 +13,7 @@ * ***** * - * Copyright (c) 1997-2010 Petter Nordahl-Hagen. + * Copyright (c) 1997-2014 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 @@ -38,7 +38,7 @@ #include "ntreg.h" -const char reged_version[] = "reged version 0.1 110511, (c) Petter N Hagen"; +const char reged_version[] = "reged version 0.1 140201, (c) Petter N Hagen"; /* Global verbosity flag */ diff --git a/sam.h b/sam.h index 3964fd7..e984bf9 100644 --- a/sam.h +++ b/sam.h @@ -4,7 +4,7 @@ ***** * * NTREG - Window registry file reader / writer library - * Copyright (c) 1997-2011 Petter Nordahl-Hagen. + * Copyright (c) 1997-2012 Petter Nordahl-Hagen. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -25,6 +25,23 @@ #include #include + +/* Account V, contains machine SID and a lot of unknonw stuff */ + +#define SID_BIN_LEN 24 /* Lenght of binary machine SID, incl header */ +#define SID_STR_MAX 50 /* Max string lenght of S-1-5-xxx-xxx-xxx-xxx type SID */ + +#define ACCOUNTDB_V_PATH "\\SAM\\Domains\\Account\\V" + +struct accountdb_V { + char unknown1[0x38]; /* No idea what it does */ + uint32_t sid_ofs; /* 38 - Offset to machine SID */ + uint32_t sid_len; /* 3c - Lenght of sid (should be 0x14?) */ + // char unknown2[0x4]; /* 40 */ + /* Data start at 0x40 ?? */ + char data[4]; +}; + /* This contains some policy settings for the account database */ #define ACCOUNTDB_F_PATH "\\SAM\\Domains\\Account\\F" @@ -89,6 +106,8 @@ struct user_F { /* Seems not to be used on failed console logins at least */ #define ACB_AUTOLOCK 0x0400 /* Account auto locked */ +/* Account Bits Fields strings (defined in libsam.c) + char *acb_fields[16] = { "Disabled" , "Homedir req." , @@ -108,6 +127,9 @@ char *acb_fields[16] = { "(unknown 0x80)" , }; +*/ + + /* Users V data struct */ /* First 0xCC bytes is pointer & len table, rest is data which * the table points to @@ -208,8 +230,9 @@ struct group_C { int fullname_len; /* 0x20 */ int unknown3_1; /* 0x24 - always zero? */ - int unknown3_2; /* 0x28 - always zero? */ - int unknown3_3; /* 0x2c - always zero? */ + + int members_ofs; /* 0x28 - offset to member list, which is SIDs */ + int members_len; /* 0x2c - member list size */ int grp_members; /* 0x30 - number of group members */ @@ -219,4 +242,55 @@ struct group_C { }; +/* Variable length binary structure that most SIDs are stored in + */ + +struct sid_binary { + uint8_t revision; /* 0x0 - Don't know. Always 1? Revision level? number of SIDs following? */ + uint8_t sections; /* 0x1 - Number of parts, 4 bytes each (unsigne integer) */ + uint8_t unknown2; /* 0x2 - Unknown, seems to be null padding */ + uint8_t unknown3; /* 0x3 - Unknown, seems to be null padding */ + + uint8_t unknown4; /* 0x4 - Unknown */ + uint8_t unknown5; /* 0x5 - Unknown */ + uint8_t unknown6; /* 0x6 - Unknown */ + uint8_t authority; /* 0x7 - Unknown, often 5 (could this be 8bit authority number?) */ + + + uint32_t array[8]; /* As many as sections value says it is */ +}; + +/* Array of SIDs */ + +struct sid_array { + int len; + struct sid_binary *sidptr; +}; + + +/* libsam.c functions */ + +int sam_get_lockoutinfo(struct hive *hdesc, int show); +short sam_handle_accountbits(struct hive *hdesc, int rid, int mode); +int sam_get_machine_sid(struct hive *hdesc, char *sidbuf); +char *sam_sid_to_string(struct sid_binary *sidbuf); +struct sid_array *sam_make_sid_array(struct sid_binary *sidbuf, int size); +void sam_free_sid_array(struct sid_array *array); +int sam_sid_cmp(struct sid_binary *s1, struct sid_binary *s2); +int sam_get_grp_members_sid(struct hive *hdesc, int grp, struct sid_array **sarray); +int sam_put_grp_members_sid(struct hive *hdesc, int grp, struct sid_array *sarray); +struct keyval *sam_get_user_grpids(struct hive *hdesc, int rid); +int sam_put_user_grpids(struct hive *hdesc, int rid, struct keyval *val); +int sam_add_user_to_grp(struct hive *hdesc, int rid, int grp); +int sam_remove_user_from_grp(struct hive *hdesc, int rid, int grp); +char *sam_get_username(struct hive *hdesc, int rid); +char *sam_get_groupname(struct hive *hdesc, int grpid); +int sam_list_users(struct hive *hdesc, int readable); +int sam_list_user_groups(struct hive *hdesc, int rid, int check); +int sam_reset_pw(struct hive *hdesc, int rid); +void sam_list_groups(struct hive *hdesc, int listmembers, int human); +int sam_reset_all_pw(struct hive *hdesc, int list); + + + #endif diff --git a/sampasswd.c b/sampasswd.c new file mode 100644 index 0000000..37a89ef --- /dev/null +++ b/sampasswd.c @@ -0,0 +1,255 @@ +/* + * sampasswd.c - SAM database, add or remove user in a group + * + * Command line utility, non-interactive to reset user password and/or + * account bits for a user in the SAM database + * + * Changes: + * 2013 - aug: cleaned up a bit for release, some debug still there + * 2013 - apr-may: reset one, all and first features + * 2012 - oct: First version, some code from earlier chntpw.c. Not released. + * + ***** + * + * Copyright (c) 1997-2014 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. + * + ***** + */ + + +#include +#include +#include +#include + +#include "ntreg.h" +#include "sam.h" + + +const char sampasswd_version[] = "sampasswd version 0.2 140201, (c) Petter N Hagen"; + + +/* Global verbosity flag */ +int gverbose = 0; + +/* Array of loaded hives */ +#define MAX_HIVES 10 +struct hive *hive[MAX_HIVES+1]; +int no_hives = 0; + +int H_SAM = -1; + + + +int do_reset(char *user, int inrid, int verb) +{ + int rid = 0; + int ret; + char *resolvedname = NULL; + char s[200]; + + if ((H_SAM < 0) || (!user && !inrid) ) return(1); + + if (inrid) { + rid = inrid; + } else { + if (*user == '0' && *(user+1) == 'x') sscanf(user,"%i",&rid); + } + + if (!rid) { /* Look up username */ + /* Extract the unnamed value out of the username-key, value is RID */ + snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\Names\\%s\\@",user); + rid = get_dword(hive[H_SAM],0,s, TPF_VK_EXACT|TPF_VK_SHORT); + if (rid == -1) { + printf("ERROR: User <%s> not found\n",user); + return(1); + } + } + + /* At this point we have a RID, so get the real username it maps to just to show it */ + resolvedname = sam_get_username(hive[H_SAM], rid); + + if (!resolvedname) return(1); /* RID lookup failed, no such user */ + + if (gverbose) printf("do_reset: Username: %s, RID = %d (0x%0x)\n",resolvedname,rid,rid); + + ret = sam_reset_pw(hive[H_SAM], rid); + + if (!ret && verb) printf("Password reset for user %s, RID = %d [0x%0x]\n",resolvedname,rid,rid); + + FREE(resolvedname); + return(ret); + +} + + + +void usage(void) +{ + printf(" [-r|-l] [-H] -u \n" + "Reset password or list users in SAM database\n" + "Mode:\n" + " -r = reset users password\n" + " -l = list users in sam\n" + "Parameters:\n" + " can be given as a username or a RID in hex with 0x in front\n" + " Example:\n" + " -r -u theboss -> resets password of user named 'theboss' if found\n" + " -r -u 0x3ea -> resets password for user with RID 0x3ea (hex)\n" + " -r -a -> Reset password of all users in administrators group (0x220)\n" + " -r -f -> Reset password of admin user with lowest RID\n" + " not counting built-in admin (0x1f4) unless it is the only admin\n" + " Usernames with international characters usually fails to be found,\n" + " please use RID number instead\n" + " If success, there will be no output, and exit code is 0\n" + "Options:\n" + " -H : For list: Human readable listing (default is parsable table)\n" + " -H : For reset: Will output confirmation message if success\n" + " -N : No allocate mode, only allow edit of existing values with same size\n" + " -E : No expand mode, do not expand hive file (safe mode)\n" + " -t : Debug trace of allocated blocks\n" + " -v : Some more verbose messages/debug\n" + ); +} + + +int main(int argc, char **argv) +{ + + extern int optind; + extern char* optarg; + + int what = 0; + int reset = 0; + int list = 0; + int mode = 0; + int human = 0; + int adm = 0; + int all = 0; + int first = 0; + int ret, wret, il; + char *hivename; + char c; + char *usr = NULL; + + char *options = "rlHu:vNEthaf"; + + while((c=getopt(argc,argv,options)) > 0) { + switch(c) { + case 'r': reset = 1; break; + case 'l': list = 2; break; + case 'u': usr = optarg; break; + case 'a': all = 1; break; + case 'f': first = 1; break; + case 'H': human = 1; break; + case 'v': mode |= HMODE_VERBOSE; gverbose = 1; break; + case 'N': mode |= HMODE_NOALLOC; break; + case 'E': mode |= HMODE_NOEXPAND; break; + case 't': mode |= HMODE_TRACE; break; + case 'h': printf("%s\n%s ",sampasswd_version,argv[0]); usage(); exit(0); break; + default: printf("%s\n%s ",sampasswd_version,argv[0]); usage(); exit(1); break; + } + } + + if (!reset && !list && !what) { + fprintf(stderr,"%s: ERROR: Mode -r or -l must be specified. -h for help\n",argv[0]); + exit(1); + } + +#if 0 /* Should both be allowed at same time?? */ + if (list && reset) { + fprintf(stderr,"%s: ERROR: Mode -r and -l impossible at the same time. -h for help\n",argv[0]); + exit(1); + } +#endif + + if (reset && !all && !first && (!usr || !*usr)) { + fprintf(stderr,"%s: ERROR: Need a user for reset, -u must be specified.\n",argv[0]); + exit(1); + } + + + /* Load hives. Only first SAM hive will be used however */ + + hivename = argv[optind+no_hives]; + if (!hivename || !*hivename) { + fprintf(stderr,"%s: ERROR: You must specify a SAM registry hive filename.\n",argv[0]); + exit(1); + } + do { + if (!(hive[no_hives] = openHive(hivename, + HMODE_RW|mode))) { + fprintf(stderr,"%s: ERROR: Unable to open/read registry hive, cannot continue\n",argv[0]); + exit(1); + } + switch(hive[no_hives]->type) { + case HTYPE_SAM: H_SAM = no_hives; break; + // case HTYPE_SOFTWARE: H_SOF = no_hives; break; + // case HTYPE_SYSTEM: H_SYS = no_hives; break; + // case HTYPE_SECURITY: H_SEC = no_hives; break; + } + no_hives++; + hivename = argv[optind+no_hives]; + } while (hivename && *hivename && no_hives < MAX_HIVES); + + if (H_SAM == -1) { + fprintf(stderr,"%s: WARNING: Hive file does not look like SAM, but continuing anyway in case detection was wrong\n" + "%s: WARNING: If it really is not a SAM file you will get strange errors or bad results\n",argv[0],argv[0]); + H_SAM = 0; + } + + + /* Do logic */ + + if (list) { + adm = sam_list_users(hive[H_SAM], human); + if (gverbose) printf(" sam_list_users found admin to be 0x%x\n",adm); + } + + if (reset) { + if (all) { + ret = sam_reset_all_pw(hive[H_SAM], human); + if (ret) { + fprintf(stderr,"%s: ERROR: Failed reset password of ALL users\n",argv[0]); + } + } else if (first) { + adm = sam_list_users(hive[H_SAM], 2); + if (!adm) { + fprintf(stderr,"%s: ERROR: Unable to reset, no admin users found\n",argv[0]); + } else { + // printf("Resetting password of user with RID %x\n",adm); + ret = do_reset(usr, adm, human); + } + } else { + ret = do_reset(usr, 0, human); + if (ret) { + fprintf(stderr,"%s: ERROR: Failed reset password for %s\n",argv[0],usr); + } + } + } + + /* write registry hive (if needed) */ + + wret = 0; + for (il = 0; il < no_hives; il++) { + // wret |= writeHive(hive[il]); + if (hive[il]->state & HMODE_DIDEXPAND) + fprintf(stderr," WARNING: Registry file %s was expanded! Experimental! Use at own risk!\n",hive[il]->filename); + while (no_hives > 0) + closeHive(hive[--no_hives]); + } + + return(ret | wret); +} + diff --git a/samusrgrp.c b/samusrgrp.c new file mode 100644 index 0000000..758a9d3 --- /dev/null +++ b/samusrgrp.c @@ -0,0 +1,292 @@ +/* + * samusrgrp.c - SAM database, add or remove user in a group + * + * Command line utility, non-inreractive to add or remove a user to/from + * a local group in the SAM database, list groups with memberships etc + * + * When run as: + * samusrtogrp - add user to group + * samusrfromgrp - remove user from a group + * or as any other name, option of what to do must be specified + * + * Changes: + * 2013 - aug: cleaned up for release, still some debug & strangeness left + * 2013 - apr-may: add, remove, list working (more or less) + * 2012 - oct: First version, never released + * + ***** + * + * Copyright (c) 1997-2014 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. + * + ***** + */ + + +#include +#include +#include +#include + +#include "ntreg.h" +#include "sam.h" + + +const char samusrgrp_version[] = "samusrgrp version 0.2 140201, (c) Petter N Hagen"; + + +/* Global verbosity flag */ +int gverbose = 0; + +/* Array of loaded hives */ +#define MAX_HIVES 10 +struct hive *hive[MAX_HIVES+1]; +int no_hives = 0; + +int H_SAM = -1; + + +/* Print machine SID. Put into here since no better place for now */ + +void cmd_machinesid(void) +{ + struct sid_binary sid; + char *sidstr; + + if (sam_get_machine_sid(hive[H_SAM], (char *)&sid)) { + sidstr = sam_sid_to_string(&sid); + puts(sidstr); + FREE(sidstr); + } +} + +/* Get and parse parameters for group changes */ + +int cmd_usrgrp(char *user, char *grp, int what, int human) +{ + int numgrp; + int rid = 0; + char *resolveduser = NULL; + char *resolvedgroup = NULL; + char s[200]; + + numgrp = strtol(grp, NULL, 0); + + if ((H_SAM < 0) || (!user)) return(1); + + if (*user == '0' && *(user+1) == 'x') sscanf(user,"%i",&rid); + + if (!rid) { /* Look up username */ + /* Extract the unnamed value out of the username-key, value is RID */ + snprintf(s,180,"\\SAM\\Domains\\Account\\Users\\Names\\%s\\@",user); + rid = get_dword(hive[H_SAM],0,s, TPF_VK_EXACT|TPF_VK_SHORT); + if (rid == -1) { + printf("User <%s> not found\n",user); + return(1); + } + } + + /* At this point we have a RID, so get the real username it maps to just to show it */ + resolveduser = sam_get_username(hive[H_SAM], rid); + if (!resolveduser) return(1); /* Fails if RID does not exists */ + + resolvedgroup = sam_get_groupname(hive[H_SAM], numgrp); + if (!resolvedgroup) return(1); /* Fails if GID does not exists */ + + + if (human) printf("%s user <%s> with RID = %d (0x%0x) %s group <%s> with GID = %d (0x%x)\n", + (what == 1 ? "Add" : "Remove"), + resolveduser, + rid, rid, + (what == 1 ? "to" : "from"), + resolvedgroup, + numgrp, numgrp ); + + FREE(resolveduser); + FREE(resolvedgroup); + + switch (what) { + case 1: return(!sam_add_user_to_grp(hive[H_SAM] ,rid, numgrp)); break; + case 2: return(!sam_remove_user_from_grp(hive[H_SAM] ,rid, numgrp)); break; + } + + + return(0); + +} + + + +void usage(void) +{ + printf(" [-a|-r] -u -g \n" + "Add or remove a (local) user to/from a group\n" + "Mode:" + " -a = add user to group\n" + " -r = remove user from group\n" + " -l = list groups\n" + " -L = list groups and also their members\n" + " -s = Print machine SID\n" + "Parameters:\n" + " can be given as a username or a RID in hex with 0x in front\n" + " is the group number, in hex with 0x in front\n" + " Example:\n" + " -a -u theboss -g 0x220 -> add user named 'theboss' group hex 220 (administrators)\n" + " -a -u 0x3ea -g 0x221 -> add user with RID (hex) 3ea group hex 221 (users)\n" + " -r -u 0x3ff -g 0x220 -> remove user RID 0x3ff from grp 0x220\n" + " Usernames with international characters usually fails to be found,\n" + " please use RID number instead\n" + " If success, there will be no output, and exit code is 0\n" + " Also, success if user already in (or not in if -r) the group\n" + "Options:\n" + " -H : Human readable output, else parsable\n" + " -N : No allocate mode, only allow edit of existing values with same size\n" + " -E : No expand mode, do not expand hive file (safe mode)\n" + " -t : Debug trace of allocated blocks\n" + " -v : Some more verbose messages/debug\n" + "Multi call binary, if program is named:\n" + " samusrtogrp -- Assume -a mode: Add a user into a group\n" + " samusrfromgrp -- Assume -r mode: Remove user from a group\n" + ); +} + + +int main(int argc, char **argv) +{ + + extern int optind; + extern char* optarg; + + int what = 0; + int add = 0; + int rem = 0; + int mode = 0; + int m = 0; + int list = 0; + int human = 0; + int ret, wret, il; + char *hivename; + char c; + char *usr = NULL; + char *grp = NULL; + + char *options = "aru:g:vNEthlLHs"; + + if (!strcmp(argv[0],"samusrtogrp")) what = 1; + if (!strcmp(argv[0],"samusrfromgrp")) what = 2; + if (!strcmp(argv[0],"samgrplist")) what = 3; + + while((c=getopt(argc,argv,options)) > 0) { + switch(c) { + case 'a': add = 1; m++; break; /* Add user */ + case 'r': rem = 2; m++; break; /* Remove user */ + case 'l': list = 1; m++; break; /* List groups */ + case 'L': list = 2; m++; break; /* List groups + members */ + case 's': list = 3; m++; break; /* Print machine sid */ + case 'u': usr = optarg; break; + case 'g': grp = optarg; break; + case 'H': human = 1; break; + case 'v': mode |= HMODE_VERBOSE; gverbose = 1; break; + case 'N': mode |= HMODE_NOALLOC; break; + case 'E': mode |= HMODE_NOEXPAND; break; + case 't': mode |= HMODE_TRACE; break; + case 'h': printf("%s\n%s ",samusrgrp_version,argv[0]); usage(); exit(0); break; + default: printf("%s\n%s ",samusrgrp_version,argv[0]); usage(); exit(1); break; + } + } + + if (m == 0) { + fprintf(stderr,"%s: ERROR: One of mode -a -r -l -L must be specified\n",argv[0]); + printf("%s\n%s ",samusrgrp_version,argv[0]); + usage(); + exit(1); + } + + if (m > 1) { + fprintf(stderr,"%s: ERROR: Please select only one of modes -a -r -l -L\n",argv[0]); + exit(1); + } + + if (!list && (!usr || !grp || !*usr || !*grp) ) { + fprintf(stderr,"%s: ERROR: Both -u and -g must be specified.\n",argv[0]); + exit(1); + } + + + /* Implicit mode parameter overrides mode based on binary name */ + if (add) what = 1; + if (rem) what = 2; + if (list) what = 3; + + // printf("add = %d, rem = %d, list = %d, what = %d\n",add,rem,list,what); + + + /* Load hives. Only first SAM hive will be used however */ + + hivename = argv[optind+no_hives]; + if (!hivename || !*hivename) { + fprintf(stderr,"%s: ERROR: You must specify a SAM registry hive filename.\n",argv[0]); + exit(1); + } + do { + if (!(hive[no_hives] = openHive(hivename, + HMODE_RW|mode))) { + fprintf(stderr,"%s: ERROR: Unable to open/read registry hive, cannot continue\n",argv[0]); + exit(1); + } + switch(hive[no_hives]->type) { + case HTYPE_SAM: H_SAM = no_hives; break; + // case HTYPE_SOFTWARE: H_SOF = no_hives; break; + // case HTYPE_SYSTEM: H_SYS = no_hives; break; + // case HTYPE_SECURITY: H_SEC = no_hives; break; + } + no_hives++; + hivename = argv[optind+no_hives]; + } while (hivename && *hivename && no_hives < MAX_HIVES); + + if (H_SAM == -1) { + fprintf(stderr,"%s: WARNING: Registry hive does not look like SAM!\n" + "%s: WARNING: Continuing anyway, may lead to strange messages/failures!\n",argv[0],argv[0]); + H_SAM = 0; + } + + + /* Do logic */ + + if (list == 3) { + cmd_machinesid(); + ret = 0; + } else { + if (list) { + sam_list_groups(hive[H_SAM], list - 1, human); + ret = 0; + } else { + ret = cmd_usrgrp(usr, grp, what, human); + if (!ret && human) printf("Success!\n"); + } + } + + /* write registry hive (if needed) */ + + wret = 0; + for (il = 0; il < no_hives; il++) { + wret |= writeHive(hive[il]); + if (hive[il]->state & HMODE_DIDEXPAND) + fprintf(stderr," WARNING: Registry file %s was expanded! Experimental! Use at own risk!\n",hive[il]->filename); + while (no_hives > 0) + closeHive(hive[--no_hives]); + } + + return(ret | wret); +} +