MSERT (Microsoft Safety Scanner) and PowerShell

MSERT promotion on twitter
The twitter tinyurl redirected to http://blogs.technet.com/b/security/archive/2012/11/15/microsoft-s-free-security-tools-microsoft-safety-scanner.aspx

The original locations of the MSERT site are:

That said, let me also share my recent experience about it :-)

While investigating an APT (Advanced Persistent Threat) in September, the CSO in my organisation asked me to run the free MSERT tool in ‘detect-only’ mode on both Windows XP (32bit) and Windows 7 (64bit) workstations.

I won’t have been able to achieve this task without PowerShell 8-)

Here’s what I did (I was using powershell V2 at that time):

  • Download the tool
  • The 32bit and 64bit file can be dowloaded from the following locations:
    http://definitionupdates.microsoft.com/download/definitionupdates/safetyscanner/amd64/msert.exe
    http://definitionupdates.microsoft.com/download/definitionupdates/safetyscanner/x86/msert.exe

    I’ve automated this task with a small PowerShell script that runs as a scheduled task under a specific domain user account who has his proxy settings configured:
    On PowerShell version 3.0 you can do:

    #Requires -Version 3.0            
    $scriptrootpath = Split-Path -parent $MyInvocation.MyCommand.Definition            
    $urlrootpath = "http://definitionupdates.microsoft.com/download/definitionupdates/safetyscanner"            
    "amd64","x86" | ForEach-Object -Process {            
        $version = $_            
        try {            
            Invoke-WebRequest -Uri "$urlrootpath/$_/msert.exe" -ErrorAction Stop -OutFile (            
                Join-Path -Path $scriptrootpath -ChildPath "\$_\msert.exe")            
        } catch {            
            Write-Warning -Message "Failed to download the $version because $($_.Exception.Message)"            
        }            
    }

    On PowerShell version 2.0 you can do:

    #Requires -Version 2.0            
    # Set the web client            
    $wc = New-Object System.Net.WebClient            
                
    $scriptrootpath = Split-Path -parent $MyInvocation.MyCommand.Definition            
                
    # Get the x64 version            
    $wc.DownloadFile(            
    'http://definitionupdates.microsoft.com/download/definitionupdates/safetyscanner/amd64/msert.exe',            
    (Join-Path -Path $scriptrootpath -ChildPath "\x64\msert.exe")            
    )            
                
    # Get the x86 version            
    $wc.DownloadFile(            
    'http://definitionupdates.microsoft.com/download/definitionupdates/safetyscanner/x86/msert.exe',            
    (Join-Path -Path $scriptrootpath -ChildPath "\x86\msert.exe")            
    )

    Note that if you need a to specify a proxy and use credentials with the Invoke-Webrequest cmdlet, you could use splatting and do:

    $securePW = ConvertTo-SecureString -AsPlainText -String $cleartxtPW -Force            
    $cred = new-object System.Management.Automation.PSCredential("$env:USERDOMAIN\$env:username",$securePW)            
    $HT = @{            
        Proxy = [system.uri]"http://my.corp.proxy.fqdn.url:8080"            
        ProxyCredential = $cred            
    }
  • Run the tool
  • I’ve stored both 32 and 64 bit versions in a central location. I’ve planned a scheduled task on all the target workstations that runs once at midnight, pulls locally the correct file (32bit for XP and 64bit for Windows 7 in my case) and launches the msert.exe with the following arguments: /F /N /Q
    MSERT commandline switches

  • Copy results to a central location
  • On the next morning, I’ve configured my daily maintenance script to copy the log file found in to a central location:

    "$env:SystemRoot\debug\msert.log"

    Each msert.log has been copied in a central share into a folder based on the computername and its location.

  • Analyse results
  • I’m not really very proud of this quick and dirty approach but to be able to quickly analyse results over thousands of files, I wrote the following function that:

    • parses the file log and splits results being appended to the log by each msert.exe invocation
    • extracts the main result code of each scan, the version of msert definition used, …
    • extracts an array of threats that contains the threat name and the raw details associated with it
    • only returns custom objects

    I’ve been using two helpers function provided by two Powershell MVPs:

    #Requires -Version 2.0
    Function Get-MsertReport {
        [cmdletBinding()]
        param(
            [parameter(mandatory=$true)]
            [string]$filepath=$null
        )
        Begin {
           
            $reports = @()
            $mainstart = 0
            $allcontent = (Get-content -Path $filepath -ReadCount 1 -TotalCount -1 -Encoding Unicode)
            $mainend = $allcontent.Length
            $allcontentatonce = Get-content  -ReadCount 0 -Path $filepath -Encoding Unicode
            $reportscount = (($allcontentatonce | Get-Matches '\-{87}') | Measure-Object).Count
            $reportsObj = $allcontent | Select-String -Pattern ([regex]'(\-){87}')
    
            if ($reportscount -eq 1)
            {
                $reports += New-Object -TypeName PSObject -Property @{
                    Index = 0
                    Start = $mainstart
                    End = $mainend
                    Value = $allcontentatonce
                }
            } else {
                for ($i=0 ; $i -le ($reportscount-1) ; $i++)
                {
                    switch($i)
                    {
                        {$i -eq 0} {
                            $first = $reportsObj[0].LineNumber
                            $second = $reportsObj[1].LineNumber
                            $reports += New-Object -TypeName PSObject -Property @{
                                Index = $_
                                Start = $first
                                End = $second
                                Value = ((Get-Content -Path $filepath -TotalCount $second)[$first..$second])
                            }
                            break
                        }
                        {$i -eq ($reportscount-1)} {
                            $prev2last = $reportsObj[$reportscount-1].LineNumber
                            $reports += New-Object -TypeName PSObject -Property @{
                                Index = $_
                                Start = $prev2last
                                End = $mainend
                                Value = (($allcontent)[$prev2last..$mainend])
                            }
                            break
                        }
                        default {
                            $next = $reportsObj[$i+1].LineNumber
                            $prev = $reportsObj[$i].LineNumber
                            $reports += New-Object -TypeName PSObject -Property @{
                                Index = $_
                                Start = $prev
                                End = $next
                                Value = ((Get-Content -Path $filepath -TotalCount $next)[($prev+1)..($next-1)])
                           }
                        }
                    }
                }
            }
        }
        Process 
        {    
            $reports | ForEach-Object -Process {
    
                # Build an empty treats array
                $Threats = @()
                
                $report = $_
                $reportvalue = $report.Value
                
                # Get the build
                $build = [version]($reportvalue | Get-Matches '^Microsoft\sSafety\sScanner\sv\d{1}\.\d{1},\s\(build(?<build>\s\d{1}\.\d{3}\.\d{3}\.\d{1})\)').build
                
                # Get the return code
                $ReturnCode = ($reportvalue | Get-Matches '^Return\scode:\s\d{1,2}\s\((?<hexcode>.*)\)' ).hexcode
    
                # Ignore partial logs or old formatted logs w/o return code
                if ($ReturnCode){
    
                # Get the date of the scan
                $startdate = $reportvalue | Get-Matches '^Started\sOn\s(?<date>.*)'
                
                # http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx
                $date = ConvertFrom-DateString -Value $startdate.date -FormatString  'ddd MMM dd HH:mm:ss yyyy'
                
                $start = ($reportvalue | Select-String -Pattern "^----------------$")[0].LineNumber
                $end   = ($reportvalue | Select-String -Pattern "^----------------$")[1].LineNumber-3
    
                $count = (($reportvalue | Get-Matches '^Threat\sdetected:\s(?<ThreatName>.*)') | Measure-Object).Count
    
                $ThreatsObj = ($reportvalue | Select-String -Pattern "^Threat\sdetected:\s(?<ThreatName>.*)") 
    
                if ($count -eq 1)
                {
                    $ThreatName = ($ThreatsObj | Get-Matches '^Threat\sdetected:\s(?<ThreatName>.*)').ThreatName
                    $ThreatDetails = (Get-Content -Path $filepath -TotalCount $report.end)[($report.start+$ThreatsObj.LineNumber)..($report.start+$end)]
                    $Threats += New-Object -TypeName PSObject -Property @{
                            ThreatName = $ThreatName
                            RawDetails = $ThreatDetails
                    }
                } else {
                    # If there's more than 1 match returned by select-string...
                    for ($i=0 ; $i -le ($count-1) ; $i++)
                    {
                        $ThreatName = ($ThreatsObj[$i]).Matches | ForEach-Object -Process {$_.Groups} | Select-Object -Last 1 -ExpandProperty Value
                        switch ($i)
                        {
                            # First
                            {$i -eq 0} {
                                $second = $report.start + ($ThreatsObj[1]).LineNumber
                                $first =  $report.start + ($ThreatsObj[0]).LineNumber
                                $ThreatDetails = (Get-Content -Path $filepath -TotalCount $second)[$first..($second-2)] #| Select-string -Pattern "^->Scan\sERROR:" -NotMatch
                                break
                            }
                            # Last
                            {$i -eq ($count-1)} {
                                $prev2last = $report.start + ($ThreatsObj[$count-1]).LineNumber
                                $ThreatDetails = (Get-Content -Path $filepath -TotalCount $report.end)[$prev2last..($report.start+$end+1)]
                                break
                            }
                            default {
                                $prev = $report.start + ($ThreatsObj[$i]).LineNumber
                                $next = $report.start + ($ThreatsObj[$i+1]).LineNumber
                                $ThreatDetails = (Get-Content -Path $filepath -TotalCount $report.end)[($prev+1)..($next-1)]
                            }
                        }
                        $Threats += New-Object -TypeName PSObject -Property @{
                            ThreatName = $ThreatName
                            RawDetails = $ThreatDetails
                        }
                    }
                }
                New-Object -TypeName PSobject -Property @{
                    Build = $build
                    Date = $date
                    ReturnCode = $ReturnCode
                    Threats = $Threats
                }
                }
            }
        } 
        End {}
    }
    

    For the statistics part, I did:

    • Grab all the results
    • $results = @()            
      $results = Get-ChildItem -Path "\\ServerName\CentralShareName" -Exclude "FolderName1","Folder2" | Where-Object -FilterScript {$_ -is [System.IO.DirectoryInfo]} | ForEach-Object -Process {            
          $Directory = $_.FullName            
          # There's an underscore between the location and the computername            
          $ComputerName = ($_.Name -split '_')[-1]            
          $hasMSERT = $isW7 = $false            
          $ComputerReport = $null            
          # if the W7.dat file is present in the folder, it's a Windows 7 based computer            
          Get-Item "$Directory\*" -Include W7.dat,msert.log | ForEach-Object -Process {            
              if ($_.Name -eq 'W7.dat') {$isW7 = $true}            
              if ($_.Name -eq 'msert.log') { $hasMSERT = $true ; $ComputerReport = Get-MsertReport -filepath $_.FullName}            
          }            
          New-Object -TypeName PSObject -Property @{            
              ComputerName = $ComputerName            
              HasMSERTlog = $hasMSERT            
              isWindows7 = $isW7            
              Report = $ComputerReport            
          }            
      }
    • Get some general figures
    • # Count the total # of folders            
      $results.Count            
                  
      # Count those that have a msertlog file            
      ($results | Where {$_.HasMSERTlog}).Count            
                  
      # Count the total # of W7 computers            
      ($results | Where { $_.isWindows7}).Count            
                  
      # Count the total # of Windows 7 that have a msert log file            
      ($results | Where { $_.isWindows7 -and $_.HasMSERTlog}).Count
    • Extract the results for a single computer
    • # Get the specific results from our test computer for a specific Msert version            
      $results | Where { $_.ComputerName -eq 'test-ComputerName'} | ForEach-Object -Process {            
          New-Object -TypeName PSObject -Property @{            
              ComputerName = $_.ComputerName            
              IsW7 = $_.isWindows7            
              ThreatsName = ($_.Report | Where {$_.Build -eq '1.135.850.0'} | Select-Object -ExpandProperty Threats | Select-Object -Property ThreatName)            
          }            
      }
    • Extract other lists
    • # Sort the unique names of malware found            
      ($results | Where {$_.Report } | Select-Object -ExpandProperty Report | Where { $_.ReturnCode -ne '0x0'} | Select-Object -ExpandProperty Threats | Select-Object -Property ThreatName | Sort-Object -Unique -Property ThreatName)            
                  
      # Total count of unique malware names found            
      ($results | Where {$_.Report } | Select-Object -ExpandProperty Report | Where { $_.ReturnCode -ne '0x0'} | Select-Object -ExpandProperty Threats | Select-Object -Property ThreatName | Sort-Object -Unique -Property ThreatName).Count            
                  
      # Unique malware name with their occurrence            
      $results | Where {$_.Report } | Select-Object -ExpandProperty Report | Where { $_.ReturnCode -ne '0x0'} | Select-Object -ExpandProperty Threats | Select-Object -Property ThreatName | Group-Object -Property ThreatName | Sort-Object -Property Count            
                  
      # Total unique computers infected after our scan             
      ($results | Where {$_.Report } | ForEach-Object -Process {            
          New-Object -TypeName PSObject -Property @{            
              ComputerName = $_.ComputerName            
              IsW7 = $_.isWindows7            
              ThreatsName = ($_.Report | Where {$_.Build -eq '1.135.850.0'} | Select-Object -ExpandProperty Threats | Select-Object -Property ThreatName)            
          }            
      }  | Where {$_.ThreatsName} | Sort-Object -Property ThreatsName).count | Format-Table -AutoSize -Wrap -Property ComputerName,isW7,@{l='Threats';e={$_.ThreatsName}}            
      
    • More and more…
    •          
                  
      # Display results with computer names and their list of threats found            
      $results | Where {$_.Report } | ForEach-Object -Process {            
          New-Object -TypeName PSObject -Property @{            
              ComputerName = $_.ComputerName            
              IsW7 = $_.isWindows7            
              ThreatsName = ($_.Report | Where {$_.Build -eq '1.135.850.0'} | Select-Object -ExpandProperty Threats | Select-Object -Property ThreatName)            
          }            
      }  | Where {$_.ThreatsName} | Sort-Object -Property ThreatsName | Format-Table -AutoSize -Wrap -Property ComputerName,isW7,@{l='Threats';e={$_.ThreatsName}}            
                  
                  
      # Display results for W7 computers            
      $results | Where {$_.Report } | ForEach-Object -Process {            
          New-Object -TypeName PSObject -Property @{            
              ComputerName = $_.ComputerName            
              IsW7 = $_.isWindows7            
              ThreatsName = ($_.Report | Where {$_.Build -eq '1.135.850.0'} | Select-Object -ExpandProperty Threats | Select-Object -Property ThreatName)            
          }            
      }  | Where {$_.ThreatsName -and $_.isW7} | Sort-Object -Property ThreatsName | Format-Table -AutoSize -Wrap -Property ComputerName,isW7,@{l='Threats';e={$_.ThreatsName}}            
                  
                  
      # Display results for XP computers            
      $results | Where {$_.Report } | ForEach-Object -Process {            
          New-Object -TypeName PSObject -Property @{            
              ComputerName = $_.ComputerName            
              IsW7 = $_.isWindows7            
              ThreatsName = ($_.Report | Where {$_.Build -eq '1.135.850.0'} | Select-Object -ExpandProperty Threats | Select-Object -Property ThreatName)            
          }            
      }  | Where {$_.ThreatsName -and -not($_.isW7)} | Sort-Object -Property ThreatsName | Format-Table -AutoSize -Wrap -Property ComputerName,isW7,@{l='Threats';e={$_.ThreatsName}}            
                  
                  
      # Count of W7 computers compromised            
      ($results | Where {$_.Report } | ForEach-Object -Process {            
          New-Object -TypeName PSObject -Property @{            
              ComputerName = $_.ComputerName            
              IsW7 = $_.isWindows7            
              ThreatsName = ($_.Report | Where {$_.Build -eq '1.135.850.0'} | Select-Object -ExpandProperty Threats | Select-Object -Property ThreatName)            
          }            
      }  | Where {$_.ThreatsName -and $_.isW7} ).Count            
                  
      # Count of XP computers            
      ($results | Where {$_.Report } | ForEach-Object -Process {            
          New-Object -TypeName PSObject -Property @{            
              ComputerName = $_.ComputerName            
              IsW7 = $_.isWindows7            
              ThreatsName = ($_.Report | Where {$_.Build -eq '1.135.850.0'} | Select-Object -ExpandProperty Threats | Select-Object -Property ThreatName)            
          }            
      }  | Where {$_.ThreatsName -and -not($_.isW7)} ).Count            
      
    • Look for a specific string in the results
    • $results | Where {$_.Report } | ForEach-Object -Process {            
          New-Object -TypeName PSObject -Property @{            
              ComputerName = $_.ComputerName            
              IsW7 = $_.isWindows7            
              Threats = ($_.Report | Where {$_.Build -eq '1.135.850.0'} | Select-Object -ExpandProperty Threats | Select-Object -ExpandProperty RawDetails | Where {$_ -match "Obfuscator"})            
          }            
      }  | Where {$_.Threats }
    • Review all the results for Windows 7 computers
    • # Display results with computer names and their list of threats found            
      $results | Where {$_.Report } | ForEach-Object -Process {            
          New-Object -TypeName PSObject -Property @{            
              ComputerName = $_.ComputerName            
              IsW7 = $_.isWindows7            
              ThreatsName = ($_.Report | Where {$_.Build -eq '1.135.850.0'} | Select-Object -ExpandProperty Threats | Select-Object -Property ThreatName)            
              Report = $_.Report            
          }            
      }  | Where {$_.ThreatsName} | % {             
          if ($_.IsW7) {            
              "$($_.ComputerName)" ;             
                          
              $_.Report| Where {$_.Build -eq '1.135.850.0'} | Select-Object -ExpandProperty Threats | % {            
                  $_.ThreatName            
                  $_.RawDetails            
              }            
                  
          }                
      }

    Last word, don’t panic when you review results. You may find many malware that are actually inactive and harmless. I recommend that you contact your antivirus editor. You should ask them how their real time protection works, they should help you analyse results as well as review your antivirus configuration and check if you’ve followed best practices.

    About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s