From 4dfca28d22895dcce01afa9af7343694460112bc Mon Sep 17 00:00:00 2001 From: Tad Date: Sat, 16 Dec 2017 06:15:17 -0500 Subject: [PATCH] Lots of refractoring --- .idea/misc.xml | 2 +- app/src/main/AndroidManifest.xml | 1 - .../us/spotco/malwarescanner/Database.java | 80 +++--- .../spotco/malwarescanner/MainActivity.java | 36 ++- .../spotco/malwarescanner/MalwareScanner.java | 227 ++++++------------ .../java/us/spotco/malwarescanner/Utils.java | 32 +++ 6 files changed, 185 insertions(+), 193 deletions(-) create mode 100644 app/src/main/java/us/spotco/malwarescanner/Utils.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 47eabe0..6faa303 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -24,7 +24,7 @@ - + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1683100..0329626 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,7 +20,6 @@ android:configChanges="orientation|keyboardHidden"> - diff --git a/app/src/main/java/us/spotco/malwarescanner/Database.java b/app/src/main/java/us/spotco/malwarescanner/Database.java index f78ada2..3d1d571 100644 --- a/app/src/main/java/us/spotco/malwarescanner/Database.java +++ b/app/src/main/java/us/spotco/malwarescanner/Database.java @@ -2,6 +2,7 @@ package us.spotco.malwarescanner; import android.content.Context; import android.os.AsyncTask; +import android.util.Log; import android.widget.TextView; import java.io.BufferedReader; @@ -36,8 +37,12 @@ public class Database { //signatureDatabases.add(new SignatureDatabase("http://cdn.malware.expert/malware.expert.hdb", "malware.expert.hdb")); } - public static boolean doesDatabaseExist() { - return databasePath.listFiles().length > 0; + public static boolean isDatabaseLoaded() { + return signaturesMD5.size() > 0 && signaturesSHA1.size() > 0 && signaturesSHA256.size() > 0; + } + + public static int getSignatureCount() { + return signaturesMD5.size() + signaturesSHA1.size() + signaturesSHA256.size(); } public static void updateDatabase(ArrayList signatureDatabases) { @@ -46,46 +51,49 @@ public class Database { } } - public static void loadDatabase(ArrayList signatureDatabases) { - signaturesMD5.clear(); - signaturesSHA1.clear(); - signaturesSHA256.clear(); - System.gc(); - for (SignatureDatabase database : signatureDatabases) { - File databaseLocation = new File(databasePath + "/" + database.getName()); - if (databaseLocation.exists()) { - if (database.getName().contains(".hdb")) {//.hdb format: md5, size, name - try { - BufferedReader reader = new BufferedReader(new FileReader(databaseLocation)); - String line; - while ((line = reader.readLine()) != null) { - String[] lineS = line.split(":"); - signaturesMD5.put(lineS[0], lineS[2]); - } - reader.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } else if (database.getName().contains(".hsb")) {//.hsb format: sha256, size, name - try { - BufferedReader reader = new BufferedReader(new FileReader(databaseLocation)); - String line; - while ((line = reader.readLine()) != null) { - String[] lineS = line.split(":"); - if (lineS[0].length() == 32) { - signaturesSHA1.put(lineS[0], lineS[2]); - } else { - signaturesSHA256.put(lineS[0], lineS[2]); + public static void loadDatabase(boolean ignoreifLoaded, ArrayList signatureDatabases) { + if (!isDatabaseLoaded() || !ignoreifLoaded && isDatabaseLoaded()) { + Log.d("Theia", "Loading database"); + signaturesMD5.clear(); + signaturesSHA1.clear(); + signaturesSHA256.clear(); + System.gc(); + for (SignatureDatabase database : signatureDatabases) { + File databaseLocation = new File(databasePath + "/" + database.getName()); + if (databaseLocation.exists()) { + if (database.getName().contains(".hdb")) {//.hdb format: md5, size, name + try { + BufferedReader reader = new BufferedReader(new FileReader(databaseLocation)); + String line; + while ((line = reader.readLine()) != null) { + String[] lineS = line.split(":"); + signaturesMD5.put(lineS[0], lineS[2]); } + reader.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } else if (database.getName().contains(".hsb")) {//.hsb format: sha256, size, name + try { + BufferedReader reader = new BufferedReader(new FileReader(databaseLocation)); + String line; + while ((line = reader.readLine()) != null) { + String[] lineS = line.split(":"); + if (lineS[0].length() == 32) { + signaturesSHA1.put(lineS[0], lineS[2]); + } else { + signaturesSHA256.put(lineS[0], lineS[2]); + } + } + reader.close(); + } catch (Exception e) { + e.printStackTrace(); } - reader.close(); - } catch (Exception e) { - e.printStackTrace(); } } } + System.gc(); } - System.gc(); } public static class Downloader extends AsyncTask { diff --git a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java index 7591bd2..4616183 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java +++ b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java @@ -1,10 +1,12 @@ package us.spotco.malwarescanner; import android.Manifest; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.os.AsyncTask; import android.os.Bundle; +import android.os.Environment; import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; @@ -16,6 +18,10 @@ import android.view.View; import android.view.WindowManager; import android.widget.TextView; +import java.io.File; +import java.util.HashSet; +import java.util.Set; + public class MainActivity extends AppCompatActivity { private boolean scanSystem = false; @@ -42,16 +48,31 @@ public class MainActivity extends AppCompatActivity { logView.append("\nDisclaimer: This is an extremely basic signature scanner\n\n"); final Database database = new Database(this, logView); - final MalwareScanner scanner = new MalwareScanner(this, logView); + final MalwareScanner malwareScanner = new MalwareScanner(this, this, true); FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (!scanner.isScannerRunning()) { - MalwareScanner.startScanner(scanSystem, scanApps, scanInternal, scanExternal); + if (!malwareScanner.getStatus().equals(AsyncTask.Status.RUNNING)) { + Set filesToScan = new HashSet<>(); + if (scanSystem) { + filesToScan.addAll(Utils.getFilesRecursive(Environment.getRootDirectory())); + } + if (scanApps) { + for (ApplicationInfo packageInfo : getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA)) { + filesToScan.add(new File(packageInfo.sourceDir)); + } + } + if (scanInternal) { + filesToScan.addAll(Utils.getFilesRecursive(Environment.getExternalStorageDirectory())); + } + if (scanExternal) { + filesToScan.addAll(Utils.getFilesRecursive(new File("/storage"))); + } + malwareScanner.execute(filesToScan); } else { - scanner.stopScanner(); + malwareScanner.cancel(true); } } }); @@ -67,7 +88,7 @@ public class MainActivity extends AppCompatActivity { private void requestPermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE }, REQUEST_PERMISSION_EXTERNAL_STORAGE); + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_EXTERNAL_STORAGE); } } @@ -76,6 +97,9 @@ public class MainActivity extends AppCompatActivity { switch (item.getItemId()) { case R.id.mnuUpdateDatabase: Database.updateDatabase(Database.signatureDatabases); + if (Database.isDatabaseLoaded()) { + Database.loadDatabase(false, Database.signatureDatabases); + } break; case R.id.mnuScanSystem: scanSystem = !item.isChecked(); diff --git a/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java b/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java index 08ae7f5..ba1efb5 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java @@ -1,11 +1,11 @@ package us.spotco.malwarescanner; +import android.app.Activity; +import android.app.NotificationManager; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.os.AsyncTask; -import android.os.Environment; import android.os.SystemClock; +import android.support.v4.app.NotificationCompat; import android.widget.TextView; import java.io.File; @@ -14,173 +14,102 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.MessageDigest; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; -public class MalwareScanner { +public class MalwareScanner extends AsyncTask, Object, String> { - private static Context context = null; - private static TextView log = null; - private static AsyncTask malwareScannerTask = null; + private Context context = null; + private TextView logOutput = null; + private boolean userFacing = false; + private NotificationManager notificationManager = null; + private long scanTime = 0; + private HashMap fileHashesMD5 = new HashMap<>(); + private HashMap fileHashesSHA1 = new HashMap<>(); + private HashMap fileHashesSHA256 = new HashMap<>(); - private static final int MAX_FILE_SIZE = (1000 * 1000) * 50; //50MB - private static long scanTime = 0; - - private static HashMap fileHashesMD5 = new HashMap<>(); - private static HashMap fileHashesSHA1 = new HashMap<>(); - private static HashMap fileHashesSHA256 = new HashMap<>(); - - public MalwareScanner(Context context, TextView log) { - MalwareScanner.context = context; - MalwareScanner.log = log; - } - - public static void startScanner(boolean scanSystem, boolean scanApps, boolean scanInternal, boolean scanExternal) { - if (Database.doesDatabaseExist()) { - malwareScannerTask = new MalwareScannerTask().execute(scanSystem, scanApps, scanInternal, scanExternal); + public MalwareScanner(Activity activity, Context context, boolean userFacing) { + this.context = context; + this.userFacing = userFacing; + if (activity != null) { + logOutput = activity.findViewById(R.id.txtLogOutput); } else { - log.append("No database found... download one first!\n"); + notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } } - public void stopScanner() { - malwareScannerTask.cancel(true); + private void logResult(String result, boolean userFacingOnly) { + if (userFacing) { + logOutput.append(result + "\n"); + } else if (!userFacingOnly) { + NotificationCompat.Builder mBuilder = + new NotificationCompat.Builder(context) + .setSmallIcon(R.drawable.ic_launcher_foreground) + .setContentTitle("Malware Detected!") + .setContentText(result); + notificationManager.notify(1, mBuilder.build()); + } } - public boolean isScannerRunning() { - return malwareScannerTask != null && (malwareScannerTask.getStatus().equals(AsyncTask.Status.PENDING) || malwareScannerTask.getStatus().equals(AsyncTask.Status.RUNNING)); + @Override + protected void onPreExecute() { + scanTime = SystemClock.elapsedRealtime(); + logResult("Starting scan...", true); } - public static class MalwareScannerTask extends AsyncTask { + @Override + protected String doInBackground(Set[] filesToScan) { + //Pre + fileHashesMD5.clear(); + fileHashesSHA1.clear(); + fileHashesSHA256.clear(); - @Override - protected void onPreExecute() { - scanTime = SystemClock.elapsedRealtime(); - log.append("Initiating scan...\n"); - fileHashesMD5.clear(); - fileHashesSHA1.clear(); - fileHashesSHA256.clear(); + publishProgress(filesToScan[0].size() + " files pending scan\n", true); + + Database.loadDatabase(true, Database.signatureDatabases); + publishProgress("Loaded database with " + Database.getSignatureCount() + " signatures\n", true); + + //Get file hashes + publishProgress("Hashing files...", true); + for (File file : filesToScan[0]) { + getFileHashes(file); } + publishProgress("Calculated MD5/SHA-1/SHA-256 hashes for all files\n", true); - @Override - protected void onCancelled() { - log.append("Scan cancelled\n"); - } - - @Override - protected String doInBackground(Boolean... booleans) { - boolean scanSystem = booleans[0]; - boolean scanApps = booleans[1]; - boolean scanInternal = booleans[2]; - boolean scanExternal = booleans[3]; - - Set filesToScan = new HashSet<>(); - - if (scanSystem) { - filesToScan.addAll(getFilesRecursive(Environment.getRootDirectory())); - } - if (scanApps) { - for (ApplicationInfo packageInfo : context.getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA)) { - filesToScan.add(new File(packageInfo.sourceDir)); - } - } - if (scanInternal) { - filesToScan.addAll(getFilesRecursive(Environment.getExternalStorageDirectory())); - } - if (scanExternal) { - filesToScan.addAll(getFilesRecursive(new File("/storage"))); - } - publishProgress(filesToScan.size() + " files pending scan\n"); - - Database.loadDatabase(Database.signatureDatabases); - publishProgress("Loaded " + Database.signaturesMD5.size() + " MD5 signatures"); - publishProgress("Loaded " + Database.signaturesSHA1.size() + " SHA-1 signatures"); - publishProgress("Loaded " + Database.signaturesSHA256.size() + " SHA-256 signatures\n"); - - publishProgress("Hashing files..."); - for (File file : filesToScan) { - getFileHashes(file); - } - publishProgress("Calculated MD5/SHA-1/SHA-256 hashes for all files\n"); - - if (Database.signaturesMD5.size() > 0) { - - for (Map.Entry file : fileHashesMD5.entrySet()) { - if (Database.signaturesMD5.containsKey(file.getKey())) { - String result = Database.signaturesMD5.get(file.getKey()); - publishProgress(file.getValue() + " detected as " + result); - } - } - publishProgress("Checked all MD5 hashes against signature databases\n"); - } else { - publishProgress("No MD5 signatures available\n"); - } - - if (Database.signaturesSHA1.size() > 0) { - for (Map.Entry file : fileHashesSHA1.entrySet()) { - if (Database.signaturesSHA1.containsKey(file.getKey())) { - String result = Database.signaturesSHA1.get(file.getKey()); - publishProgress(file.getValue() + " detected as " + result); - } - } - publishProgress("Checked all SHA-1 hashes against signature databases\n"); - } else { - publishProgress("No SHA-1 signatures available\n"); - } - - if (Database.signaturesSHA256.size() > 0) { - for (Map.Entry file : fileHashesSHA256.entrySet()) { - if (Database.signaturesSHA256.containsKey(file.getKey())) { - String result = Database.signaturesSHA256.get(file.getKey()); - publishProgress(file.getValue() + " detected as " + result); - } - } - publishProgress("Checked all SHA-256 hashes against signature databases\n"); - } else { - publishProgress("No SHA-256 signatures available\n"); - } - - System.gc(); - - return null; - } - - @Override - protected void onProgressUpdate(String... progress) { - log.append(progress[0] + "\n"); - } - - @Override - protected void onPostExecute(String result) { - log.append("Scan completed in " + ((SystemClock.elapsedRealtime() - scanTime) / 1000) + " seconds!\n\n\n\n\n"); - } + //Check the hashes + checkSignature("MD5", fileHashesMD5, Database.signaturesMD5); + checkSignature("SHA-1", fileHashesSHA1, Database.signaturesSHA1); + checkSignature("SHA-256", fileHashesSHA256, Database.signaturesSHA256); + //Post + fileHashesMD5.clear(); + fileHashesSHA1.clear(); + fileHashesSHA256.clear(); + System.gc(); + publishProgress("Scan completed in " + ((SystemClock.elapsedRealtime() - scanTime) / 1000) + " seconds!\n\n\n\n", true); + return null; } - private static Set getFilesRecursive(File root) { - Set filesAll = new HashSet<>(); - - File[] files = root.listFiles(); - if (files != null && files.length > 0) { - for (File f : files) { - if (f.isDirectory()) { - Set filesTmp = getFilesRecursive(f); - if (filesTmp != null) { - filesAll.addAll(filesTmp); - } - } else { - if(f.length() <= MAX_FILE_SIZE) {//Exclude files larger than 50MB for performance - filesAll.add(f); - } - } - } - } - - return filesAll; + @Override + protected void onProgressUpdate(Object... objects) { + logResult((String) objects[0], (boolean) objects[1]); } - private static void getFileHashes(File file) { + private void checkSignature(String hashType, HashMap signaturesToCheck, HashMap signatureDatabase) { + if (signatureDatabase.size() > 0) { + for (Map.Entry file : signaturesToCheck.entrySet()) { + if (signatureDatabase.containsKey(file.getKey())) { + String result = signatureDatabase.get(file.getKey()); + publishProgress(result + " in " + file.getValue(), false); + } + } + publishProgress("Checked all " + hashType + " hashes against signature databases\n", true); + } else { + publishProgress("No " + hashType + " signatures available\n", true); + } + } + + private void getFileHashes(File file) { try { InputStream fis = new FileInputStream(file); diff --git a/app/src/main/java/us/spotco/malwarescanner/Utils.java b/app/src/main/java/us/spotco/malwarescanner/Utils.java new file mode 100644 index 0000000..cae583d --- /dev/null +++ b/app/src/main/java/us/spotco/malwarescanner/Utils.java @@ -0,0 +1,32 @@ +package us.spotco.malwarescanner; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +public class Utils { + + private static final int MAX_FILE_SIZE = (1000 * 1000) * 50; //50MB + + public static Set getFilesRecursive(File root) { + Set filesAll = new HashSet<>(); + + File[] files = root.listFiles(); + if (files != null && files.length > 0) { + for (File f : files) { + if (f.isDirectory()) { + Set filesTmp = getFilesRecursive(f); + if (filesTmp != null) { + filesAll.addAll(filesTmp); + } + } else { + if (f.length() <= MAX_FILE_SIZE) {//Exclude files larger than 50MB for performance + filesAll.add(f); + } + } + } + } + + return filesAll; + } +}