All posts

PowerShell Anti-Virus?…Kinda: Leveraging VirusTotal API to check file hashes with PowerShell

Today’s short project came from my exploration of VirusTotal and the expansive amount of data/tools that they have available. Their most powerful tool is their API, which allows us to perform searches of URLs, Hashes,…

PowerShell Anti-Virus?…Kinda: Leveraging VirusTotal API to check file hashes with PowerShell

PowerShell Anti-Virus?…Kinda

While the majority of my content is going to be PowerShell or JavaScript-based, my content will vary from big multi-part projects to short one-page ones that introduce some cool proof-of-concepts or just ideas that I thought would be cool.

Today’s short project came from my exploration of VirusTotal and the expansive amount of data/tools that they have available. Their most powerful tool is their API, which allows us to perform searches of URLs, Hashes, and Files, even giving us the ability to directly upload files to be scanned. While this feature is awesome, if we want to scan files en-masse, this method would take up a lot of bandwidth and time, so instead, we’ll be using the API to check for hashes.

The idea is simple: if we enumerate the file system, we can hash every file we find and check it against the VirusTotal hash database. If we find something, we can log it or delete it, similar to a function performed by modern-day Anti-Virus solutions. However, this is not an Anti-Virus solution, it can, however, be a feature of one or a method of finding viruses on your system.

Anti-Virus solutions are much more complex, using different data sets, signatures, and heuristics to find even the most advanced threats. This, however, comes nowhere close but is fun to experiment with. The point of this project is to give you a base to build off of, where you can go and script out other important features of an AV solution. In the end, it’s all about being creative and having fun with it.

If you haven’t already, create an account on VirusTotal since we’ll be using their API to check for hashes. Note that free users are limited to 4 API calls per minute, and thus, we will implement that in the code. If you’re a premium user, then you’re not rate-limited.

We’ll first start with enumerating the file system, which is quite simple. We’ll be using the Get-ChildItem cmdlet and use the recurse option so that it goes through subdirectories as well. Now, if we provide it a path to the root of the file system, we can grab every file. We’ll then need to loop through that list of files, so we’ll pipe the output to a ForEachObject loop and use $_ to reference every file. Here’s an example below.

# Iterate File System To Go Through Every Single File
Get-ChildItem -Path C:\ -Recurse -File -Force | ForEach-Object {
        
}

Now, we’ll need to generate a hash for every file that we have, this is also very simple. We’ll be using the Get-FileHash cmdlet and we’ll pipe the output to the Select-Object cmdlet with the -ExpandProperty option so that we can strictly get the hash value, like below.

# Iterate File System To Go Through Every Single File
Get-ChildItem -Path C:\ -Recurse -File -Force | ForEach-Object {
        # Use $_ to refer to each object.
        $Hash = Get-FileHash -Path $_ | Select-Object -ExpandProperty Hash
}

Now, we’ll want to reach out to the Virus Total API. For this, we’ll build a function that uses the Invoke-RestMethod cmdlet to send our hash to VT and then return the response. We’ll need to insert our API key into the headers before making the GET request.

function Get-VirusTotal([string]$hash){
    $headers=@{}
    $headers.Add("x-apikey", "APIKEY")
    $headers.Add("accept", "application/json")
    $response = Invoke-RestMethod -Uri "https://www.virustotal.com/api/v3/search?query=$hash" -Method GET -Headers $headers
    return $response.data.attributes
}

The returned data will look something like this:

size                   : 28
first_seen_itw_date    : 1648393845
times_submitted        : 1352
magic                  : data
tags                   : {}
type_description       : unknown
last_analysis_date     : 1674847677
meaningful_name        : .GamingRoot
last_submission_date   : 1674913476
sha1                   : a8bb3f9491ee357df22e000cb2b380def473a8cb
unique_sources         : 1278
first_submission_date  : 1637623783
last_analysis_results  : @{Bkav=; Lionic=; tehtris=; ClamAV=; CMC=; CAT-QuickHeal=; ALYac=; Malwarebytes=; VIPRE=;
                         Sangfor=; K7AntiVirus=; Alibaba=; K7GW=; Trustlook=; BitDefenderTheta=; VirIT=; Cyren=;
                         SymantecMobileInsight=; Symantec=; Elastic=; ESET-NOD32=; APEX=; TrendMicro-HouseCall=;
                         Avast=; Cynet=; Kaspersky=; BitDefender=; NANO-Antivirus=; ViRobot=; MicroWorld-eScan=;
                         Rising=; Emsisoft=; Baidu=; F-Secure=; DrWeb=; Zillya=; TrendMicro=; McAfee-GW-Edition=;
                         SentinelOne=; Trapmine=; FireEye=; Sophos=; Paloalto=; Avast-Mobile=; Jiangmin=; Webroot=;
                         Avira=; MAX=; Antiy-AVL=; Kingsoft=; Microsoft=; Gridinsoft=; Xcitium=; Arcabit=;
                         SUPERAntiSpyware=; ZoneAlarm=; GData=; Google=; BitDefenderFalx=; AhnLab-V3=; Acronis=;
                         McAfee=; TACHYON=; VBA32=; Cylance=; Zoner=; Tencent=; Yandex=; Ikarus=; MaxSecure=;
                         Fortinet=; AVG=; Cybereason=; Panda=}
reputation             : 2
names                  : {.GamingRoot, GamingRoot, $RM30G32.GamingRoot, $R07FL0Q.GamingRoot…}
last_modification_date : 1674913477
last_analysis_stats    : @{harmless=0; type-unsupported=14; suspicious=0; confirmed-timeout=0; timeout=0; failure=0;
                         malicious=0; undetected=60}
ssdeep                 : 3:X1clIlrln:XeSlx
sha256                 : 44358c2aef7d41aa55758d909041c9b1a3ae050c44466dadada33bdffdda34aa
md5                    : 4e2042aabd9ff2d19007754d03b09229
total_votes            : @{harmless=2; malicious=0}

This is the bulk of the functionality. From here, we can customize the output to our liking. For example, we could create a hashtable that contains the file name, path, and hash of the file if Virus Total returns the file as being malicious. We can then append this hash to an array and print that array at the end so we know if we found anything malicious. You can then decide what to do with said files. You can also prompt and check if a user is using the free API or Premium and adjust the code as necessary.

The final bit of code up to this point would look something like this:

# Initialize Array That Contain Hash Table
$FoundFiles = @()

# Fetch From VT API
function Get-VirusTotal([string]$hash){
    $headers=@{}
    $headers.Add("x-apikey", "APIKEY")
    $headers.Add("accept", "application/json")
    $response = Invoke-RestMethod -Uri "https://www.virustotal.com/api/v3/search?query=$hash" -Method GET -Headers $headers
    return $response.data.attributes
}

# Prompt User If Virus Total Free Or Premium
$APISubscription = Read-Host "Are You Using Virus Total Free or Premium?[Free] "

if ($APISubscription -eq "Free" || $APISubscription -eq "") {
    # Iterate File System To Go Through Every Single File
    Get-ChildItem -Path C:\ -Recurse -File -Force | ForEach-Object {
        # Use $_ to refer to each object.
        $Hash = Get-FileHash -Path $_ | Select-Object -ExpandProperty Hash
        # Send file hash and return VT results
        $VTResults = Get-VirusTotal $Hash
        # If VT returns any malicious hits do something
        if ($VTResults.last_analysis_stats.malicious -gt 1) {
            # Create Hash Table Include File Name, File Location, Malware Type & File Hash; Append This To Found Files
            $FlaggedFile = @{filename=$_.Name; filePath=$_; fileHash=$Hash; maliciousCount=$VTResults.last_analysis_stats.malicious}
            $FoundFiles.Add($FlaggedFile) 
        }
        # Free API Is Limited To 4 Lookups A Minute
        Start-Sleep -Seconds 15
    }
    Write-Host $FoundFiles
}

I hope you enjoyed this short article! Stay tuned for more!

Discussion

No comments yet