From 50038492604f6c60270000509203bf95d9147655 Mon Sep 17 00:00:00 2001 From: M66B Date: Mon, 25 Sep 2023 16:29:49 +0200 Subject: [PATCH] Added malware protection --- .../eu/faircode/netguard/ActivityMain.java | 46 ++++++++ .../eu/faircode/netguard/ApplicationEx.java | 5 + .../eu/faircode/netguard/ServiceSinkhole.java | 100 ++++++++++++++++-- app/src/main/res/menu/main.xml | 4 + app/src/main/res/values/strings.xml | 2 + 5 files changed, 148 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/eu/faircode/netguard/ActivityMain.java b/app/src/main/java/eu/faircode/netguard/ActivityMain.java index 073e691f..c9cbbb74 100644 --- a/app/src/main/java/eu/faircode/netguard/ActivityMain.java +++ b/app/src/main/java/eu/faircode/netguard/ActivityMain.java @@ -73,6 +73,9 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; import java.util.List; public class ActivityMain extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener { @@ -111,6 +114,8 @@ public class ActivityMain extends AppCompatActivity implements SharedPreferences public static final String EXTRA_METERED = "Metered"; public static final String EXTRA_SIZE = "Size"; + private static final String MALWARE_URL = "https://urlhaus.abuse.ch/downloads/hostfile/"; + @Override protected void onCreate(Bundle savedInstanceState) { Log.i(TAG, "Create version=" + Util.getSelfVersionName(this) + "/" + Util.getSelfVersionCode(this)); @@ -858,6 +863,7 @@ public class ActivityMain extends AppCompatActivity implements SharedPreferences menu.findItem(R.id.menu_sort_name).setChecked(true); menu.findItem(R.id.menu_lockdown).setChecked(prefs.getBoolean("lockdown", false)); + menu.findItem(R.id.menu_malware).setChecked(prefs.getBoolean("malware", false)); return super.onPrepareOptionsMenu(menu); } @@ -903,6 +909,10 @@ public class ActivityMain extends AppCompatActivity implements SharedPreferences menu_lockdown(item); return true; + case R.id.menu_malware: + menu_malware(item); + return true; + case R.id.menu_log: if (Util.canFilter(this)) if (IAB.isPurchased(ActivityPro.SKU_LOG, this)) @@ -1198,6 +1208,42 @@ public class ActivityMain extends AppCompatActivity implements SharedPreferences WidgetLockdown.updateWidgets(this); } + private void menu_malware(MenuItem item) { + item.setChecked(!item.isChecked()); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + prefs.edit().putBoolean("malware", item.isChecked()).apply(); + if (item.isChecked()) + try { + final File file = new File(getFilesDir(), "malware.txt"); + new DownloadTask(this, new URL(MALWARE_URL), file, new DownloadTask.Listener() { + @Override + public void onCompleted() { + prefs.edit().putBoolean("filter", true).apply(); + ServiceSinkhole.reload("malware download", ActivityMain.this, false); + } + + @Override + public void onCancelled() { + prefs.edit().putBoolean("malware", false).apply(); + } + + @Override + public void onException(Throwable ex) { + Toast.makeText(ActivityMain.this, ex.getMessage(), Toast.LENGTH_LONG).show(); + } + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } catch (MalformedURLException ex) { + Toast.makeText(this, ex.toString(), Toast.LENGTH_LONG).show(); + } + else { + SharedPreferences.Editor editor = prefs.edit(); + for (String key : prefs.getAll().keySet()) + if (key.startsWith("malware.")) + editor.remove(key); + editor.apply(); + } + } + private void menu_about() { // Create view LayoutInflater inflater = LayoutInflater.from(this); diff --git a/app/src/main/java/eu/faircode/netguard/ApplicationEx.java b/app/src/main/java/eu/faircode/netguard/ApplicationEx.java index 3b7e0da8..7e26673f 100644 --- a/app/src/main/java/eu/faircode/netguard/ApplicationEx.java +++ b/app/src/main/java/eu/faircode/netguard/ApplicationEx.java @@ -74,5 +74,10 @@ public class ApplicationEx extends Application { access.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); access.setBypassDnd(true); nm.createNotificationChannel(access); + + NotificationChannel malware = new NotificationChannel("malware", getString(R.string.setting_malware), NotificationManager.IMPORTANCE_HIGH); + malware.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); + malware.setBypassDnd(true); + nm.createNotificationChannel(malware); } } diff --git a/app/src/main/java/eu/faircode/netguard/ServiceSinkhole.java b/app/src/main/java/eu/faircode/netguard/ServiceSinkhole.java index 941a0424..247afa60 100644 --- a/app/src/main/java/eu/faircode/netguard/ServiceSinkhole.java +++ b/app/src/main/java/eu/faircode/netguard/ServiceSinkhole.java @@ -144,7 +144,9 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS private boolean temporarilyStopped = false; private long last_hosts_modified = 0; + private long last_malware_modified = 0; private Map mapHostsBlocked = new HashMap<>(); + private Map mapMalware = new HashMap<>(); private Map mapUidAllowed = new HashMap<>(); private Map mapUidKnown = new HashMap<>(); private final Map> mapUidIPFilters = new HashMap<>(); @@ -1471,6 +1473,7 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS if (filter) { prepareUidAllowed(listAllowed, listRule); prepareHostsBlocked(); + prepareMalwareList(); prepareUidIPFilters(null); prepareForwarding(); } else { @@ -1478,6 +1481,7 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS mapUidAllowed.clear(); mapUidKnown.clear(); mapHostsBlocked.clear(); + mapMalware.clear(); mapUidIPFilters.clear(); mapForward.clear(); lock.writeLock().unlock(); @@ -1555,6 +1559,7 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS mapUidAllowed.clear(); mapUidKnown.clear(); mapHostsBlocked.clear(); + mapMalware.clear(); mapUidIPFilters.clear(); mapForward.clear(); mapNotify.clear(); @@ -1633,6 +1638,63 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS lock.writeLock().unlock(); } + private void prepareMalwareList() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ServiceSinkhole.this); + boolean malware = prefs.getBoolean("filter", false) && prefs.getBoolean("malware", false); + File file = new File(getFilesDir(), "malware.txt"); + if (!malware || !file.exists() || !file.canRead()) { + Log.i(TAG, "Malware use=" + malware + " exists=" + file.exists()); + lock.writeLock().lock(); + mapMalware.clear(); + lock.writeLock().unlock(); + return; + } + + boolean changed = (file.lastModified() != last_malware_modified); + if (!changed && mapMalware.size() > 0) { + Log.i(TAG, "Malware unchanged"); + return; + } + last_malware_modified = file.lastModified(); + + lock.writeLock().lock(); + + mapMalware.clear(); + + int count = 0; + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(file)); + String line; + while ((line = br.readLine()) != null) { + int hash = line.indexOf('#'); + if (hash >= 0) + line = line.substring(0, hash); + line = line.trim(); + if (line.length() > 0) { + String[] words = line.split("\\s+"); + if (words.length > 1) { + count++; + mapMalware.put(words[1], true); + } else + Log.i(TAG, "Invalid malware file line: " + line); + } + } + Log.i(TAG, count + " malware read"); + } catch (IOException ex) { + Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex)); + } finally { + if (br != null) + try { + br.close(); + } catch (IOException exex) { + Log.e(TAG, exex.toString() + "\n" + Log.getStackTraceString(exex)); + } + } + + lock.writeLock().unlock(); + } + private void prepareUidIPFilters(String dname) { SharedPreferences lockdown = getSharedPreferences("lockdown", Context.MODE_PRIVATE); @@ -1884,6 +1946,20 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS Log.i(TAG, "New IP " + rr); prepareUidIPFilters(rr.QName); } + if (rr.uid > 0 && !TextUtils.isEmpty(rr.AName)) { + lock.readLock().lock(); + boolean malware = (mapMalware.containsKey(rr.AName) && mapMalware.get(rr.AName)); + lock.readLock().unlock(); + + if (malware) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean notified = prefs.getBoolean("malware." + rr.uid, false); + if (!notified) { + prefs.edit().putBoolean("malware." + rr.uid, true).apply(); + notifyNewApplication(rr.uid, true); + } + } + } } // Called from native code @@ -2257,7 +2333,7 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); if (IAB.isPurchased(ActivityPro.SKU_NOTIFY, context) && prefs.getBoolean("install", true)) { int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); - notifyNewApplication(uid); + notifyNewApplication(uid, false); } } @@ -2299,7 +2375,7 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS } }; - public void notifyNewApplication(int uid) { + public void notifyNewApplication(int uid, boolean malware) { if (uid < 0) return; @@ -2323,18 +2399,24 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS TypedValue tv = new TypedValue(); getTheme().resolveAttribute(R.attr.colorPrimary, tv, true); - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "notify"); + NotificationCompat.Builder builder = new NotificationCompat.Builder(this, + malware ? "malware" : "notify"); builder.setSmallIcon(R.drawable.ic_security_white_24dp) .setContentIntent(pi) .setColor(tv.data) .setAutoCancel(true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + if (malware) builder.setContentTitle(name) - .setContentText(getString(R.string.msg_installed_n)); - else - builder.setContentTitle(getString(R.string.app_name)) - .setContentText(getString(R.string.msg_installed, name)); + .setContentText(getString(R.string.msg_malware, name)); + else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + builder.setContentTitle(name) + .setContentText(getString(R.string.msg_installed_n)); + else + builder.setContentTitle(getString(R.string.app_name)) + .setContentText(getString(R.string.msg_installed, name)); + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) builder.setCategory(NotificationCompat.CATEGORY_STATUS) @@ -2696,7 +2778,7 @@ public class ServiceSinkhole extends VpnService implements SharedPreferences.OnS ServiceSinkhole.reload("notification", ServiceSinkhole.this, false); // Update notification - notifyNewApplication(uid); + notifyNewApplication(uid, false); // Update UI Intent ruleset = new Intent(ActivityMain.ACTION_RULES_CHANGED); diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml index e9766c38..30ba9535 100644 --- a/app/src/main/res/menu/main.xml +++ b/app/src/main/res/menu/main.xml @@ -51,6 +51,10 @@ android:id="@+id/menu_lockdown" android:checkable="true" android:title="@string/setting_lockdown"/> + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1d800159..d8377159 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -102,6 +102,7 @@ Seamless VPN handover on reload Close connections on reload Lockdown traffic + Malware protection Track network usage Reset network usage Show resolved domain names @@ -184,6 +185,7 @@ NetGuard has been disabled, likely by using another VPN based app \'%1$s\' installed Has been installed + Possible malware: \'%1$s\' %1$s attempted internet access Attempted internet access Action completed