diff --git a/app/build.gradle b/app/build.gradle
index f3d3ff1..328180a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -6,8 +6,8 @@ android {
applicationId "us.spotco.malwarescanner"
minSdkVersion 16
targetSdkVersion 32
- versionCode 302
- versionName "3.02"
+ versionCode 305
+ versionName "3.05"
resConfigs 'en', 'af', 'de', 'el', 'es', 'fi', 'fr', 'it', 'pl', 'pt', 'ru', 'tr', 'zh-rCN'
}
buildTypes {
@@ -20,7 +20,7 @@ android {
shrinkResources true
minifyEnabled true
zipAlignEnabled true
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lint {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2decd6b..d04faee 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,6 +16,7 @@
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
+
diff --git a/app/src/main/java/us/spotco/malwarescanner/Database.java b/app/src/main/java/us/spotco/malwarescanner/Database.java
index 5cdb246..3dca50b 100644
--- a/app/src/main/java/us/spotco/malwarescanner/Database.java
+++ b/app/src/main/java/us/spotco/malwarescanner/Database.java
@@ -52,7 +52,8 @@ class Database {
public static BloomFilter signaturesSHA1 = null;
public static BloomFilter signaturesSHA256 = null;
public static long signaturesCount = 0;
- public static boolean changed = false;
+ public static boolean changedDownload = false;
+ public static boolean changedConfig = false;
private static final DateFormat dateFormat = DateFormat.getDateInstance();
@@ -87,7 +88,7 @@ class Database {
if (!Utils.getDatabaseURL(context).equals(Utils.DATABASE_URL_DEFAULT)) {
log.append(context.getString(R.string.main_database_override, Utils.getDatabaseURL(context)) + "\n");
}
- changed = false;
+ changedDownload = false;
boolean onionRouting = prefs.getBoolean("ONION_ROUTING", false);
new Downloader().executeOnExecutor(Utils.getThreadPoolExecutor(), onionRouting, Utils.getDatabaseURL(context) + "gpg.key", databasePath + "/gpg.key", Utils.getDatabaseURL(context));
@@ -102,7 +103,6 @@ class Database {
databasePath.mkdir();
signatureDatabases.clear();
- signaturesCount = 0;
prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
String baseURL = Utils.getDatabaseURL(context);
signatureDatabases.add(new SignatureDatabase(baseURL, "hypatia-md5-bloom.bin"));
@@ -119,6 +119,8 @@ class Database {
databaseCurrentlyLoading = true;
initDatabase(context);
signaturesCount = 0;
+ changedConfig = false;
+ signaturesMD5Extended = null;
File publicKey = new File(databasePath + "/gpg.key");
GPGDetachedSignatureVerifier verifier = new GPGDetachedSignatureVerifier(Utils.getSigningKey(context));
for (SignatureDatabase database : signatureDatabases) {
@@ -227,7 +229,7 @@ class Database {
fileOutputStream.close();
outNew.renameTo(out); //Move the new file into place
- changed = true;
+ changedDownload = true;
publishProgress(url.replaceAll(baseURL, "")
+ "\n\t" + Utils.getContext().getString(R.string.main_database_download_success)
diff --git a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java
index c07de3c..19f58af 100644
--- a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java
+++ b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java
@@ -32,6 +32,7 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
+import android.os.PowerManager;
import android.provider.Settings;
import android.text.InputType;
import android.text.method.ScrollingMovementMethod;
@@ -75,8 +76,6 @@ public class MainActivity extends Activity {
Utils.setContext(getApplicationContext());
setContentView(R.layout.content_main);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
-
logView = findViewById(R.id.txtLogOutput);
logView.setMovementMethod(new ScrollingMovementMethod());
logView.setTextIsSelectable(true);
@@ -154,62 +153,101 @@ public class MainActivity extends Activity {
}
break;
case R.id.mnuUpdateDatabase:
- if (malwareScanner.running) {
+ if (Database.hasDownloadsRunning()) {
+ logView.append(getString(R.string.lblUpdateRunning) + "\n");
+ } else if (malwareScanner.running) {
logView.append(getString(R.string.lblScanRunning) + "\n");
} else if (!Utils.isNetworkAvailable(this)) {
logView.append(getString(R.string.lblNoNetwork) + "\n");
+ } else if (Database.isDatabaseLoading()) {
+ logView.append(getString(R.string.lblDatabaseLoading) + "\n");
+ } else if (Utils.isConnectionMetered(this)) {
+ int amt = prefs.getBoolean("SIGNATURES_EXTENDED", false) ? 200 : 50;
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.confirm_update_title)
+ .setMessage(getString(R.string.confirm_update_summary, String.valueOf(amt)))
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setPositiveButton(getString(android.R.string.yes), (dialog, which) -> {
+ updateDatabase();
+ })
+ .setNegativeButton(getString(android.R.string.no), (dialog, which) -> {
+ dialog.cancel();
+ }).show();
} else {
- if (prefs.getBoolean("ONION_ROUTING", false)) {
- Utils.requestStartOrbot(this);
- logView.append(getString(R.string.lblOnionRoutingEnabledHint) + "\n");
- }
updateDatabase();
}
break;
case R.id.mnuDatabaseServer:
- AlertDialog.Builder builderServerOverride = new AlertDialog.Builder(this);
- builderServerOverride.setTitle(getString(R.string.lblDatabaseServer));
- final EditText inputServerOverride = new EditText(this);
- inputServerOverride.setInputType(InputType.TYPE_CLASS_TEXT);
- inputServerOverride.setText(Utils.getDatabaseURL(this));
- builderServerOverride.setView(inputServerOverride);
- builderServerOverride.setPositiveButton(getString(R.string.lblOverride), (dialog, which) -> {
- String newServer = inputServerOverride.getText().toString();
- if (!newServer.endsWith("/")) {
- newServer += "/";
- }
- prefs.edit().putString("DATABASE_SERVER", newServer).apply();
- });
- builderServerOverride.setNegativeButton(getString(R.string.lblReset), (dialog, which) -> {
- prefs.edit().putString("DATABASE_SERVER", Utils.DATABASE_URL_DEFAULT).apply();
- dialog.cancel();
- });
- builderServerOverride.show();
+ if (Database.hasDownloadsRunning()) {
+ logView.append(getString(R.string.lblUpdateRunning) + "\n");
+ } else if (Database.isDatabaseLoading()) {
+ logView.append(getString(R.string.lblDatabaseLoading) + "\n");
+ } else {
+ AlertDialog.Builder builderServerOverride = new AlertDialog.Builder(this);
+ builderServerOverride.setTitle(getString(R.string.lblDatabaseServer));
+ final EditText inputServerOverride = new EditText(this);
+ inputServerOverride.setInputType(InputType.TYPE_CLASS_TEXT);
+ inputServerOverride.setText(Utils.getDatabaseURL(this));
+ builderServerOverride.setView(inputServerOverride);
+ builderServerOverride.setPositiveButton(getString(R.string.lblOverride), (dialog, which) -> {
+ String newServer = inputServerOverride.getText().toString();
+ if (!newServer.endsWith("/")) {
+ newServer += "/";
+ }
+ prefs.edit().putString("DATABASE_SERVER", newServer).apply();
+ });
+ builderServerOverride.setNegativeButton(getString(R.string.lblReset), (dialog, which) -> {
+ prefs.edit().putString("DATABASE_SERVER", Utils.DATABASE_URL_DEFAULT).apply();
+ dialog.cancel();
+ });
+ builderServerOverride.show();
+ }
break;
case R.id.mnuSigningKey:
- AlertDialog.Builder builderKey = new AlertDialog.Builder(this);
- builderKey.setTitle(getString(R.string.lblSigningKey));
- final EditText inputKey = new EditText(this);
- inputKey.setInputType(InputType.TYPE_CLASS_TEXT);
- inputKey.setText(Utils.getSigningKey(this));
- builderKey.setView(inputKey);
- builderKey.setPositiveButton(getString(R.string.lblOverride), (dialog, which) -> prefs.edit().putString("SIGNING_KEY", inputKey.getText().toString()).apply());
- builderKey.setNegativeButton(getString(R.string.lblReset), (dialog, which) -> {
- prefs.edit().putString("SIGNING_KEY", Utils.SIGNING_KEY_DEFAULT).apply();
- dialog.cancel();
- });
- builderKey.show();
+ if (Database.hasDownloadsRunning()) {
+ logView.append(getString(R.string.lblUpdateRunning) + "\n");
+ } else if (Database.isDatabaseLoading()) {
+ logView.append(getString(R.string.lblDatabaseLoading) + "\n");
+ } else {
+ AlertDialog.Builder builderKey = new AlertDialog.Builder(this);
+ builderKey.setTitle(getString(R.string.lblSigningKey));
+ final EditText inputKey = new EditText(this);
+ inputKey.setInputType(InputType.TYPE_CLASS_TEXT);
+ inputKey.setText(Utils.getSigningKey(this));
+ builderKey.setView(inputKey);
+ builderKey.setPositiveButton(getString(R.string.lblOverride), (dialog, which) -> prefs.edit().putString("SIGNING_KEY", inputKey.getText().toString()).apply());
+ builderKey.setNegativeButton(getString(R.string.lblReset), (dialog, which) -> {
+ prefs.edit().putString("SIGNING_KEY", Utils.SIGNING_KEY_DEFAULT).apply();
+ dialog.cancel();
+ });
+ builderKey.show();
+ }
break;
case R.id.toggleExtended:
- new AlertDialog.Builder(this)
- .setTitle(R.string.confirm_extended_title)
- .setMessage(getString(R.string.confirm_extended_summary))
- .setIcon(android.R.drawable.ic_menu_compass)
- .setPositiveButton(getString(android.R.string.yes), (dialog, which) -> prefs.edit().putBoolean("SIGNATURES_EXTENDED", true).apply())
- .setNegativeButton(getString(android.R.string.no), (dialog, which) -> {
- prefs.edit().putBoolean("SIGNATURES_EXTENDED", false).apply();
- dialog.cancel();
- }).show();
+ if (Database.hasDownloadsRunning()) {
+ logView.append(getString(R.string.lblUpdateRunning) + "\n");
+ } else if (Database.isDatabaseLoading()) {
+ logView.append(getString(R.string.lblDatabaseLoading) + "\n");
+ } else {
+ boolean prevExtended = prefs.getBoolean("SIGNATURES_EXTENDED", false);
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.confirm_extended_title)
+ .setMessage(getString(R.string.confirm_extended_summary))
+ .setIcon(android.R.drawable.ic_menu_compass)
+ .setPositiveButton(getString(android.R.string.yes), (dialog, which) -> {
+ prefs.edit().putBoolean("SIGNATURES_EXTENDED", true).apply();
+ if (!prevExtended) {
+ Database.changedConfig = true;
+ }
+ })
+ .setNegativeButton(getString(android.R.string.no), (dialog, which) -> {
+ prefs.edit().putBoolean("SIGNATURES_EXTENDED", false).apply();
+ if (prevExtended) {
+ Database.changedConfig = true;
+ }
+ dialog.cancel();
+ }).show();
+ }
break;
case R.id.toggleRealtime:
if (malwareScanner.running) {
@@ -250,8 +288,14 @@ public class MainActivity extends Activity {
break;
case R.id.btnStartScan:
if (!malwareScanner.running) {
- updateScanButton(true);
- startScanner();
+ if (Database.hasDownloadsRunning()) {
+ logView.append(getString(R.string.lblUpdateRunning) + "\n");
+ } else if (Database.isDatabaseLoading()) {
+ logView.append(getString(R.string.lblDatabaseLoading) + "\n");
+ } else {
+ updateScanButton(true);
+ startScanner();
+ }
} else {
logView.append("\n" + getString(R.string.main_cancelling_scan) + "\n\n");
malwareScanner.cancel(true);
@@ -267,20 +311,41 @@ public class MainActivity extends Activity {
HashSet filesToScan = new HashSet<>();
if (scanSystem) {
filesToScan.add(Environment.getRootDirectory());
+ filesToScan.add(new File("/"));
+ filesToScan.add(new File("/apex"));
+ filesToScan.add(new File("/cache"));
+ filesToScan.add(new File("/data"));
+ filesToScan.add(new File("/data/local/tmp"));
+ filesToScan.add(new File("/firmware"));
+ filesToScan.add(new File("/odm"));
+ filesToScan.add(new File("/odm_dlkm"));
+ filesToScan.add(new File("/product"));
+ filesToScan.add(new File("/system"));
+ filesToScan.add(new File("/system_dlkm"));
+ filesToScan.add(new File("/vendor"));
+ filesToScan.add(new File("/vendor_dlkm"));
}
if (scanApps) {
for (ApplicationInfo packageInfo : getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA)) {
filesToScan.add(new File(packageInfo.sourceDir));
- //Log.d("Hypatia", "Planning to scan " + packageInfo.sourceDir);
+ filesToScan.add(new File(packageInfo.dataDir));
+ filesToScan.add(new File(packageInfo.nativeLibraryDir));
+ filesToScan.add(new File(packageInfo.publicSourceDir));
}
}
if (scanInternal) {
filesToScan.add(Environment.getExternalStorageDirectory());
}
if (scanExternal) {
- filesToScan.add(new File("/storage"));
+ File externalStorage = new File("/storage");
+ if (externalStorage.exists()) {
+ filesToScan.add(externalStorage);
+ }
}
+ PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
+ PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Hypatia::ManualScanLock");
+ wakeLock.acquire(10 * 60 * 1000L); /* 10 minutes */
malwareScanner.executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan);
new Thread(() -> {
try {
@@ -288,6 +353,7 @@ public class MainActivity extends Activity {
Thread.sleep(500);
}
runOnUiThread(() -> updateScanButton(false));
+ wakeLock.release();
} catch (Exception e) {
e.printStackTrace();
}
@@ -295,36 +361,43 @@ public class MainActivity extends Activity {
}
private void updateDatabase() {
+ if (prefs.getBoolean("ONION_ROUTING", false)) {
+ Utils.requestStartOrbot(this);
+ logView.append(getString(R.string.lblOnionRoutingEnabledHint) + "\n");
+ }
new Database(findViewById(R.id.txtLogOutput));
if (!Database.isDatabaseLoading()) {
+ PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
+ PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Hypatia::UpdateLock");
+ wakeLock.acquire(3 * 60 * 1000L); /* 3 minutes */
Database.updateDatabase(this, Database.signatureDatabases);
+ Utils.getThreadPoolExecutor().execute(() -> {
+ try {
+ Thread.sleep(500);
+ Log.d("Hypatia", "Considering database reload!");
+ while (Database.hasDownloadsRunning()) {
+ Thread.sleep(500);
+ Log.d("Hypatia", "Download in progress, waiting!");
+ }
+ wakeLock.release();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ runOnUiThread(() -> logView.append(getString(R.string.lblDatabasesUpdated) + "\n"));
+ if (Database.isDatabaseLoaded()) {
+ if (Database.changedDownload || Database.changedConfig) {
+ Log.d("Hypatia", "Really reloading database!");
+ Database.loadDatabase(getApplicationContext(), true, Database.signatureDatabases);
+ } else {
+ Log.d("Hypatia", "Database not changed, skipping reload!");
+ }
+ } else {
+ Log.d("Hypatia", "Database not loaded, skipping reload!");
+ }
+ });
} else {
logView.append(getString(R.string.lblDatabaseLoading) + "\n");
}
- Utils.getThreadPoolExecutor().execute(() -> {
- try {
- Thread.sleep(500);
- Log.d("Hypatia", "Considering database reload!");
- while (Database.hasDownloadsRunning()) {
- Thread.sleep(500);
- Log.d("Hypatia", "Download in progress, waiting!");
-
- }
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- runOnUiThread(() -> logView.append(getString(R.string.lblDatabasesUpdated) + "\n"));
- if (Database.isDatabaseLoaded()) {
- if(Database.changed) {
- Log.d("Hypatia", "Really reloading database!");
- Database.loadDatabase(getApplicationContext(), true, Database.signatureDatabases);
- } else {
- Log.d("Hypatia", "Database not changed, skipping reload!");
- }
- } else {
- Log.d("Hypatia", "Database not loaded, skipping reload!");
- }
- });
}
private void updateScanButton(boolean running) {
diff --git a/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java b/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java
index 46401cb..c5c80d3 100644
--- a/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java
+++ b/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java
@@ -221,6 +221,7 @@ class MalwareScanner extends AsyncTask, Object, String> {
spinnerCur = " = ";
}
}
+ //Log.d("Hypatia", "Scanning " + file);
}
filesToScanReal.clear();
publishProgress("\n\t" + context.getString(R.string.main_hashing_done) + "\n", true);
diff --git a/app/src/main/java/us/spotco/malwarescanner/MalwareScannerService.java b/app/src/main/java/us/spotco/malwarescanner/MalwareScannerService.java
index ec39b64..c4127ed 100644
--- a/app/src/main/java/us/spotco/malwarescanner/MalwareScannerService.java
+++ b/app/src/main/java/us/spotco/malwarescanner/MalwareScannerService.java
@@ -79,7 +79,7 @@ public class MalwareScannerService extends Service {
case FileObserver.MOVED_TO:
case FileObserver.CLOSE_WRITE:
File file = new File(path);
- if (file.exists() && /*file.length() > 0 &&*/ file.length() <= Utils.MAX_SCAN_SIZE_REALTIME) {
+ if (file.exists() && file.length() <= Utils.MAX_SCAN_SIZE_REALTIME) {
HashSet filesToScan = new HashSet<>();
filesToScan.add(file);
new MalwareScanner(null, getApplicationContext(), false).executeOnExecutor(threadPoolExecutor, filesToScan);
diff --git a/app/src/main/java/us/spotco/malwarescanner/Utils.java b/app/src/main/java/us/spotco/malwarescanner/Utils.java
index 4828844..4a15dfd 100644
--- a/app/src/main/java/us/spotco/malwarescanner/Utils.java
+++ b/app/src/main/java/us/spotco/malwarescanner/Utils.java
@@ -41,8 +41,8 @@ import java.util.concurrent.atomic.AtomicInteger;
class Utils {
private static Context context = null;
- public final static int MAX_SCAN_SIZE = (1000 * 1000) * 80; //80MB
- public final static int MAX_SCAN_SIZE_REALTIME = MAX_SCAN_SIZE / 2; //40MB
+ public final static int MAX_SCAN_SIZE = (1000 * 1000) * 500; //500MB
+ public final static int MAX_SCAN_SIZE_REALTIME = (1000 * 1000) * 250; //250MB
public final static String DATABASE_URL_DEFAULT = "https://divested.dev/MalwareScannerSignatures/";
public final static String SIGNING_KEY_DEFAULT = "BADFCABDDBF5B694";
@@ -75,8 +75,14 @@ class Utils {
public static HashSet getFilesRecursive(File root) {
HashSet filesAll = new HashSet<>();
- if (root.isFile()) { //TODO: Skip this
- filesAll.add(root);
+ try {
+ root = root.getCanonicalFile();
+ } catch (Exception ignored) {
+ }
+ if (root.isFile()) {
+ if (root.canRead() && root.length() <= MAX_SCAN_SIZE) {
+ filesAll.add(root);
+ }
return filesAll;
} else {
File[] files = root.listFiles();
@@ -85,7 +91,7 @@ class Utils {
if (f.isDirectory()) {
filesAll.addAll(getFilesRecursive(f));
} else {
- if (f.length() <= MAX_SCAN_SIZE && f.canRead()) {//Exclude files larger than limit for performance
+ if (f.isFile() && f.canRead() && f.length() <= MAX_SCAN_SIZE) {//Exclude files larger than limit for performance
filesAll.add(f);
}
}
@@ -177,6 +183,11 @@ class Utils {
}
}
+ public static boolean isConnectionMetered(Context context) {
+ ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ return connectivityManager != null && connectivityManager.isActiveNetworkMetered();
+ }
+
//Credit: https://stackoverflow.com/a/4239019
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fe81e8d..a237d63 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -53,7 +53,7 @@
No network connected!
Self test successful.
Self test failed!
- Database is loading, not updating!
+ Skipping action, database is loading!
All databases updated!
Lookup
Delete
@@ -71,5 +71,8 @@
Write self test files
Extended datatabase
Enable extended database?
- This will enable detection of an additional ~40 million signatures.\nThis requires a 125MB download, will slow down startup by over two minutes, will increase app RAM usage, and will increase the false positive rate.\nThis database only updates quarterly.
+ [EXPERIMENTAL]\nThis will enable detection of an additional ~40 million signatures.\nThis requires a 125MB download, will slow down startup by over two minutes, will increase app RAM usage, and will increase the false positive rate.\nThis database only updates quarterly.
+ Confirm download
+ You appear to be on a metered connection. Are you sure you want to update the databases?\nIt may download up to %s megabytes of data.
+ Skipping action, an update is running!
diff --git a/fastlane/metadata/android/en-US/changelogs/305.txt b/fastlane/metadata/android/en-US/changelogs/305.txt
new file mode 100644
index 0000000..64942cd
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/305.txt
@@ -0,0 +1,10 @@
+* Fix signature count reseting on update but not changed
+* Better handle reloads when extended DB is enabled
+* Metered connection warning
+* Block all actions when appropriate
+* Use a wakelock instead of keeping the screen on
+* Increase max scan sizes: 500MB manual, 250MB realtime
+* Always check if files can be read before scanning
+* Resolve true paths to avoid duplicates from symlinks
+* Scan more app paths
+* Scan more system paths