Add ability to whitelist malicious connections

Needed for #105
This commit is contained in:
emanuele-f 2021-10-21 14:49:23 +02:00
parent c20b56a4ac
commit bd18c5e195
21 changed files with 347 additions and 111 deletions

View File

@ -73,7 +73,7 @@
android:launchMode="singleTop" android:launchMode="singleTop"
android:parentActivityName=".activities.MainActivity" /> android:parentActivityName=".activities.MainActivity" />
<activity <activity
android:name=".activities.EditMaskActivity" android:name=".activities.EditListActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:parentActivityName=".activities.EditFilterActivity" /> android:parentActivityName=".activities.EditFilterActivity" />
<activity <activity

View File

@ -412,10 +412,10 @@ public class CaptureService extends VpnService implements Runnable {
intent, Utils.getIntentFlags(PendingIntent.FLAG_UPDATE_CURRENT)); intent, Utils.getIntentFlags(PendingIntent.FLAG_UPDATE_CURRENT));
String rule_label; String rule_label;
if(conn.blacklisted_domain) if(conn.isBlacklistedHost())
rule_label = MatchList.getLabel(this, MatchList.RuleType.HOST, conn.info); rule_label = MatchList.getRuleLabel(this, MatchList.RuleType.HOST, conn.info);
else else
rule_label = MatchList.getLabel(this, MatchList.RuleType.IP, conn.dst_ip); rule_label = MatchList.getRuleLabel(this, MatchList.RuleType.IP, conn.dst_ip);
mBlacklistedBuilder mBlacklistedBuilder
.setContentIntent(pi) .setContentIntent(pi)

View File

@ -27,6 +27,7 @@ import com.emanuelef.remote_capture.interfaces.ConnectionsListener;
import com.emanuelef.remote_capture.model.AppStats; import com.emanuelef.remote_capture.model.AppStats;
import com.emanuelef.remote_capture.model.ConnectionDescriptor; import com.emanuelef.remote_capture.model.ConnectionDescriptor;
import com.emanuelef.remote_capture.model.ConnectionUpdate; import com.emanuelef.remote_capture.model.ConnectionUpdate;
import com.emanuelef.remote_capture.model.MatchList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -46,6 +47,7 @@ public class ConnectionsRegister {
private int mUntrackedItems; private int mUntrackedItems;
private final Map<Integer, AppStats> mAppsStats; private final Map<Integer, AppStats> mAppsStats;
private final ArrayList<ConnectionsListener> mListeners; private final ArrayList<ConnectionsListener> mListeners;
private final MatchList mWhitelist;
public ConnectionsRegister(int _size) { public ConnectionsRegister(int _size) {
mTail = 0; mTail = 0;
@ -55,6 +57,7 @@ public class ConnectionsRegister {
mItemsRing = new ConnectionDescriptor[mSize]; mItemsRing = new ConnectionDescriptor[mSize];
mListeners = new ArrayList<>(); mListeners = new ArrayList<>();
mAppsStats = new HashMap<>(); // uid -> AppStats mAppsStats = new HashMap<>(); // uid -> AppStats
mWhitelist = PCAPdroid.getInstance().getMalwareWhitelist();
} }
private int firstPos() { private int firstPos() {
@ -124,6 +127,7 @@ public class ConnectionsRegister {
mAppsStats.put(uid, stats); mAppsStats.put(uid, stats);
} }
conn.updateWhitelist(mWhitelist);
processConnectionStatus(conn); processConnectionStatus(conn);
stats.num_connections++; stats.num_connections++;
@ -165,7 +169,10 @@ public class ConnectionsRegister {
stats.bytes += bytes_delta; stats.bytes += bytes_delta;
//Log.d(TAG, "update " + update.incr_id + " -> " + update.update_type); //Log.d(TAG, "update " + update.incr_id + " -> " + update.update_type);
boolean host_changed = (update.info != null) && (!update.info.equals(conn.info));
conn.processUpdate(update); conn.processUpdate(update);
if(host_changed)
conn.updateWhitelist(mWhitelist);
processConnectionStatus(conn); processConnectionStatus(conn);
changed_pos[k++] = (pos + mSize - first_pos) % mSize; changed_pos[k++] = (pos + mSize - first_pos) % mSize;
@ -196,6 +203,34 @@ public class ConnectionsRegister {
listener.connectionsChanges(mNumItems); listener.connectionsChanges(mNumItems);
} }
public synchronized void refreshConnectionsWhitelist() {
ArrayList<Integer>changed_pos = new ArrayList<>();
for(int i = 0; i< mNumItems; i++) {
ConnectionDescriptor conn = mItemsRing[i];
if(conn != null) {
boolean was_blacklisted = conn.isBlacklisted();
conn.updateWhitelist(mWhitelist);
if (conn.isBlacklisted() != was_blacklisted)
changed_pos.add(i);
}
}
// Notify listeners
if(changed_pos.size() > 0) {
int[] changed = new int[changed_pos.size()];
int i = 0;
for(Integer item: changed_pos)
changed[i++] = item;
for(ConnectionsListener listener: mListeners)
listener.connectionsUpdated(changed);
}
}
public synchronized void addListener(ConnectionsListener listener) { public synchronized void addListener(ConnectionsListener listener) {
mListeners.add(listener); mListeners.add(listener);

View File

@ -34,6 +34,7 @@ import java.lang.ref.WeakReference;
public class PCAPdroid extends Application { public class PCAPdroid extends Application {
private MatchList mVisMask; private MatchList mVisMask;
private MatchList mMalwareWhitelist;
private BlacklistsStatus mBlacklistsStatus; private BlacklistsStatus mBlacklistsStatus;
private Context mLocalizedContext; private Context mLocalizedContext;
private static WeakReference<PCAPdroid> mInstance; private static WeakReference<PCAPdroid> mInstance;
@ -71,6 +72,10 @@ public class PCAPdroid extends Application {
return mInstance.get(); return mInstance.get();
} }
public Billing getBilling(Context ctx) {
return new Billing(ctx);
}
public MatchList getVisualizationMask() { public MatchList getVisualizationMask() {
if(mVisMask == null) if(mVisMask == null)
mVisMask = new MatchList(this, Prefs.PREF_VISUALIZATION_MASK); mVisMask = new MatchList(this, Prefs.PREF_VISUALIZATION_MASK);
@ -85,7 +90,10 @@ public class PCAPdroid extends Application {
return mBlacklistsStatus; return mBlacklistsStatus;
} }
public Billing getBilling(Context ctx) { public MatchList getMalwareWhitelist() {
return new Billing(ctx); if(mMalwareWhitelist == null)
mMalwareWhitelist = new MatchList(this, Prefs.PREF_MALWARE_WHITELIST);
return mMalwareWhitelist;
} }
} }

View File

@ -32,6 +32,7 @@ import com.emanuelef.remote_capture.PCAPdroid;
import com.emanuelef.remote_capture.R; import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.model.ConnectionDescriptor.Status; import com.emanuelef.remote_capture.model.ConnectionDescriptor.Status;
import com.emanuelef.remote_capture.model.FilterDescriptor; import com.emanuelef.remote_capture.model.FilterDescriptor;
import com.emanuelef.remote_capture.model.ListInfo;
import com.emanuelef.remote_capture.model.MatchList; import com.emanuelef.remote_capture.model.MatchList;
import com.google.android.material.chip.Chip; import com.google.android.material.chip.Chip;
@ -75,7 +76,8 @@ public class EditFilterActivity extends BaseActivity {
mStatusError = findViewById(R.id.status_error); mStatusError = findViewById(R.id.status_error);
findViewById(R.id.edit_mask).setOnClickListener(v -> { findViewById(R.id.edit_mask).setOnClickListener(v -> {
Intent editIntent = new Intent(this, EditMaskActivity.class); Intent editIntent = new Intent(this, EditListActivity.class);
editIntent.putExtra(EditListActivity.LIST_TYPE_EXTRA, ListInfo.Type.VISUALIZATION_MASK);
startActivity(editIntent); startActivity(editIntent);
}); });

View File

@ -30,24 +30,38 @@ import androidx.annotation.NonNull;
import com.emanuelef.remote_capture.R; import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.Utils; import com.emanuelef.remote_capture.Utils;
import com.emanuelef.remote_capture.adapters.MaskEditAdapter; import com.emanuelef.remote_capture.adapters.ListEditAdapter;
import com.emanuelef.remote_capture.fragments.EditMaskFragment; import com.emanuelef.remote_capture.fragments.EditListFragment;
import com.emanuelef.remote_capture.model.ListInfo;
import com.emanuelef.remote_capture.model.MatchList;
public class EditMaskActivity extends BaseActivity { /* An activity to edit a MatchList, specified via LIST_INFO_EXTRA */
private static final String TAG = "MaskEditActivity"; public class EditListActivity extends BaseActivity {
private static final String TAG = "EditListActivity";
public static final String LIST_TYPE_EXTRA = "list_type";
private ListInfo mListInfo;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setTitle(R.string.edit_rules); assert(getIntent() != null);
setContentView(R.layout.edit_mask_activity); ListInfo.Type ltype = (ListInfo.Type) getIntent().getSerializableExtra(LIST_TYPE_EXTRA);
assert(ltype != null);
mListInfo = new ListInfo(ltype);
setTitle(mListInfo.getTitle());
setContentView(R.layout.edit_list_activity);
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.replace(R.id.mask_fragment, new EditMaskFragment()) .replace(R.id.fragment, new EditListFragment())
.commit(); .commit();
} }
public MatchList getList() {
return mListInfo.getList();
}
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater(); MenuInflater inflater = getMenuInflater();
@ -65,15 +79,15 @@ public class EditMaskActivity extends BaseActivity {
return false; return false;
if(id == R.id.copy_to_clipboard) { if(id == R.id.copy_to_clipboard) {
String contents = Utils.adapter2Text((MaskEditAdapter)lv.getAdapter()); String contents = Utils.adapter2Text((ListEditAdapter)lv.getAdapter());
Utils.copyToClipboard(this, contents); Utils.copyToClipboard(this, contents);
return true; return true;
} else if(id == R.id.share) { } else if(id == R.id.share) {
String contents = Utils.adapter2Text((MaskEditAdapter)lv.getAdapter()); String contents = Utils.adapter2Text((ListEditAdapter)lv.getAdapter());
Intent intent = new Intent(android.content.Intent.ACTION_SEND); Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("text/plain"); intent.setType("text/plain");
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, getString(R.string.hidden_connections_rules)); intent.putExtra(android.content.Intent.EXTRA_SUBJECT, getString(mListInfo.getShareSubject()));
intent.putExtra(android.content.Intent.EXTRA_TEXT, contents); intent.putExtra(android.content.Intent.EXTRA_TEXT, contents);
startActivity(Intent.createChooser(intent, getResources().getString(R.string.share))); startActivity(Intent.createChooser(intent, getResources().getString(R.string.share)));

View File

@ -67,6 +67,7 @@ import com.emanuelef.remote_capture.interfaces.AppStateListener;
import com.emanuelef.remote_capture.model.AppState; import com.emanuelef.remote_capture.model.AppState;
import com.emanuelef.remote_capture.CaptureService; import com.emanuelef.remote_capture.CaptureService;
import com.emanuelef.remote_capture.model.CaptureSettings; import com.emanuelef.remote_capture.model.CaptureSettings;
import com.emanuelef.remote_capture.model.ListInfo;
import com.emanuelef.remote_capture.model.Prefs; import com.emanuelef.remote_capture.model.Prefs;
import com.emanuelef.remote_capture.R; import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.Utils; import com.emanuelef.remote_capture.Utils;
@ -193,6 +194,7 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
Menu navMenu = mNavView.getMenu(); Menu navMenu = mNavView.getMenu();
navMenu.findItem(R.id.open_root_log).setVisible(Prefs.isRootCaptureEnabled(mPrefs)); navMenu.findItem(R.id.open_root_log).setVisible(Prefs.isRootCaptureEnabled(mPrefs));
navMenu.findItem(R.id.edit_malware_whitelist).setVisible(Prefs.isMalwareDetectionEnabled(this, mPrefs));
} }
private void setupNavigationDrawer() { private void setupNavigationDrawer() {
@ -347,6 +349,10 @@ public class MainActivity extends BaseActivity implements NavigationView.OnNavig
startActivity(intent); startActivity(intent);
} else } else
Utils.showToast(this, R.string.capture_not_started); Utils.showToast(this, R.string.capture_not_started);
} else if(id == R.id.edit_malware_whitelist) {
Intent intent = new Intent(MainActivity.this, EditListActivity.class);
intent.putExtra(EditListActivity.LIST_TYPE_EXTRA, ListInfo.Type.MALWARE_WHITELIST);
startActivity(intent);
} else if(id == R.id.open_root_log) { } else if(id == R.id.open_root_log) {
Intent intent = new Intent(MainActivity.this, LogviewActivity.class); Intent intent = new Intent(MainActivity.this, LogviewActivity.class);
startActivity(intent); startActivity(intent);

View File

@ -35,10 +35,10 @@ import com.emanuelef.remote_capture.model.MatchList;
import java.util.Iterator; import java.util.Iterator;
public class MaskEditAdapter extends ArrayAdapter<MatchList.Rule> implements TextAdapter { public class ListEditAdapter extends ArrayAdapter<MatchList.Rule> implements TextAdapter {
private final LayoutInflater mLayoutInflater; private final LayoutInflater mLayoutInflater;
public MaskEditAdapter(Context context, Iterator<MatchList.Rule> items) { public ListEditAdapter(Context context, Iterator<MatchList.Rule> items) {
super(context, R.layout.rule_item); super(context, R.layout.rule_item);
mLayoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mLayoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

View File

@ -55,6 +55,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.emanuelef.remote_capture.AppsResolver; import com.emanuelef.remote_capture.AppsResolver;
import com.emanuelef.remote_capture.CaptureService; import com.emanuelef.remote_capture.CaptureService;
import com.emanuelef.remote_capture.ConnectionsRegister; import com.emanuelef.remote_capture.ConnectionsRegister;
import com.emanuelef.remote_capture.PCAPdroid;
import com.emanuelef.remote_capture.R; import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.Utils; import com.emanuelef.remote_capture.Utils;
import com.emanuelef.remote_capture.activities.AppDetailsActivity; import com.emanuelef.remote_capture.activities.AppDetailsActivity;
@ -308,18 +309,24 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
if(app != null) { if(app != null) {
MenuItem item = menu.findItem(R.id.hide_app); MenuItem item = menu.findItem(R.id.hide_app);
String label = MatchList.getLabel(ctx, RuleType.APP, app.getName()); String label = MatchList.getRuleLabel(ctx, RuleType.APP, Integer.toString(app.getUid()));
item.setTitle(label); item.setTitle(label);
item.setVisible(true); item.setVisible(true);
item = menu.findItem(R.id.search_app); item = menu.findItem(R.id.search_app);
item.setTitle(label); item.setTitle(label);
item.setVisible(true); item.setVisible(true);
if(conn.isBlacklisted()) {
item = menu.findItem(R.id.whitelist_app);
item.setTitle(label);
item.setVisible(true);
}
} }
if((conn.info != null) && (!conn.info.isEmpty())) { if((conn.info != null) && (!conn.info.isEmpty())) {
MenuItem item = menu.findItem(R.id.hide_host); MenuItem item = menu.findItem(R.id.hide_host);
String label = MatchList.getLabel(ctx, RuleType.HOST, conn.info); String label = MatchList.getRuleLabel(ctx, RuleType.HOST, conn.info);
item.setTitle(label); item.setTitle(label);
item.setVisible(true); item.setVisible(true);
@ -331,18 +338,29 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
if(!rootDomain.equals(conn.info)) { if(!rootDomain.equals(conn.info)) {
item = menu.findItem(R.id.hide_root_domain); item = menu.findItem(R.id.hide_root_domain);
item.setTitle(MatchList.getLabel(ctx, RuleType.ROOT_DOMAIN, rootDomain)); item.setTitle(MatchList.getRuleLabel(ctx, RuleType.ROOT_DOMAIN, rootDomain));
item.setVisible(true);
}
if(conn.isBlacklistedHost()) {
item = menu.findItem(R.id.whitelist_host);
item.setTitle(label);
item.setVisible(true); item.setVisible(true);
} }
} }
String label = MatchList.getLabel(ctx, RuleType.IP, conn.dst_ip); String label = MatchList.getRuleLabel(ctx, RuleType.IP, conn.dst_ip);
menu.findItem(R.id.hide_ip).setTitle(label); menu.findItem(R.id.hide_ip).setTitle(label);
menu.findItem(R.id.search_ip).setTitle(label); menu.findItem(R.id.search_ip).setTitle(label);
if(conn.isBlacklistedIp())
menu.findItem(R.id.whitelist_ip).setTitle(label);
label = MatchList.getLabel(ctx, RuleType.PROTOCOL, conn.l7proto); label = MatchList.getRuleLabel(ctx, RuleType.PROTOCOL, conn.l7proto);
menu.findItem(R.id.hide_proto).setTitle(label); menu.findItem(R.id.hide_proto).setTitle(label);
menu.findItem(R.id.search_proto).setTitle(label); menu.findItem(R.id.search_proto).setTitle(label);
if(!conn.isBlacklisted())
menu.findItem(R.id.whitelist_menu).setVisible(false);
} }
private void setQuery(String query) { private void setQuery(String query) {
@ -358,50 +376,73 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
}); });
} }
private void recheckBlacklistedConnections() {
ConnectionsRegister reg = CaptureService.getConnsRegister();
if(reg != null)
reg.refreshConnectionsWhitelist();
}
@Override @Override
public boolean onContextItemSelected(@NonNull MenuItem item) { public boolean onContextItemSelected(@NonNull MenuItem item) {
ConnectionDescriptor conn = mAdapter.getClickedItem(); ConnectionDescriptor conn = mAdapter.getClickedItem();
MatchList whitelist = PCAPdroid.getInstance().getMalwareWhitelist();
boolean mask_changed = false;
boolean whitelist_changed = false;
if(conn == null) if(conn == null)
return super.onContextItemSelected(item); return super.onContextItemSelected(item);
int id = item.getItemId(); int id = item.getItemId();
String label = item.getTitle().toString();
if(id == R.id.hide_app) if(id == R.id.hide_app) {
mAdapter.mMask.addApp(conn.uid, label); mAdapter.mMask.addApp(conn.uid);
else if(id == R.id.hide_host) mask_changed = true;
mAdapter.mMask.addHost(conn.info, label); } else if(id == R.id.hide_host) {
else if(id == R.id.hide_ip) mAdapter.mMask.addHost(conn.info);
mAdapter.mMask.addIp(conn.dst_ip, label); mask_changed = true;
else if(id == R.id.hide_proto) } else if(id == R.id.hide_ip) {
mAdapter.mMask.addProto(conn.l7proto, label); mAdapter.mMask.addIp(conn.dst_ip);
else if(id == R.id.hide_root_domain) mask_changed = true;
mAdapter.mMask.addRootDomain(Utils.getRootDomain(conn.info), label); } else if(id == R.id.hide_proto) {
else if(id == R.id.search_app) { mAdapter.mMask.addProto(conn.l7proto);
mask_changed = true;
} else if(id == R.id.hide_root_domain) {
mAdapter.mMask.addRootDomain(Utils.getRootDomain(conn.info));
mask_changed = true;
} else if(id == R.id.search_app)
setQuery(Objects.requireNonNull( setQuery(Objects.requireNonNull(
mApps.get(conn.uid, 0)).getPackageName()); mApps.get(conn.uid, 0)).getPackageName());
return true; else if(id == R.id.search_host)
} else if(id == R.id.search_host) {
setQuery(conn.info); setQuery(conn.info);
return true; else if(id == R.id.search_ip)
} else if(id == R.id.search_ip) {
setQuery(conn.dst_ip); setQuery(conn.dst_ip);
return true; else if(id == R.id.search_proto)
} else if(id == R.id.search_proto) {
setQuery(conn.l7proto); setQuery(conn.l7proto);
return true; else if(id == R.id.whitelist_app) {
whitelist.addApp(conn.uid);
whitelist_changed = true;
} else if(id == R.id.whitelist_ip) {
whitelist.addIp(conn.dst_ip);
whitelist_changed = true;
} else if(id == R.id.whitelist_host) {
whitelist.addHost(conn.info);
whitelist_changed = true;
} else if(id == R.id.open_app_details) { } else if(id == R.id.open_app_details) {
Intent intent = new Intent(requireContext(), AppDetailsActivity.class); Intent intent = new Intent(requireContext(), AppDetailsActivity.class);
intent.putExtra(AppDetailsActivity.APP_UID_EXTRA, conn.uid); intent.putExtra(AppDetailsActivity.APP_UID_EXTRA, conn.uid);
startActivity(intent); startActivity(intent);
return true;
} else } else
return super.onContextItemSelected(item); return super.onContextItemSelected(item);
mAdapter.mMask.save(); if(mask_changed) {
mAdapter.mFilter.showMasked = false; mAdapter.mMask.save();
refreshFilteredConnections(); mAdapter.mFilter.showMasked = false;
refreshFilteredConnections();
} else if(whitelist_changed) {
whitelist.save();
recheckBlacklistedConnections();
}
return true; return true;
} }

View File

@ -35,35 +35,35 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.emanuelef.remote_capture.PCAPdroid;
import com.emanuelef.remote_capture.R; import com.emanuelef.remote_capture.R;
import com.emanuelef.remote_capture.adapters.MaskEditAdapter; import com.emanuelef.remote_capture.activities.EditListActivity;
import com.emanuelef.remote_capture.adapters.ListEditAdapter;
import com.emanuelef.remote_capture.model.MatchList; import com.emanuelef.remote_capture.model.MatchList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
public class EditMaskFragment extends Fragment { public class EditListFragment extends Fragment {
private MaskEditAdapter mAdapter; private ListEditAdapter mAdapter;
private TextView mEmptyText; private TextView mEmptyText;
private ArrayList<MatchList.Rule> mSelected = new ArrayList<>(); private ArrayList<MatchList.Rule> mSelected = new ArrayList<>();
private MatchList mMask; private MatchList mList;
private ListView mListView; private ListView mListView;
private static final String TAG = "MaskEditFragment"; private static final String TAG = "EditListFragment";
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) { ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.edit_mask_fragment, container, false); return inflater.inflate(R.layout.edit_list_fragment, container, false);
} }
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
mListView = view.findViewById(R.id.listview); mListView = view.findViewById(R.id.listview);
mEmptyText = view.findViewById(R.id.mask_empty); mEmptyText = view.findViewById(R.id.list_empty);
mMask = PCAPdroid.getInstance().getVisualizationMask(); mList = ((EditListActivity)requireActivity()).getList();
mAdapter = new MaskEditAdapter(requireContext(), mMask.iterRules()); mAdapter = new ListEditAdapter(requireContext(), mList.iterRules());
mListView.setAdapter(mAdapter); mListView.setAdapter(mAdapter);
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
mListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() { mListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
@ -82,7 +82,7 @@ public class EditMaskFragment extends Fragment {
@Override @Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) { public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = requireActivity().getMenuInflater(); MenuInflater inflater = requireActivity().getMenuInflater();
inflater.inflate(R.menu.mask_edit_cab, menu); inflater.inflate(R.menu.list_edit_cab, menu);
return true; return true;
} }
@ -98,12 +98,12 @@ public class EditMaskFragment extends Fragment {
if(id == R.id.delete_entry) { if(id == R.id.delete_entry) {
if(mSelected.size() >= mAdapter.getCount()) { if(mSelected.size() >= mAdapter.getCount()) {
mAdapter.clear(); mAdapter.clear();
mMask.clear(); mList.clear();
mMask.save(); mList.save();
} else { } else {
for(MatchList.Rule item : mSelected) for(MatchList.Rule item : mSelected)
mAdapter.remove(item); mAdapter.remove(item);
updateMask(); updateList();
} }
mode.finish(); mode.finish();
@ -137,12 +137,11 @@ public class EditMaskFragment extends Fragment {
mEmptyText.setVisibility((mAdapter.getCount() == 0) ? View.VISIBLE : View.GONE); mEmptyText.setVisibility((mAdapter.getCount() == 0) ? View.VISIBLE : View.GONE);
} }
private void updateMask() { private void updateList() {
ArrayList<MatchList.Rule> toRemove = new ArrayList<>(); ArrayList<MatchList.Rule> toRemove = new ArrayList<>();
Iterator<MatchList.Rule> iter = mList.iterRules();
Iterator<MatchList.Rule> iter = mMask.iterRules(); // Remove the mList rules which are not in the adapter dataset
// Remove the mMask rules which are not in the adapter dataset
while(iter.hasNext()) { while(iter.hasNext()) {
MatchList.Rule rule = iter.next(); MatchList.Rule rule = iter.next();
@ -151,8 +150,8 @@ public class EditMaskFragment extends Fragment {
} }
if(toRemove.size() > 0) { if(toRemove.size() > 0) {
mMask.removeRules(toRemove); mList.removeRules(toRemove);
mMask.save(); mList.save();
} }
} }
} }

View File

@ -73,11 +73,14 @@ public class ConnectionDescriptor implements Serializable {
public int incr_id; public int incr_id;
public int status; public int status;
private int tcp_flags; private int tcp_flags;
public boolean blacklisted_ip; private boolean blacklisted_ip;
public boolean blacklisted_domain; private boolean blacklisted_host;
/* Internal */ /* Internal */
public boolean alerted = false; public boolean alerted;
private boolean whitelisted_ip;
private boolean whitelisted_host;
private boolean whitelisted_app;
public ConnectionDescriptor(int _incr_id, int _ipver, int _ipproto, String _src_ip, String _dst_ip, public ConnectionDescriptor(int _incr_id, int _ipver, int _ipproto, String _src_ip, String _dst_ip,
int _src_port, int _dst_port, int _uid, long when) { int _src_port, int _dst_port, int _uid, long when) {
@ -101,7 +104,7 @@ public class ConnectionDescriptor implements Serializable {
rcvd_pkts = update.rcvd_pkts; rcvd_pkts = update.rcvd_pkts;
status = (update.status & 0x00FF); status = (update.status & 0x00FF);
blacklisted_ip = (update.status & 0x0100) != 0; blacklisted_ip = (update.status & 0x0100) != 0;
blacklisted_domain = (update.status & 0x0200) != 0; blacklisted_host = (update.status & 0x0200) != 0;
last_seen = update.last_seen; last_seen = update.last_seen;
tcp_flags = update.tcp_flags; tcp_flags = update.tcp_flags;
} }
@ -164,8 +167,16 @@ public class ConnectionDescriptor implements Serializable {
return (tcp_flags & 0xFF); return (tcp_flags & 0xFF);
} }
public boolean isBlacklistedIp() { return !whitelisted_app && !whitelisted_ip && blacklisted_ip; }
public boolean isBlacklistedHost() { return !whitelisted_app && !whitelisted_host && blacklisted_host; }
public boolean isBlacklisted() { public boolean isBlacklisted() {
return blacklisted_ip || blacklisted_domain; return isBlacklistedIp() || isBlacklistedHost();
}
public void updateWhitelist(MatchList whitelist) {
whitelisted_app = whitelist.matchesApp(uid);
whitelisted_ip = whitelist.matchesIP(dst_ip);
whitelisted_host = whitelist.matchesHost(info);
} }
@Override @Override

View File

@ -0,0 +1,75 @@
/*
* This file is part of PCAPdroid.
*
* PCAPdroid 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.
*
* PCAPdroid 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 PCAPdroid. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2020-21 - Emanuele Faranda
*/
package com.emanuelef.remote_capture.model;
import androidx.annotation.NonNull;
import com.emanuelef.remote_capture.PCAPdroid;
import com.emanuelef.remote_capture.R;
public class ListInfo {
private final Type mType;
public enum Type {
VISUALIZATION_MASK,
MALWARE_WHITELIST,
}
public ListInfo(Type tp) {
mType = tp;
}
public @NonNull MatchList getList() {
switch(mType) {
case VISUALIZATION_MASK:
return PCAPdroid.getInstance().getVisualizationMask();
case MALWARE_WHITELIST:
return PCAPdroid.getInstance().getMalwareWhitelist();
}
assert false;
return null;
}
public int getTitle() {
switch(mType) {
case VISUALIZATION_MASK:
return R.string.edit_hidden_connections;
case MALWARE_WHITELIST:
return R.string.edit_malware_whitelist;
}
assert false;
return 0;
}
public int getShareSubject() {
switch(mType) {
case VISUALIZATION_MASK:
return R.string.hidden_connections_rules;
case MALWARE_WHITELIST:
return R.string.malware_whitelist_rules;
}
assert false;
return 0;
}
}

View File

@ -64,21 +64,17 @@ public class MatchList {
PROTOCOL PROTOCOL
} }
public static class Rule { public class Rule {
private final String mLabel; private final String mLabel;
private final RuleType mType; private final RuleType mType;
private final Object mValue; private final Object mValue;
Rule(RuleType tp, Object value, String label) { private Rule(RuleType tp, Object value) {
mLabel = label; mLabel = MatchList.getRuleLabel(mContext, tp, value.toString());
mType = tp; mType = tp;
mValue = value; mValue = value;
} }
public Rule(Context ctx, RuleType tp, Object value) {
this(tp, value, MatchList.getLabel(ctx, tp, value.toString()));
}
public String getLabel() { public String getLabel() {
return mLabel; return mLabel;
} }
@ -124,7 +120,7 @@ public class MatchList {
.apply(); .apply();
} }
public static String getLabel(Context ctx, RuleType tp, String value) { public static String getRuleLabel(Context ctx, RuleType tp, String value) {
int resid; int resid;
switch(tp) { switch(tp) {
@ -137,6 +133,14 @@ public class MatchList {
return ""; return "";
} }
if(tp == RuleType.APP) {
AppsResolver resolver = new AppsResolver(ctx);
AppDescriptor app = resolver.get(Integer.parseInt(value), 0);
if(app != null)
value = app.getName();
}
return Utils.formatTextValue(ctx, null, italic, resid, value).toString(); return Utils.formatTextValue(ctx, null, italic, resid, value).toString();
} }
@ -167,8 +171,6 @@ public class MatchList {
if(ruleArray == null) if(ruleArray == null)
return; return;
AppsResolver resolver = new AppsResolver(mContext);
for(JsonElement el: ruleArray) { for(JsonElement el: ruleArray) {
JsonObject ruleObj = el.getAsJsonObject(); JsonObject ruleObj = el.getAsJsonObject();
RuleType type; RuleType type;
@ -181,25 +183,15 @@ public class MatchList {
} }
String val = ruleObj.get("value").getAsString(); String val = ruleObj.get("value").getAsString();
String valLabel = val; addRule(new Rule(type, val));
if(type == RuleType.APP) {
AppDescriptor app = resolver.get(Integer.parseInt(val), 0);
if(app != null)
valLabel = app.getName();
}
String label = getLabel(mContext, type, valLabel);
addRule(new Rule(type, val, label));
} }
} }
public void addApp(int uid, String label) { addRule(new Rule(RuleType.APP, uid, label)); } public void addApp(int uid) { addRule(new Rule(RuleType.APP, uid)); }
public void addIp(String ip, String label) { addRule(new Rule(RuleType.IP, ip, label)); } public void addIp(String ip) { addRule(new Rule(RuleType.IP, ip)); }
public void addHost(String info, String label) { addRule(new Rule(RuleType.HOST, info, label)); } public void addHost(String info) { addRule(new Rule(RuleType.HOST, info)); }
public void addProto(String proto, String label) { addRule(new Rule(RuleType.PROTOCOL, proto, label)); } public void addProto(String proto) { addRule(new Rule(RuleType.PROTOCOL, proto)); }
public void addRootDomain(String domain, String label) { addRule(new Rule(RuleType.ROOT_DOMAIN, domain, label)); } public void addRootDomain(String domain) { addRule(new Rule(RuleType.ROOT_DOMAIN, domain)); }
static private String matchKey(RuleType tp, Object val) { static private String matchKey(RuleType tp, Object val) {
return tp + "@" + val; return tp + "@" + val;
@ -223,14 +215,36 @@ public class MatchList {
} }
} }
public boolean matches(ConnectionDescriptor conn) { public boolean matchesApp(int uid) {
boolean hasInfo = ((conn.info != null) && (!conn.info.isEmpty())); return mMatches.containsKey(matchKey(RuleType.APP, uid));
}
return(mMatches.containsKey(matchKey(RuleType.APP, conn.uid)) || public boolean matchesIP(String ip) {
mMatches.containsKey(matchKey(RuleType.IP, conn.dst_ip)) || return mMatches.containsKey(matchKey(RuleType.IP, ip));
mMatches.containsKey(matchKey(RuleType.PROTOCOL, conn.l7proto)) || }
(hasInfo && mMatches.containsKey(matchKey(RuleType.HOST, conn.info))) ||
(hasInfo && mMatches.containsKey(matchKey(RuleType.ROOT_DOMAIN, Utils.getRootDomain(conn.info))))); public boolean matchesProto(String l7proto) {
return mMatches.containsKey(matchKey(RuleType.PROTOCOL, l7proto));
}
public boolean matchesHost(String host) {
return mMatches.containsKey(matchKey(RuleType.HOST, host));
}
public boolean matchesRootDomain(String root_domain) {
return mMatches.containsKey(matchKey(RuleType.ROOT_DOMAIN, root_domain));
}
public boolean matches(ConnectionDescriptor conn) {
if(mMatches.isEmpty())
return false;
boolean hasInfo = ((conn.info != null) && (!conn.info.isEmpty()));
return(matchesApp(conn.uid) ||
matchesIP(conn.dst_ip) ||
matchesIP(conn.l7proto) ||
(hasInfo && matchesHost(conn.info))) ||
(hasInfo && matchesRootDomain(Utils.getRootDomain(conn.info)));
} }
public Iterator<Rule> iterRules() { public Iterator<Rule> iterRules() {

View File

@ -47,6 +47,7 @@ public class Prefs {
public static final String PREF_APP_THEME = "app_theme"; public static final String PREF_APP_THEME = "app_theme";
public static final String PREF_ROOT_CAPTURE = "root_capture"; public static final String PREF_ROOT_CAPTURE = "root_capture";
public static final String PREF_VISUALIZATION_MASK = "vis_mask"; public static final String PREF_VISUALIZATION_MASK = "vis_mask";
public static final String PREF_MALWARE_WHITELIST = "maware_whitelist";
public static final String PREF_PCAPDROID_TRAILER = "pcapdroid_trailer"; public static final String PREF_PCAPDROID_TRAILER = "pcapdroid_trailer";
public enum DumpMode { public enum DumpMode {

View File

@ -31,7 +31,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:text="@string/edit_rules" android:text="@string/edit_list"
style="?attr/materialButtonOutlinedStyle" style="?attr/materialButtonOutlinedStyle"
android:textColor="@color/colorTabText" /> android:textColor="@color/colorTabText" />
</RelativeLayout> </RelativeLayout>

View File

@ -5,7 +5,7 @@
android:orientation="vertical"> android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/mask_fragment" android:id="@+id/fragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />

View File

@ -10,13 +10,13 @@
android:scrollbarStyle="outsideOverlay" /> android:scrollbarStyle="outsideOverlay" />
<TextView <TextView
android:id="@+id/mask_empty" android:id="@+id/list_empty"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:layout_marginTop="40dp" android:layout_marginTop="40dp"
android:textStyle="italic" android:textStyle="italic"
android:textSize="15sp" android:textSize="15sp"
android:text="@string/no_rules"> android:text="@string/list_is_empty">
</TextView> </TextView>
</RelativeLayout> </RelativeLayout>

View File

@ -61,6 +61,28 @@
</menu> </menu>
</item> </item>
<item android:id="@+id/whitelist_menu" android:title="@string/whitelist_action">
<menu>
<item
android:id="@+id/whitelist_app"
android:title=""
android:visible="false"
tools:title="@string/app_val" />
<item
android:id="@+id/whitelist_ip"
android:title=""
android:visible="false"
tools:title="@string/ip_address_val" />
<item
android:id="@+id/whitelist_host"
android:title=""
android:visible="false"
tools:title="@string/host_val" />
</menu>
</item>
<item <item
android:id="@+id/open_app_details" android:id="@+id/open_app_details"
android:title="@string/app_details" /> android:title="@string/app_details" />

View File

@ -9,6 +9,10 @@
android:id="@+id/action_stats" android:id="@+id/action_stats"
android:title="@string/stats" android:title="@string/stats"
android:icon="@drawable/ic_list" /> android:icon="@drawable/ic_list" />
<item
android:id="@+id/edit_malware_whitelist"
android:title="@string/malware_whitelist"
android:icon="@drawable/ic_checklist" />
<item <item
android:id="@+id/open_root_log" android:id="@+id/open_root_log"
android:title="@string/root_log" android:title="@string/root_log"

View File

@ -181,9 +181,9 @@
<string name="capturing_from">Capturing packets from \"%1$s\"</string> <string name="capturing_from">Capturing packets from \"%1$s\"</string>
<string name="edit_filter">Edit Filter</string> <string name="edit_filter">Edit Filter</string>
<string name="show_hidden_connections">Show hidden connections</string> <string name="show_hidden_connections">Show hidden connections</string>
<string name="edit_rules">Edit Rules</string> <string name="edit_list">Edit List</string>
<string name="hidden_connections_rules">Hidden Connections Rules</string> <string name="hidden_connections_rules">Hidden Connections Rules</string>
<string name="no_rules">No rules</string> <string name="list_is_empty">List is empty</string>
<string name="malicious_connection_app">Malicious connection detected (%1$s)</string> <string name="malicious_connection_app">Malicious connection detected (%1$s)</string>
<string name="show_only_malicious">Only malicious connections</string> <string name="show_only_malicious">Only malicious connections</string>
<string name="security">Security</string> <string name="security">Security</string>
@ -192,5 +192,9 @@
<string name="blacklists_status">Blacklists Status</string> <string name="blacklists_status">Blacklists Status</string>
<string name="blacklists_status_summary">Up-to-date: %1$d/%2$d ― Domain rules: %3$s ― IP rules: %4$s\nLast update: %5$s</string> <string name="blacklists_status_summary">Up-to-date: %1$d/%2$d ― Domain rules: %3$s ― IP rules: %4$s\nLast update: %5$s</string>
<string name="reset">Reset</string> <string name="reset">Reset</string>
<string name="malware_whitelist">Malware Whitelist</string>
<string name="malware_whitelist_rules">Malware Whitelist Rules</string>
<string name="edit_hidden_connections">Edit Hidden Connections</string>
<string name="edit_malware_whitelist">Edit Malware Whitelist</string>
</resources> </resources>