mirror of
https://github.com/emanuele-f/PCAPdroid.git
synced 2026-06-16 21:10:57 +08:00
Implement blacklist-based malware detection
A notification is generated when a connection matches known malicious domains or IP addresses. The connections view reports malicious connections with a skull icon. A filter can be set to only show them. Needed for #105
This commit is contained in:
parent
6b623ea34d
commit
cb4bbc454d
24
app/src/main/java/com/emanuelef/remote_capture/Billing.java
Normal file
24
app/src/main/java/com/emanuelef/remote_capture/Billing.java
Normal file
@ -0,0 +1,24 @@
|
||||
package com.emanuelef.remote_capture;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/* Billing stub */
|
||||
public class Billing {
|
||||
// SKUs
|
||||
public static final String NO_ADS_SKU = "no_ads";
|
||||
public static final String MALWARE_DETECTION_SKU = "malware_detection";
|
||||
|
||||
protected final Context mContext;
|
||||
|
||||
public Billing(Context ctx) {
|
||||
mContext = ctx;
|
||||
}
|
||||
|
||||
public boolean isAvailable(String sku) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isPurchased(String sku) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,7 @@ import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
@ -49,12 +50,17 @@ import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.emanuelef.remote_capture.activities.ConnectionsActivity;
|
||||
import com.emanuelef.remote_capture.activities.MainActivity;
|
||||
import com.emanuelef.remote_capture.fragments.ConnectionsFragment;
|
||||
import com.emanuelef.remote_capture.model.AppDescriptor;
|
||||
import com.emanuelef.remote_capture.model.CaptureSettings;
|
||||
import com.emanuelef.remote_capture.model.ConnectionDescriptor;
|
||||
import com.emanuelef.remote_capture.model.ConnectionUpdate;
|
||||
import com.emanuelef.remote_capture.model.FilterDescriptor;
|
||||
import com.emanuelef.remote_capture.model.MatchList;
|
||||
import com.emanuelef.remote_capture.model.Prefs;
|
||||
import com.emanuelef.remote_capture.model.VPNStats;
|
||||
import com.emanuelef.remote_capture.pcap_dump.FileDumper;
|
||||
@ -71,6 +77,7 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
private static final String TAG = "CaptureService";
|
||||
private static final String VpnSessionName = "PCAPdroid VPN";
|
||||
private static final String NOTIFY_CHAN_VPNSERVICE = "VPNService";
|
||||
private static final String NOTIFY_CHAN_BLACKLISTED = "Blacklisted";
|
||||
private static final int NOTIFY_ID_VPNSERVICE = 1;
|
||||
private static CaptureService INSTANCE;
|
||||
private ParcelFileDescriptor mParcelFileDescriptor;
|
||||
@ -86,10 +93,12 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
private PcapDumper mDumper;
|
||||
private ConnectionsRegister conn_reg;
|
||||
private Uri mPcapUri;
|
||||
private NotificationCompat.Builder mNotificationBuilder;
|
||||
private NotificationCompat.Builder mStatusBuilder;
|
||||
private NotificationCompat.Builder mBlacklistedBuilder;
|
||||
private long mMonitoredNetwork;
|
||||
private ConnectivityManager.NetworkCallback mNetworkCallback;
|
||||
private AppsResolver appsResolver;
|
||||
private boolean mMalwareDetectionEnabled;
|
||||
|
||||
/* The maximum connections to log into the ConnectionsRegister. Older connections are dropped.
|
||||
* Max Estimated max memory usage: less than 4 MB. */
|
||||
@ -135,7 +144,7 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
private int abortStart() {
|
||||
// NOTE: startForeground must be called before stopSelf, otherwise an exception will occur
|
||||
setupNotifications();
|
||||
startForeground(NOTIFY_ID_VPNSERVICE, getNotification());
|
||||
startForeground(NOTIFY_ID_VPNSERVICE, getStatusNotification());
|
||||
|
||||
stopSelf();
|
||||
sendServiceStatus(SERVICE_STATUS_STOPPED);
|
||||
@ -231,6 +240,13 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
} else
|
||||
app_filter_uid = -1;
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
mMalwareDetectionEnabled = Prefs.isMalwareDetectionEnabled(this, prefs);
|
||||
if(mMalwareDetectionEnabled) {
|
||||
// TODO load from URL
|
||||
Utils.unzip(getResources().openRawResource(R.raw.malware_blacklist), getCacheDir().getPath());
|
||||
}
|
||||
|
||||
if(!mSettings.root_capture) {
|
||||
Log.i(TAG, "Using DNS server " + dns_server);
|
||||
|
||||
@ -289,7 +305,7 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
mThread.start();
|
||||
|
||||
setupNotifications();
|
||||
startForeground(NOTIFY_ID_VPNSERVICE, getNotification());
|
||||
startForeground(NOTIFY_ID_VPNSERVICE, getStatusNotification());
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@ -327,16 +343,21 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
// VPN running notification channel
|
||||
NotificationChannel chan = new NotificationChannel(NOTIFY_CHAN_VPNSERVICE,
|
||||
NOTIFY_CHAN_VPNSERVICE, NotificationManager.IMPORTANCE_LOW); // low: no sound
|
||||
nm.createNotificationChannel(chan);
|
||||
|
||||
// Blacklisted connection notification channel
|
||||
chan = new NotificationChannel(NOTIFY_CHAN_BLACKLISTED,
|
||||
NOTIFY_CHAN_BLACKLISTED, NotificationManager.IMPORTANCE_HIGH);
|
||||
nm.createNotificationChannel(chan);
|
||||
}
|
||||
|
||||
// Notification builder
|
||||
// Status notification builder
|
||||
PendingIntent pi = PendingIntent.getActivity(this, 0,
|
||||
new Intent(this, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
mNotificationBuilder = new NotificationCompat.Builder(this, NOTIFY_CHAN_VPNSERVICE)
|
||||
new Intent(this, MainActivity.class), Utils.getIntentFlags(PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
mStatusBuilder = new NotificationCompat.Builder(this, NOTIFY_CHAN_VPNSERVICE)
|
||||
.setSmallIcon(R.drawable.ic_logo)
|
||||
.setColor(ContextCompat.getColor(this, R.color.colorPrimary))
|
||||
.setContentIntent(pi)
|
||||
@ -344,40 +365,64 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
.setAutoCancel(false)
|
||||
.setContentTitle(getResources().getString(R.string.capture_running))
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setCategory(NotificationCompat.CATEGORY_STATUS)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW); // see IMPORTANCE_LOW
|
||||
|
||||
// CATEGORY_RECOMMENDATION makes the notification visible on the home screen of Android TVs.
|
||||
// However this is not what CATEGORY_RECOMMENDATION is designed for, so it should be avoided.
|
||||
/*
|
||||
if(Utils.isTv(this)) {
|
||||
// This is the icon which is visualized
|
||||
Drawable banner = ContextCompat.getDrawable(this, R.drawable.banner);
|
||||
BitmapDrawable largeIcon = Utils.scaleDrawable(getResources(), banner,
|
||||
banner.getIntrinsicWidth(), banner.getIntrinsicHeight());
|
||||
|
||||
if(largeIcon != null)
|
||||
mNotificationBuilder.setLargeIcon(largeIcon.getBitmap());
|
||||
|
||||
// On Android TV it must be shown as a recommendation
|
||||
mNotificationBuilder.setCategory(NotificationCompat.CATEGORY_RECOMMENDATION);
|
||||
} else*/
|
||||
mNotificationBuilder.setCategory(NotificationCompat.CATEGORY_STATUS);
|
||||
// Blacklisted notification builder
|
||||
mBlacklistedBuilder = new NotificationCompat.Builder(this, NOTIFY_CHAN_BLACKLISTED)
|
||||
.setSmallIcon(R.drawable.ic_skull)
|
||||
.setAutoCancel(true)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setCategory(NotificationCompat.CATEGORY_STATUS)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH); // see IMPORTANCE_HIGH
|
||||
}
|
||||
|
||||
private Notification getNotification() {
|
||||
private Notification getStatusNotification() {
|
||||
String msg = String.format(getString(R.string.notification_msg),
|
||||
Utils.formatBytes(last_bytes), Utils.formatNumber(this, last_connections));
|
||||
|
||||
mNotificationBuilder.setContentText(msg);
|
||||
mStatusBuilder.setContentText(msg);
|
||||
|
||||
return mNotificationBuilder.build();
|
||||
return mStatusBuilder.build();
|
||||
}
|
||||
|
||||
private void updateNotification() {
|
||||
Notification notification = getNotification();
|
||||
Notification notification = getStatusNotification();
|
||||
NotificationManagerCompat.from(this).notify(NOTIFY_ID_VPNSERVICE, notification);
|
||||
}
|
||||
|
||||
public void notifyBlacklistedConnection(ConnectionDescriptor conn) {
|
||||
int uid = conn.uid;
|
||||
|
||||
AppDescriptor app = appsResolver.get(conn.uid, 0);
|
||||
assert app != null;
|
||||
|
||||
FilterDescriptor filter = new FilterDescriptor();
|
||||
filter.onlyBlacklisted = true;
|
||||
|
||||
Intent intent = new Intent(this, ConnectionsActivity.class)
|
||||
.putExtra(ConnectionsFragment.FILTER_EXTRA, filter)
|
||||
.putExtra(ConnectionsFragment.QUERY_EXTRA, app.getPackageName());
|
||||
PendingIntent pi = PendingIntent.getActivity(this, 0,
|
||||
intent, Utils.getIntentFlags(PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
|
||||
String rule_label;
|
||||
if(conn.blacklisted_domain)
|
||||
rule_label = MatchList.getLabel(this, MatchList.RuleType.HOST, conn.info);
|
||||
else
|
||||
rule_label = MatchList.getLabel(this, MatchList.RuleType.IP, conn.dst_ip);
|
||||
|
||||
mBlacklistedBuilder
|
||||
.setContentIntent(pi)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setContentTitle(String.format(getResources().getString(R.string.malicious_connection_app), app.getName()))
|
||||
.setContentText(rule_label);
|
||||
Notification notification = mBlacklistedBuilder.build();
|
||||
|
||||
// Use the UID as the notification ID to group alerts from the same app
|
||||
mHandler.post(() -> NotificationManagerCompat.from(this).notify(uid, notification));
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private void registerNetworkCallbacks() {
|
||||
if(mNetworkCallback != null)
|
||||
@ -578,6 +623,8 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
|
||||
public int isRootCapture() { return(mSettings.root_capture ? 1 : 0); }
|
||||
|
||||
public int malwareDetectionEnabled() { return(mMalwareDetectionEnabled ? 1 : 0); }
|
||||
|
||||
public int addPcapdroidTrailer() { return(mSettings.pcapdroid_trailer ? 1 : 0); }
|
||||
|
||||
public int getAppFilterUid() { return(app_filter_uid); }
|
||||
@ -688,11 +735,11 @@ public class CaptureService extends VpnService implements Runnable {
|
||||
});
|
||||
}
|
||||
|
||||
public static String getPcapdWorkingDir(Context ctx) {
|
||||
public static String getWorkingDir(Context ctx) {
|
||||
return ctx.getCacheDir().getAbsolutePath();
|
||||
}
|
||||
public String getPcapdWorkingDir() {
|
||||
return getPcapdWorkingDir(this);
|
||||
public String getWorkingDir() {
|
||||
return getWorkingDir(this);
|
||||
}
|
||||
|
||||
public String getLibprogPath(String prog_name) {
|
||||
|
||||
@ -65,6 +65,13 @@ public class ConnectionsRegister {
|
||||
return (mTail - 1 + mSize) % mSize;
|
||||
}
|
||||
|
||||
private void processConnectionStatus(ConnectionDescriptor conn) {
|
||||
if(!conn.alerted && conn.isBlacklisted()) {
|
||||
CaptureService.requireInstance().notifyBlacklistedConnection(conn);
|
||||
conn.alerted = true;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void newConnections(ConnectionDescriptor[] conns) {
|
||||
if(conns.length > mSize) {
|
||||
// take the most recent
|
||||
@ -79,6 +86,7 @@ public class ConnectionsRegister {
|
||||
//Log.d(TAG, "newConnections[" + mNumItems + "/" + mSize +"]: insert " + conns.length +
|
||||
// " items at " + mTail + " (removed: " + out_items + " at " + firstPos() + ")");
|
||||
|
||||
// Remove old connections
|
||||
if(out_items > 0) {
|
||||
int pos = firstPos();
|
||||
removedItems = new ConnectionDescriptor[out_items];
|
||||
@ -101,6 +109,7 @@ public class ConnectionsRegister {
|
||||
}
|
||||
}
|
||||
|
||||
// Add new connections
|
||||
for(ConnectionDescriptor conn: conns) {
|
||||
mItemsRing[mTail] = conn;
|
||||
mTail = (mTail + 1) % mSize;
|
||||
@ -115,6 +124,8 @@ public class ConnectionsRegister {
|
||||
mAppsStats.put(uid, stats);
|
||||
}
|
||||
|
||||
processConnectionStatus(conn);
|
||||
|
||||
stats.num_connections++;
|
||||
stats.bytes += conn.rcvd_bytes + conn.sent_bytes;
|
||||
}
|
||||
@ -155,6 +166,7 @@ public class ConnectionsRegister {
|
||||
|
||||
//Log.d(TAG, "update " + update.incr_id + " -> " + update.update_type);
|
||||
conn.processUpdate(update);
|
||||
processConnectionStatus(conn);
|
||||
|
||||
changed_pos[k++] = (pos + mSize - first_pos) % mSize;
|
||||
}
|
||||
|
||||
@ -37,6 +37,15 @@ public class PCAPdroid extends Application {
|
||||
Utils.setAppTheme(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resources getResources() {
|
||||
if(mLocalizedContext == null)
|
||||
return super.getResources();
|
||||
|
||||
// Ensure that the selected locale is used
|
||||
return mLocalizedContext.getResources();
|
||||
}
|
||||
|
||||
public static PCAPdroid getInstance() {
|
||||
return mInstance.get();
|
||||
}
|
||||
@ -48,12 +57,7 @@ public class PCAPdroid extends Application {
|
||||
return mVisMask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resources getResources() {
|
||||
if(mLocalizedContext == null)
|
||||
return super.getResources();
|
||||
|
||||
// Ensure that the selected locale is used
|
||||
return mLocalizedContext.getResources();
|
||||
public Billing getBilling(Context ctx) {
|
||||
return new Billing(ctx);
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,7 @@ package com.emanuelef.remote_capture;
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.UiModeManager;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
@ -69,7 +70,11 @@ import com.emanuelef.remote_capture.model.AppDescriptor;
|
||||
import com.emanuelef.remote_capture.model.Prefs;
|
||||
import com.emanuelef.remote_capture.views.AppsListView;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
@ -85,6 +90,8 @@ import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class Utils {
|
||||
public static final int UID_UNKNOWN = -1;
|
||||
@ -632,4 +639,44 @@ public class Utils {
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static int getIntentFlags(int flags) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
flags |= PendingIntent.FLAG_IMMUTABLE;
|
||||
return flags;
|
||||
}
|
||||
|
||||
public static boolean unzip(InputStream is, String dstpath) {
|
||||
try(ZipInputStream zipIn = new ZipInputStream(is)) {
|
||||
ZipEntry entry = zipIn.getNextEntry();
|
||||
|
||||
while (entry != null) {
|
||||
File dst = new File(dstpath + File.separator + entry.getName());
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
if(!dst.mkdirs()) {
|
||||
Log.w("unzip", "Could not create directories");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Extract file
|
||||
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dst));
|
||||
|
||||
byte[] bytesIn = new byte[4096];
|
||||
int read = 0;
|
||||
while ((read = zipIn.read(bytesIn)) != -1)
|
||||
bos.write(bytesIn, 0, read);
|
||||
bos.close();
|
||||
}
|
||||
|
||||
zipIn.closeEntry();
|
||||
entry = zipIn.getNextEntry();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ public class AppDetailsActivity extends BaseActivity {
|
||||
|
||||
findViewById(R.id.show_connections).setOnClickListener(v -> {
|
||||
Intent intent = new Intent(this, ConnectionsActivity.class);
|
||||
intent.putExtra(ConnectionsFragment.FILTER_EXTRA, dsc.getPackageName());
|
||||
intent.putExtra(ConnectionsFragment.QUERY_EXTRA, dsc.getPackageName());
|
||||
startActivity(intent);
|
||||
});
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@ -23,6 +22,7 @@ public class EditFilterActivity extends BaseActivity {
|
||||
private static final String TAG = "FilterEditActivity";
|
||||
private FilterDescriptor mFilter;
|
||||
private CheckBox mShowMasked;
|
||||
private CheckBox mOnlyBlacklisted;
|
||||
private Chip mStatusOpen;
|
||||
private Chip mStatusClosed;
|
||||
private Chip mStatusUnreachable;
|
||||
@ -50,12 +50,13 @@ public class EditFilterActivity extends BaseActivity {
|
||||
mFilter = new FilterDescriptor();
|
||||
|
||||
mShowMasked = findViewById(R.id.show_masked);
|
||||
mOnlyBlacklisted = findViewById(R.id.only_blacklisted);
|
||||
mStatusOpen = findViewById(R.id.status_open);
|
||||
mStatusClosed = findViewById(R.id.status_closed);
|
||||
mStatusUnreachable = findViewById(R.id.status_unreachable);
|
||||
mStatusError = findViewById(R.id.status_error);
|
||||
|
||||
((Button)findViewById(R.id.edit_mask)).setOnClickListener(v -> {
|
||||
findViewById(R.id.edit_mask).setOnClickListener(v -> {
|
||||
Intent editIntent = new Intent(this, EditMaskActivity.class);
|
||||
startActivity(editIntent);
|
||||
});
|
||||
@ -73,6 +74,7 @@ public class EditFilterActivity extends BaseActivity {
|
||||
|
||||
private void model2view() {
|
||||
mShowMasked.setChecked(mFilter.showMasked);
|
||||
mOnlyBlacklisted.setChecked(mFilter.onlyBlacklisted);
|
||||
|
||||
Chip selected_status = null;
|
||||
switch(mFilter.status) {
|
||||
@ -87,6 +89,7 @@ public class EditFilterActivity extends BaseActivity {
|
||||
|
||||
private void view2model() {
|
||||
mFilter.showMasked = mShowMasked.isChecked();
|
||||
mFilter.onlyBlacklisted = mOnlyBlacklisted.isChecked();
|
||||
|
||||
if(mStatusOpen.isChecked())
|
||||
mFilter.status = Status.STATUS_OPEN;
|
||||
|
||||
@ -21,8 +21,6 @@ package com.emanuelef.remote_capture.activities;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@ -35,10 +33,8 @@ import com.emanuelef.remote_capture.R;
|
||||
import com.emanuelef.remote_capture.Utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class LogviewActivity extends BaseActivity {
|
||||
private static final String TAG = "LogviewActivity";
|
||||
@ -58,7 +54,7 @@ public class LogviewActivity extends BaseActivity {
|
||||
|
||||
private String readLog() {
|
||||
try {
|
||||
String logpath = CaptureService.getPcapdWorkingDir(this) + "/pcapd.log";
|
||||
String logpath = CaptureService.getWorkingDir(this) + "/pcapd.log";
|
||||
BufferedReader reader = new BufferedReader(new FileReader(logpath));
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
@ -33,6 +33,8 @@ import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
import com.emanuelef.remote_capture.Billing;
|
||||
import com.emanuelef.remote_capture.PCAPdroid;
|
||||
import com.emanuelef.remote_capture.Utils;
|
||||
import com.emanuelef.remote_capture.model.Prefs;
|
||||
import com.emanuelef.remote_capture.R;
|
||||
@ -79,15 +81,19 @@ public class SettingsActivity extends BaseActivity {
|
||||
private Preference mProxyPrefs;
|
||||
private Preference mIpv6Enabled;
|
||||
private DropDownPreference mCapInterface;
|
||||
private SwitchPreference mMalwareDetectionEnabled;
|
||||
private Billing mIab;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey);
|
||||
mIab = PCAPdroid.getInstance().getBilling(requireContext());
|
||||
|
||||
setupUdpExporterPrefs();
|
||||
setupHttpServerPrefs();
|
||||
setupSocks5ProxyPrefs();
|
||||
setupCapturePrefs();
|
||||
setupSecurityPrefs();
|
||||
setupOtherPrefs();
|
||||
|
||||
socks5ProxyHideShow(mTlsDecryptionEnabled.isChecked());
|
||||
@ -157,6 +163,17 @@ public class SettingsActivity extends BaseActivity {
|
||||
refreshInterfaces();
|
||||
}
|
||||
|
||||
private void setupSecurityPrefs() {
|
||||
mMalwareDetectionEnabled = findPreference(Prefs.PREF_MALWARE_DETECTION);
|
||||
|
||||
if(!mIab.isAvailable(Billing.MALWARE_DETECTION_SKU)) {
|
||||
getPreferenceScreen().removePreference(findPreference("security"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Billing code here
|
||||
}
|
||||
|
||||
private void setupSocks5ProxyPrefs() {
|
||||
mProxyPrefs = findPreference("proxy_prefs");
|
||||
mTlsHelp = findPreference("tls_how_to");
|
||||
|
||||
@ -73,6 +73,7 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ImageView icon;
|
||||
ImageView blacklistedInd;
|
||||
TextView statusInd;
|
||||
TextView remote;
|
||||
TextView l7proto;
|
||||
@ -91,6 +92,7 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
|
||||
statusInd = itemView.findViewById(R.id.status_ind);
|
||||
appName = itemView.findViewById(R.id.app_name);
|
||||
lastSeen = itemView.findViewById(R.id.last_seen);
|
||||
blacklistedInd = itemView.findViewById(R.id.blacklisted);
|
||||
|
||||
Context context = itemView.getContext();
|
||||
mProtoAndPort = context.getString(R.string.proto_and_port);
|
||||
@ -134,6 +136,8 @@ public class ConnectionsAdapter extends RecyclerView.Adapter<ConnectionsAdapter.
|
||||
else
|
||||
color = R.color.statusError;
|
||||
statusInd.setTextColor(context.getResources().getColor(color));
|
||||
|
||||
blacklistedInd.setVisibility(conn.isBlacklisted() ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -79,6 +79,7 @@ import java.util.Objects;
|
||||
public class ConnectionsFragment extends Fragment implements ConnectionsListener, SearchView.OnQueryTextListener {
|
||||
private static final String TAG = "ConnectionsFragment";
|
||||
public static final String FILTER_EXTRA = "filter";
|
||||
public static final String QUERY_EXTRA = "query";
|
||||
private Handler mHandler;
|
||||
private ConnectionsAdapter mAdapter;
|
||||
private FloatingActionButton mFabDown;
|
||||
@ -225,8 +226,13 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
|
||||
Intent intent = requireActivity().getIntent();
|
||||
|
||||
if(intent != null) {
|
||||
search = intent.getStringExtra(FILTER_EXTRA);
|
||||
FilterDescriptor filter = (FilterDescriptor) intent.getSerializableExtra(FILTER_EXTRA);
|
||||
if(filter != null) {
|
||||
mAdapter.mFilter = filter;
|
||||
fromIntent = true;
|
||||
}
|
||||
|
||||
search = intent.getStringExtra(QUERY_EXTRA);
|
||||
if((search != null) && !search.isEmpty()) {
|
||||
// Avoid hiding the interesting items
|
||||
mAdapter.mFilter.showMasked = true;
|
||||
|
||||
@ -73,6 +73,11 @@ public class ConnectionDescriptor implements Serializable {
|
||||
public int incr_id;
|
||||
public int status;
|
||||
private int tcp_flags;
|
||||
public boolean blacklisted_ip;
|
||||
public boolean blacklisted_domain;
|
||||
|
||||
/* Internal */
|
||||
public boolean alerted = false;
|
||||
|
||||
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) {
|
||||
@ -88,12 +93,15 @@ public class ConnectionDescriptor implements Serializable {
|
||||
}
|
||||
|
||||
public void processUpdate(ConnectionUpdate update) {
|
||||
// The "update_type" is used to limit the amount of data sent via the JNI
|
||||
if((update.update_type & ConnectionUpdate.UPDATE_STATS) != 0) {
|
||||
sent_bytes = update.sent_bytes;
|
||||
rcvd_bytes = update.rcvd_bytes;
|
||||
sent_pkts = update.sent_pkts;
|
||||
rcvd_pkts = update.rcvd_pkts;
|
||||
status = update.status;
|
||||
status = (update.status & 0x00FF);
|
||||
blacklisted_ip = (update.status & 0x0100) != 0;
|
||||
blacklisted_domain = (update.status & 0x0200) != 0;
|
||||
last_seen = update.last_seen;
|
||||
tcp_flags = update.tcp_flags;
|
||||
}
|
||||
@ -156,6 +164,10 @@ public class ConnectionDescriptor implements Serializable {
|
||||
return (tcp_flags & 0xFF);
|
||||
}
|
||||
|
||||
public boolean isBlacklisted() {
|
||||
return blacklisted_ip || blacklisted_domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return "[proto=" + ipproto + "/" + l7proto + "]: " + src_ip + ":" + src_port + " -> " +
|
||||
|
||||
@ -27,14 +27,17 @@ import java.io.Serializable;
|
||||
public class FilterDescriptor implements Serializable {
|
||||
public Status status = Status.STATUS_INVALID;
|
||||
public boolean showMasked = false;
|
||||
public boolean onlyBlacklisted = false;
|
||||
|
||||
public boolean isSet() {
|
||||
return (status != Status.STATUS_INVALID)
|
||||
|| onlyBlacklisted
|
||||
|| (!showMasked && !PCAPdroid.getInstance().getVisualizationMask().isEmpty());
|
||||
}
|
||||
|
||||
public boolean matches(ConnectionDescriptor conn) {
|
||||
return (showMasked || !PCAPdroid.getInstance().getVisualizationMask().matches(conn))
|
||||
&& (!onlyBlacklisted || conn.isBlacklisted())
|
||||
&& ((status == Status.STATUS_INVALID) || (conn.getStatus().equals(status)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,6 +75,10 @@ public class MatchList {
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
public Rule(Context ctx, RuleType tp, Object value) {
|
||||
this(tp, value, MatchList.getLabel(ctx, tp, value.toString()));
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
@ -19,8 +19,11 @@
|
||||
|
||||
package com.emanuelef.remote_capture.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.emanuelef.remote_capture.Billing;
|
||||
import com.emanuelef.remote_capture.PCAPdroid;
|
||||
import com.emanuelef.remote_capture.Utils;
|
||||
|
||||
public class Prefs {
|
||||
@ -32,6 +35,7 @@ public class Prefs {
|
||||
public static final String PREF_SOCKS5_PROXY_IP_KEY = "socks5_proxy_ip_address";
|
||||
public static final String PREF_SOCKS5_PROXY_PORT_KEY = "socks5_proxy_port";
|
||||
public static final String PREF_CAPTURE_INTERFACE = "capture_interface";
|
||||
public static final String PREF_MALWARE_DETECTION = "malware_detection";
|
||||
public static final String PREF_TLS_DECRYPTION_ENABLED_KEY = "tls_decryption_enabled";
|
||||
public static final String PREF_APP_FILTER = "app_filter";
|
||||
public static final String PREF_HTTP_SERVER_PORT = "http_server_port";
|
||||
@ -77,4 +81,8 @@ public class Prefs {
|
||||
public static boolean isRootCaptureEnabled(SharedPreferences p) { return(Utils.isRootAvailable() && p.getBoolean(PREF_ROOT_CAPTURE, false)); }
|
||||
public static boolean isPcapdroidTrailerEnabled(SharedPreferences p) { return(p.getBoolean(PREF_PCAPDROID_TRAILER, false)); }
|
||||
public static String getCaptureInterface(SharedPreferences p) { return(p.getString(PREF_CAPTURE_INTERFACE, "@inet")); }
|
||||
public static boolean isMalwareDetectionEnabled(Context ctx, SharedPreferences p) {
|
||||
return(PCAPdroid.getInstance().getBilling(ctx).isPurchased(Billing.MALWARE_DETECTION_SKU)
|
||||
&& p.getBoolean(PREF_MALWARE_DETECTION, false));
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ add_library(vpnproxy-jni SHARED
|
||||
ip_lru.c
|
||||
ndpi_master_protos.c
|
||||
crc32.c
|
||||
blacklist.c
|
||||
pcap_utils.c)
|
||||
|
||||
# nDPI
|
||||
|
||||
177
app/src/main/jni/vpnproxy-jni/blacklist.c
Normal file
177
app/src/main/jni/vpnproxy-jni/blacklist.c
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "vpnproxy.h"
|
||||
#include "common/utils.h"
|
||||
|
||||
typedef struct string_entry {
|
||||
char *key;
|
||||
UT_hash_handle hh;
|
||||
} string_entry_t;
|
||||
|
||||
struct blacklist {
|
||||
string_entry_t *domains;
|
||||
struct ndpi_detection_module_struct *ndpi;
|
||||
bool locked;
|
||||
uint64_t num_ips;
|
||||
uint64_t num_domains;
|
||||
};
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
blacklist_t* blacklist_init(struct ndpi_detection_module_struct *ndpi) {
|
||||
if(!ndpi)
|
||||
return NULL;
|
||||
|
||||
blacklist_t *bl = (blacklist_t*) calloc(1, sizeof(blacklist_t));
|
||||
if(!bl)
|
||||
return NULL;
|
||||
|
||||
bl->ndpi = ndpi;
|
||||
return bl;
|
||||
}
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
int blacklist_load_file(blacklist_t *bl, const char *path) {
|
||||
FILE *f;
|
||||
char buffer[256];
|
||||
int num_dm_ok = 0, num_dm_fail = 0;
|
||||
int num_ip_ok = 0, num_ip_fail = 0;
|
||||
|
||||
if(bl->locked) {
|
||||
log_e("Blacklist is locked. Run blacklist_clear and load it again.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
f = fopen(path, "r");
|
||||
if(!f) {
|
||||
log_e("Could not open blacklist file \"%s\" [%d]: %s", path, errno, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
struct in_addr in_addr;
|
||||
char *item = fgets(buffer, sizeof(buffer), f);
|
||||
if(!item)
|
||||
break;
|
||||
|
||||
if(!item[0] || (item[0] == '#') || (item[0] == '\n'))
|
||||
continue;
|
||||
|
||||
item[strcspn(buffer, "\r\n")] = '\0';
|
||||
bool is_net = strchr(buffer, '/');
|
||||
|
||||
if(strchr(buffer, ':')) {
|
||||
// IPv6 not supported
|
||||
num_ip_fail++;
|
||||
continue;
|
||||
} else if(is_net || inet_pton(AF_INET, item, &in_addr) == 1) {
|
||||
// IPv4 Address/subnet
|
||||
if(!is_net && ((in_addr.s_addr == 0) || (in_addr.s_addr == 0xFFFFFFFF) || (in_addr.s_addr == 0x7F000001)))
|
||||
continue; // invalid
|
||||
|
||||
if(ndpi_load_ip_category(bl->ndpi, item, PCAPDROID_NDPI_CATEGORY_MALWARE) == 0)
|
||||
num_ip_ok++;
|
||||
else
|
||||
num_ip_fail++;
|
||||
} else {
|
||||
// Domain
|
||||
if(blacklist_match_domain(bl, item))
|
||||
continue; // duplicate domain
|
||||
|
||||
string_entry_t *entry = malloc(sizeof(string_entry_t));
|
||||
if(!entry) {
|
||||
num_dm_fail++;
|
||||
continue;
|
||||
}
|
||||
|
||||
entry->key = strdup(item);
|
||||
if(!entry->key) {
|
||||
free(entry);
|
||||
num_dm_fail++;
|
||||
continue;
|
||||
}
|
||||
|
||||
HASH_ADD_KEYPTR(hh, bl->domains, entry->key, strlen(entry->key), entry);
|
||||
num_dm_ok++;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
log_d("Blacklist loaded[%s]: %d domains (%d failed), %d IPs (%d failed)",
|
||||
strrchr(path, '/') + 1, num_dm_ok, num_dm_fail, num_ip_ok, num_ip_fail);
|
||||
|
||||
bl->num_domains += num_dm_ok;
|
||||
bl->num_ips += num_ip_ok;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
void blacklist_clear(blacklist_t *bl) {
|
||||
string_entry_t *entry, *tmp;
|
||||
|
||||
HASH_ITER(hh, bl->domains, entry, tmp) {
|
||||
HASH_DELETE(hh, bl->domains, entry);
|
||||
free(entry->key);
|
||||
free(entry);
|
||||
}
|
||||
|
||||
bl->domains = NULL;
|
||||
bl->locked = false;
|
||||
bl->num_ips = bl->num_domains = 0;
|
||||
}
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
void blacklist_destroy(blacklist_t *bl) {
|
||||
blacklist_clear(bl);
|
||||
free(bl);
|
||||
}
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
bool blacklist_match_ip(blacklist_t *bl, uint32_t ip) {
|
||||
char ipstr[INET_ADDRSTRLEN];
|
||||
struct in_addr addr;
|
||||
ipstr[0] = '\0';
|
||||
ndpi_protocol_category_t cat = 0;
|
||||
|
||||
if(!bl->locked) {
|
||||
ndpi_enable_loaded_categories(bl->ndpi);
|
||||
bl->locked = true;
|
||||
}
|
||||
|
||||
addr.s_addr = ip;
|
||||
inet_ntop(AF_INET, &addr, ipstr, sizeof(ipstr));
|
||||
|
||||
ndpi_get_custom_category_match(bl->ndpi, ipstr, strlen(ipstr), &cat);
|
||||
return(cat == PCAPDROID_NDPI_CATEGORY_MALWARE);
|
||||
}
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
bool blacklist_match_domain(blacklist_t *bl, const char *domain) {
|
||||
string_entry_t *entry = NULL;
|
||||
|
||||
HASH_FIND_STR(bl->domains, domain, entry);
|
||||
return(entry != NULL);
|
||||
}
|
||||
21
app/src/main/jni/vpnproxy-jni/blacklist.h
Normal file
21
app/src/main/jni/vpnproxy-jni/blacklist.h
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Created by emanuele on 10/20/21.
|
||||
//
|
||||
|
||||
#ifndef PCAPDROID_BLACKLIST_H
|
||||
#define PCAPDROID_BLACKLIST_H
|
||||
|
||||
#include "ndpi_api.h"
|
||||
|
||||
#define PCAPDROID_NDPI_CATEGORY_MALWARE NDPI_PROTOCOL_CATEGORY_CUSTOM_1
|
||||
|
||||
typedef struct blacklist blacklist_t;
|
||||
|
||||
blacklist_t* blacklist_init(struct ndpi_detection_module_struct *ndpi);
|
||||
void blacklist_destroy(blacklist_t *bl);
|
||||
void blacklist_clear(blacklist_t *bl);
|
||||
int blacklist_load_file(blacklist_t *bl, const char *path);
|
||||
bool blacklist_match_ip(blacklist_t *bl, uint32_t ip);
|
||||
bool blacklist_match_domain(blacklist_t *bl, const char *domain);
|
||||
|
||||
#endif //PCAPDROID_BLACKLIST_H
|
||||
@ -173,7 +173,7 @@ static int connectPcapd(vpnproxy_data_t *proxy) {
|
||||
char capture_interface[16];
|
||||
|
||||
getStringPref(proxy, "getPcapDumperBpf", bpf, sizeof(bpf));
|
||||
getStringPref(proxy, "getPcapdWorkingDir", workdir, PATH_MAX);
|
||||
getStringPref(proxy, "getWorkingDir", workdir, PATH_MAX);
|
||||
getStringPref(proxy, "getCaptureInterface", capture_interface, sizeof(capture_interface));
|
||||
get_libprog_path(proxy, "pcapd", pcapd, sizeof(pcapd));
|
||||
|
||||
|
||||
@ -320,15 +320,15 @@ conn_data_t* new_connection(vpnproxy_data_t *proxy, const zdtun_5tuple_t *tuple,
|
||||
data->uid = uid;
|
||||
|
||||
// Try to resolve host name via the LRU cache
|
||||
zdtun_ip_t ip = tuple->dst_ip;
|
||||
data->info = ip_lru_find(proxy->ip_to_host, &ip);
|
||||
const zdtun_ip_t dst_ip = tuple->dst_ip;
|
||||
data->info = ip_lru_find(proxy->ip_to_host, &dst_ip);
|
||||
|
||||
if(data->info) {
|
||||
char resip[INET6_ADDRSTRLEN];
|
||||
int family = (tuple->ipver == 4) ? AF_INET : AF_INET6;
|
||||
|
||||
resip[0] = '\0';
|
||||
inet_ntop(family, &ip, resip, sizeof(resip));
|
||||
inet_ntop(family, &dst_ip, resip, sizeof(resip));
|
||||
|
||||
log_d("Host LRU cache HIT: %s -> %s", resip, data->info);
|
||||
|
||||
@ -361,6 +361,17 @@ conn_data_t* new_connection(vpnproxy_data_t *proxy, const zdtun_5tuple_t *tuple,
|
||||
}
|
||||
}
|
||||
|
||||
if(proxy->malware_detection.bl && (tuple->ipver == 4)) {
|
||||
data->blacklisted_ip = blacklist_match_ip(proxy->malware_detection.bl, tuple->dst_ip.ip4);
|
||||
if(data->blacklisted_ip) {
|
||||
char appbuf[64];
|
||||
char buf[256];
|
||||
|
||||
get_appname_by_uid(proxy, data->uid, appbuf, sizeof(appbuf));
|
||||
log_w("Blacklisted dst ip: %s[%s]", zdtun_5tuple2str(tuple, buf, sizeof(buf)), appbuf);
|
||||
}
|
||||
}
|
||||
|
||||
return(data);
|
||||
}
|
||||
|
||||
@ -431,6 +442,18 @@ void conn_end_ndpi_detection(conn_data_t *data, vpnproxy_data_t *proxy, const zd
|
||||
break;
|
||||
}
|
||||
|
||||
if(proxy->malware_detection.bl && data->info && data->info[0] && !data->blacklisted_domain) {
|
||||
// TODO early match
|
||||
data->blacklisted_domain = blacklist_match_domain(proxy->malware_detection.bl, data->info);
|
||||
if(data->blacklisted_domain) {
|
||||
char appbuf[64];
|
||||
char buf[512];
|
||||
|
||||
get_appname_by_uid(proxy, data->uid, appbuf, sizeof(appbuf));
|
||||
log_w("Blacklisted domain [%s]: %s[%s]", data->info, zdtun_5tuple2str(tuple, buf, sizeof(buf)), appbuf);
|
||||
}
|
||||
}
|
||||
|
||||
data->update_type |= CONN_UPDATE_INFO;
|
||||
conn_free_ndpi(data);
|
||||
}
|
||||
@ -642,7 +665,8 @@ static jobject getConnUpdate(vpnproxy_data_t *proxy, const vpn_conn_t *conn) {
|
||||
if(data->update_type & CONN_UPDATE_STATS) {
|
||||
(*env)->CallVoidMethod(env, update, mids.connUpdateSetStats, data->last_seen,
|
||||
data->sent_bytes, data->rcvd_bytes, data->sent_pkts, data->rcvd_pkts,
|
||||
(data->tcp_flags[0] << 8) | data->tcp_flags[1], data->status);
|
||||
(data->tcp_flags[0] << 8) | data->tcp_flags[1],
|
||||
(data->blacklisted_domain << 9) | (data->blacklisted_ip << 8) | (data->status & 0xFF));
|
||||
}
|
||||
if(data->update_type & CONN_UPDATE_INFO) {
|
||||
jobject info = (*env)->NewStringUTF(env, data->info ? data->info : "");
|
||||
@ -1006,6 +1030,15 @@ void account_packet(vpnproxy_data_t *proxy, const zdtun_pkt_t *pkt, uint8_t from
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
static char* get_path(const char *subpath) {
|
||||
strncpy(global_proxy->workdir + global_proxy->basepath_len, subpath,
|
||||
sizeof(global_proxy->workdir) - global_proxy->basepath_len - 1);
|
||||
global_proxy->workdir[sizeof(global_proxy->workdir) - 1] = 0;
|
||||
return global_proxy->workdir;
|
||||
}
|
||||
|
||||
/* ******************************************************* */
|
||||
|
||||
static int run_tun(JNIEnv *env, jclass vpn, int tunfd, jint sdk) {
|
||||
netd_resolve_waiting = 0;
|
||||
jclass vpn_class = (*env)->GetObjectClass(env, vpn);
|
||||
@ -1058,8 +1091,13 @@ static int run_tun(JNIEnv *env, jclass vpn, int tunfd, jint sdk) {
|
||||
.ipv6 = {
|
||||
.enabled = (bool) getIntPref(env, vpn, "getIPv6Enabled"),
|
||||
.dns_server = getIPv6Pref(env, vpn, "getIpv6DnsServer"),
|
||||
},
|
||||
.malware_detection = {
|
||||
.enabled = (bool) getIntPref(env, vpn, "malwareDetectionEnabled"),
|
||||
}
|
||||
};
|
||||
getStringPref(&proxy, "getWorkingDir", proxy.workdir, sizeof(proxy.workdir));
|
||||
proxy.basepath_len = strlen(proxy.workdir);
|
||||
|
||||
// Enable or disable the PCAPdroid trailer
|
||||
pcap_set_pcapdroid_trailer((bool)getIntPref(env, vpn, "addPcapdroidTrailer"));
|
||||
@ -1073,12 +1111,27 @@ static int run_tun(JNIEnv *env, jclass vpn, int tunfd, jint sdk) {
|
||||
/* nDPI */
|
||||
proxy.ndpi = init_ndpi();
|
||||
init_protocols_bitmask(&masterProtos);
|
||||
|
||||
if(proxy.ndpi == NULL) {
|
||||
log_f("nDPI initialization failed");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
// Load blacklist
|
||||
if(proxy.malware_detection.enabled) {
|
||||
blacklist_t *bl = blacklist_init(proxy.ndpi);
|
||||
if(bl == NULL) {
|
||||
log_f("Blacklist initialization failed");
|
||||
ndpi_exit_detection_module(proxy.ndpi);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
blacklist_load_file(bl, get_path("/maltrail-malware-domains.txt"));
|
||||
blacklist_load_file(bl, get_path("/emerging-Block-IPs.txt"));
|
||||
blacklist_load_file(bl, get_path("/abuse_sslipblacklist.txt"));
|
||||
blacklist_load_file(bl, get_path("/feodotracker_ipblocklist.txt"));
|
||||
proxy.malware_detection.bl = bl;
|
||||
}
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
if(proxy.pcap_dump.enabled) {
|
||||
@ -1108,6 +1161,8 @@ static int run_tun(JNIEnv *env, jclass vpn, int tunfd, jint sdk) {
|
||||
conns_clear(&proxy.new_conns, true);
|
||||
conns_clear(&proxy.conns_updates, true);
|
||||
|
||||
if(proxy.malware_detection.bl)
|
||||
blacklist_destroy(proxy.malware_detection.bl);
|
||||
ndpi_exit_detection_module(proxy.ndpi);
|
||||
|
||||
if(proxy.pcap_dump.buffer) {
|
||||
@ -1193,4 +1248,4 @@ Java_com_emanuelef_remote_1capture_CaptureService_getPcapHeader(JNIEnv *env, jcl
|
||||
}
|
||||
|
||||
return barray;
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
#include <stdbool.h>
|
||||
#include "zdtun.h"
|
||||
#include "ip_lru.h"
|
||||
#include "blacklist.h"
|
||||
#include "ndpi_api.h"
|
||||
#include "common/uid_resolver.h"
|
||||
#include "third_party/uthash.h"
|
||||
@ -82,6 +83,8 @@ typedef struct conn_data {
|
||||
bool pending_notification;
|
||||
bool to_purge;
|
||||
bool request_done;
|
||||
bool blacklisted_ip;
|
||||
bool blacklisted_domain;
|
||||
char *request_data;
|
||||
char *url;
|
||||
uint8_t update_type;
|
||||
@ -118,6 +121,8 @@ typedef struct vpnproxy_data {
|
||||
ndpi_ptree_t *known_dns_servers;
|
||||
uid_resolver_t *resolver;
|
||||
ip_lru_t *ip_to_host;
|
||||
char workdir[PATH_MAX];
|
||||
int basepath_len;
|
||||
struct timeval last_pkt_ts; // Packet timestamp, reported into the exported PCAP
|
||||
uint64_t now_ms; // Monotonic timestamp, see refresh_time
|
||||
u_int num_dropped_pkts;
|
||||
@ -153,6 +158,11 @@ typedef struct vpnproxy_data {
|
||||
struct in6_addr dns_server;
|
||||
} ipv6;
|
||||
|
||||
struct {
|
||||
bool enabled;
|
||||
blacklist_t *bl;
|
||||
} malware_detection;
|
||||
|
||||
capture_stats_t capture_stats;
|
||||
} vpnproxy_data_t;
|
||||
|
||||
|
||||
8
app/src/main/res/drawable/ic_skull.xml
Normal file
8
app/src/main/res/drawable/ic_skull.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="512" android:viewportWidth="512"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!--
|
||||
All brand icons are trademarks of their respective owners.
|
||||
-->
|
||||
<path android:fillColor="@android:color/white" android:pathData="M256,0C114.6,0 0,100.3 0,224c0,70.1 36.9,132.6 94.5,173.7 9.6,6.9 15.2,18.1 13.5,29.9l-9.4,66.2c-1.4,9.6 6,18.2 15.7,18.2L192,512v-56c0,-4.4 3.6,-8 8,-8h16c4.4,0 8,3.6 8,8v56h64v-56c0,-4.4 3.6,-8 8,-8h16c4.4,0 8,3.6 8,8v56h77.7c9.7,0 17.1,-8.6 15.7,-18.2l-9.4,-66.2c-1.7,-11.7 3.8,-23 13.5,-29.9C475.1,356.6 512,294.1 512,224 512,100.3 397.4,0 256,0zM160,320c-35.3,0 -64,-28.7 -64,-64s28.7,-64 64,-64 64,28.7 64,64 -28.7,64 -64,64zM352,320c-35.3,0 -64,-28.7 -64,-64s28.7,-64 64,-64 64,28.7 64,64 -28.7,64 -64,64z"/>
|
||||
</vector>
|
||||
@ -40,13 +40,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/status_ind"
|
||||
app:layout_constraintEnd_toStartOf="@+id/blacklisted"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
tools:text="Android" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_ind"
|
||||
android:textSize="12sp"
|
||||
android:minEms="3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
@ -54,6 +55,16 @@
|
||||
tools:textColor="#FF28BC36"
|
||||
tools:text="@string/conn_status_open" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/blacklisted"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="12sp"
|
||||
app:layout_constraintEnd_toStartOf="@id/status_ind"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/status_ind"
|
||||
app:tint="@color/colorTabText"
|
||||
android:src="@drawable/ic_skull" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/last_seen"
|
||||
android:textSize="12sp"
|
||||
|
||||
@ -15,7 +15,8 @@
|
||||
<RelativeLayout
|
||||
android:id="@+id/connections_mask"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginBottom="15dp">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/show_masked"
|
||||
@ -30,12 +31,18 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:text="@string/edit_rules"
|
||||
style="?attr/materialButtonOutlinedStyle"
|
||||
android:textColor="@color/colorTabText" />
|
||||
</RelativeLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/only_blacklisted"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:text="@string/show_only_malicious" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@ -185,5 +185,10 @@
|
||||
<string name="edit_rules">Edit Rules</string>
|
||||
<string name="hidden_connections_rules">Hidden Connections Rules</string>
|
||||
<string name="no_rules">No rules</string>
|
||||
<string name="malicious_connection_app">Malicious connection detected (%1$s)</string>
|
||||
<string name="show_only_malicious">Only malicious connections</string>
|
||||
<string name="security">Security</string>
|
||||
<string name="malware_detection">Malware Detection</string>
|
||||
<string name="malware_detection_summary">"Detect possibly malicious connections and trigger alerts. NOTE: the detection can produce false positives and is not a valid alternative to an antivirus."</string>
|
||||
</resources>
|
||||
|
||||
|
||||
@ -105,6 +105,15 @@
|
||||
</Preference>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="@string/security" app:iconSpaceReserved="false" app:key="security">
|
||||
<SwitchPreference
|
||||
app:key="malware_detection"
|
||||
app:title="@string/malware_detection"
|
||||
app:iconSpaceReserved="false"
|
||||
app:summary="@string/malware_detection_summary"
|
||||
app:defaultValue="false" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="@string/other_prefs" app:iconSpaceReserved="false" >
|
||||
<DropDownPreference
|
||||
app:key="app_theme"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user