Files
osrepodbmgr/osrepodbmgr.Core/Workers/VirusTotal.cs

399 lines
17 KiB
C#
Raw Normal View History

2017-05-19 01:13:53 +01:00
//
// Author:
// Natalia Portillo claunia@claunia.com
//
// Copyright (c) 2017, © Claunia.com
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the distribution.
// * Neither the name of the [ORGANIZATION] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
using System;
using System.Collections.Generic;
2017-05-19 18:16:16 +01:00
using System.IO;
using System.Threading;
using System.Threading.Tasks;
2017-05-19 01:13:53 +01:00
using SharpCompress.Compressors.BZip2;
2017-05-19 18:16:16 +01:00
using SharpCompress.Compressors.Deflate;
2017-05-19 01:13:53 +01:00
using SharpCompress.Compressors.LZMA;
2017-05-19 18:16:16 +01:00
using VirusTotalNET;
using VirusTotalNET.Objects;
using VirusTotalNET.Results;
2017-05-19 01:13:53 +01:00
namespace osrepodbmgr.Core
{
public static partial class Workers
{
static VirusTotal vTotal;
public static bool TestVirusTotal(string key)
{
VirusTotal vt;
FileReport report = null;
try
{
Task.Run(async () =>
{
vt = new VirusTotal(key);
2017-12-29 19:31:26 +00:00
report = await vt.GetFileReportAsync("b82758fc5f737a58078d3c60e2798a70d895443a86aa39adf52dec70e98c2bed");
2017-05-19 01:13:53 +01:00
}).Wait();
}
catch(Exception ex)
{
if(Failed != null)
Failed(ex.InnerException.Message);
return false;
}
return report != null && report.MD5 == "0bf60adb1435639a42b490e7e80d25c7";
}
public static bool InitVirusTotal(string key)
{
VirusTotal vt = null;
FileReport report = null;
try
{
Task.Run(async () =>
{
vt = new VirusTotal(key);
2017-12-29 19:31:26 +00:00
report = await vt.GetFileReportAsync("b82758fc5f737a58078d3c60e2798a70d895443a86aa39adf52dec70e98c2bed");
2017-05-19 01:13:53 +01:00
}).Wait();
}
catch(Exception ex)
{
if(Failed != null)
Failed(ex.InnerException.Message);
return false;
}
if(report != null && report.MD5 == "0bf60adb1435639a42b490e7e80d25c7")
{
vTotal = vt;
Context.virusTotalEnabled = true;
return true;
}
return false;
}
public static void VirusTotalFileFromRepo(DBFile file)
{
try
{
if(!Context.virusTotalEnabled)
{
if(Failed != null)
Failed("VirusTotal is not usable");
return;
}
if(vTotal == null)
{
if(Failed != null)
Failed("VirusTotal is not initalized");
}
FileReport fResult = null;
if(UpdateProgress != null)
UpdateProgress("Requesting existing report to VirusTotal", null, 0, 0);
#if DEBUG
stopwatch.Restart();
#endif
2017-05-19 01:13:53 +01:00
Task.Run(async () =>
{
2017-12-29 19:31:26 +00:00
fResult = await vTotal.GetFileReportAsync(file.Sha256);
2017-05-19 01:13:53 +01:00
}).Wait();
#if DEBUG
stopwatch.Stop();
Console.WriteLine("Core.VirusTotalFileFromRepo({0}): VirusTotal took {1} seconds to answer for SHA256 request", file, stopwatch.Elapsed.TotalSeconds);
#endif
2017-05-19 01:13:53 +01:00
2017-12-29 19:31:26 +00:00
if(fResult.ResponseCode == VirusTotalNET.ResponseCodes.FileReportResponseCode.NotPresent)
2017-05-19 01:13:53 +01:00
{
if(Failed != null)
Failed(fResult.VerboseMsg);
return;
}
2017-12-29 19:31:26 +00:00
if(fResult.ResponseCode != VirusTotalNET.ResponseCodes.FileReportResponseCode.Queued)
2017-05-19 01:13:53 +01:00
{
2017-12-29 19:31:26 +00:00
if(fResult.ResponseCode == VirusTotalNET.ResponseCodes.FileReportResponseCode.Present)
2017-05-19 01:13:53 +01:00
{
if(fResult.Positives > 0)
2017-05-19 01:13:53 +01:00
{
file.HasVirus = true;
if(fResult.Scans != null)
2017-05-19 01:13:53 +01:00
{
foreach(KeyValuePair<string, ScanEngine> engine in fResult.Scans)
2017-05-19 01:13:53 +01:00
{
if(engine.Value.Detected)
{
file.Virus = engine.Value.Result;
file.VirusTotalTime = engine.Value.Update;
dbCore.DBOps.UpdateFile(file);
2017-05-19 01:13:53 +01:00
if(ScanFinished != null)
ScanFinished(file);
2017-05-19 01:13:53 +01:00
return;
}
2017-05-19 01:13:53 +01:00
}
}
}
else
{
// If no scan has been done, mark as false.
// If a positive has already existed don't overwrite it.
file.HasVirus = false;
file.Virus = null;
file.VirusTotalTime = DateTime.UtcNow;
2017-05-19 01:13:53 +01:00
dbCore.DBOps.UpdateFile(file);
2017-05-19 01:13:53 +01:00
if(ScanFinished != null)
ScanFinished(file);
2017-05-19 01:13:53 +01:00
return;
}
2017-05-19 01:13:53 +01:00
}
string repoPath;
AlgoEnum algorithm;
2017-05-19 01:13:53 +01:00
if(File.Exists(Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(),
file.Sha256[1].ToString(), file.Sha256[2].ToString(),
file.Sha256[3].ToString(), file.Sha256[4].ToString(),
file.Sha256 + ".gz")))
{
repoPath = Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(),
file.Sha256[1].ToString(), file.Sha256[2].ToString(),
file.Sha256[3].ToString(), file.Sha256[4].ToString(),
file.Sha256 + ".gz");
algorithm = AlgoEnum.GZip;
}
else if(File.Exists(Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(),
file.Sha256[1].ToString(), file.Sha256[2].ToString(),
file.Sha256[3].ToString(), file.Sha256[4].ToString(),
file.Sha256 + ".bz2")))
{
repoPath = Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(),
file.Sha256[1].ToString(), file.Sha256[2].ToString(),
file.Sha256[3].ToString(), file.Sha256[4].ToString(),
file.Sha256 + ".bz2");
algorithm = AlgoEnum.BZip2;
}
else if(File.Exists(Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(),
file.Sha256[1].ToString(), file.Sha256[2].ToString(),
file.Sha256[3].ToString(), file.Sha256[4].ToString(),
file.Sha256 + ".lzma")))
{
repoPath = Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(),
file.Sha256[1].ToString(), file.Sha256[2].ToString(),
file.Sha256[3].ToString(), file.Sha256[4].ToString(),
file.Sha256 + ".lzma");
algorithm = AlgoEnum.LZMA;
}
2017-06-06 22:50:45 +01:00
else if(File.Exists(Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(),
file.Sha256[1].ToString(), file.Sha256[2].ToString(),
file.Sha256[3].ToString(), file.Sha256[4].ToString(),
file.Sha256 + ".lz")))
{
repoPath = Path.Combine(Settings.Current.RepositoryPath, file.Sha256[0].ToString(),
file.Sha256[1].ToString(), file.Sha256[2].ToString(),
file.Sha256[3].ToString(), file.Sha256[4].ToString(),
file.Sha256 + ".lz");
algorithm = AlgoEnum.LZip;
}
else
{
if(Failed != null)
Failed(string.Format("Cannot find file with hash {0} in the repository", file.Sha256));
return;
}
2017-05-19 01:13:53 +01:00
if(UpdateProgress != null)
UpdateProgress("Uncompressing file...", null, 0, 0);
2017-05-19 01:13:53 +01:00
FileStream inFs = new FileStream(repoPath, FileMode.Open, FileAccess.Read);
Stream zStream = null;
2017-05-19 01:13:53 +01:00
switch(algorithm)
{
case AlgoEnum.GZip:
zStream = new GZipStream(inFs, SharpCompress.Compressors.CompressionMode.Decompress);
break;
case AlgoEnum.BZip2:
zStream = new BZip2Stream(inFs, SharpCompress.Compressors.CompressionMode.Decompress);
break;
case AlgoEnum.LZMA:
byte[] properties = new byte[5];
inFs.Read(properties, 0, 5);
inFs.Seek(8, SeekOrigin.Current);
zStream = new LzmaStream(properties, inFs, inFs.Length - 13, file.Length);
break;
2017-06-06 22:50:45 +01:00
case AlgoEnum.LZip:
zStream = new LZipStream(inFs, SharpCompress.Compressors.CompressionMode.Decompress);
break;
}
2017-05-19 01:13:53 +01:00
ScanResult sResult = null;
#if DEBUG
stopwatch.Restart();
#endif
// Cannot use zStream directly, VirusTotal.NET requests the size *sigh*
string tmpFile = Path.Combine(Settings.Current.TemporaryFolder, Path.GetTempFileName());
FileStream outFs = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite);
zStream.CopyTo(outFs);
zStream.Close();
outFs.Seek(0, SeekOrigin.Begin);
#if DEBUG
stopwatch.Stop();
Console.WriteLine("Core.VirusTotalFileFromRepo({0}): Uncompressing took {1} seconds", file, stopwatch.Elapsed.TotalSeconds);
#endif
if(UpdateProgress != null)
UpdateProgress("Uploading file to VirusTotal...", null, 0, 0);
#if DEBUG
stopwatch.Restart();
#endif
Task.Run(async () =>
{
2017-12-29 19:31:26 +00:00
sResult = await vTotal.ScanFileAsync(outFs, file.Sha256); // Keep filename private, sorry!
}).Wait();
#if DEBUG
stopwatch.Stop();
Console.WriteLine("Core.VirusTotalFileFromRepo({0}): Upload to VirusTotal took {1} seconds", file, stopwatch.Elapsed.TotalSeconds);
#endif
outFs.Close();
2017-05-19 01:13:53 +01:00
File.Delete(tmpFile);
2017-05-19 01:13:53 +01:00
2017-12-29 19:31:26 +00:00
if(sResult == null || sResult.ResponseCode == VirusTotalNET.ResponseCodes.ScanFileResponseCode.Error)
{
if(sResult == null)
{
if(Failed != null)
Failed("Cannot send file to VirusTotal");
}
else
Failed(sResult.VerboseMsg);
2017-05-19 01:13:53 +01:00
return;
}
2017-05-19 01:13:53 +01:00
// Seems that we are faster than them, getting a lot of "not queued" responses...
Thread.Sleep(2500);
2017-05-19 01:13:53 +01:00
Task.Run(async () =>
2017-05-19 01:13:53 +01:00
{
2017-12-29 19:31:26 +00:00
fResult = await vTotal.GetFileReportAsync(file.Sha256);
}).Wait();
2017-05-19 01:13:53 +01:00
}
if(UpdateProgress != null)
UpdateProgress("Waiting for VirusTotal analysis...", null, 0, 0);
#if DEBUG
stopwatch.Restart();
#endif
2017-05-19 03:24:34 +01:00
int counter = 0;
2017-12-29 19:31:26 +00:00
while(fResult.ResponseCode == VirusTotalNET.ResponseCodes.FileReportResponseCode.Queued)
2017-05-19 01:13:53 +01:00
{
2017-05-19 03:24:34 +01:00
// Timeout...
if(counter == 10)
break;
2017-05-19 18:22:54 +01:00
2017-05-19 01:13:53 +01:00
// Wait 15 seconds so we fall in the 4 requests/minute
Thread.Sleep(15000);
Task.Run(async () =>
{
2017-12-29 19:31:26 +00:00
fResult = await vTotal.GetFileReportAsync(file.Sha256);
2017-05-19 01:13:53 +01:00
}).Wait();
2017-05-19 03:24:34 +01:00
counter++;
2017-05-19 01:13:53 +01:00
}
#if DEBUG
stopwatch.Stop();
Console.WriteLine("Core.VirusTotalFileFromRepo({0}): VirusTotal took {1} seconds to do the analysis", file, stopwatch.Elapsed.TotalSeconds);
#endif
2017-05-19 01:13:53 +01:00
2017-12-29 19:31:26 +00:00
if(fResult.ResponseCode != VirusTotalNET.ResponseCodes.FileReportResponseCode.Present)
2017-05-19 01:13:53 +01:00
{
if(Failed != null)
Failed(fResult.VerboseMsg);
return;
}
if(fResult.Positives > 0)
{
file.HasVirus = true;
if(fResult.Scans != null)
{
foreach(KeyValuePair<string, ScanEngine> engine in fResult.Scans)
{
if(engine.Value.Detected)
{
file.Virus = engine.Value.Result;
file.VirusTotalTime = engine.Value.Update;
dbCore.DBOps.UpdateFile(file);
if(ScanFinished != null)
ScanFinished(file);
return;
}
}
}
}
else
{
// If no scan has been done, mark as false.
// If a positive has already existed don't overwrite it.
file.HasVirus = false;
file.Virus = null;
file.VirusTotalTime = DateTime.UtcNow;
dbCore.DBOps.UpdateFile(file);
if(ScanFinished != null)
ScanFinished(file);
return;
}
}
catch(Exception ex)
{
if(Failed != null)
Failed(string.Format("Exception {0} when calling VirusTotal", ex.InnerException.Message));
2017-06-13 18:21:12 +01:00
#if DEBUG
Console.WriteLine("Exception {0}\n{1}", ex.Message, ex.InnerException);
#endif
2017-05-19 01:13:53 +01:00
}
}
}
}