diff --git a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java index 31ab3d21..60d0faba 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java +++ b/app/src/main/java/com/emanuelef/remote_capture/CaptureService.java @@ -142,7 +142,8 @@ public class CaptureService extends VpnService implements Runnable { private boolean mStopping; private Blacklists mBlacklists; private Blocklist mBlocklist; - private MatchList mWhitelist; + private MatchList mMalwareWhitelist; + private MatchList mFirewallWhitelist; private SparseArray mIfIndexToName; private boolean mSocks5Enabled; private String mSocks5Address; @@ -451,13 +452,14 @@ public class CaptureService extends VpnService implements Runnable { } } - mWhitelist = PCAPdroid.getInstance().getMalwareWhitelist(); + mMalwareWhitelist = PCAPdroid.getInstance().getMalwareWhitelist(); mBlacklists = PCAPdroid.getInstance().getBlacklists(); if(mMalwareDetectionEnabled && !mBlacklists.needsUpdate()) reloadBlacklists(); checkBlacklistsUpdates(); mBlocklist = PCAPdroid.getInstance().getBlocklist(); + mFirewallWhitelist = PCAPdroid.getInstance().getFirewallWhitelist(); mConnUpdateThread = new Thread(this::connUpdateWork, "UpdateListener"); mConnUpdateThread.start(); @@ -1302,6 +1304,7 @@ public class CaptureService extends VpnService implements Runnable { if(mMalwareDetectionEnabled) reloadMalwareWhitelist(); reloadBlocklist(); + reloadFirewallWhitelist(); } } @@ -1377,12 +1380,20 @@ public class CaptureService extends VpnService implements Runnable { reloadBlocklist(mBlocklist.toListDescriptor()); } + public void reloadFirewallWhitelist() { + if(!mBilling.isFirewallVisible()) + return; + + Log.i(TAG, "reloading firewall whitelist"); + reloadFirewallWhitelist(Prefs.isFirewallWhitelistMode(mPrefs) ? mFirewallWhitelist.toListDescriptor() : null); + } + public static void reloadMalwareWhitelist() { if((INSTANCE == null) || !INSTANCE.mMalwareDetectionEnabled) return; Log.i(TAG, "reloading malware whitelist"); - reloadMalwareWhitelist(INSTANCE.mWhitelist.toListDescriptor()); + reloadMalwareWhitelist(INSTANCE.mMalwareWhitelist.toListDescriptor()); } public static void setFirewallEnabled(boolean enabled) { @@ -1433,6 +1444,7 @@ public class CaptureService extends VpnService implements Runnable { private static native void setDnsServer(String server); private static native void reloadBlacklists(); private static native boolean reloadBlocklist(MatchList.ListDescriptor blocklist); + private static native boolean reloadFirewallWhitelist(MatchList.ListDescriptor whitelist); private static native boolean reloadMalwareWhitelist(MatchList.ListDescriptor whitelist); public static native void askStatsDump(); public static native byte[] getPcapHeader(); diff --git a/app/src/main/java/com/emanuelef/remote_capture/PCAPdroid.java b/app/src/main/java/com/emanuelef/remote_capture/PCAPdroid.java index 38f608ad..dfda636e 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/PCAPdroid.java +++ b/app/src/main/java/com/emanuelef/remote_capture/PCAPdroid.java @@ -48,6 +48,7 @@ public class PCAPdroid extends Application { private static final String TAG = "PCAPdroid"; private MatchList mVisMask; private MatchList mMalwareWhitelist; + private MatchList mFirewallWhitelist; private Blocklist mBlocklist; private Blacklists mBlacklists; private CtrlPermissions mCtrlPermissions; @@ -125,6 +126,31 @@ public class PCAPdroid extends Application { return mBlocklist; } + // use some safe defaults to guarantee basic services + private void initFirewallWhitelist() { + mFirewallWhitelist.addApp(0 /* root */); + mFirewallWhitelist.addApp(1000 /* android */); + mFirewallWhitelist.addApp(getPackageName() /* PCAPdroid */); + mFirewallWhitelist.addApp("com.google.android.gms" /* Google Play Services */); + mFirewallWhitelist.addApp("com.google.android.gsf" /* Google Services Framework (push notifications) */); + mFirewallWhitelist.addApp("com.google.android.ims" /* Carrier Services */); + mFirewallWhitelist.addApp("com.sec.spp.push" /* Samsung Push Service */); + mFirewallWhitelist.save(); + } + + public MatchList getFirewallWhitelist() { + if(mFirewallWhitelist == null) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + mFirewallWhitelist = new MatchList(mLocalizedContext, Prefs.PREF_FIREWALL_WHITELIST); + + if(!Prefs.isFirewallWhitelistInitialized(prefs)) { + initFirewallWhitelist(); + Prefs.setFirewallWhitelistInitialized(prefs); + } + } + return mFirewallWhitelist; + } + public CtrlPermissions getCtrlPermissions() { if(mCtrlPermissions == null) mCtrlPermissions = new CtrlPermissions(this); diff --git a/app/src/main/java/com/emanuelef/remote_capture/activities/FirewallActivity.java b/app/src/main/java/com/emanuelef/remote_capture/activities/FirewallActivity.java index 5da556cb..76ea2cd5 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/activities/FirewallActivity.java +++ b/app/src/main/java/com/emanuelef/remote_capture/activities/FirewallActivity.java @@ -19,11 +19,14 @@ package com.emanuelef.remote_capture.activities; +import android.annotation.SuppressLint; +import android.content.SharedPreferences; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; +import androidx.preference.PreferenceManager; import androidx.viewpager2.adapter.FragmentStateAdapter; import androidx.viewpager2.widget.ViewPager2; @@ -32,15 +35,20 @@ import com.emanuelef.remote_capture.R; import com.emanuelef.remote_capture.fragments.EditListFragment; import com.emanuelef.remote_capture.fragments.FirewallStatus; import com.emanuelef.remote_capture.model.ListInfo; +import com.emanuelef.remote_capture.model.Prefs; import com.google.android.material.tabs.TabLayoutMediator; public class FirewallActivity extends BaseActivity { private static final String TAG = "Firewall"; private ViewPager2 mPager; + private StateAdapter mPagerAdapter; + private boolean mHasWhitelist = false; + private SharedPreferences mPrefs; private static final int POS_STATUS = 0; private static final int POS_BLOCKLIST = 1; - private static final int TOTAL_COUNT = 2; + private static final int POS_WHITELIST = 2; + private static final int TOTAL_COUNT = 3; @Override protected void onCreate(Bundle savedInstanceState) { @@ -48,11 +56,12 @@ public class FirewallActivity extends BaseActivity { setTitle(R.string.firewall); setContentView(R.layout.tabs_activity); + mPrefs = PreferenceManager.getDefaultSharedPreferences(this); mPager = findViewById(R.id.pager); setupTabs(); } - private static class StateAdapter extends FragmentStateAdapter { + private class StateAdapter extends FragmentStateAdapter { StateAdapter(final FragmentActivity fa) { super(fa); } @NonNull @@ -66,11 +75,13 @@ public class FirewallActivity extends BaseActivity { return new FirewallStatus(); case POS_BLOCKLIST: return EditListFragment.newInstance(ListInfo.Type.BLOCKLIST); + case POS_WHITELIST: + return EditListFragment.newInstance(ListInfo.Type.FIREWALL_WHITELIST); } } @Override - public int getItemCount() { return TOTAL_COUNT; } + public int getItemCount() { return mHasWhitelist ? TOTAL_COUNT : (TOTAL_COUNT - 1); } public int getPageTitle(final int position) { switch (position) { @@ -79,18 +90,32 @@ public class FirewallActivity extends BaseActivity { return R.string.status; case POS_BLOCKLIST: return R.string.blocklist; + case POS_WHITELIST: + return R.string.whitelist; } } } + @SuppressLint("NotifyDataSetChanged") + public void recheckTabs() { + boolean whitelist_mode = Prefs.isFirewallWhitelistMode(mPrefs); + if(mHasWhitelist == whitelist_mode) + return; + + mHasWhitelist = whitelist_mode; + mPagerAdapter.notifyDataSetChanged(); + } + private void setupTabs() { - final StateAdapter stateAdapter = new StateAdapter(this); - mPager.setAdapter(stateAdapter); + mPagerAdapter = new StateAdapter(this); + mPager.setAdapter(mPagerAdapter); new TabLayoutMediator(findViewById(R.id.tablayout), mPager, (tab, position) -> - tab.setText(getString(stateAdapter.getPageTitle(position))) + tab.setText(getString(mPagerAdapter.getPageTitle(position))) ).attach(); + recheckTabs(); + // TODO fix DPAD navigation on Android TV, see MainActivity.onKeyDown } } diff --git a/app/src/main/java/com/emanuelef/remote_capture/adapters/AppsStatsAdapter.java b/app/src/main/java/com/emanuelef/remote_capture/adapters/AppsStatsAdapter.java index 9fee995e..777435a8 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/adapters/AppsStatsAdapter.java +++ b/app/src/main/java/com/emanuelef/remote_capture/adapters/AppsStatsAdapter.java @@ -39,6 +39,7 @@ import com.emanuelef.remote_capture.model.AppDescriptor; import com.emanuelef.remote_capture.model.AppStats; import com.emanuelef.remote_capture.AppsResolver; import com.emanuelef.remote_capture.model.Blocklist; +import com.emanuelef.remote_capture.model.MatchList; import java.util.ArrayList; import java.util.Collections; @@ -50,6 +51,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter mStats; @@ -59,6 +61,7 @@ public class AppsStatsAdapter extends RecyclerView.Adapter(); mFirewallAvailable = Billing.newInstance(context).isFirewallVisible(); diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/AppsFragment.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/AppsFragment.java index 1390cdba..2f2a19ef 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/fragments/AppsFragment.java +++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/AppsFragment.java @@ -20,6 +20,7 @@ package com.emanuelef.remote_capture.fragments; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -34,6 +35,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceManager; import com.emanuelef.remote_capture.CaptureService; import com.emanuelef.remote_capture.ConnectionsRegister; @@ -46,6 +48,8 @@ import com.emanuelef.remote_capture.interfaces.ConnectionsListener; import com.emanuelef.remote_capture.model.AppStats; import com.emanuelef.remote_capture.model.Blocklist; import com.emanuelef.remote_capture.model.ConnectionDescriptor; +import com.emanuelef.remote_capture.model.MatchList; +import com.emanuelef.remote_capture.model.Prefs; import com.emanuelef.remote_capture.views.EmptyRecyclerView; public class AppsFragment extends Fragment implements ConnectionsListener { @@ -126,9 +130,16 @@ public class AppsFragment extends Fragment implements ConnectionsListener { if(stats == null) return; + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(requireContext()); boolean isBlocked = PCAPdroid.getInstance().getBlocklist().matchesApp(stats.getUid()); menu.findItem(R.id.block_app).setVisible(!isBlocked); + if(Prefs.isFirewallWhitelistMode(prefs)) { + boolean isWhitelisted = PCAPdroid.getInstance().getFirewallWhitelist().matchesApp(stats.getUid()); + menu.findItem(R.id.add_to_whitelist).setVisible(!isWhitelisted); + menu.findItem(R.id.remove_from_whitelist).setVisible(isWhitelisted); + } + menu.findItem(R.id.unblock_app_permanently).setVisible(isBlocked); menu.findItem(R.id.unblock_app_10m).setVisible(isBlocked) .setTitle(getString(R.string.unblock_for_n_minutes, 10)); @@ -142,6 +153,8 @@ public class AppsFragment extends Fragment implements ConnectionsListener { public boolean onContextItemSelected(@NonNull MenuItem item) { int id = item.getItemId(); Blocklist blocklist = PCAPdroid.getInstance().getBlocklist(); + MatchList whitelist = PCAPdroid.getInstance().getFirewallWhitelist(); + boolean whitelistChanged = false; AppStats app = mAdapter.getSelectedItem(); if(id == R.id.block_app) @@ -154,10 +167,21 @@ public class AppsFragment extends Fragment implements ConnectionsListener { blocklist.unblockAppForMinutes(app.getUid(), 60); else if(id == R.id.unblock_app_8h) blocklist.unblockAppForMinutes(app.getUid(), 480); - else + else if(id == R.id.add_to_whitelist) { + whitelist.addApp(app.getUid()); + whitelistChanged = true; + } else if(id == R.id.remove_from_whitelist) { + whitelist.removeApp(app.getUid()); + whitelistChanged = true; + } else return super.onContextItemSelected(item); - blocklist.saveAndReload(); + if(whitelistChanged) { + whitelist.save(); + if (CaptureService.isServiceActive()) + CaptureService.requireInstance().reloadFirewallWhitelist(); + } else + blocklist.saveAndReload(); // refresh the item mAdapter.notifyItemChanged(app); diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java index 17398bb1..ced1d302 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java +++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/ConnectionsFragment.java @@ -24,6 +24,7 @@ import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -47,6 +48,7 @@ import androidx.appcompat.widget.SearchView; import androidx.core.view.MenuProvider; import androidx.fragment.app.Fragment; import androidx.lifecycle.Lifecycle; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.RecyclerView; @@ -67,6 +69,7 @@ import com.emanuelef.remote_capture.adapters.ConnectionsAdapter; import com.emanuelef.remote_capture.model.FilterDescriptor; import com.emanuelef.remote_capture.model.MatchList; import com.emanuelef.remote_capture.model.MatchList.RuleType; +import com.emanuelef.remote_capture.model.Prefs; import com.emanuelef.remote_capture.views.EmptyRecyclerView; import com.emanuelef.remote_capture.interfaces.ConnectionsListener; import com.emanuelef.remote_capture.activities.EditFilterActivity; @@ -295,12 +298,15 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener MenuItem item; Billing billing = Billing.newInstance(ctx); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); boolean firewallVisible = billing.isFirewallVisible(); + boolean whitelistMode = Prefs.isFirewallWhitelistMode(prefs); boolean showPurchaseFirewall = (!billing.isPurchased(Billing.FIREWALL_SKU) && billing.isAvailable(Billing.FIREWALL_SKU)) && !CaptureService.isCapturingAsRoot(); boolean blockVisible = false; boolean unblockVisible = false; Blocklist blocklist = PCAPdroid.getInstance().getBlocklist(); + MatchList fwWhitelist = PCAPdroid.getInstance().getFirewallWhitelist(); if(app != null) { boolean appBlocked = blocklist.matchesApp(app.getUid()); @@ -333,6 +339,12 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener item.setTitle(label); item.setVisible(true); } + + if(firewallVisible && whitelistMode) { + boolean whitelisted = fwWhitelist.matchesApp(app.getUid()); + menu.findItem(R.id.add_to_whitelist).setVisible(!whitelisted); + menu.findItem(R.id.remove_from_whitelist).setVisible(whitelisted); + } } if((conn.info != null) && (!conn.info.isEmpty())) { @@ -442,11 +454,13 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener Context ctx = requireContext(); ConnectionDescriptor conn = mAdapter.getSelectedItem(); MatchList whitelist = PCAPdroid.getInstance().getMalwareWhitelist(); + MatchList fwWhitelist = PCAPdroid.getInstance().getFirewallWhitelist(); Blocklist blocklist = PCAPdroid.getInstance().getBlocklist(); boolean firewallPurchased = Billing.newInstance(ctx).isPurchased(Billing.FIREWALL_SKU); boolean mask_changed = false; boolean whitelist_changed = false; boolean blocklist_changed = false; + boolean firewall_wl_changed = false; if(conn == null) return super.onContextItemSelected(item); @@ -531,6 +545,12 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener } else if(id == R.id.unblock_domain) { blocklist.removeHost(Utils.getSecondLevelDomain(conn.info)); blocklist_changed = true; + } else if(id == R.id.add_to_whitelist) { + fwWhitelist.addApp(conn.uid); + firewall_wl_changed = true; + } else if(id == R.id.remove_from_whitelist) { + fwWhitelist.removeApp(conn.uid); + firewall_wl_changed = true; } else if(id == R.id.open_app_details) { Intent intent = new Intent(requireContext(), AppDetailsActivity.class); intent.putExtra(AppDetailsActivity.APP_UID_EXTRA, conn.uid); @@ -555,6 +575,10 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener } else if(whitelist_changed) { whitelist.save(); CaptureService.reloadMalwareWhitelist(); + } else if(firewall_wl_changed) { + fwWhitelist.save(); + if(CaptureService.isServiceActive()) + CaptureService.requireInstance().reloadFirewallWhitelist(); } else if(blocklist_changed) blocklist.saveAndReload(); diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/EditListFragment.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/EditListFragment.java index 517b4c60..88b955eb 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/fragments/EditListFragment.java +++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/EditListFragment.java @@ -254,6 +254,9 @@ public class EditListFragment extends Fragment implements MatchList.ListChangeLi else if(mListInfo.getType() == ListInfo.Type.BLOCKLIST) { if(CaptureService.isServiceActive()) CaptureService.requireInstance().reloadBlocklist(); + } else if(mListInfo.getType() == ListInfo.Type.FIREWALL_WHITELIST) { + if(CaptureService.isServiceActive()) + CaptureService.requireInstance().reloadFirewallWhitelist(); } } diff --git a/app/src/main/java/com/emanuelef/remote_capture/fragments/FirewallStatus.java b/app/src/main/java/com/emanuelef/remote_capture/fragments/FirewallStatus.java index ebbc977c..55f4e5a9 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/fragments/FirewallStatus.java +++ b/app/src/main/java/com/emanuelef/remote_capture/fragments/FirewallStatus.java @@ -37,6 +37,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SwitchCompat; import androidx.core.content.ContextCompat; import androidx.core.view.MenuProvider; @@ -51,16 +52,20 @@ import com.emanuelef.remote_capture.PCAPdroid; import com.emanuelef.remote_capture.R; import com.emanuelef.remote_capture.Utils; import com.emanuelef.remote_capture.activities.ConnectionsActivity; +import com.emanuelef.remote_capture.activities.FirewallActivity; import com.emanuelef.remote_capture.activities.MainActivity; import com.emanuelef.remote_capture.model.Blocklist; import com.emanuelef.remote_capture.model.ConnectionDescriptor; import com.emanuelef.remote_capture.model.FilterDescriptor; +import com.emanuelef.remote_capture.model.MatchList; import com.emanuelef.remote_capture.model.Prefs; public class FirewallStatus extends Fragment implements MenuProvider { private static final String TAG = "FirewallStatus"; + private static boolean whitelistWarningAck = false; private Handler mHandler; private SharedPreferences mPrefs; + private Menu mMenu; private SwitchCompat mToggle; private ImageView mStatusIcon; private TextView mStatus; @@ -69,6 +74,7 @@ public class FirewallStatus extends Fragment implements MenuProvider { private TextView mNumRules; private TextView mLastBlock; private Blocklist mBlocklist; + private MatchList mWhitelist; private int mOkColor, mWarnColor, mGrayColor; @Override @@ -84,6 +90,7 @@ public class FirewallStatus extends Fragment implements MenuProvider { mPrefs = PreferenceManager.getDefaultSharedPreferences(ctx); mBlocklist = PCAPdroid.getInstance().getBlocklist(); + mWhitelist = PCAPdroid.getInstance().getFirewallWhitelist(); mStatus = view.findViewById(R.id.status); mHandler = new Handler(Looper.getMainLooper()); mStatusIcon = view.findViewById(R.id.status_icon); @@ -121,6 +128,7 @@ public class FirewallStatus extends Fragment implements MenuProvider { @Override public void onCreateMenu(@NonNull Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.firewall_menu, menu); + mMenu = menu; mToggle = (SwitchCompat) menu.findItem(R.id.toggle_btn).getActionView(); mToggle.setOnCheckedChangeListener((buttonView, isChecked) -> { @@ -135,9 +143,9 @@ public class FirewallStatus extends Fragment implements MenuProvider { updateStatus(); }); + menu.findItem(R.id.whitelist_mode).setChecked(Prefs.isFirewallWhitelistMode(mPrefs)); menu.findItem(R.id.block_new_apps).setChecked(Prefs.blockNewApps(mPrefs)); - - updateStatus(); + reloadMode(); } @Override @@ -153,16 +161,52 @@ public class FirewallStatus extends Fragment implements MenuProvider { item.setChecked(checked); mPrefs.edit().putBoolean(Prefs.PREF_BLOCK_NEW_APPS, checked).apply(); return true; + } else if(id == R.id.whitelist_mode) { + boolean checked = !item.isChecked(); + if(checked && !whitelistWarningAck) { + AlertDialog dialog = new AlertDialog.Builder(requireContext()) + .setTitle(R.string.whitelist_mode) + .setMessage(R.string.firewall_whitelist_notice) + .setPositiveButton(R.string.ok, (d, whichButton) -> { + whitelistWarningAck = true; + item.setChecked(true); + mPrefs.edit().putBoolean(Prefs.PREF_FIREWALL_WHITELIST_MODE, true).apply(); + reloadMode(); + }) + .setNegativeButton(R.string.cancel_action, (d, whichButton) -> {}) + .show(); + + dialog.setCanceledOnTouchOutside(false); + } else { + item.setChecked(checked); + mPrefs.edit().putBoolean(Prefs.PREF_FIREWALL_WHITELIST_MODE, checked).apply(); + reloadMode(); + } + return true; } return false; } + private void reloadMode() { + boolean whitelist_mode = Prefs.isFirewallWhitelistMode(mPrefs); + + if(mMenu != null) + mMenu.findItem(R.id.block_new_apps).setVisible(!whitelist_mode); + + if(CaptureService.isServiceActive()) + CaptureService.requireInstance().reloadFirewallWhitelist(); + + ((FirewallActivity)requireActivity()).recheckTabs(); + updateStatus(); + } + private void updateStatus() { Context ctx = requireContext(); ConnectionsRegister reg = CaptureService.getConnsRegister(); boolean is_running = CaptureService.isServiceActive(); boolean is_enabled = Prefs.isFirewallEnabled(ctx, mPrefs); + boolean whitelist_mode = Prefs.isFirewallWhitelistMode(mPrefs); if(!is_running) { mStatusIcon.setImageResource(R.drawable.ic_shield); @@ -190,7 +234,7 @@ public class FirewallStatus extends Fragment implements MenuProvider { mNumBlocked.setText(Utils.formatIntShort(((reg != null) ? reg.getNumBlockedConnections() : 0))); mNumChecked.setText(Utils.formatIntShort(CaptureService.getNumCheckedFirewallConnections())); mLastBlock.setText(Utils.formatEpochMin(ctx, ((reg != null) ? reg.getLastFirewallBlock() / 1000 : 0))); - mNumRules.setText(Utils.formatIntShort(mBlocklist.getSize())); + mNumRules.setText(Utils.formatIntShort(mBlocklist.getSize() + (whitelist_mode ? mWhitelist.getSize() : 0))); // Periodic update mHandler.postDelayed(this::updateStatus, 1000); diff --git a/app/src/main/java/com/emanuelef/remote_capture/model/ListInfo.java b/app/src/main/java/com/emanuelef/remote_capture/model/ListInfo.java index cb6f7d60..fb09a999 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/model/ListInfo.java +++ b/app/src/main/java/com/emanuelef/remote_capture/model/ListInfo.java @@ -33,6 +33,7 @@ public class ListInfo { VISUALIZATION_MASK, MALWARE_WHITELIST, BLOCKLIST, + FIREWALL_WHITELIST, } public ListInfo(Type tp) { @@ -51,6 +52,8 @@ public class ListInfo { return PCAPdroid.getInstance().getMalwareWhitelist(); case BLOCKLIST: return PCAPdroid.getInstance().getBlocklist(); + case FIREWALL_WHITELIST: + return PCAPdroid.getInstance().getFirewallWhitelist(); } assert false; @@ -65,6 +68,8 @@ public class ListInfo { return R.string.malware_whitelist_rules; case BLOCKLIST: return R.string.firewall_rules; + case FIREWALL_WHITELIST: + return R.string.whitelist; } assert false; @@ -79,6 +84,8 @@ public class ListInfo { return R.string.malware_whitelist_help; case BLOCKLIST: return 0; + case FIREWALL_WHITELIST: + return R.string.firewall_whitelist_help; } assert false; diff --git a/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java b/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java index 071bb543..10905b17 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java +++ b/app/src/main/java/com/emanuelef/remote_capture/model/Prefs.java @@ -46,6 +46,9 @@ public class Prefs { public static final String PAYLOAD_MODE_FULL = "full"; public static final String DEFAULT_PAYLOAD_MODE = PAYLOAD_MODE_MINIMAL; + // used to initialize the whitelist with some safe defaults + public static final int FIREWALL_WHITELIST_INIT_VER = 1; + public static final String PREF_COLLECTOR_IP_KEY = "collector_ip_address"; public static final String PREF_COLLECTOR_PORT_KEY = "collector_port"; public static final String PREF_SOCKS5_PROXY_IP_KEY = "socks5_proxy_ip_address"; @@ -66,6 +69,9 @@ public class Prefs { public static final String PREF_MALWARE_WHITELIST = "malware_whitelist"; public static final String PREF_PCAPDROID_TRAILER = "pcapdroid_trailer"; public static final String PREF_BLOCKLIST = "bl"; + public static final String PREF_FIREWALL_WHITELIST_MODE = "firewall_wl_mode"; + public static final String PREF_FIREWALL_WHITELIST_INIT_VER = "firewall_wl_init"; + public static final String PREF_FIREWALL_WHITELIST = "firewall_whitelist"; public static final String PREF_START_AT_BOOT = "start_at_boot"; public static final String PREF_SNAPLEN = "snaplen"; public static final String PREF_MAX_PKTS_PER_FLOW = "max_pkts_per_flow"; @@ -140,6 +146,10 @@ public class Prefs { p.edit().putBoolean(PREF_LOCKDOWN_VPN_NOTICE_SHOWN, true).apply(); } + public static void setFirewallWhitelistInitialized(SharedPreferences p) { + p.edit().putInt(PREF_FIREWALL_WHITELIST_INIT_VER, FIREWALL_WHITELIST_INIT_VER).apply(); + } + /* Prefs with defaults */ public static String getCollectorIp(SharedPreferences p) { return(p.getString(PREF_COLLECTOR_IP_KEY, "127.0.0.1")); } public static int getCollectorPort(SharedPreferences p) { return(Integer.parseInt(p.getString(PREF_COLLECTOR_PORT_KEY, "1234"))); } @@ -172,9 +182,10 @@ public class Prefs { public static boolean isPrivateDnsBlockingEnabled(SharedPreferences p) { return(p.getBoolean(PREF_AUTO_BLOCK_PRIVATE_DNS, true)); } public static boolean lockdownVpnNoticeShown(SharedPreferences p) { return(p.getBoolean(PREF_LOCKDOWN_VPN_NOTICE_SHOWN, false)); } public static boolean blockNewApps(SharedPreferences p) { return(p.getBoolean(PREF_BLOCK_NEW_APPS, false)); } + public static boolean isFirewallWhitelistMode(SharedPreferences p) { return(p.getBoolean(PREF_FIREWALL_WHITELIST_MODE, false)); } + public static boolean isFirewallWhitelistInitialized(SharedPreferences p) { return(p.getInt(PREF_FIREWALL_WHITELIST_INIT_VER, 0) == FIREWALL_WHITELIST_INIT_VER); } public static String getMitmproxyOpts(SharedPreferences p) { return(p.getString(PREF_MITMPROXY_OPTS, "")); } - public static String asString(Context ctx) { SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(ctx); diff --git a/app/src/main/jni/core/jni_impl.c b/app/src/main/jni/core/jni_impl.c index 8adb24e9..ae930f54 100644 --- a/app/src/main/jni/core/jni_impl.c +++ b/app/src/main/jni/core/jni_impl.c @@ -779,6 +779,53 @@ Java_com_emanuelef_remote_1capture_CaptureService_reloadBlocklist(JNIEnv *env, j /* ******************************************************* */ +JNIEXPORT jboolean JNICALL +Java_com_emanuelef_remote_1capture_CaptureService_reloadFirewallWhitelist(JNIEnv *env, jclass clazz, + jobject whitelist) { + pcapdroid_t *pd = global_pd; + if(!pd) { + log_e("NULL pd instance"); + return false; + } + + if(pd->root_capture) { + log_e("firewall in root mode not implemented"); + return false; + } + + if(pd->firewall.new_wl != NULL) { + log_e("previous whitelist not loaded yet"); + return false; + } + + if(whitelist == NULL) { + pd->firewall.wl_enabled = false; + log_d("firewall whitelist is disabled"); + return true; + } + + blacklist_t *wl = blacklist_init(); + if(!wl) { + log_e("blacklist_init failed"); + return false; + } + + if(blacklist_load_list_descriptor(wl, env, whitelist) < 0) { + blacklist_destroy(wl); + return false; + } + + blacklists_stats_t stats; + blacklist_get_stats(wl, &stats); + log_d("reloadFirewallWhitelist: %d apps, %d domains, %d IPs", stats.num_apps, stats.num_domains, stats.num_ips); + + pd->firewall.new_wl = wl; + pd->firewall.wl_enabled = true; + return true; +} + +/* ******************************************************* */ + JNIEXPORT jboolean JNICALL Java_com_emanuelef_remote_1capture_CaptureService_reloadMalwareWhitelist(JNIEnv *env, jclass clazz, jobject whitelist) { diff --git a/app/src/main/jni/core/pcapdroid.c b/app/src/main/jni/core/pcapdroid.c index 228da8df..1666c148 100644 --- a/app/src/main/jni/core/pcapdroid.c +++ b/app/src/main/jni/core/pcapdroid.c @@ -374,8 +374,9 @@ pd_conn_t* pd_new_connection(pcapdroid_t *pd, const zdtun_5tuple_t *tuple, int u conn->data->uid = data->uid; - if(!conn->data->to_block && pd->firewall.enabled - && blacklist_match_uid(pd->firewall.bl, conn->data->uid)) + if(!conn->data->to_block && pd->firewall.enabled && ( + blacklist_match_uid(pd->firewall.bl, conn->data->uid) || + (pd->firewall.wl_enabled && pd->firewall.wl && !blacklist_match_uid(pd->firewall.wl, conn->data->uid)))) conn->data->netd_block_missed = true; zdtun_5tuple2str(&conn->tuple, buf, sizeof(buf)); @@ -439,6 +440,12 @@ pd_conn_t* pd_new_connection(pcapdroid_t *pd, const zdtun_5tuple_t *tuple, int u fw_num_checked_connections++; } + if(pd->firewall.enabled && pd->firewall.wl_enabled && pd->firewall.wl && !data->to_block && data->uid != UID_NETD) { + // whitelist mode: block any app unless it's explicitly whitelisted. + // The blocklist still has priority to determine if a connection should be blocked. + data->to_block = !blacklist_match_uid(pd->firewall.wl, data->uid); + } + return(data); } @@ -887,6 +894,8 @@ static int check_blocked_conn_cb(pcapdroid_t *pd, const zdtun_5tuple_t *tuple, p blacklist_match_ip(fw_bl, &dst_ip, tuple->ipver) || (data->info && data->info[0] && blacklist_match_domain(fw_bl, data->info)); } + if(pd->firewall.enabled && pd->firewall.wl_enabled && pd->firewall.wl && !data->to_block && data->uid != UID_NETD) + data->to_block = !blacklist_match_uid(pd->firewall.wl, data->uid); if(old_block != data->to_block) { data->update_type |= CONN_UPDATE_STATS; @@ -1030,6 +1039,13 @@ void pd_housekeeping(pcapdroid_t *pd) { pd->firewall.bl = pd->firewall.new_bl; pd->firewall.new_bl = NULL; iter_active_connections(pd, check_blocked_conn_cb); + } else if(pd->firewall.new_wl) { + // Load new whitelist + if(pd->firewall.wl) + blacklist_destroy(pd->firewall.wl); + pd->firewall.wl = pd->firewall.new_wl; + pd->firewall.new_wl = NULL; + iter_active_connections(pd, check_blocked_conn_cb); } } @@ -1213,6 +1229,10 @@ int pd_run(pcapdroid_t *pd) { blacklist_destroy(pd->firewall.bl); if(pd->firewall.new_bl) blacklist_destroy(pd->firewall.new_bl); + if(pd->firewall.wl) + blacklist_destroy(pd->firewall.wl); + if(pd->firewall.new_wl) + blacklist_destroy(pd->firewall.new_wl); if(pd->malware_detection.enabled) { if(pd->malware_detection.reload_in_progress) { diff --git a/app/src/main/jni/core/pcapdroid.h b/app/src/main/jni/core/pcapdroid.h index 789ab55c..e247893a 100644 --- a/app/src/main/jni/core/pcapdroid.h +++ b/app/src/main/jni/core/pcapdroid.h @@ -263,8 +263,11 @@ typedef struct pcapdroid { struct { bool enabled; - blacklist_t *bl; + blacklist_t *bl; // blocklist blacklist_t *new_bl; + bool wl_enabled; + blacklist_t *wl; // whitelist + blacklist_t *new_wl; } firewall; } pcapdroid_t; diff --git a/app/src/main/res/layout/app_item.xml b/app/src/main/res/layout/app_item.xml index 4588123d..98e71627 100644 --- a/app/src/main/res/layout/app_item.xml +++ b/app/src/main/res/layout/app_item.xml @@ -1,6 +1,7 @@ + + + + + + + + + Additional mitmproxy options Provide additional options for mitmproxy Error while starting the mitm addon. Check the log for details + Whitelist mode + In this mode all the connections will be blocked, unless manually whitelisted. You may miss push notifications without a proper whitelist + These rules specify which connections to allow. Blocklist rules have priority over whitelist rules + Add to whitelist + Remove from whitelist