Get-MD4Hash

Greetings, Earthlings.

Today was a pretty lazy Sunday, in which I was throwing around some ideas involving pass-the-hash attacks and Windows password hashes.  Nothing ground-breaking there, but during my research, I realized that there's very little out there for just simply generating an NT hash from a password, which if you're reading this you probably already know is just the MD4 hash of the UTF16 little endian-encoded password.  There is nothing built into the .NET framework for computing MD4 hashes at all, which is a shame when you think of how simple and easy it is in comparison to generate MD5, SHA1, SHA2, etc., hashes in .NET.  It's easy to understand why there are no .NET commodities for the MD4 algorithm though, since it is ancient and horribly broken from a security standpoint.  I even thought about implementing MD4 myself from scratch using nothing but RFC 1320, but it wasn't that important to merit all that work.  This is the type of thing I just wanted to bang out on a Sunday afternoon without much bother... not a long, full-fledged project.  And of course there are some C, C++, Python, etc. implementations floating around out there, but I didn't see anything specifically for .NET or for Powershell that was just too easy to ignore, so I decided to write something.

I knew Microsoft already had a perfect implementation of MD4 lying around... no reason to re-invent that wheel.  It's in bcrypt.dll, right next to a bunch of other much better algorithms.  (But I need MD4 ... for... research...)     I wrote this in C#, but I ported it to Powershell because I think Powershell fits better on this blog.  I call it Get-MD4Hash, and it simply returns the MD4 hash of whatever byte array you feed it.  If you specifically want to make it an NTLM hash, make sure you feed it a string that is encoded in Unicode (wide characters.)  I verified its accuracy by giving my real workstation password to the cmdlet, then I dumped the SAM on my machine and verified that it's the exact same hash.

Function Get-MD4Hash
{
<#
.SYNOPSIS
    This cmdlet returns the MD4 hash of the data that is input.
    WARNING: MD4 is not secure, so it should NEVER be used to 
    protect sensitive data. This cmdlet is for research purposes only!

.DESCRIPTION
    This cmdlet returns the MD4 hash of the data that is input.
    WARNING: MD4 is not secure, so it should NEVER be used to 
    protect sensitive data. This cmdlet is for research purposes only!
    This cmdlet uses Microsoft's implementation of MD4, exported 
    from bcrypt.dll. The implementation is fully compliant with
    RFC 1320. This cmdlet takes a byte array as input, not a string.
    So if you wanted to hash a string (such as a password,) you 
    need to convert it to a byte array first.

.EXAMPLE
    Get-MD4Hash -DataToHash $([Text.Encoding]::Unicode.GetBytes("YourPassword1!"))

.PARAMETER DataToHash
    A byte array that represents the data that you want to hash.

.INPUTS
    A byte array containing the data you wish to hash.

.OUTPUTS
    A 128-bit hexadecimal string - the MD4 hash of your data.

.NOTES
    Author: Ryan Ries, 2014, ryan@myotherpcisacloud.com

.LINK
    https://myotherpcisacloud.com
#>
    [CmdletBinding()]
    Param ([Parameter(Mandatory=$True, ValueFromPipeline=$False)]           
           [Byte[]]$DataToHash)
    END
    {        
        Set-StrictMode -Version Latest
        Add-Type -TypeDefinition @'
        using System;
        using System.Text;
        using System.Runtime.InteropServices;
        public class BCrypt
        {
            [DllImport("bcrypt.dll", CharSet = CharSet.Auto)]
            public static extern NTStatus BCryptOpenAlgorithmProvider(
                [Out] out IntPtr phAlgorithm,
                [In] string pszAlgId,
                [In, Optional] string pszImplementation,
                [In] UInt32 dwFlags);

            [DllImport("bcrypt.dll")]
            public static extern NTStatus BCryptCloseAlgorithmProvider(
                [In, Out] IntPtr hAlgorithm,
                [In] UInt32 dwFlags);

            [DllImport("bcrypt.dll", CharSet = CharSet.Auto)]
            public static extern NTStatus BCryptCreateHash(
                [In, Out] IntPtr hAlgorithm,
                [Out] out IntPtr phHash,
                [Out] IntPtr pbHashObject,
                [In, Optional] UInt32 cbHashObject,
                [In, Optional] IntPtr pbSecret,
                [In] UInt32 cbSecret,
                [In] UInt32 dwFlags);

            [DllImport("bcrypt.dll")]
            public static extern NTStatus BCryptDestroyHash(
                [In, Out] IntPtr hHash);

            [DllImport("bcrypt.dll")]
            public static extern NTStatus BCryptHashData(
                [In, Out] IntPtr hHash,
                [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,
                [In] int cbInput,
                [In] UInt32 dwFlags);

            [DllImport("bcrypt.dll")]
            public static extern NTStatus BCryptFinishHash(
                [In, Out] IntPtr hHash,
                [Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,
                [In] int cbInput,
                [In] UInt32 dwFlags);

            [Flags]
            public enum AlgOpsFlags : uint
            {            
                BCRYPT_PROV_DISPATCH = 0x00000001,
                BCRYPT_ALG_HANDLE_HMAC_FLAG = 0x00000008,
                BCRYPT_HASH_REUSABLE_FLAG = 0x00000020
            }

            // This is a gigantic enum and I don't want to copy all of it into this Powershell script.
            // Basically anything other than zero means something went wrong.
            public enum NTStatus : uint
            {
                STATUS_SUCCESS = 0x00000000
            }
        }
'@

        [Byte[]]$HashBytes   = New-Object Byte[] 16
        [IntPtr]$PHAlgorithm = [IntPtr]::Zero
        [IntPtr]$PHHash      = [IntPtr]::Zero
        $NTStatus = [BCrypt]::BCryptOpenAlgorithmProvider([Ref] $PHAlgorithm, 'MD4', $Null, 0)
        If ($NTStatus -NE 0)
        {
            Write-Error "BCryptOpenAlgorithmProvider failed with NTSTATUS $NTStatus"
            If ($PHAlgorithm -NE [IntPtr]::Zero)
            {
                $NTStatus = [BCrypt]::BCryptCloseAlgorithmProvider($PHAlgorithm, 0)
            }
            Return
        }
        $NTStatus = [BCrypt]::BCryptCreateHash($PHAlgorithm, [Ref] $PHHash, [IntPtr]::Zero, 0, [IntPtr]::Zero, 0, 0)
        If ($NTStatus -NE 0)
        {
            Write-Error "BCryptCreateHash failed with NTSTATUS $NTStatus"
            If ($PHHash -NE [IntPtr]::Zero)
            {
                $NTStatus = [BCrypt]::BCryptDestroyHash($PHHash)                
            }
            If ($PHAlgorithm -NE [IntPtr]::Zero)
            {
                $NTStatus = [BCrypt]::BCryptCloseAlgorithmProvider($PHAlgorithm, 0)
            }
            Return
        }

        $NTStatus = [BCrypt]::BCryptHashData($PHHash, $DataToHash, $DataToHash.Length, 0)
        $NTStatus = [BCrypt]::BCryptFinishHash($PHHash, $HashBytes, $HashBytes.Length, 0)

        If ($PHHash -NE [IntPtr]::Zero)
        {
            $NTStatus = [BCrypt]::BCryptDestroyHash($PHHash)
        }
        If ($PHAlgorithm -NE [IntPtr]::Zero)
        {
            $NTStatus = [BCrypt]::BCryptCloseAlgorithmProvider($PHAlgorithm, 0)
        }
        
        $HashString = New-Object System.Text.StringBuilder
        Foreach ($Byte In $HashBytes)
        {
            [Void]$HashString.Append($Byte.ToString("X2"))
        }
        Return $HashString.ToString()
    }
}
Comments are closed