Added support for VirusTotal.

This commit is contained in:
2017-05-19 01:13:53 +01:00
parent f384995866
commit 6ff82e7b56
21 changed files with 1040 additions and 92 deletions

View File

@@ -1,3 +1,12 @@
2017-05-19 Natalia Portillo <claunia@claunia.com>
* Context.cs:
* Settings.cs:
* packages.config:
* Workers/VirusTotal.cs:
* osrepodbmgr.Core.csproj:
Added support for VirusTotal.
2017-05-18 Natalia Portillo <claunia@claunia.com>
* DBOps.cs:

View File

@@ -53,6 +53,7 @@ namespace osrepodbmgr.Core
public static bool userExtracting;
public static bool usableDotNetZip;
public static string clamdVersion;
public static bool virusTotalEnabled;
public delegate void UnarChangeStatusDelegate();
public static event UnarChangeStatusDelegate UnarChangeStatus;

View File

@@ -45,6 +45,8 @@ namespace osrepodbmgr.Core
public string ClamdHost;
public ushort ClamdPort;
public bool ClamdIsLocal;
public bool UseVirusTotal;
public string VirusTotalKey;
}
public static class Settings
@@ -152,6 +154,20 @@ namespace osrepodbmgr.Core
else
Current.ClamdIsLocal = false;
if(parsedPreferences.TryGetValue("UseVirusTotal", out obj))
{
Current.ClamdIsLocal = ((NSNumber)obj).ToBool();
}
else
Current.ClamdIsLocal = false;
if(parsedPreferences.TryGetValue("VirusTotalKey", out obj))
{
Current.ClamdHost = ((NSString)obj).ToString();
}
else
Current.ClamdHost = null;
prefsFs.Close();
}
else {
@@ -195,6 +211,8 @@ namespace osrepodbmgr.Core
Current.ClamdHost = (string)key.GetValue("ClamdHost");
Current.ClamdPort = (ushort)key.GetValue("ClamdPort");
Current.ClamdIsLocal = (bool)key.GetValue("ClamdIsLocal");
Current.UseVirusTotal = (bool)key.GetValue("UseVirusTotal");
Current.VirusTotalKey = (string)key.GetValue("VirusTotalKey");
}
break;
default:
@@ -251,6 +269,8 @@ namespace osrepodbmgr.Core
root.Add("ClamdHost", Current.ClamdHost);
root.Add("ClamdPort", Current.ClamdPort);
root.Add("ClamdIsLocal", Current.ClamdIsLocal);
root.Add("UseVirusTotal", Current.UseVirusTotal);
root.Add("VirusTotalKey", Current.VirusTotalKey);
string preferencesPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Preferences");
string preferencesFilePath = Path.Combine(preferencesPath, "com.claunia.museum.osrepodbmgr.plist");
@@ -280,6 +300,8 @@ namespace osrepodbmgr.Core
key.SetValue("ClamdHost", Current.ClamdHost);
key.SetValue("ClamdPort", Current.ClamdPort);
key.SetValue("ClamdIsLocal", Current.ClamdIsLocal);
key.SetValue("UseVirusTotal", Current.UseVirusTotal);
key.SetValue("VirusTotalKey", Current.VirusTotalKey);
}
break;
default:
@@ -320,6 +342,8 @@ namespace osrepodbmgr.Core
Current.ClamdHost = null;
Current.ClamdPort = 3310;
Current.ClamdIsLocal = false;
Current.UseVirusTotal = false;
Current.VirusTotalKey = null;
}
}
}

View File

@@ -0,0 +1,341 @@
//
// 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 VirusTotalNET;
using VirusTotalNET.Results;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using VirusTotalNET.Objects;
using System.Collections.Generic;
using SharpCompress.Compressors.Deflate;
using SharpCompress.Compressors.BZip2;
using SharpCompress.Compressors.LZMA;
using System.Threading;
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);
report = await vt.GetFileReport("b82758fc5f737a58078d3c60e2798a70d895443a86aa39adf52dec70e98c2bed");
}).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);
report = await vt.GetFileReport("b82758fc5f737a58078d3c60e2798a70d895443a86aa39adf52dec70e98c2bed");
}).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);
Task.Run(async () =>
{
fResult = await vTotal.GetFileReport(file.Sha256);
}).Wait();
if(fResult.ResponseCode == VirusTotalNET.ResponseCodes.ReportResponseCode.Error)
{
if(Failed != null)
Failed(fResult.VerboseMsg);
return;
}
if(fResult.ResponseCode == VirusTotalNET.ResponseCodes.ReportResponseCode.Present)
{
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;
}
}
string repoPath;
AlgoEnum algorithm;
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;
}
else
{
if(Failed != null)
Failed(string.Format("Cannot find file with hash {0} in the repository", file.Sha256));
return;
}
if(UpdateProgress != null)
UpdateProgress("Uncompressing file...", null, 0, 0);
FileStream inFs = new FileStream(repoPath, FileMode.Open, FileAccess.Read);
Stream zStream = null;
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);
break;
}
ScanResult sResult = null;
// 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(UpdateProgress != null)
UpdateProgress("Uploading file to VirusTotal...", null, 0, 0);
Task.Run(async () =>
{
sResult = await vTotal.ScanFile(outFs, file.Sha256); // Keep filename private, sorry!
}).Wait();
outFs.Close();
File.Delete(tmpFile);
if(sResult == null || sResult.ResponseCode == VirusTotalNET.ResponseCodes.ScanResponseCode.Error)
{
if(sResult == null)
{
if(Failed != null)
Failed("Cannot send file to VirusTotal");
}
else
Failed(sResult.VerboseMsg);
return;
}
Task.Run(async () =>
{
fResult = await vTotal.GetFileReport(file.Sha256);
}).Wait();
if(UpdateProgress != null)
UpdateProgress("Waiting for VirusTotal analysis...", null, 0, 0);
while(fResult.ResponseCode == VirusTotalNET.ResponseCodes.ReportResponseCode.StillQueued)
{
// Wait 15 seconds so we fall in the 4 requests/minute
Thread.Sleep(15000);
Task.Run(async () =>
{
fResult = await vTotal.GetFileReport(file.Sha256);
}).Wait();
}
if(fResult.ResponseCode != VirusTotalNET.ResponseCodes.ReportResponseCode.Present)
{
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));
}
}
}
}

View File

@@ -47,6 +47,11 @@
<Reference Include="nClam">
<HintPath>..\packages\nClam.3.0.0\lib\net45\nClam.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
<Reference Include="VirusTotal.NET">
<HintPath>..\packages\VirusTotal.NET.1.5.3\lib\net45\VirusTotal.NET.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
@@ -70,6 +75,7 @@
<Compile Include="Workers\Miscellaneous.cs" />
<Compile Include="Workers\DiscImageChef.cs" />
<Compile Include="Workers\Clamd.cs" />
<Compile Include="Workers\VirusTotal.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -6,4 +6,7 @@
<package id="plist-cil" version="1.15.0" targetFramework="net45" />
<package id="SharpCompress" version="0.15.2" targetFramework="net45" />
<package id="System.Data.SQLite.Core" version="1.0.105.0" targetFramework="net45" />
<package id="System.Dynamic.Runtime" version="4.3.0" targetFramework="net45" />
<package id="System.Net.Http" version="4.3.1" targetFramework="net45" />
<package id="VirusTotal.NET" version="1.5.3" targetFramework="net45" />
</packages>