PSH3 CTP2 Malware Analysis Module based on virustotal.com API v2

I’ve been using virustotal for years on a daily basis to submit suspect files detected by our security compliance audit script (or missed by a desktop antivirus software). We’ve been filling up an excel spreadsheet with the incident related information and especially what’s the malware name and what was the virustotal URL that gave us this (crucial) identification piece of info. Now I’d like to automate the reporting and I came up the other day with a new idea while reading the automation capabilities provided by virustotal.com. Before doing a monthly or a weekly report, it could be nice to (re)analyse the suspicious files we’ve been collected. So I started reading the new V2 public API documentation on this page: https://www.virustotal.com/documentation/public-api/v2/ and a few hours later I’ve finished writing a malware analysis module for powsershell V3.
If you want to give it a try, the first thing to do is to signup on the virustotal.com page to get your API key.
Then to install it, you do:

new-item "$env:userprofile\documents\windowspowershell\modules\VT Malware Analysis" -type directory
copy ".\VT Malware Analysis.ps*" "$env:userprofile\documents\windowspowershell\modules\VT Malware Analysis"
Import-Module -Name "VT Malware Analysis"
$APIkey = "the_API_key_you_got_from_VT_after_signup"
# List the functions provided by the module
Get-Command -Module "VT Malware Analysis"
# Get help on a command
Get-Help Send-MaliciousURLSample -full

Here is the ‘VT Malware Analysis.psd1’ file content

@{
    ModuleVersion = '1.0.0'
    Author='Emin Atac'
    Copyright='Emin Atac'    
    Description='VT Malware Analysis is a module to help you interact with the V2 API provided by virustotal.com'
    Guid='bcbcd41b-619b-4f73-904e-6e530f752362'
    RootModule='VT Malware Analysis.psm1'
    PowerShellVersion = '3.0'
} 

and the ‘VT Malware Analysis.psm1’ file content


#region  Functions

function Send-MalwareFileSample
{
[cmdletbinding(DefaultParameterSetName='',SupportsTransactions=$false)]
param(
    [Parameter(Mandatory=$true,ParameterSetName='',ValueFromPipeline=$true,Position=0)]
    [system.array]$File=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=1)]
    [System.Management.Automation.SwitchParameter]$VerboseMode = $false,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=2)]
    [System.Management.Automation.SwitchParameter]$Intensive = $false,
    
    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=3)]
    [System.URI]$Proxy=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=4)]
    [System.Management.Automation.PSCredential]$ProxyCredential=$null
)
# Store the start time
$BigStart = (Get-Date)
if ($APIkey -eq $null)
{
    Write-Host -ForegroundColor Gray -Object ("Define first a `$APIkey variable with the key you got from virustotal, see help")
    break
}
# Prepare a hashtable for splatting
$otherparams = @{}
if ($Proxy)
{
    $otherparams += @{Proxy = $Proxy}
    if ($ProxyCredential)
    {
       $otherparams += @{ProxyCredentials = $ProxyCredential}
    }
}
# Initialize empty arrays
$arResponses = @()
$arSamples = @()
# Loop for all submitted input File object
foreach ($item in $File)
{
    # First check if it's type is already a system.IO.FileInfo
    if ($item -is [system.IO.Fileinfo])
    {
        if ($item.length -lt 20MB)
        {
            # We can immediately add it to our samples array
            Write-Verbose -Message "Adding $($item.Fullname) to malware array" -Verbose:$VerboseMode
            $arSamples += $item
        } else {
            Write-Host -ForegroundColor Yellow -Object ("$($item.Fullname) will not be checked as its size exceeds 20B")
        }
    } else {
        # If strings were typed as input
        if (Test-Path -Path $item)
        {
            # As it exists, we can add it to the array is its size is less than 20MB
            if ((Get-Item -Path $item).length -lt 20MB)
            {
                $arSamples += Get-item -Path $item
            } else {
                Write-Host -ForegroundColor Yellow -Object ("$item will not be checked as its size exceeds 20B")
            }
        } else {
            # Let us know that we can't find it 
                # if ($Verbose) { Write-Host -ForegroundColor Yellow -Object ("$item was not found")}
                Write-Verbose -Message ("$item was not found") -Verbose:$VerboseMode
        }
    }
}
# Initialize variables
$count = $i = 0
$Start =  (Get-Date)
# Now loop for each element we have in our array
foreach ($item in $arSamples)
{
    # Show some progress activity...
    $i++
    Write-Progress -activity "Dealing with file: $($item.fullname)" -status "Percent added: " -PercentComplete (($i/$arSamples.Count)*100)
    # Read the file content as byte array
    $bytear = Get-Content $item.Fullname -ReadCount 0 -Encoding byte
    # Encode in iso-8859-1
    $filedata = [System.Text.Encoding]::GetEncoding("iso-8859-1").GetString($bytear)
    # Get a random GUID
    $boundary = [System.Guid]::NewGuid().ToString();
    # Use a Here-string to build the content of the multipart/form-data
    $body = @"
--$boundary
Content-Disposition: form-data; name="apikey"

$APIkey
--$boundary
Content-Disposition: form-data; name="file"; filename="$($item.Fullname)"
Content-Type: application/octet-stream

$filedata
--$boundary--
"@
    if ($Intensive)
    {
        # Skip, as we don't care about API limits
        Write-Verbose -Message "Continuing as intensive switch was specified" -Verbose:$VerboseMode
    } else {
        $count++
        $End = New-Timespan $Start (Get-Date)
        if ($End -le (New-TimeSpan -Minutes 1))
        {
            # If the elapsed time if less than a minute, we'll check how many operations we did
            if ($count -le 4)
            {
                Write-Verbose -Message "Continuing as count is $count and the 1 minute limit was not reached yet" -Verbose:$VerboseMode
                # We did not reach yet the maximum of 4 operations per minute
            } else {
                # We have to wait as only 4 operations of any kind is allowed per minute
                $Waitfor = ((New-TimeSpan -Minutes 1) - $End).Seconds
                Write-Verbose -Message "Waiting for $Waitfor seconds as max count of $count reached and 1 minute not yet elapsed" -Verbose:$VerboseMode
                Start-Sleep -Seconds  $Waitfor
                # Reset the count and the start date
                $count = 1
                $Start = (Get-Date)
            } # end of less than 4 operations
        } else {
            # More than a minute elapsed, we can safely reset the count and the start
            $count = 1
            $Start = (Get-Date)
        } # end of less than a minute
    } # end of Intensive
    # Here we go !
    $webresponse = $null
    try
    {
        # Invoke-RestMethod returns a PSCustom object
        $webresponse = Invoke-RestMethod -Method Post -Uri ("https://www.virustotal.com/vtapi/v2/file/scan") -ContentType "multipart/form-data; boundary=$boundary" -Body $body @otherparams -ErrorAction Stop
    } 
    catch
    {
        Write-Host -ForegroundColor Red -Object ("The following error occured while posting request: " + $_)
    }
    # Add the response to an array
    if ($webresponse -ne $null)
    {
        $arResponses += New-Object -TypeName PSObject -Property @{            
            Resource = $webresponse.Resource
            Code = $webresponse.response_code
            Filename = $item.Fullname
        }
        # We can convert it to JSON to display it if verbose mode was enabled
        if ($VerboseMode)
        {
            $webresponse | ConvertTo-Json
        }
        Start-Sleep -Seconds 2
    } else {
        Write-Host -ForegroundColor Red -Object ("The response of the web request is null for some reasons")
    }
}
# Calculate how much time elapsed
$BigEnd = New-Timespan $BigStart (Get-Date)                    
Write-Verbose -Message ("Operation completed in: $('{0}' -f $BigEnd)") -Verbose:$VerboseMode
# Finally return our array
return $arResponses
<#
    
.SYNOPSIS    
    Send a malware sample to be analysed by virustotal
   
.DESCRIPTION  
    Send a malware file using the public API v2 of virustotal.com
    The file is sent by forming a multipart/form-data post web request over https.

.PARAMETER File
    The path of the file to be submitted to virustotal.com

.PARAMETER VerboseMode
    Switch parameter that will activate a verbose console output

.PARAMETER Intensive
    Switch to override the default limit of 4 requests of any nature per minute if you have the private API

.PARAMETER Proxy
    Set the proxy address to use (optional)

.PARAMETER ProxyCredential
    Set the credentials to be used with the proxy address (optional)
     
.LINK    
    https://p0w3rsh3ll.wordpress.com

.EXAMPLE
    Send-MalwareFileSample -File .\eicar.com
    Upload the file eicar.com to virustotal.com
     
.EXAMPLE
    Get-item  .\eicar.com | Send-MalwareFileSample
    Upload the file eicar.com to virustotal.com

.EXAMPLE
    Get-item  .\eicar.com | Send-MalwareFileSample -Proxy "http://my.internal.proxy.address"
    Upload the file eicar.com to virustotal.com using the internal http://my.internal.proxy.address

.NOTES    
    Name: Send-MalwareFileSample
    Author: Emin Atac
    DateCreated: 24/02/2012

#>
} # end of Send-MalwareFileSample function

Function Get-MalwareFileReport
{
[cmdletbinding(DefaultParameterSetName='',SupportsTransactions=$false)]
param(
    [Parameter(Mandatory=$true,ParameterSetName='',ValueFromPipeline=$true,Position=0)]
    [system.array]$InputObject=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=1)]
    [System.Management.Automation.SwitchParameter]$VerboseMode = $false,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=2)]
    [System.Management.Automation.SwitchParameter]$Intensive = $false,
    
    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=3)]
    [System.URI]$Proxy=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=4)]
    [System.Management.Automation.PSCredential]$ProxyCredential=$null
)
# Store the start time
$BigStart = (Get-Date)
if ($APIkey -eq $null)
{
    Write-Host -ForegroundColor Gray -Object ("Define first a `$APIkey variable with the key you got from virustotal, see help")
    break
}
# Prepare a hashtable for splatting
$otherparams = @{}
if ($Proxy)
{
    $otherparams += @{Proxy = $Proxy}
    if ($ProxyCredential)
    {
       $otherparams += @{ProxyCredentials = $ProxyCredential}
    }
}
# Initialize empty arrays
$arResponses = @()
$arSamples = @()
# Make sure that the InputObject has all the properties we expect
foreach ($item in $InputObject)
{
    if ($item.Code -eq 1)
    {
        # If the item was indeed present and it could be retrieved it will be 1.
        $arSamples += $item
    } elseif ($item.Code -eq -2) {
        # If the requested item is still queued for analysis it will be -2.
        Write-Host -ForegroundColor Yellow -Object ("The item $($item.Filename) is still queued for analysis")
    } elseif ($item.Code -eq 0) {
        # if the item you searched for was not present in VirusTotal's dataset this result will be 0.
        Write-Host -ForegroundColor Red -Object ("The item $($item.Filename) was not found in the VT dataset")
    } else {
        Write-Host -ForegroundColor Red -Object ("Unknown response from API: $($item.Code)")
    }
}
# Initialize variables
$count = $i = 0
# Now that we are sure that we have only system.io.fileinfo objects in our arSamples array
$Start =  (Get-Date)
foreach ($item in $arSamples)
{
    # Show some progress activity...
    $i++
    Write-Progress -activity "Dealing with file: $($item.Filename)" -status "Percent added: " -PercentComplete (($i/$arSamples.Count)*100)
    # Get a random GUID
    $boundary = [System.Guid]::NewGuid().ToString();
    # Use a Here-string to build the content of the multipart/form-data
    $respbody = @"
--$boundary
Content-Disposition: form-data; name="apikey"

$APIkey
--$boundary
Content-Disposition: form-data; name="resource"

$($item.resource)
--$boundary--
"@
    Write-Debug -Message $respbody
    if ($Intensive)
    {
        # Skip, as we don't care about API limits
        Write-Verbose -Message "Continuing as intensive switch was specified" -Verbose:$VerboseMode
    } else {
        $count++
        $End = New-Timespan $Start (Get-Date)
        if ($End -le (New-TimeSpan -Minutes 1))
        {
            # If the elapsed time if less than a minute, we'll check how many operations we did
            if ($count -le 4)
            {
                Write-Verbose -Message "Continuing as count is $count and the 1 minute limit was not reached yet" -Verbose:$VerboseMode
                # We did not reach yet the maximum of 4 operations per minute
            } else {
                # We have to wait as only 4 operations of any kind is allowed per minute
                $Waitfor = ((New-TimeSpan -Minutes 1) - $End).Seconds
                Write-Verbose -Message "Waiting for $Waitfor seconds as max count of $count reached and 1 minute not yet elapsed" -Verbose:$VerboseMode
                Start-Sleep -Seconds  $Waitfor
                # Reset the count and the start date
                $count = 1
                $Start = (Get-Date)
            } # end of less than 4 operations
        } else {
            # More than a minute elapsed, we can safely reset the count and the start
            $count = 1
            $Start = (Get-Date)
        } # end of less than a minute
    } # end of Intensive
    # Here we go !
    $webresponse = $null
    try
    {
        # Invoke-RestMethod returns a PSCustom object
        $webresponse = Invoke-RestMethod -Method Post -Uri ("https://www.virustotal.com/vtapi/v2/file/report") -ContentType "multipart/form-data; boundary=$boundary" -Body $respbody @otherparams -ErrorAction Stop
    } 
    catch
    {
        Write-Host -ForegroundColor Red -Object ("The following error occured while posting request: " + $_)
    }
    # Add the response to an array
    if ($webresponse -ne $null)
    {
        $fullURL = [system.URI]$webresponse.permalink
        $linkar = @($fullURL.Segments)
        $browsableURL = $fullURL.Scheme + "://" + $fullURL.Host + (-join ($linkar[0..($linkar.Count-2)]))
        # $browsableURL
        $arResponses += New-Object -TypeName PSObject -Property @{            
            ScanDate = [datetime]$webresponse.scan_date
            Total = $webresponse.total
            Positives = $webresponse.positives
            Resource = $webresponse.Resource
            Code = $webresponse.response_code
            Filename = $item.Filename
            Link = $browsableURL
            Scans = $webresponse.scans
        }
        Start-Sleep -Seconds 2
        # We can convert it to JSON to display it if verbose mode was enabled
        if ($VerboseMode)
        {
            $webresponse | ConvertTo-Json
        }
    } else {
        Write-Host -ForegroundColor Red -Object ("The response of the web request is null for some reasons")
    } # end of if webresponse is null
} # end of foreach
# Calculate how much time elapsed
$BigEnd = New-Timespan $BigStart (Get-Date)                    
Write-Verbose -Message ("Operation completed in: $('{0}' -f $BigEnd)") -Verbose:$VerboseMode
# Finally return our array
return $arResponses
<#
    
.SYNOPSIS    
    Retrieve the report of a malware sample already sent to virustotal
   
.DESCRIPTION  
    Retrieve the report of a previously submitted sample file by using the public API v2 of virustotal.com
    The report is retrieved by forming a multipart/form-data post web request over https.

.PARAMETER InputObject
    It represents a PSCustom object that has 3 properties: a filename, a resource and a response from the API when submitted

.PARAMETER VerboseMode
    Switch parameter that will activate a verbose console output

.PARAMETER Intensive
    Switch to override the default limit of 4 requests of any nature per minute if you have the private API

.PARAMETER Proxy
    Set the proxy address to use (optional)

.PARAMETER ProxyCredential
    Set the credentials to be used with the proxy address (optional)
     
.LINK    
    https://p0w3rsh3ll.wordpress.com

.EXAMPLE
    Send-MalwareFileSample -File .\eicar.com | Get-MalwareFileReport
    Upload the file eicar.com to virustotal.com and retrieve its report
     
.EXAMPLE
    Get-item  .\eicar.com | Send-MalwareFileSample | Get-MalwareFileReport
    Upload the file eicar.com to virustotal.com and retrieve its report

.NOTES    
    Name: Get-MalwareFileReport
    Author: Emin Atac
    DateCreated: 24/02/2012

#>     
} # end of Get-MalwareFileReport function

function Send-MaliciousURLSample
{
[cmdletbinding(DefaultParameterSetName='',SupportsTransactions=$false)]
param(
    [Parameter(Mandatory=$true,ParameterSetName='',ValueFromPipeline=$true,Position=0)]
    [system.array]$URL=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=1)]
    [System.Management.Automation.SwitchParameter]$VerboseMode = $false,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=2)]
    [System.Management.Automation.SwitchParameter]$Intensive = $false,
    
    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=3)]
    [System.URI]$Proxy=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=4)]
    [System.Management.Automation.PSCredential]$ProxyCredential=$null
)
# Store the start time
$BigStart = (Get-Date)
if ($APIkey -eq $null)
{
    Write-Host -ForegroundColor Gray -Object ("Define first a `$APIkey variable with the key you got from virustotal, see help")
    break
}
# Prepare a hashtable for splatting
$otherparams = @{}
if ($Proxy)
{
    $otherparams += @{Proxy = $Proxy}
    if ($ProxyCredential)
    {
       $otherparams += @{ProxyCredentials = $ProxyCredential}
    }
}
# Initialize empty arrays
$arResponses = @()
# Initialize variables
$count = $i = 0
$Start =  (Get-Date)
foreach ($item in $URL)
{
    # Show some progress activity...
    $i++
    Write-Progress -activity "Dealing with file: $item" -status "Percent added: " -PercentComplete (($i/$URL.Count)*100)
    # Get a random GUID
    $boundary = [System.Guid]::NewGuid().ToString();
    # Use a Here-string to build the content of the multipart/form-data
    $urlbody = @"
--$boundary
Content-Disposition: form-data; name="apikey"

$APIkey
--$boundary
Content-Disposition: form-data; name="url"

$item
--$boundary--
"@
    Write-Debug -Message $urlbody
    if ($Intensive)
    {
        # Skip, as we don't care about API limits
        Write-Verbose -Message "Continuing as intensive switch was specified" -Verbose:$VerboseMode
    } else {
        $count++
        $End = New-Timespan $Start (Get-Date)
        if ($End -le (New-TimeSpan -Minutes 1))
        {
            # If the elapsed time if less than a minute, we'll check how many operations we did
            if ($count -le 4)
            {
                Write-Verbose -Message "Continuing as count is $count and the 1 minute limit was not reached yet" -Verbose:$VerboseMode
                # We did not reach yet the maximum of 4 operations per minute
            } else {
                # We have to wait as only 4 operations of any kind is allowed per minute
                $Waitfor = ((New-TimeSpan -Minutes 1) - $End).Seconds
                Write-Verbose -Message "Waiting for $Waitfor seconds as max count of $count reached and 1 minute not yet elapsed" -Verbose:$VerboseMode
                Start-Sleep -Seconds  $Waitfor
                # Reset the count and the start date
                $count = 1
                $Start = (Get-Date)
            } # end of less than 4 operations
        } else {
            # More than a minute elapsed, we can safely reset the count and the start
            $count = 1
            $Start = (Get-Date)
        } # end of less than a minute
    } # end of Intensive
    # Here we go !
    $webresponse = $null
    try
    {
        # Invoke-RestMethod returns a PSCustom object
        $webresponse = Invoke-RestMethod -Method Post  -Uri ("https://www.virustotal.com/vtapi/v2/url/scan") -ContentType "multipart/form-data; boundary=$boundary" -Body $urlbody @otherparams -ErrorAction Stop
    } 
    catch
    {
        Write-Host -ForegroundColor Red -Object ("The following error occured while posting request: " + $_)
    }
    # Add the response to an array
    if ($webresponse -ne $null)
    {
        $arResponses += New-Object -TypeName PSObject -Property @{            
            Resource = $webresponse.resource
            Code = $webresponse.response_code
            SubmittedURL = $item
            SubmissionDate = [datetime]$webresponse.scan_date
            ScanID = $webresponse.scan_id
        }
        Start-Sleep -Seconds 2
        # We can convert it to JSON to display it if verbose mode was enabled
        if ($VerboseMode)
        {
            $webresponse | ConvertTo-Json
        }
    } else {
        Write-Host -ForegroundColor Red -Object ("The response of the web request is null for some reasons")
    }
}
# Calculate how much time elapsed
$BigEnd = New-Timespan $BigStart (Get-Date)                    
Write-Verbose -Message ("Operation completed in: $('{0}' -f $BigEnd)") -Verbose:$VerboseMode
# Finally return our array
return $arResponses
<#
    
.SYNOPSIS    
    Send a malicious URL to be analysed by virustotal
   
.DESCRIPTION  
    Send a malicious URL using the public API v2 of virustotal.com
    The URL is sent by forming a multipart/form-data post web request over https.

.PARAMETER URL
    The URL to be submitted to virustotal.com

.PARAMETER VerboseMode
    Switch parameter that will activate a verbose console output

.PARAMETER Intensive
    Switch to override the default limit of 4 requests of any nature per minute if you have the private API

.PARAMETER Proxy
    Set the proxy address to use (optional)

.PARAMETER ProxyCredential
    Set the credentials to be used with the proxy address (optional)
     
.LINK    
    https://p0w3rsh3ll.wordpress.com

.EXAMPLE
    Send-MaliciousURLSample -URL http://www.eicar.org/download/eicar.com.txt
    Submit a malicious URL to virustotal.com
     
.EXAMPLE
    "http://www.eicar.org/download/eicar.com.txt" | Send-MaliciousURLSample
    Submit a malicious URL to virustotal.com

.EXAMPLE
    "http://www.eicar.org/download/eicar.com.txt" | Send-MaliciousURLSample -Proxy "http://my.internal.proxy.address"
    Submit a malicious URL to virustotal.com using the internal http://my.internal.proxy.address

.NOTES    
    Name: Send-MaliciousURLSample
    Author: Emin Atac
    DateCreated: 24/02/2012

#>

} # end of function Send-MaliciousURLSample

Function Get-MaliciousURLReport
{
[cmdletbinding(DefaultParameterSetName='',SupportsTransactions=$false)]
param(
    [Parameter(Mandatory=$true,ParameterSetName='',ValueFromPipeline=$true,Position=0)]
    [system.array]$InputObject=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=1)]
    [System.Management.Automation.SwitchParameter]$VerboseMode = $false,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=2)]
    [System.Management.Automation.SwitchParameter]$Intensive = $false,
    
    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=3)]
    [System.URI]$Proxy=$null,

    [parameter(Mandatory=$false,ParameterSetName='',ValueFromPipeline=$false,Position=4)]
    [System.Management.Automation.PSCredential]$ProxyCredential=$null
)
# Store the start time
$BigStart = (Get-Date)
if ($APIkey -eq $null)
{
    Write-Host -ForegroundColor Gray -Object ("Define first a `$APIkey variable with the key you got from virustotal, see help")
    break
}
# Prepare a hashtable for splatting
$otherparams = @{}
if ($Proxy)
{
    $otherparams += @{Proxy = $Proxy}
    if ($ProxyCredential)
    {
       $otherparams += @{ProxyCredentials = $ProxyCredential}
    }
}
# Initialize empty arrays
$arResponses = @()
$arSamples = @()
# Make sure that the InputObject has all the properties we expect
foreach ($item in $InputObject)
{
    if ($item.Code -eq 1)
    {
        # If the item was indeed present and it could be retrieved it will be 1.
        $arSamples += $item
    } elseif ($item.Code -eq -2) {
        # If the requested item is still queued for analysis it will be -2.
        Write-Host -ForegroundColor Yellow -Object ("The item $($item.SubmittedURL) is still queued for analysis")
    } elseif ($item.Code -eq 0) {
        # if the item you searched for was not present in VirusTotal's dataset this result will be 0.
        Write-Host -ForegroundColor Red -Object ("The item $($item.SubmittedURL) was not found in the VT dataset")
    } else {
        Write-Host -ForegroundColor Red -Object ("Unknown response from API: $($item.Code)")
    }
}
# Initialize variables
$count = $i = 0
# Now that we are sure that we have only system.io.fileinfo objects in our arSamples array
$Start =  (Get-Date)
foreach ($item in $arSamples)
{
    # Show some progress activity...
    $i++
    Write-Progress -activity "Dealing with file: $($item.SubmittedURL)" -status "Percent added: " -PercentComplete (($i/$arSamples.Count)*100)
    # Get a random GUID
    $boundary = [System.Guid]::NewGuid().ToString();
$respbody = @"
--$boundary
Content-Disposition: form-data; name="apikey"

$APIkey
--$boundary
Content-Disposition: form-data; name="resource"

$($item.SubmittedURL)
--$boundary--
"@
# $($item.scanID)
# $respbody
    Write-Debug -Message $respbody
    if ($Intensive)
    {
        # Skip, as we don't care about API limits
        Write-Verbose -Message "Continuing as intensive switch was specified" -Verbose:$VerboseMode
    } else {
        $count++
        $End = New-Timespan $Start (Get-Date)
        if ($End -le (New-TimeSpan -Minutes 1))
        {
            # If the elapsed time if less than a minute, we'll check how many operations we did
            if ($count -le 4)
            {
                Write-Verbose -Message "Continuing as count is $count and the 1 minute limit was not reached yet" -Verbose:$VerboseMode
                # We did not reach yet the maximum of 4 operations per minute
            } else {
                # We have to wait as only 4 operations of any kind is allowed per minute
                $Waitfor = ((New-TimeSpan -Minutes 1) - $End).Seconds
                Write-Verbose -Message "Waiting for $Waitfor seconds as max count of $count reached and 1 minute not yet elapsed" -Verbose:$VerboseMode
                Start-Sleep -Seconds  $Waitfor
                # Reset the count and the start date
                $count = 1
                $Start = (Get-Date)
            } # end of less than 4 operations
        } else {
            # More than a minute elapsed, we can safely reset the count and the start
            $count = 1
            $Start = (Get-Date)
        } # end of less than a minute
    } # end of Intensive
    # Here we go !
    $webresponse = $null
    try
    {
        # Invoke-RestMethod returns a PSCustom object
        $webresponse = Invoke-RestMethod -Method Post -Uri ("https://www.virustotal.com/vtapi/v2/url/report") -ContentType "multipart/form-data; boundary=$boundary" -Body $respbody @otherparams -ErrorAction Stop
    } 
    catch
    {
        Write-Host -ForegroundColor Red -Object ("The following error occured while posting request: " + $_)
    }
    # Add the response to an array
    if ($webresponse -ne $null)
    {
        $fullURL = [system.URI]$webresponse.permalink
        $linkar = @($fullURL.Segments)
        $browsableURL = $fullURL.Scheme + "://" + $fullURL.Host + (-join ($linkar[0..($linkar.Count-2)]))
        # $browsableURL
        $arResponses += New-Object -TypeName PSObject -Property @{            
            ScanDate = [datetime]$webresponse.scan_date
            Total = $webresponse.total
            Positives = $webresponse.positives
            Resource = $webresponse.resource
            Code = $webresponse.response_code
            URL = $webresponse.url
            Link = $browsableURL
            Scans = $webresponse.scans
        }
        Start-Sleep -Seconds 2
        # We can convert it to JSON to display it if verbose mode was enabled
        if ($VerboseMode)
        {
            $webresponse | ConvertTo-Json
        }
    } else {
        Write-Host -ForegroundColor Red -Object ("The response of the web request is null for some reasons")
    } # end of if webresponse is null
} # end of foreach
# Calculate how much time elapsed
$BigEnd = New-Timespan $BigStart (Get-Date)                    
Write-Verbose -Message ("Operation completed in: $('{0}' -f $BigEnd)") -Verbose:$VerboseMode
# Finally return our array
return $arResponses
<#
    
.SYNOPSIS    
    Retrieve the report of a malicious URL sample already sent to virustotal
   
.DESCRIPTION  
    Retrieve the report of a previously submitted malicious URL by using the public API v2 of virustotal.com
    The report is retrieved by forming a multipart/form-data post web request over https.

.PARAMETER InputObject
    It represents a PSCustom object that has 3 properties: a filename, a resource and a response from the API when submitted

.PARAMETER VerboseMode
    Switch parameter that will activate a verbose console output

.PARAMETER Intensive
    Switch to override the default limit of 4 requests of any nature per minute if you have the private API

.PARAMETER Proxy
    Set the proxy address to use (optional)

.PARAMETER ProxyCredential
    Set the credentials to be used with the proxy address (optional)
     
.LINK    
    https://p0w3rsh3ll.wordpress.com

.EXAMPLE
    Send-MaliciousURLSample -URL http://www.eicar.org/download/eicar.com.txt | Get-MaliciousURLReport
    Submit a malicious URL to virustotal.com and retrieve its report
     
.EXAMPLE
    "http://www.eicar.org/download/eicar.com.txt" | Send-MaliciousURLSample | Get-MaliciousURLReport
    Submit a malicious URL to virustotal.com and retrieve its report

.NOTES    
    Name: Get-MaliciousURLReport
    Author: Emin Atac
    DateCreated: 24/02/2012

#>     
} # end of function Get-MaliciousURLReport

#endregion Functions

Export-ModuleMember -Function *
Advertisements

2 thoughts on “PSH3 CTP2 Malware Analysis Module based on virustotal.com API v2

  1. I have copied the code without modification except to place my key within it.

    The Send-MalwareFileSample function always returns “(403) Forbidden.”

    My first thought was something wrong with the key, but eventually I was able to find another program (in another language) that used this same key and worked.

    I doubt that you will be able to solve my problem but maybe you encountered something similar or at least have a “debugging strategy” you used during development?

    Any pointers would be appreciated.

    Thanks for the code.


    HerbM

    • Hi,

      I’m sorry to hear that and I’d like to thank you for letting me know that the code doesn’t work anymore.

      The above code is more than 1 year old and hasn’t been revised since the stable/official version of PowerShell 3.0 was released. In the meantime, Google acquired virustotal.com.

      I’ll probably revise the code in my blog as soon as I find some time to do it. But don’t hold your breath.

      You may have already found it, there are now many scripts in various languages to interact with the API v2 mentioned at the bottom of this page:
      https://www.virustotal.com/en-gb/documentation/public-api/v2/

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