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.
//
2017-12-30 00:32:21 +00:00
2017-05-19 01:13:53 +01:00
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-12-30 00:32:21 +00:00
using SharpCompress.Compressors ;
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 ;
2017-12-30 00:32:21 +00:00
using VirusTotalNET.ResponseCodes ;
2017-05-19 18:16:16 +01:00
using VirusTotalNET.Results ;
2017-05-19 01:13:53 +01:00
2018-02-23 02:14:58 +00:00
namespace apprepodbmgr.Core
2017-05-19 01:13:53 +01:00
{
public static partial class Workers
{
static VirusTotal vTotal ;
public static bool TestVirusTotal ( string key )
{
VirusTotal vt ;
FileReport report = null ;
try
{
Task . Run ( async ( ) = >
{
2020-08-22 21:37:02 +01:00
vt = new VirusTotal ( key ) ;
2017-12-30 00:32:21 +00:00
report =
await vt . GetFileReportAsync ( "b82758fc5f737a58078d3c60e2798a70d895443a86aa39adf52dec70e98c2bed" ) ;
2017-05-19 01:13:53 +01:00
} ) . Wait ( ) ;
}
catch ( Exception ex )
{
2017-12-30 00:32:21 +00:00
Failed ? . Invoke ( ex . InnerException ? . Message ) ;
2020-08-22 21:37:02 +01:00
2017-05-19 01:13:53 +01:00
return false ;
}
return report ! = null & & report . MD5 = = "0bf60adb1435639a42b490e7e80d25c7" ;
}
public static bool InitVirusTotal ( string key )
{
2017-12-30 00:32:21 +00:00
VirusTotal vt = null ;
2017-05-19 01:13:53 +01:00
FileReport report = null ;
try
{
Task . Run ( async ( ) = >
{
2020-08-22 21:37:02 +01:00
vt = new VirusTotal ( key ) ;
2017-12-30 00:32:21 +00:00
report =
await vt . GetFileReportAsync ( "b82758fc5f737a58078d3c60e2798a70d895443a86aa39adf52dec70e98c2bed" ) ;
2017-05-19 01:13:53 +01:00
} ) . Wait ( ) ;
}
catch ( Exception ex )
{
2017-12-30 00:32:21 +00:00
Failed ? . Invoke ( ex . InnerException ? . Message ) ;
2020-08-22 21:37:02 +01:00
2017-05-19 01:13:53 +01:00
return false ;
}
2020-08-22 21:37:02 +01:00
if ( report = = null | |
report . MD5 ! = "0bf60adb1435639a42b490e7e80d25c7" )
return false ;
2017-12-30 00:32:21 +00:00
vTotal = vt ;
Context . VirusTotalEnabled = true ;
2020-08-22 21:37:02 +01:00
2017-12-30 00:32:21 +00:00
return true ;
2017-05-19 01:13:53 +01:00
}
2017-12-30 00:32:21 +00:00
public static void VirusTotalFileFromRepo ( DbFile file )
2017-05-19 01:13:53 +01:00
{
try
{
2017-12-30 00:32:21 +00:00
if ( ! Context . VirusTotalEnabled )
2017-05-19 01:13:53 +01:00
{
2017-12-30 00:32:21 +00:00
Failed ? . Invoke ( "VirusTotal is not usable" ) ;
2020-08-22 21:37:02 +01:00
2017-05-19 01:13:53 +01:00
return ;
}
2020-08-22 21:37:02 +01:00
if ( vTotal = = null )
Failed ? . Invoke ( "VirusTotal is not initalized" ) ;
2017-05-19 01:13:53 +01:00
FileReport fResult = null ;
2017-12-30 00:32:21 +00:00
UpdateProgress ? . Invoke ( "Requesting existing report to VirusTotal" , null , 0 , 0 ) ;
2017-05-19 01:13:53 +01:00
2020-08-22 21:37:02 +01:00
#if DEBUG
2017-05-19 01:56:19 +01:00
stopwatch . Restart ( ) ;
2020-08-22 21:37:02 +01:00
#endif
Task . Run ( async ( ) = >
{
fResult = await vTotal . GetFileReportAsync ( file . Sha256 ) ;
} ) . Wait ( ) ;
#if DEBUG
2017-05-19 01:56:19 +01:00
stopwatch . Stop ( ) ;
2020-08-22 21:37:02 +01:00
2017-12-30 00:32:21 +00:00
Console . WriteLine ( "Core.VirusTotalFileFromRepo({0}): VirusTotal took {1} seconds to answer for SHA256 request" ,
file , stopwatch . Elapsed . TotalSeconds ) ;
2020-08-22 21:37:02 +01:00
#endif
2017-05-19 01:13:53 +01:00
2017-12-30 00:32:21 +00:00
if ( fResult . ResponseCode = = FileReportResponseCode . NotPresent )
2017-05-19 01:13:53 +01:00
{
2017-12-30 00:32:21 +00:00
Failed ? . Invoke ( fResult . VerboseMsg ) ;
2020-08-22 21:37:02 +01:00
2017-05-19 01:13:53 +01:00
return ;
}
2017-12-30 00:32:21 +00:00
if ( fResult . ResponseCode ! = FileReportResponseCode . Queued )
2017-05-19 01:13:53 +01:00
{
2020-08-22 21:37:02 +01:00
if ( fResult . ResponseCode = = FileReportResponseCode . Present )
2017-05-19 01:56:19 +01:00
if ( fResult . Positives > 0 )
2017-05-19 01:13:53 +01:00
{
2017-05-19 01:56:19 +01:00
file . HasVirus = true ;
2020-08-22 21:37:02 +01:00
2017-05-19 01:56:19 +01:00
if ( fResult . Scans ! = null )
foreach ( KeyValuePair < string , ScanEngine > engine in fResult . Scans )
2017-05-19 01:13:53 +01:00
{
2020-08-22 21:37:02 +01:00
if ( ! engine . Value . Detected )
continue ;
2017-12-30 00:32:21 +00:00
file . Virus = engine . Value . Result ;
file . VirusTotalTime = engine . Value . Update ;
dbCore . DbOps . UpdateFile ( file ) ;
2017-05-19 01:13:53 +01:00
2017-12-30 00:32:21 +00:00
ScanFinished ? . Invoke ( file ) ;
2017-05-19 01:13:53 +01:00
2017-12-30 00:32:21 +00:00
return ;
2017-05-19 01:13:53 +01:00
}
}
2017-05-19 01:56:19 +01:00
else
{
// If no scan has been done, mark as false.
// If a positive has already existed don't overwrite it.
2017-12-30 00:32:21 +00:00
file . HasVirus = false ;
file . Virus = null ;
2017-05-19 01:56:19 +01:00
file . VirusTotalTime = DateTime . UtcNow ;
2017-05-19 01:13:53 +01:00
2017-12-30 00:32:21 +00:00
dbCore . DbOps . UpdateFile ( file ) ;
2017-05-19 01:13:53 +01:00
2017-12-30 00:32:21 +00:00
ScanFinished ? . Invoke ( file ) ;
2017-05-19 01:13:53 +01:00
2017-05-19 01:56:19 +01:00
return ;
}
2017-05-19 01:13:53 +01:00
2017-12-30 00:32:21 +00:00
string repoPath ;
2017-05-19 01:56:19 +01:00
AlgoEnum algorithm ;
2017-05-19 01:13:53 +01:00
2017-05-19 01:56:19 +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" ) ;
2020-08-22 21:37:02 +01:00
2017-05-19 01:56:19 +01:00
algorithm = AlgoEnum . GZip ;
}
else if ( File . Exists ( Path . Combine ( Settings . Current . RepositoryPath , file . Sha256 [ 0 ] . ToString ( ) ,
2017-12-30 00:32:21 +00:00
file . Sha256 [ 1 ] . ToString ( ) , file . Sha256 [ 2 ] . ToString ( ) ,
file . Sha256 [ 3 ] . ToString ( ) , file . Sha256 [ 4 ] . ToString ( ) ,
file . Sha256 + ".bz2" ) ) )
2017-05-19 01:56:19 +01:00
{
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" ) ;
2020-08-22 21:37:02 +01:00
2017-05-19 01:56:19 +01:00
algorithm = AlgoEnum . BZip2 ;
}
else if ( File . Exists ( Path . Combine ( Settings . Current . RepositoryPath , file . Sha256 [ 0 ] . ToString ( ) ,
2017-12-30 00:32:21 +00:00
file . Sha256 [ 1 ] . ToString ( ) , file . Sha256 [ 2 ] . ToString ( ) ,
file . Sha256 [ 3 ] . ToString ( ) , file . Sha256 [ 4 ] . ToString ( ) ,
file . Sha256 + ".lzma" ) ) )
2017-05-19 01:56:19 +01:00
{
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" ) ;
2020-08-22 21:37:02 +01:00
2017-05-19 01:56:19 +01:00
algorithm = AlgoEnum . LZMA ;
}
2017-06-06 22:50:45 +01:00
else if ( File . Exists ( Path . Combine ( Settings . Current . RepositoryPath , file . Sha256 [ 0 ] . ToString ( ) ,
2017-12-30 00:32:21 +00:00
file . Sha256 [ 1 ] . ToString ( ) , file . Sha256 [ 2 ] . ToString ( ) ,
file . Sha256 [ 3 ] . ToString ( ) , file . Sha256 [ 4 ] . ToString ( ) ,
file . Sha256 + ".lz" ) ) )
2017-06-06 22:50:45 +01:00
{
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" ) ;
2020-08-22 21:37:02 +01:00
2017-06-06 22:50:45 +01:00
algorithm = AlgoEnum . LZip ;
}
2017-05-19 01:56:19 +01:00
else
{
2017-12-30 00:32:21 +00:00
Failed ? . Invoke ( $"Cannot find file with hash {file.Sha256} in the repository" ) ;
2020-08-22 21:37:02 +01:00
2017-05-19 01:56:19 +01:00
return ;
}
2017-05-19 01:13:53 +01:00
2017-12-30 00:32:21 +00:00
UpdateProgress ? . Invoke ( "Uncompressing file..." , null , 0 , 0 ) ;
2017-05-19 01:13:53 +01:00
2020-08-22 21:37:02 +01:00
var inFs = new FileStream ( repoPath , FileMode . Open , FileAccess . Read ) ;
Stream zStream = null ;
2017-05-19 01:13:53 +01:00
2017-05-19 01:56:19 +01:00
switch ( algorithm )
{
case AlgoEnum . GZip :
2017-12-30 00:32:21 +00:00
zStream = new GZipStream ( inFs , CompressionMode . Decompress ) ;
2020-08-22 21:37:02 +01:00
2017-05-19 01:56:19 +01:00
break ;
case AlgoEnum . BZip2 :
2017-12-30 00:32:21 +00:00
zStream = new BZip2Stream ( inFs , CompressionMode . Decompress ) ;
2020-08-22 21:37:02 +01:00
2017-05-19 01:56:19 +01:00
break ;
case AlgoEnum . LZMA :
byte [ ] properties = new byte [ 5 ] ;
inFs . Read ( properties , 0 , 5 ) ;
inFs . Seek ( 8 , SeekOrigin . Current ) ;
2017-05-21 14:25:10 +01:00
zStream = new LzmaStream ( properties , inFs , inFs . Length - 13 , file . Length ) ;
2020-08-22 21:37:02 +01:00
2017-05-19 01:56:19 +01:00
break ;
2017-06-06 22:50:45 +01:00
case AlgoEnum . LZip :
2017-12-30 00:32:21 +00:00
zStream = new LZipStream ( inFs , CompressionMode . Decompress ) ;
2020-08-22 21:37:02 +01:00
2017-06-06 22:50:45 +01:00
break ;
2017-05-19 01:56:19 +01:00
}
2017-05-19 01:13:53 +01:00
2017-05-19 01:56:19 +01:00
ScanResult sResult = null ;
2020-08-22 21:37:02 +01:00
#if DEBUG
2017-05-19 01:56:19 +01:00
stopwatch . Restart ( ) ;
2020-08-22 21:37:02 +01:00
#endif
2017-05-19 01:56:19 +01:00
// Cannot use zStream directly, VirusTotal.NET requests the size *sigh*
2020-08-22 21:37:02 +01:00
string tmpFile = Path . Combine ( Settings . Current . TemporaryFolder , Path . GetTempFileName ( ) ) ;
var outFs = new FileStream ( tmpFile , FileMode . Create , FileAccess . ReadWrite ) ;
2017-12-30 00:32:21 +00:00
zStream ? . CopyTo ( outFs ) ;
zStream ? . Close ( ) ;
2017-05-19 01:56:19 +01:00
outFs . Seek ( 0 , SeekOrigin . Begin ) ;
2020-08-22 21:37:02 +01:00
#if DEBUG
2017-05-19 01:56:19 +01:00
stopwatch . Stop ( ) ;
2020-08-22 21:37:02 +01:00
2017-12-30 00:32:21 +00:00
Console . WriteLine ( "Core.VirusTotalFileFromRepo({0}): Uncompressing took {1} seconds" , file ,
stopwatch . Elapsed . TotalSeconds ) ;
2020-08-22 21:37:02 +01:00
#endif
2017-05-19 01:56:19 +01:00
2017-12-30 00:32:21 +00:00
UpdateProgress ? . Invoke ( "Uploading file to VirusTotal..." , null , 0 , 0 ) ;
2017-05-19 01:56:19 +01:00
2020-08-22 21:37:02 +01:00
#if DEBUG
2017-05-19 01:56:19 +01:00
stopwatch . Restart ( ) ;
2020-08-22 21:37:02 +01:00
#endif
2017-05-19 01:56:19 +01:00
Task . Run ( async ( ) = >
{
2017-12-29 19:31:26 +00:00
sResult = await vTotal . ScanFileAsync ( outFs , file . Sha256 ) ; // Keep filename private, sorry!
2017-05-19 01:56:19 +01:00
} ) . Wait ( ) ;
2020-08-22 21:37:02 +01:00
#if DEBUG
2017-05-19 01:56:19 +01:00
stopwatch . Stop ( ) ;
2020-08-22 21:37:02 +01:00
2017-12-30 00:32:21 +00:00
Console . WriteLine ( "Core.VirusTotalFileFromRepo({0}): Upload to VirusTotal took {1} seconds" , file ,
stopwatch . Elapsed . TotalSeconds ) ;
2020-08-22 21:37:02 +01:00
#endif
2017-05-19 01:56:19 +01:00
outFs . Close ( ) ;
2017-05-19 01:13:53 +01:00
2017-05-19 01:56:19 +01:00
File . Delete ( tmpFile ) ;
2017-05-19 01:13:53 +01:00
2020-08-22 21:37:02 +01:00
if ( sResult = = null | |
sResult . ResponseCode = = ScanFileResponseCode . Error )
2017-05-19 01:56:19 +01:00
{
2020-08-22 21:37:02 +01:00
if ( sResult = = null )
Failed ? . Invoke ( "Cannot send file to VirusTotal" ) ;
else
Failed ( sResult . VerboseMsg ) ;
2017-05-19 01:13:53 +01:00
2017-05-19 01:56:19 +01:00
return ;
}
2017-05-19 01:13:53 +01:00
2017-05-19 01:56:19 +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
2020-08-22 21:37:02 +01:00
Task . Run ( async ( ) = >
{
fResult = await vTotal . GetFileReportAsync ( file . Sha256 ) ;
} ) . Wait ( ) ;
2017-05-19 01:13:53 +01:00
}
2017-12-30 00:32:21 +00:00
UpdateProgress ? . Invoke ( "Waiting for VirusTotal analysis..." , null , 0 , 0 ) ;
2017-05-19 01:13:53 +01:00
2020-08-22 21:37:02 +01:00
#if DEBUG
2017-05-19 01:56:19 +01:00
stopwatch . Restart ( ) ;
2020-08-22 21:37:02 +01:00
#endif
2017-05-19 03:24:34 +01:00
int counter = 0 ;
2020-08-22 21:37:02 +01:00
2017-12-30 00:32:21 +00:00
while ( fResult . ResponseCode = = FileReportResponseCode . Queued )
2017-05-19 01:13:53 +01:00
{
2017-05-19 03:24:34 +01:00
// Timeout...
2020-08-22 21:37:02 +01:00
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 ) ;
2020-08-22 21:37:02 +01:00
Task . Run ( async ( ) = >
{
fResult = await vTotal . GetFileReportAsync ( file . Sha256 ) ;
} ) . Wait ( ) ;
2017-05-19 03:24:34 +01:00
counter + + ;
2017-05-19 01:13:53 +01:00
}
2020-08-22 21:37:02 +01:00
#if DEBUG
2017-05-19 01:56:19 +01:00
stopwatch . Stop ( ) ;
2020-08-22 21:37:02 +01:00
2017-12-30 00:32:21 +00:00
Console . WriteLine ( "Core.VirusTotalFileFromRepo({0}): VirusTotal took {1} seconds to do the analysis" ,
file , stopwatch . Elapsed . TotalSeconds ) ;
2020-08-22 21:37:02 +01:00
#endif
2017-05-19 01:13:53 +01:00
2017-12-30 00:32:21 +00:00
if ( fResult . ResponseCode ! = FileReportResponseCode . Present )
2017-05-19 01:13:53 +01:00
{
2017-12-30 00:32:21 +00:00
Failed ? . Invoke ( fResult . VerboseMsg ) ;
2020-08-22 21:37:02 +01:00
2017-05-19 01:13:53 +01:00
return ;
}
if ( fResult . Positives > 0 )
{
file . HasVirus = true ;
2020-08-22 21:37:02 +01:00
if ( fResult . Scans = = null )
return ;
2017-12-30 00:32:21 +00:00
foreach ( KeyValuePair < string , ScanEngine > engine in fResult . Scans )
2017-05-19 01:13:53 +01:00
{
2020-08-22 21:37:02 +01:00
if ( ! engine . Value . Detected )
continue ;
2017-05-19 01:13:53 +01:00
2017-12-30 00:32:21 +00:00
file . Virus = engine . Value . Result ;
file . VirusTotalTime = engine . Value . Update ;
dbCore . DbOps . UpdateFile ( file ) ;
2017-05-19 01:13:53 +01:00
2017-12-30 00:32:21 +00:00
ScanFinished ? . Invoke ( file ) ;
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.
2017-12-30 00:32:21 +00:00
file . HasVirus = false ;
file . Virus = null ;
2017-05-19 01:13:53 +01:00
file . VirusTotalTime = DateTime . UtcNow ;
2017-12-30 00:32:21 +00:00
dbCore . DbOps . UpdateFile ( file ) ;
2017-05-19 01:13:53 +01:00
2017-12-30 00:32:21 +00:00
ScanFinished ? . Invoke ( file ) ;
2017-05-19 01:13:53 +01:00
}
}
catch ( Exception ex )
{
2017-12-30 00:32:21 +00:00
Failed ? . Invoke ( $"Exception {ex.InnerException.Message} when calling VirusTotal" ) ;
2020-08-22 21:37:02 +01:00
#if DEBUG
2017-06-13 18:21:12 +01:00
Console . WriteLine ( "Exception {0}\n{1}" , ex . Message , ex . InnerException ) ;
2020-08-22 21:37:02 +01:00
#endif
2017-05-19 01:13:53 +01:00
}
}
}
2017-12-30 00:32:21 +00:00
}