Modifying Windows NT Logon Credentials Hernán Ochoa hochoa@core-sdi.com Introduction A common attack against Windows NT consists in obtaining usernames and LM/NT password hashes using tools such as L0phtCrack, or tcpdump-smb. These are then used to gain unauthorized access to file and printer shares on the attacked server. To be able to use this username/hashes pairs instead of the commonly used username/password pairs, the attacker must use some kind of modified SMB client. SAMBA, a Unix implementation of the SMB/CIFS protocol, is normally used by attackers due to the availability of its source code, what makes its modification to conform to their needs extremely simple. However, its use also limits the attack to file and printer shares. Although SAMBA in its latest versions began to implement the MS RPC protocol, it doesn't implement yet all the funcionality given by Windows NT common administration utilites, and probably never will. This paper describes how to change the logon credentials of a logged on user on a Windows NT Server/Workstation, which automatically lets the attacker take advantage of any native Windows NT application that makes use of the user's username and hashes for authentication, such as the Registry Editor (regedt32.exe), User Manager for Domains (usrmgr.exe), Service Control Manager etc. Windows NT Logon and Authentication Model There are three basic components that take part in the NT logon and authentication model: logon processes, the LSA server process, and authentication packages. * Logon Processes: A logon process is a component trusted by the operating system to monitor I/O devices for logon attempts (A network is considered to be a device). * The LSA (Local Security Authority) Server Process: A user-mode process (LSASS.EXE) that is responsible for the local system security policy, user authentication, and sending security audit messages to the Event Log. * Authentication Packages: This components (implemented as DLLs) are responsible for performing the actual user's credentials authentication, creating a new LSA Logon Session for the user and returning a set of SIDs and other information appropiate for inclusion in a Token object. This represents the user's security context for access to NT operations. Also, the authentication package may associate one or more supplementary pieces of credential information with the user's logon session for later reference. The LSA links to these DLLs at system startup by reading the entries in the registry key \HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa under AuthenticationPackages. Logon processes receives logon attempts and send the user's credentials to the LSA for authentication. This authentication is carried out by the authentication packages. Each logon process must register itself to the LSA at startup, and at that moment it selects a certain authentication package to use. The API that authentication packages must implement, and the API available to logon processes are documented in the LSAAUTH.HLP help file from the Windows NT DDK. WinLogon and MsV1_0 The Windows NT default logon process for interactive logons is called Winlogon (WINLOGON.EXE) , it intercepts logon attempts from the keyboard. At startup, WinLogon registers itself to the LSA as a logon process calling the function LsaRegisterLogonProcess. This gives it back a Lsa logon process handle and establishes a LPC connection with the LSA authentication port (\LsaAuthenticationPort) that will be used for exchanging information during logon, logoff, and password operations. Then it obtains an association ID for the default authentication package, MSV1_0 (MSV1_0.DLL) by calling LsaLookupAuthenticationPackage. This is the package that it will use to authenticate the user's credentials. Everytime WinLogon wants to logon a user to the system, it calls LsaLogonUser, specifying its own LSA logon process Handle, the Package ID of Msv1_0 and the user's username and password in the call. This ends up in LsaApLogonUserEx implemented in MSV1_0.DLL, here the user's username and password are authenticated using the SAM Database (local or remote), and if the authentication was successfull, a logon session is created calling LsaCreateLogonSession assigning to it a LogonId (LUID) generated by the package. After this, MSv1_0 adds supplementary credentials to the logon session by calling LsaAddCredential, this credentials happen to be the user's username, domain name and LM/NT hashes of his/her password. This information is then used by the LAN Manager and other services when the user attempts to access remote nodes and it is the information we need to change in order to use the stolen username and hashes in the attack. Msv1_0 also obtains from the SAM Database the SID of the user account and the groups the account belongs to and passes this to the LSA Server Process., which obtains a list of privileges associated with the account and groups, gathers all this information and calls the NT Executive to create an Access Token for the user. A handle to this toke is then returned to WinLogon who starts a shell program, normally EXPLORER.EXE. An Access Token is linked to the Lsa Logon Session it belongs to by its AuthenticationId, which is no other than the LogonId generated by the authentication Package. Modifying MSV1_0 Credentials The approach chosen to modify these credentials was to figure out the undocumented structures the LSA Server Process uses to store the logon sessions and their associated credentials. Knowing this we can proceed to directly edit in memory the current user's credentials and change them for the ones of the user we want to impersonate. Keep in mind this is guesswork, and some things might be innacurate: The LSA Server Process keeps the logon sessions in a single-linked list. There is a global structure where it keeps a pointer to this list and the number of logon sessions created (there isn't a straightforward way to obtain the location of this structure, it resides somewhere in LSASRV.DLL, a dynamic link library used by the LSA where the logon session functions and structures are implemented). struct LsaLogonSessionArray { // Pointer to the linked list uint32_t pLogonSessionsList; // number of logon sessions uint32_t logonSessionsCount; }; pLogonSessionsList is a pointer to the single-linked list of logon sessions, this is the format of an item of that list: struct LsaLogonSession { // Pointer to next logon session uint32_t pNextLogonSession; // LogonId (LUID) uint32_t logonIDLow; uint32_t logonIDHigh; // Pointer to the username in Unicode format uint32_t pUNICODE_USERNAME; // Pointer to the user's domain name in Unicode format uint32_t pUNICODE_DOMAINNAME; // Unkown. It appears to be a pointer to another structure where among other things // the RID of the user is stored. uint32_t unkown1; // It appears to be a a reference count of the logon session. uint32_t referenceCount; // Pointer to an array of credentials uint32_t pLsaCredentialsArray; }; pLsaCredentialsArray points to another structure which i called LsaCredentialsArray. if pLsaCredentials == 0 then the logon session doesn't have any associated credentials. struct LsaCredentialsArray { // Unkown uint32_t unkown1; // Unkown uint32_t unkown2; // pointer to the credentials uint32_t pLsaCredentials[]; }; During the research i didn't observed logon sessions with more than one set of credentials but i decided to call this an array anyway. pLsaCredentials lead us finally to the credentials added by authentication packages. struct LsaCredentials { // Unkown uint32_t unkown1; // Value of the Primary Key of the credentials STRING PrimaryKey; // Credentials STRING Credentials; }; STRING has this format: struct STRING { USHORT Length; USHORT MaximumLength; PCHAR Buffer; }; Every set of credentials must be given a Primery Key value, this is a string the authentication package can later use to reference to this credentials when calling LsaGetCredentials. MSv1_0 uses "Primary" as the Primary Key value. Credentials is not neccesary a characters string. In the case of Msv1_0, Credentials.Buffer points to a structure like this: struct MsvCredentials { UNICODE_STRING UNICODE_DOMAIN; UNICODE_STRING UNICODE_USERNAME; uint8_t NTHash[16]; uint8_t LMHash[16]; UCHAR domainname[UNICODE_DOMAIN.Length]; UCHAR username[UNICODE_USERNAME.Length]; }; This is the information that is used by LAN Manager and other services that uses the Msv1_0 authentication package when accessing remote nodes. Changing this allow us to perform SMB/CIFS/MS RPC attacks based on stolen username/password hashes from a Windows NT Workstation/Server. UNICODE_DOMAIN and UNICODE_USERNAME are not normal unicode strings, its Buffer member isn't a direct pointer to the unicode string, but an offset from the beggining of the MsvCredentials structure address. This is arranged in this way by Ms1_0 when it calls LsaAddCredential to make this information independent of a process address space. Another thing to notice, is that Section 4.2 MsV1_0 Credential Formats of LSAAUTH.HLP states that the credentials stored by Msv1_0 don't include the Domain Name, but they do. Knowing all this, the procedure to change the current user logon credentials is: * Obtain the current user's Access Token AuthenticationId. This can easily be done using the WIN32 API: // We obtain the current process access token OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken); // We obtain from that token the statistics information GetTokenInformation( hToken, TokenStatistics, &tokenstats, sizeof(tokenstats), &len) // the AuthenticationId member of the returned structure of type TOKEN_STATISTICS // contains the logon ID associated with this token. logonID = tokenstats.AuthenticationId; * Obtain the pointer to the LsaLogonSessionArray. As mentioned above, there isn't a straightforward way to obtain this pointer, this is the method I used: + I obtain a hProcess handle to the LSA Server Process executable image using OpenProcess with PROCESS_ALL_ACCESS (you need to have debug privileges to do this). + I inject a DLL into LSASS.EXE process address space creating a remote thread. This DLL contains a function that will do all the work . + This function calls GetModuleHandle to obtain a hModule to the MSV1_0.DLL and obtains the address of the function LsaApInitializePackage using GetProcAddress(). LsaApInitializePackage is a function every authentication package must implement, and it is called by the LSA after loading the package's DLL. In this call the authentication package receives a pointer to a LsaDispatchTable. This table contains pointers to the LSA's functions available to authentication packages, such as LsaAddCredential and LsaGetCredentials (these functions are implemented in LSASRV.DLL). + Adding 38h to the LsaApInitializePackage address I obtain the address of a variable where msv1_0.dll stores a pointer to the function LsaAddCredential. + I obtain the address of LsaAddCredential from this variable and I add 4h to it. This gives me the address of a CRITICAL_SECTION object LSASRV.DLL uses to synchronize accesses to the LsaLogonSessionArray structure. + By adding 1Ch to this address we finally obtain the pointer to LsaLogonSessionArray. The LsaLogonSessionArray is placed in memory following the previously mentioned CRITICAL_SECTION object.The value 1Ch results from adding the CRITICAL_SECTION object size (18h) plus 4 bytes, which is the size of another variable LSASRV.DLL uses, located just between the CRITICAL_SECTION object and the LsaLogonSessionArray. + Once I have the LsaLogonSessionArray address the function in the remote thread sends it back to me using a named pipe. * Having the LsaLogonSessionArray address, I walk the single-linked list searching for a LsaLogonSession with a LogonID equal to the token's AuthenticationId. This is done using ReadProcessMemory and the hProcess of LSASS.EXE. * Finally, All that's left to do is changing the MsV1_0 credentials associated with that LsaLogonSession. This is the less intrusive way to change the credentials, and will only allow you to change the LM and NT hashes of the user. Why? because they are of fixed length (16 bytes each) and overwriting them in memory is enough, but the user's Username and Domain are Unicode strings of variable length and you need to reallocate the whole credentials block to be able to replace them with longer ones. (For example, if the Username is "MyUser" and you want to change it for "MyAnotherUser", you need a bigger buffer for the new Unicode string.) The suggested approach here consists in injecting again a DLL into LSASS.EXE process address space and calling LsaDelCredential and LsaAddCredential just like MsV1_0 would do, to replace the whole credentials with a new ones containing the new Username and Domain. Conclusion The modification of the logon credentials lets the attacker use Windows NT in stolen username/hashes based attacks. It makes possible the remote manipulation of the attacked server in a way that couldn't be done before without the plain-text passwords. It does not represent a new security hole by itself, but there are no doubts it extends the range of action of intruders.