Directly load the pre-processed bloom filters

No more conversion, much quicker loading, much smaller downloads

Signed-off-by: Tad <tad@spotco.us>
This commit is contained in:
Tad 2023-12-21 23:26:19 -05:00
parent a004d22c07
commit 1c29038125
No known key found for this signature in database
GPG key ID: B286E9F57A07424B
13 changed files with 184 additions and 160 deletions

1
.idea/misc.xml generated
View file

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DesignSurface"> <component name="DesignSurface">
<option name="filePathToZoomLevelMap"> <option name="filePathToZoomLevelMap">

View file

@ -6,8 +6,8 @@ android {
applicationId "us.spotco.malwarescanner" applicationId "us.spotco.malwarescanner"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 32 targetSdkVersion 32
versionCode 110 versionCode 112
versionName "2.34" versionName "2.35"
resConfigs 'en', 'af', 'de', 'el', 'es', 'fi', 'fr', 'it', 'pl', 'pt', 'ru', 'tr', 'zh-rCN' resConfigs 'en', 'af', 'de', 'el', 'es', 'fi', 'fr', 'it', 'pl', 'pt', 'ru', 'tr', 'zh-rCN'
} }
buildTypes { buildTypes {

View file

@ -26,12 +26,9 @@ import android.widget.TextView;
import com.google.common.hash.BloomFilter; import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels; import com.google.common.hash.Funnels;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Proxy; import java.net.Proxy;
@ -40,7 +37,6 @@ import java.nio.charset.StandardCharsets;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.Date; import java.util.Date;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.zip.GZIPInputStream;
class Database { class Database {
@ -52,11 +48,8 @@ class Database {
public final static ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases = new ConcurrentLinkedQueue<>(); public final static ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases = new ConcurrentLinkedQueue<>();
public static BloomFilter<String> signaturesMD5 = null; public static BloomFilter<String> signaturesMD5 = null;
public static int amtSignaturesMD5 = 0;
public static BloomFilter<String> signaturesSHA1 = null; public static BloomFilter<String> signaturesSHA1 = null;
public static int amtSignaturesSHA1 = 0;
public static BloomFilter<String> signaturesSHA256 = null; public static BloomFilter<String> signaturesSHA256 = null;
public static int amtSignaturesSHA256 = 0;
private static final DateFormat dateFormat = DateFormat.getDateInstance(); private static final DateFormat dateFormat = DateFormat.getDateInstance();
@ -69,14 +62,9 @@ class Database {
} }
public static boolean isDatabaseLoaded() { public static boolean isDatabaseLoaded() {
return areDatabasesAvailable() && databaseFullyLoaded && (amtSignaturesMD5 > 0 || amtSignaturesSHA1 > 0 || amtSignaturesSHA256 > 0); return areDatabasesAvailable() && databaseFullyLoaded;
} }
public static int getSignatureCount() {
return amtSignaturesMD5 + amtSignaturesSHA1 + amtSignaturesSHA256;
}
public static void updateDatabase(Context context, ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases) { public static void updateDatabase(Context context, ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases) {
initDatabase(context); initDatabase(context);
log.append(context.getString(R.string.main_database_updating, String.valueOf(signatureDatabases.size())) + "\n"); log.append(context.getString(R.string.main_database_updating, String.valueOf(signatureDatabases.size())) + "\n");
@ -97,37 +85,11 @@ class Database {
databasePath.mkdir(); databasePath.mkdir();
signatureDatabases.clear(); signatureDatabases.clear();
prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE); prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
String baseURL = Utils.getDatabaseURL(context); String baseURL = Utils.getDatabaseURL(context);
if (prefs.getBoolean("SIGNATURES_TARGETEDTHREATS", true)) { signatureDatabases.add(new SignatureDatabase(baseURL, "hypatia-md5-bloom.bin"));
signatureDatabases.add(new SignatureDatabase(baseURL, "targetedthreats.hdb.gz")); signatureDatabases.add(new SignatureDatabase(baseURL, "hypatia-sha1-bloom.bin"));
signatureDatabases.add(new SignatureDatabase(baseURL, "targetedthreats.hsb.gz")); signatureDatabases.add(new SignatureDatabase(baseURL, "hypatia-sha256-bloom.bin"));
}
if (prefs.getBoolean("SIGNATURES_AMNESTY", true)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "amnesty.hsb.gz"));
}
if (prefs.getBoolean("SIGNATURES_STALKERWARE", true)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "stalkerware.hsb.gz"));
}
if (prefs.getBoolean("SIGNATURES_ESET", true)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "eset.hdb.gz"));
signatureDatabases.add(new SignatureDatabase(baseURL, "eset.hsb.gz"));
}
if (prefs.getBoolean("SIGNATURES_MALWAREBAZAAR", false)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "malware_bazaar-full.hsb.gz"));
} else if (prefs.getBoolean("SIGNATURES_MALWAREBAZAAR-ANDROID", true)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "malware_bazaar.hsb.gz"));
}
if (prefs.getBoolean("SIGNATURES_CLAMAV", false)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "main.hdb.gz"));
signatureDatabases.add(new SignatureDatabase(baseURL, "main.hsb.gz"));
signatureDatabases.add(new SignatureDatabase(baseURL, "daily.hdb.gz"));
signatureDatabases.add(new SignatureDatabase(baseURL, "daily.hsb.gz"));
} else if (prefs.getBoolean("SIGNATURES_CLAMAV-ANDROID", true)) {
signatureDatabases.add(new SignatureDatabase(baseURL, "Android.hdb.gz"));
signatureDatabases.add(new SignatureDatabase(baseURL, "Android.hsb.gz"));
}
} }
public static void loadDatabase(Context context, boolean forceReload, ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases) { public static void loadDatabase(Context context, boolean forceReload, ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases) {
@ -135,14 +97,6 @@ class Database {
databaseFullyLoaded = false; databaseFullyLoaded = false;
databaseCurrentlyLoading = true; databaseCurrentlyLoading = true;
initDatabase(context); initDatabase(context);
//XXX: These must be kept in sync with the databases
signaturesMD5 = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 200000, 0.001); //200k
amtSignaturesMD5 = 0;
signaturesSHA1 = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 5000000, 0.001); //5m
amtSignaturesSHA1 = 0;
signaturesSHA256 = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), 1000000, 0.001); //1m
amtSignaturesSHA256 = 0;
System.gc();
File publicKey = new File(databasePath + "/gpg.key"); File publicKey = new File(databasePath + "/gpg.key");
GPGDetachedSignatureVerifier verifier = new GPGDetachedSignatureVerifier(Utils.getSigningKey(context)); GPGDetachedSignatureVerifier verifier = new GPGDetachedSignatureVerifier(Utils.getSigningKey(context));
for (SignatureDatabase database : signatureDatabases) { for (SignatureDatabase database : signatureDatabases) {
@ -153,41 +107,22 @@ class Database {
boolean validated = verifier.verify(databaseLocation, databaseSigLocation, publicKey); boolean validated = verifier.verify(databaseLocation, databaseSigLocation, publicKey);
if (validated) { if (validated) {
Log.d("Hypatia", "Successfully validated database"); Log.d("Hypatia", "Successfully validated database");
BufferedReader reader; FileInputStream databaseLoading = new FileInputStream(databaseLocation);
if (databaseLocation.getName().endsWith(".gz")) { switch (databaseLocation.getName()) {
reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(databaseLocation)))); case "hypatia-md5-bloom.bin":
} else { Log.d("Hypatia", "Processing md5");
reader = new BufferedReader(new FileReader(databaseLocation)); signaturesMD5 = BloomFilter.readFrom(databaseLoading, Funnels.stringFunnel(StandardCharsets.US_ASCII));
break;
case "hypatia-sha1-bloom.bin":
Log.d("Hypatia", "Processing sha1");
signaturesSHA1 = BloomFilter.readFrom(databaseLoading, Funnels.stringFunnel(StandardCharsets.US_ASCII));
break;
case "hypatia-sha256-bloom.bin":
Log.d("Hypatia", "Processing sha256");
signaturesSHA256 = BloomFilter.readFrom(databaseLoading, Funnels.stringFunnel(StandardCharsets.US_ASCII));
break;
} }
String line; databaseLoading.close();
if (database.getName().contains(".hdb")) {//.hdb format: md5, size, name
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
String[] lineS = line.split(":");
if (lineS[0].length() > 0) {
if (signaturesMD5.put(lineS[0])) {
amtSignaturesMD5++;
}
}
}
}
} else if (database.getName().contains(".hsb")) {//.hsb format: sha256, size, name
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
String[] lineS = line.split(":");
if (lineS[0].length() == 32) {
if (signaturesSHA1.put(lineS[0])) {
amtSignaturesSHA1++;
}
} else if (lineS[0].length() > 0) {
if (signaturesSHA256.put(lineS[0])) {
amtSignaturesSHA256++;
}
}
}
}
}
reader.close();
} else { } else {
Log.w("Hypatia", "Failed to validate database"); Log.w("Hypatia", "Failed to validate database");
} }
@ -197,10 +132,6 @@ class Database {
} }
System.gc(); System.gc();
} }
Log.w("Hypatia", "Loaded database - md5: " + amtSignaturesMD5 + ", sha1: " + amtSignaturesSHA1 + ", sha256: " + amtSignaturesSHA256);
signaturesMD5.put("44d88612fea8a8f36de82e1278abb02f");
signaturesSHA256.put("6a0b4866f143c32e651662cebf7f380d27b0db809db3b6a34cf34c7436ab6bbf");
System.gc(); System.gc();
databaseFullyLoaded = true; databaseFullyLoaded = true;
databaseCurrentlyLoading = false; databaseCurrentlyLoading = false;

View file

@ -131,57 +131,6 @@ public class MainActivity extends Activity {
creditsDialog.show(); creditsDialog.show();
} }
private String localizeDBDescription(String desc) {
return desc
.replaceAll("AUTHOR", getString(R.string.db_desc_author))
.replaceAll("LICENSE", getString(R.string.db_desc_license))
.replaceAll("SIZE_SMALL", getString(R.string.db_desc_size_small))
.replaceAll("SIZE_MEDIUM", getString(R.string.db_desc_size_medium))
.replaceAll("SIZE_LARGE", getString(R.string.db_desc_size_large))
.replaceAll("SIZE", getString(R.string.db_desc_size))
.replaceAll("SOURCE", getString(R.string.db_desc_source));
}
private void selectDatabases() {
final String[] databases = {
localizeDBDescription("ClamAV: Android Only\n • SIZE: SIZE_MEDIUM\n • LICENSE: GPL-2.0\n • AUTHOR: Cisco\n • SOURCE: https://clamav.net\n"),
localizeDBDescription("ClamAV: Full\n • SIZE: SIZE_LARGE\n • LICENSE: GPL-2.0\n • AUTHOR: Cisco\n • SOURCE: https://clamav.net\n"),
localizeDBDescription("ESET\n • SIZE: SIZE_SMALL\n • LICENSE: BSD 2-Clause\n • AUTHOR: ESET\n • SOURCE: https://github.com/eset/malware-ioc\n"),
localizeDBDescription("Targeted Threats\n • SIZE: SIZE_SMALL\n • LICENSE: CC BY-SA 4.0\n • AUTHOR: Nex\n • SOURCE: https://github.com/botherder/targetedthreats\n"),
localizeDBDescription("Amnesty Tech Investigations\n • SIZE: SIZE_SMALL\n • LICENSE: CC BY 2.0\n • AUTHOR: Amnesty International\n • SOURCE: https://github.com/amnestytech/investigations\n"),
localizeDBDescription("Stalkerware\n • SIZE: SIZE_SMALL\n • LICENSE: CC BY 4.0\n • AUTHOR: Echap\n • SOURCE: https://github.com/AssoEchap/stalkerware-indicators\n"),
localizeDBDescription("MalwareBazaar: Android Only\n • SIZE: SIZE_SMALL\n • LICENSE: CC0\n • AUTHOR: Abuse.ch\n • SOURCE: https://bazaar.abuse.ch\n"),
localizeDBDescription("MalwareBazaar: Full\n • SIZE: SIZE_LARGE\n • LICENSE: CC0\n • AUTHOR: Abuse.ch\n • SOURCE: https://bazaar.abuse.ch")};
final boolean[] databaseDefaults = {
prefs.getBoolean("SIGNATURES_CLAMAV-ANDROID", true),
prefs.getBoolean("SIGNATURES_CLAMAV", false),
prefs.getBoolean("SIGNATURES_ESET", true),
prefs.getBoolean("SIGNATURES_TARGETEDTHREATS", true),
prefs.getBoolean("SIGNATURES_AMNESTY", true),
prefs.getBoolean("SIGNATURES_STALKERWARE", true),
prefs.getBoolean("SIGNATURES_MALWAREBAZAAR-ANDROID", true),
prefs.getBoolean("SIGNATURES_MALWAREBAZAAR", false)};
Dialog databaseDialog;
AlertDialog.Builder databaseBuilder = new AlertDialog.Builder(this);
databaseBuilder.setTitle(R.string.lblSelectDatabasesTitle);
databaseBuilder.setMultiChoiceItems(databases, databaseDefaults, (dialogInterface, i, selected) -> databaseDefaults[i] = selected);
databaseBuilder.setPositiveButton("OK", (dialogInterface, i) -> {
prefs.edit().putBoolean("SIGNATURES_CLAMAV-ANDROID", databaseDefaults[0]).apply();
prefs.edit().putBoolean("SIGNATURES_CLAMAV", databaseDefaults[1]).apply();
prefs.edit().putBoolean("SIGNATURES_ESET", databaseDefaults[2]).apply();
prefs.edit().putBoolean("SIGNATURES_TARGETEDTHREATS", databaseDefaults[3]).apply();
prefs.edit().putBoolean("SIGNATURES_AMNESTY", databaseDefaults[4]).apply();
prefs.edit().putBoolean("SIGNATURES_STALKERWARE", databaseDefaults[5]).apply();
prefs.edit().putBoolean("SIGNATURES_MALWAREBAZAAR-ANDROID", databaseDefaults[6]).apply();
prefs.edit().putBoolean("SIGNATURES_MALWAREBAZAAR", databaseDefaults[7]).apply();
});
databaseDialog = databaseBuilder.create();
databaseDialog.show();
}
@Override @Override
public final boolean onOptionsItemSelected(MenuItem item) { public final boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
@ -211,9 +160,6 @@ public class MainActivity extends Activity {
updateDatabase(); updateDatabase();
} }
break; break;
case R.id.mnuSelectDatabases:
selectDatabases();
break;
case R.id.mnuDatabaseServer: case R.id.mnuDatabaseServer:
AlertDialog.Builder builderServerOverride = new AlertDialog.Builder(this); AlertDialog.Builder builderServerOverride = new AlertDialog.Builder(this);
builderServerOverride.setTitle(getString(R.string.lblDatabaseServer)); builderServerOverride.setTitle(getString(R.string.lblDatabaseServer));
@ -314,7 +260,6 @@ public class MainActivity extends Activity {
filesToScan.add(new File("/storage")); filesToScan.add(new File("/storage"));
} }
malwareScanner.executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan); malwareScanner.executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan);
new Thread(() -> { new Thread(() -> {
try { try {

View file

@ -135,7 +135,7 @@ class MalwareScanner extends AsyncTask<HashSet<File>, Object, String> {
} }
} }
if (Database.isDatabaseLoaded()) { if (Database.isDatabaseLoaded()) {
publishProgress("\t" + context.getString(R.string.main_database_loaded, NumberFormat.getInstance().format(Database.getSignatureCount())) + "\n", true); publishProgress("\t" + context.getString(R.string.main_database_loaded, "?") + "\n", true);
//Get file hashes //Get file hashes
publishProgress("\t" + context.getString(R.string.main_hashing_files), true); publishProgress("\t" + context.getString(R.string.main_hashing_files), true);

View file

@ -108,6 +108,7 @@ public class MalwareScannerService extends Service {
.setContentTitle(getText(R.string.lblNotificationRealtimeTitle)) .setContentTitle(getText(R.string.lblNotificationRealtimeTitle))
.setContentText(getText(R.string.lblNotificationRealtimeText)) .setContentText(getText(R.string.lblNotificationRealtimeText))
.setPriority(Notification.PRIORITY_MIN) .setPriority(Notification.PRIORITY_MIN)
.setOngoing(true)
.setShowWhen(false); .setShowWhen(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -119,7 +120,7 @@ public class MalwareScannerService extends Service {
private void updateForegroundNotification() { private void updateForegroundNotification() {
foregroundNotification foregroundNotification
.setSubText(NumberFormat.getInstance().format(Database.getSignatureCount()) + " sigs" + "" + getString(R.string.main_files_scanned_count, NumberFormat.getInstance().format(Utils.FILES_SCANNED))); .setSubText(getString(R.string.main_files_scanned_count, NumberFormat.getInstance().format(Utils.FILES_SCANNED)));
notificationManager.notify(-1, foregroundNotification.build()); notificationManager.notify(-1, foregroundNotification.build());
} }

View file

@ -14,9 +14,6 @@
<item <item
android:id="@+id/mnuUpdateDatabase" android:id="@+id/mnuUpdateDatabase"
android:title="@string/lblUpdateDatabase" /> android:title="@string/lblUpdateDatabase" />
<item
android:id="@+id/mnuSelectDatabases"
android:title="@string/lblSelectDatabases" />
<item <item
android:id="@+id/mnuDatabaseServer" android:id="@+id/mnuDatabaseServer"
android:title="@string/lblDatabaseServer" /> android:title="@string/lblDatabaseServer" />

View file

@ -0,0 +1,2 @@
* Elimiates all local database conversion
* Server now serves up the optimized bloom filters directly for smaller downloads

View file

@ -16,6 +16,8 @@ sigtool -u daily.c*d
#MD5 #MD5
grep "Andr\\." main.hdb >> Android.hdb grep "Andr\\." main.hdb >> Android.hdb
grep "Andr\\." daily.hdb >> Android.hdb grep "Andr\\." daily.hdb >> Android.hdb
#grep "Java\\." main.hdb >> Android.hdb
#grep "Java\\." daily.hdb >> Android.hdb
grep "Unix\\." main.hdb >> Android.hdb grep "Unix\\." main.hdb >> Android.hdb
grep "Unix\\." daily.hdb >> Android.hdb grep "Unix\\." daily.hdb >> Android.hdb
grep "Multios\\." main.hdb >> Android.hdb grep "Multios\\." main.hdb >> Android.hdb
@ -24,6 +26,8 @@ grep "Multios\\." daily.hdb >> Android.hdb
#SHA #SHA
grep "Andr\\." main.hsb >> Android.hsb grep "Andr\\." main.hsb >> Android.hsb
grep "Andr\\." daily.hsb >> Android.hsb grep "Andr\\." daily.hsb >> Android.hsb
#grep "Java\\." main.hsb >> Android.hsb
#grep "Java\\." daily.hsb >> Android.hsb
grep "Unix\\." main.hsb >> Android.hsb grep "Unix\\." main.hsb >> Android.hsb
grep "Unix\\." daily.hsb >> Android.hsb grep "Unix\\." daily.hsb >> Android.hsb
grep "Multios\\." main.hsb >> Android.hsb grep "Multios\\." main.hsb >> Android.hsb

View file

@ -8,7 +8,7 @@ processHashes() {
dos2unix $1/samples.$2 dos2unix $1/samples.$2
while IFS= read -r line while IFS= read -r line
do do
echo "$line":0:ESET."$name" >> ./eset.$3; echo "$line" >> ./eset.$2;
done < "$1/samples.$2"; done < "$1/samples.$2";
fi; fi;
} }
@ -17,5 +17,3 @@ export -f processHashes;
find . -maxdepth 2 -mindepth 1 -type d -exec bash -c 'processHashes "{}" md5 hdb' \; find . -maxdepth 2 -mindepth 1 -type d -exec bash -c 'processHashes "{}" md5 hdb' \;
find . -maxdepth 2 -mindepth 1 -type d -exec bash -c 'processHashes "{}" sha1 hsb' \; find . -maxdepth 2 -mindepth 1 -type d -exec bash -c 'processHashes "{}" sha1 hsb' \;
find . -maxdepth 2 -mindepth 1 -type d -exec bash -c 'processHashes "{}" sha256 hsb' \; find . -maxdepth 2 -mindepth 1 -type d -exec bash -c 'processHashes "{}" sha256 hsb' \;
gzip *.hdb;
gzip *.hsb;

View file

@ -1,6 +0,0 @@
#!/bin/bash
#License: GPLv3
#Description: Hypatia conversion script for https://bazaar.abuse.ch/export/csv/full/ (CC0)
grep -v "^#" full.csv | grep -e "\"apk\"" -e "\"application/x-executable\"" -e "\"application/x-sharedlib\"" | awk '{ print $3 } ' | sed 's/^"//' | sed 's/",$/:0:MB/' | sort -u > malware_bazaar.hsb;
grep -v "^#" full.csv | awk '{ print $3 } ' | sed 's/^"//' | sed 's/",$/:0:MB/' | sort -u > malware_bazaar-full.hsb;
gzip *.hsb;

14
scripts/0sign.sh Normal file
View file

@ -0,0 +1,14 @@
for database in *.bin
do
if [ -f "$database.sig" ]; then
#If it does exist sign if it doesn't match
if ! gpg --verify "$database.sig"; then
rm "$database.sig";
gpg --sign --local-user 6395FC9911EDCD6158712DF7BADFCABDDBF5B694 --detach-sign "$database";
fi;
else
#Sign it if it doesn't exist
gpg --sign --local-user 6395FC9911EDCD6158712DF7BADFCABDDBF5B694 --detach-sign "$database";
fi;
done

139
scripts/Main.java Normal file
View file

@ -0,0 +1,139 @@
/*
Copyright (c) 2023 Divested Computing Group
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPInputStream;
public class Main {
public static int amtSignaturesRead = 0;
public static BloomFilter<String> signaturesMD5 = null;
public static int amtSignaturesMD5 = 0;
public static BloomFilter<String> signaturesSHA1 = null;
public static int amtSignaturesSHA1 = 0;
public static BloomFilter<String> signaturesSHA256 = null;
public static int amtSignaturesSHA256 = 0;
public static void main(String[] args) {
signaturesMD5 = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.US_ASCII), 150000, 0.00001); //150k
signaturesSHA1 = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.US_ASCII), 4500000, 0.00001); //4.5m
signaturesSHA256 = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.US_ASCII), 800000, 0.00001); //800k
for (File databaseLocation : new File(args[0]).listFiles()) {
System.out.println("Processing: " + databaseLocation);
try {
BufferedReader reader;
if (databaseLocation.getName().endsWith(".gz")) {
reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(databaseLocation))));
} else {
reader = new BufferedReader(new FileReader(databaseLocation));
}
String line;
if (databaseLocation.getName().contains(".hdb")) {//.hdb format: md5, size, name
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
String[] lineS = line.split(":");
if (lineS[0].length() > 0) {
if (signaturesMD5.put(lineS[0])) {
amtSignaturesMD5++;
}
amtSignaturesRead++;
}
}
}
} else if (databaseLocation.getName().contains(".hsb")) {//.hsb format: sha256, size, name
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
String[] lineS = line.split(":");
if (lineS[0].length() == 32) {
if (signaturesSHA1.put(lineS[0])) {
amtSignaturesSHA1++;
}
amtSignaturesRead++;
} else if (lineS[0].length() > 0) {
if (signaturesSHA256.put(lineS[0])) {
amtSignaturesSHA256++;
}
amtSignaturesRead++;
}
}
}
} else if (databaseLocation.getName().contains(".md5")) {//one signature per line
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
if (signaturesMD5.put(line)) {
amtSignaturesMD5++;
}
amtSignaturesRead++;
}
}
} else if (databaseLocation.getName().contains(".sha1")) {//one signature per line
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
if (signaturesSHA1.put(line)) {
amtSignaturesSHA1++;
}
amtSignaturesRead++;
}
}
} else if (databaseLocation.getName().contains(".sha256")) {//one signature per line
while ((line = reader.readLine()) != null) {
if (line.length() > 0) {
if (signaturesSHA256.put(line)) {
amtSignaturesSHA256++;
}
amtSignaturesRead++;
}
}
}
reader.close();
System.out.println("\tmd5: " + amtSignaturesMD5 + ", sha1: " + amtSignaturesSHA1 + ", sha256: " + amtSignaturesSHA256);
} catch (Exception e) {
e.printStackTrace();
}
}
signaturesMD5.put("44d88612fea8a8f36de82e1278abb02f"); //Eicar test file
signaturesSHA256.put("6a0b4866f143c32e651662cebf7f380d27b0db809db3b6a34cf34c7436ab6bbf"); //Hypatia test file
System.out.println("Total: " + amtSignaturesRead);
System.out.println("Mismatch: " + (amtSignaturesRead-amtSignaturesMD5-amtSignaturesSHA1-amtSignaturesSHA256));
System.out.println("Loaded all databases - md5: " + amtSignaturesMD5 + ", sha1: " + amtSignaturesSHA1 + ", sha256: " + amtSignaturesSHA256);
System.out.println("Expected false postive rate - md5: " + signaturesMD5.expectedFpp() + ", sha1: " + signaturesSHA1.expectedFpp() + ", sha256: " + signaturesSHA256.expectedFpp());
try {
FileOutputStream fileSignaturesMD5 = new FileOutputStream(new File(args[0]) + "/hypatia-md5-bloom.bin");
signaturesMD5.writeTo(fileSignaturesMD5);
fileSignaturesMD5.close();
FileOutputStream fileSignaturesSHA1 = new FileOutputStream(new File(args[0]) + "/hypatia-sha1-bloom.bin");
signaturesSHA1.writeTo(fileSignaturesSHA1);
fileSignaturesSHA1.close();
FileOutputStream fileSignaturesSHA256 = new FileOutputStream(new File(args[0]) + "/hypatia-sha256-bloom.bin");
signaturesSHA256.writeTo(fileSignaturesSHA256);
fileSignaturesSHA256.close();
} catch (Exception e) { e.printStackTrace(); }
}
}