/ * * (C) Jeremy Allison 1997. All rights reserved. * * This program is free for commercial and non-commercial use. * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * THIS SOFTWARE IS PROVIDED BY JEREMY ALLISON `` AS IS '' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE dISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * /
#include
#include "des.h"
. / * * Program to dump the Lanman and NT MD4 Hashed passwords from * an NT SAM database into a Samba smbpasswd file Needs Administrator * privillages to run * Takes one arg -. The name of the machine whose SAM database you * wish to dump , IF this arg is not given it dumps the local machine * account data. * /
/ * * Convert System Error to Char. Returns * Memory Allocated with locallock. * /
char * error_to_string (DWORD error) {char * msgbuf; if (FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, error, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), / * Default language * / (char *) & msgbuf, 0, NULL) == 0 ) return 0; return msgbuf;..} / * * return a pointer to a string describing an os error * error_to_string returns a pointer to LocalAlloc'ed * memory Cache it and release when the next one is * requested * /.
Char * STR_OSERR (DWORD ERR) {static char * lastmsg = 0;
IF (Lastmsg) LocalFree (Hlocal);
Lastmsg = error_to_string (err); return LastMsg;}
/ * Utility Function to Get Allocate A SID from a name. * Looks on local machine. SID IS Allocated with localloc * and must be freed by the caller. * Returns True on success, false on five. * /
Bool get_sid (const char * name, sid ** ppsid) {sID_NAME_USE SID_USE; DWORD SID_SIZE = 0; DWORD DOM_SIZE = 0; Char * Domain
* Ppsid = 0; if (LookupAccountName (0, name, 0, & sid_size, 0, & dom_size, & sid_use) == 0) {if (! GetLastError () = ERROR_INSUFFICIENT_BUFFER) {fprintf (stderr, "get_sid: LookupAccountName for size on name % s failed. Error WAS% S / N ", Name, Str_OSERR (GetLastError ()); Return False;}}
* PPSID = (SID *) Localalloc (LMEM_FIXED, SID_SIZE); DOMAIN = (char *) Localalloc (LMEM_FIXED, DOM_SIZE); if (* ppsid == 0 || Domain == 0) {fprintf (stderr, "get_sid: localalloc Failed. Error WAS% S / N ", Str_OSERR (GetLastError ())); if (* ppsid) localfree ((hlocal) * ppsid); if (domain) localfree ((hlocal); * ppsID = 0; Return FALSE;} if (LookupAccountName (0, name, * ppsid, & sid_size, domain, & dom_size, & sid_use) == 0) {fprintf (stderr, "get_sid: LookupAccountName failed for name% s Error was% s / n.", name Str_OSERR (GetLastError ())); LocalFree ((Hlocal); LocalFree ((Hlocal) Domain; * ppsid = 0; Return False;
Localfree (hlocal); return true;}
/ * * Utility function to setup a security descriptor * from a varargs list of char * name followed by a DWORD access * mask. The access control list is allocated with LocalAlloc * and must be freed by the caller. * Returns TRUE on success, False on five. * /
Bool create_sd_from_list (security_descriptor * sdout, int num, ...) {va_list ap; sid ** sIDS = 0; char * name; dword amask; dword acl_size; pacl pacl = 0; int i;
IF ((SIDS = (sID **) Calloc (1, SID *) * NUM) == 0) {fprintf (stderr, "create_sd_from_list: calloc fail./N"); return false;}
ACL_SIZE = Num * (SIZEOF (ACL) SIZEOF (Access_Allowed_Ace) SizeOf (DWORD));
/ * Collect all the sid's * / va_start (ap, num); for (i = 0; i
/ * Add the ACL into the sd * / if (SetSecurityDescriptorDacl (sdout, TRUE, pacl, FALSE) == FALSE) {fprintf (stderr,. "Create_sd_from_list:. SetSecurityDescriptorDacl fail Error was% s / n", str_oserr (GetLastError ( ))); Goto cleanup;} for (i = 0; i Cleanup: IF (SIDS! = 0) {for (i = 0; I / * * Function to go over all the users in the sam and set an acl * on them. * / int set_userkeys_security (HKEY start, const char * path, SECURITY_DESCRIPTOR * psd, HKEY * return_key) {HKEY key; DWORD err; char usersid [128]; DWORD indx = 0; / * Open the path and enum all the user keys - setting . the same security on them * / if (! (err = RegOpenKeyEx (start, path, 0, KEY_ENUMERATE_SUB_KEYS, & key)) = ERROR_SUCCESS) {fprintf (stderr, "set_userkeys_security: Failed to open key% s to enumerate / Error was. % s. / n ", path, str_oserr (err)); Return -1;} / * Now Enumerate the Subkeys, Setting The security on Them all; * / do {dword size; filetime ft; Size = sizeof (userSid); err = regenumKeyex (key, indx, usersid, & size, 0, 0, 0, & ft); if (err == error_success) {HKEY SUBKEY; INDX ; if ((Err = RegopenKeyex (Key, UserSid, 0, Write_DAC, & Subkey))! = error_success) {fprintf (stderr, "set_userkeys_security: failed to open key% s to set security. / error WAS% s. / n ", usersid, str_oserr (err)); RegCloseKey (key); return -1;} if ((err = RegSetKeySecurity (subkey, DACL_SECURITY_INFORMATION, psd)) = ERROR_SUCCESS) {fprintf (stderr,!" set_userkeys_security: Failed to set security On Key% s. / error WAS% s. / n ", usersid, str_oserr (err)); regcloseKey (Subkey); regcloseKey (key); return -1;} regcloseKey (Subkey);}} while (Err == Error_suCcess); if (Err! = Error_NO_MORE_ITEMS) {regcloseKey (key); return -1;} if (return_key == 0) RegcloseKey (key); else * return_key = key; return 0;} / * * Function to Travel Down The Sam Security Tree in The Registry and Restore * The Correct ACL on Them. Returns 0 on success. -1 on fail. * / INT restore_sam_tree_access (HKEY Start) {char path [128]; char * p; hkey key; dword err; security_descriptor sd; dword admin_mask; Admin_mask = Write_dac | Read_Control; IF (Create_SD_FROM_LIST (& SD, 2, "System", Generic_all, "Administrat", Admin_Mask) == false) Return -1; STRCPY (path, "security // sam // domains // account // user); / * Remove The security on the user keys first. * / If (set_userkeys_security (start, path, & sd, 0)! = 0) Return -1; / * Now go up the path, restoring security * / do {if ((err = RegOpenKeyEx (start, path, 0, WRITE_DAC, & key)) = ERROR_SUCCESS!) {Fprintf (stderr, "restore_sam_tree_access: Failed to open key% s To set / security s. / n ", PATH, STR_OSERR (ERR)); RETURN-1;} IF ((Err = RegSetKeySecurity (key, dacl_security_information, & sd)! = error_success) {fprintf (stderr, "RESTORE_SAM_TREE_ACCESS: FAILED TO SET Security On Key% S. / Error WAS% S. / N", Path, Str_OsoRR (ERR)); RegcloseKey (key); return -1;} regclosekey, p = strrchr (Path) , '//'); if (p! = 0) * p = 0;} while (p! = 0); return 0;} / * * Function to travel the security tree and add Administrators * access as WRITE_DAC, READ_CONTROL and READ. * Returns 0 on success. -1 on fail if no security was changed, * -2 on fail if security was changed. * / int set_sam_tree_access (HKEY start, HKEY * return_key) {char path [128]; char * p; HKEY key; DWORD err; BOOL security_changed = FALSE; SECURITY_DESCRIPTOR sd; DWORD admin_mask; BOOL finished = FALSE; Admin_mask = write_dac | read_control | key_query_value | key_enumerate_sub_keys; IF (Create_SD_FROM_LIST (& SD, 2, "System", Generic_all, "Administrat", Admin_Mask) == false) Return -1; Strcpy (path, "security // sam // domains // account /// user); p = strchr (path, '//'); Do {IF (p! = 0) * p = 0; else finished = true; if ((Err = regopenkeyex (Start, Path, 0, Write_DAC, & Key)! = error_success) {fprintf (stderr, "set_sam_tree_access: failed To Open Key% S. Set / Security. Error WAS% s. / N ", PATH, STR_OSERR (ERR)); RETURN (Security_Changed? -2: -1);} if ((Err = RegSetKeySecurity (key, dacl_security_information, & SD))! = error_success) {fprintf (stderr, "set_sam_tree_access: failed to set security on key% s. / error WAS% s. / n", path, str_oserr (err)); regclosekey (key); return (security_changed ? -2: -1);} security_changed = true; regcloseKey (key); if (p! = 0) {* p = '//'; p = strchr (p, '//');}} while ! finished); if (set_userkeys_security (start, path, & sd, & key)! = 0) RETURN-2; if (return_key == 0) RegcloseKey (key); else * return_key = key; return 0;} / * * Function to get a little-endian int from an offset Into * a byte arrness. * / INT get_int (char * array) {return ((array [0] & 0xff) ((Array [1] << 8) & 0xff00) ((Array [2] << 16) & 0xff0000) ((Array [3] << 24) & 0xff000000)); / * * Convert a 7 byte array into an 8 byte des key with odd parity. * / Void str_to_key (unsigned char * smr, unsigned char * key) {void des_set_odd_parity (des_cblock *); 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 convelectr the rid to the first decrypt key. * / Void Sid_to_Key1 (unsigned long sid, unsigned char deskey [8]) {UNSIGNED Char S [7]; S [0] = (unsigned char); 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 (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); / * * Function to split a 'V' entry Into a users name, passwords and comment. * / INT CHECK_VP (Char * VP, INT VP_SIZE, CHAR ** Username, Char ** Fullname, Char ** Comment, Char ** Homedir, Char * Lanman, Int * Got_Lanman, Char * MD4, INT * GOT_MD4, DWORD RID) { des_key_schedule ks1, ks2; des_cblock deskey1, deskey2; int username_offset = get_int (vp 0xC); int username_len = get_int (vp 0x10); int fullname_offset = get_int (vp 0x18); int fullname_len = get_int (vp 0x1c); int comment_offset = get_int (vp 0x24); int comment_len = get_int (vp 0x28); int homedir_offset = get_int (vp 0x48); int homedir_len = get_int (vp 0x4c); int pw_offset = get_int (vp 0x9c); * username = 0; * fullname = 0; * comment = 0; * homedir = 0; * got_lanman = 0; * got_md4 = 0; if (username_len <0 || username_offset <0 || comment_len <0 || fullname_len <0 || homedir_offset <0 || comment_offset <0 || pw_offset <0) return -1; username_offset = 0xCC; fullname_offset = 0xCC; Comment_offset = 0xcc; homedir_offset = 0xcc; PW_OFFSET = 0xcc; IF ((* username = (char *) malloc (username_len 1)) == 0) {fprintf (stderr, "check_vp: malloc fail for username./n"); return -1;} if ((* fullname = (char *) Malloc (Fullname_len 1)) == 0) {fprintf (stderr, "check_vp: malloc fail for username./N); Free (* username); * username = 0; return -1;} if ((* Comment = (char *) Malloc (Comment_len 1)) == 0) {FPRINTF (stderr, "check_vp: malloc fail for Comment./N"); Free (* username); * username = 0; free (* fullname); * fullname = 0; return -1;} IF (* homedir = (char *) Malloc (Homedir_len 1)) == 0) {fprintf (stderr, "check_vp: malloc fail for homedir./ n "); free (* username); * username = 0; free (* fulname); * full (* comment); * comment = 0; return -1;} wcstombs (* username, (wchar_t * ) (vp username_offset), username_len / sizeof (wchar_t)); (* username) [username_len / sizeof (wchar_t)] = 0; wcstombs (* fullname, (wchar_t *) (vp fullname_offset), fullname_len / sizeof (wchar_t )); (* Fullname) [fullname_len / sizeof (wchar_t)] = 0; WCSTombs (* C OMMENT, (WCHAR_T *) (VP Comment_offset), comment_len / sizeof (wchar_t)); (* Comment) [comment_len / sizeof (wchar_t)] = 0; WCSTombs (* Homedir, (Wchar_t *) (VP HomeDir_offset), Homedir_len / sizeof (wchar_t)); (* HomeDir) [homedir_len / sizeof (wchar_t)] = 0; if (pw_offset> = vp_size) {/ * no password * / * got_lanman = 0; * got_md4 = 0; Return 0; } / * Check That The Password Offset Plus The size of the lantword offset Plus The size of the lantnin and md4 Hashes Fits WITHIN THE V RECORD. * / IF (PW_OFFSET 32> VP_SIZE) {/ * account disabled? * / * Got_lanman = -1; * got_md4 = - 1; Return 0;} / * 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); vp = pw_offset; / * Decrypt the lanman password hash as two 8 byte blocks * / des_ecb_encrypt ((des_cblock *) vp, (des_cblock *) lanman, ks1, DES_DECRYPT);. des_ecb_encrypt ((des_cblock * (VP 8), (DES_CBLOCK *) & Lanman [8], KS2, DES_DECRYPT); VP = 16; / * Decrypt The NT MD4 Password Hash AS Two 8 Byte Blocks. * / des_ecb_encrypt ((DES_CBLOCK *) VP, (DES_CBLOCK *) MD4, KS1, DES_DECRYPT); DES_ECB_ENCRYPT ((DES_CBLOCK *) (VP 8), (DES_CBLOCK *) & MD4 [8], KS2, DES_DECRYPT); * got_lanman = 1; * got_md4 = 1; return 0;} / * * Function to Print Out A 16 Byte Array As Hex. * / Void Print_HexVal (Char * VAL) {Int i; for (i = 0; i <16; i ) Printf ("% 02x", (unsigned char) VAL [I]);} / * Function to strip out any ':' or '/ n', '/ r' from a text * string. * / Void strip_text (char * txt) {char * p; for (p = strchr (txt, ':'); p; p = strchr (p 1, ')) * p =' _ '; for (P = StrChr (txt, '/ n'); p; p = strchr (p 1, '/ n')) * p = '_'; for (p = strchr (txt, '/ r'); p; P = strChr (p 1, '/ r')) * p = '_';} / * * Function to Dump a users SMBPASSWD Entry ONTO STDOUT. * RETURNS 0 on success, -1 on five. * / INT PrintOut_smb_ENTRY (HKEY User, DWord Rid) {DWORD ERR; DWORD TYP; CHAR * VP; Char Lanman [16]; char MD4_hash [16]; char * username; char * fullname; char * comment; char * * homedir; int got_lanman; int got_md4; / * Find Out How much space We need for the 'V' Value. * / If ((Err = RegQueryValueex (User, "V", 0, & Type, 0, & size)! = Error_success) {fprintf (stderr, " PrintOut_smb_entry: unable to determine size neededed / for user 'V' Value. Error WAS% s. / n. ", str_oserr (err)); return -1;} IF ((vp = (char *) malloc (size)) == 0) {FPRINTF (stderr, "printout_smb_entry: malloc fail for user entry./N"); return -1;} if ((Err = RegQueryValueex (User, "V", 0, & Type, (lpbyte) VP, & size))! = error_success) {fprintf (stderr, "printout_smb_entry: unable to read user 'v' value. / error WAS% s. / n. / error WAS% s. / n. / error WAS% s. / n. / er_oserr (err)); free (vp); return -1; } / * Check heuristics * / if (check_vp (vp, size, & username, & fullname, & comment, & homedir, lanman, & got_lanman, md4_hash, & got_md4, rid)! = 0) {fprintf (stderr, "Failed to parse entry for RID% X / N ", RID); Free (VP); Return 0;} / * EnSure Username of Comment Don't Have Any Nasty SUPRISES for US Such As an Embedded ':' or '/ N' - See MultiPL e UNIX passwd field update security bugs for details ... * / strip_text (username); strip_text (fullname); strip_text (comment); / * If homedir contains a drive letter this mangles it - but it protects the integrity of the smbpasswd file * / Strip_text (homedir); Printf ("% s:% d:", username, rid); if (got_lanman) {if (got_lant == -1) / * disabled account? * / printf ("*********** ********************** "); Else Print_HexVal (LANMAN);} else printf (" no password *********** ******** "); Printf (": "); if (got_md4) {IF (got_md4 == -1) / * disabled account? * / Printf (" ********* ************************ "); Else Print_HexVal (MD4_HASH);} else printf (" no password ***************** ********** "); Printf (": "); if (* fullname) Printf ("% s ", fullname); if (* fullname && * comment) Printf (", "); IF (* comment) Printf ("% s", comment); Printf (":"); if (* homedir) Printf ("% s", homedir); Printf (": / n"); free (username) Free (Comment); Free (Homedir); Free (VP); return 0;} / * * Function to Go Through all the user sid's - dumping out * Their sam value. Returns 0 on success, -1 on five. * / INT Enumerate_Users (HKEY Key) {DWORD INDX = 0; DWORD ERR; DWORD RID; CHAR USERSID [128]; DO {dWord size; filetime ft; Size = sizeof (userSid); err = regenumKeyex (key, indx, usersid, & size, 0, 0, 0, & ft); if (err == error_success) {HKEY SUBKEY; INDX ; if ((Err = Regopenkeyex (key, usersid, 0, key_query_value, & subkey))! = error_success) {fprintf (stderr, "enumerate_users: failed to open key% s to read value. / error WAS% s. / n ", Usersid, str_oserr (err)); regcloseKey (key); return -1;} rid = start, 0, 16); / * Hack As we know there is a names key here * / if (rid! = 0) {IF (Subkey, RID)! = 0) {regcloseKey (Subkey); return -1;}} regcloseKey (Subkey);}} while (err == error_success); if (err! = Error_no_more_items) { RegcloseKey (Key); Return -1;} return 0;} / * * Print usage message and die. * / Void usage (const char * arg0) {fprintf (stderr, "usage:% s / * * Usage: // machine * / INT Main (int Argc, char ** argv) {char username [128]; dword size; hkey start_key = hkey_local_machine; hkey users_key; int err; IF (Argc> 2) USAGE (Argv [0]); / * * Ensure We are running as administrator before * we will run run. * / Size = sizeof (username); if (username (username, & size) == false {fprintf (stderr, "% s: getusername () failed. Error WAS% s. ", Argv [0], STR_OSERR (GetLastError ())); Return -1;} IF (StriCMP ("Administrator", UserName! = 0) {FPRINTF (stderr, "% s: you must be running as user administrator / to run this program / n", argv [0]); return -1;} / * * Open a connection to the remote machines registry. * / If (argc == 2) {if ((err = RegConnectRegistry (argv [1], HKEY_LOCAL_MACHINE, & start_key))! = ERROR_SUCCESS) {fprintf (stderr, "% S: Failed to connect to registry on remote computer% s. / error WAS% s. / n ", argv [0], argv [1], str_oserr (err)); return -1;}} / * * We need TO GET to HKEY_LOCAL_MACHINE / SECURITY / SAM / DOMAINS / Account / Users. * The security on this key normally doesn't allow administrators * to read - We need to add this. * / if ((err = set_sam_tree_access (start_key, & users_key)) = 0!) {if (err == -2) restore_sam_tree_access (start_key); return -1;}. / * Print the users SAM entries in smbpasswd format onto stdout * / enumerate_users (users_key); RegCloseKey (users_key); / * reset the security on the SAM * / restore_sam_tree_access (start_key); if (! start_key = HKEY_LOCAL_MACHINE) RegCloseKey (start_key); return 0;}