2nd-level domain rules now match all subdomains

A rule like "example.com" is now interpreted as "*.example.com", so it
will match all its sub-domains (e.g. "yet.another.example.com"). This is
importantant, in particular, to match malware subdomains.
This commit is contained in:
emanuele-f 2022-05-19 12:20:07 +02:00
parent 667a9b6e39
commit bc5589d2e1
6 changed files with 76 additions and 24 deletions

View File

@ -797,7 +797,7 @@ public class Utils {
}
// a.example.org -> example.org
public static String getRootDomain(String domain) {
public static String getSecondLevelDomain(String domain) {
int tldPos = domain.lastIndexOf(".");
if(tldPos <= 0)

View File

@ -351,11 +351,17 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
item.setVisible(true);
String dm_clean = Utils.cleanDomain(conn.info);
String rootDomain = Utils.getRootDomain(dm_clean);
String rootDomain = Utils.getSecondLevelDomain(dm_clean);
if(!rootDomain.equals(dm_clean)) {
item = menu.findItem(R.id.hide_root_domain);
item.setTitle(Utils.shorten(MatchList.getRuleLabel(ctx, RuleType.ROOT_DOMAIN, rootDomain), max_length));
label = Utils.shorten(MatchList.getRuleLabel(ctx, RuleType.HOST, rootDomain), max_length);
item = menu.findItem(R.id.hide_domain);
item.setTitle(label);
item.setVisible(true);
item = menu.findItem(R.id.block_domain);
item.setTitle(label);
item.setVisible(true);
}
@ -426,8 +432,8 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
} else if(id == R.id.hide_proto) {
mAdapter.mMask.addProto(conn.l7proto);
mask_changed = true;
} else if(id == R.id.hide_root_domain) {
mAdapter.mMask.addRootDomain(Utils.getRootDomain(conn.info));
} else if(id == R.id.hide_domain) {
mAdapter.mMask.addHost(Utils.getSecondLevelDomain(conn.info));
mask_changed = true;
} else if(id == R.id.hide_country) {
mAdapter.mMask.addCountry(conn.country);
@ -459,6 +465,9 @@ public class ConnectionsFragment extends Fragment implements ConnectionsListener
} else if(id == R.id.block_host) {
blocklist.addHost(conn.info);
blocklist_changed = true;
} else if(id == R.id.block_domain) {
blocklist.addHost(Utils.getSecondLevelDomain(conn.info));
blocklist_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);

View File

@ -66,7 +66,6 @@ public class MatchList {
APP,
IP,
HOST,
ROOT_DOMAIN,
PROTOCOL,
COUNTRY
}
@ -146,7 +145,6 @@ public class MatchList {
switch(tp) {
case APP: resid = R.string.app_val; break;
case IP: resid = R.string.ip_address_val; break;
case ROOT_DOMAIN: value = "*" + value; // fallthrough
case HOST: resid = R.string.host_val; break;
case PROTOCOL: resid = R.string.protocol_val; break;
case COUNTRY: resid = R.string.country_val; break;
@ -196,18 +194,24 @@ public class MatchList {
for(JsonElement el: ruleArray) {
JsonObject ruleObj = el.getAsJsonObject();
String typeStr = ruleObj.get("type").getAsString();
String val = ruleObj.get("value").getAsString();
RuleType type;
try {
type = RuleType.valueOf(ruleObj.get("type").getAsString());
type = RuleType.valueOf(typeStr);
} catch (IllegalArgumentException e) {
// can happen if format is changed, ignore
e.printStackTrace();
continue;
// can happen if format is changed
if(typeStr.equals("ROOT_DOMAIN")) {
Log.i(TAG, String.format("ROOT_DOMAIN %s migrated", val));
type = RuleType.HOST;
mFormatMigration = true;
} else {
e.printStackTrace();
continue;
}
}
String val = ruleObj.get("value").getAsString();
// Handle migration from old uid-based format
if(type == RuleType.APP) {
try {
@ -241,7 +245,6 @@ public class MatchList {
public void addIp(String ip) { addRule(new Rule(RuleType.IP, ip)); }
public void addHost(String info) { addRule(new Rule(RuleType.HOST, Utils.cleanDomain(info))); }
public void addProto(String proto) { addRule(new Rule(RuleType.PROTOCOL, proto)); }
public void addRootDomain(String domain) { addRule(new Rule(RuleType.ROOT_DOMAIN, domain)); }
public void addCountry(String country_code) { addRule(new Rule(RuleType.COUNTRY, country_code)); }
public void addApp(int uid) {
@ -325,11 +328,16 @@ public class MatchList {
}
public boolean matchesHost(String host) {
return mMatches.containsKey(matchKey(RuleType.HOST, Utils.cleanDomain(host)));
}
// Keep in sync with the native blacklist_match_domain
host = Utils.cleanDomain(host);
public boolean matchesRootDomain(String root_domain) {
return mMatches.containsKey(matchKey(RuleType.ROOT_DOMAIN, root_domain));
// exact domain match
if(mMatches.containsKey(matchKey(RuleType.HOST, host)))
return true;
// 2nd-level domain match
String domain = Utils.getSecondLevelDomain(host);
return !domain.equals(host) && mMatches.containsKey(matchKey(RuleType.HOST, domain));
}
public boolean matchesCountry(String country_code) {
@ -345,8 +353,7 @@ public class MatchList {
matchesIP(conn.dst_ip) ||
matchesProto(conn.l7proto) ||
matchesCountry(conn.country) ||
(hasInfo && matchesHost(conn.info))) ||
(hasInfo && matchesRootDomain(Utils.getRootDomain(conn.info)));
(hasInfo && matchesHost(conn.info)));
}
public Iterator<Rule> iterRules() {

View File

@ -17,6 +17,8 @@
* Copyright 2020-21 - Emanuele Faranda
*/
#define _GNU_SOURCE
#include <string.h>
#include "pcapdroid.h"
#include "common/utils.h"
@ -265,14 +267,41 @@ bool blacklist_match_ipstr(blacklist_t *bl, const char *ip_str) {
/* ******************************************************* */
static char* get_second_level_domain(const char *domain) {
char *dot = (char*) memrchr(domain, '.', strlen(domain));
if(!dot || (dot == domain))
return (char*)domain;
dot = (char*) memrchr(domain, '.', dot - domain);
if(!dot)
return (char*)domain;
return dot + 1;
}
/* ******************************************************* */
bool blacklist_match_domain(blacklist_t *bl, const char *domain) {
// Keep in sync with MatchList.matchesHost
string_entry_t *entry = NULL;
if(strncmp(domain, "www.", 4) == 0)
domain += 4;
// exact domain match
HASH_FIND_STR(bl->domains, domain, entry);
return(entry != NULL);
if(entry != NULL)
return true;
// 2nd-level domain match
char *domain2 = get_second_level_domain(domain);
if(domain2 != domain) {
HASH_FIND_STR(bl->domains, domain2, entry);
if(entry != NULL)
return true;
}
return false;
}
/* ******************************************************* */

View File

@ -34,7 +34,8 @@ static void test_match() {
// Use blacklist
assert1(blacklist_match_domain(bl, "www.example.org"));
//assert1(blacklist_match_domain(bl, "some.example.org")); // TODO support subdomains matching
assert1(blacklist_match_domain(bl, "some.example.org"));
assert1(blacklist_match_domain(bl, "evil.some.example.org"));
assert0(blacklist_match_ipstr(bl, "1.2.3.0"));
assert1(blacklist_match_ipstr(bl, "1.2.3.4"));

View File

@ -24,7 +24,7 @@
android:visible="false" />
<item
android:id="@+id/hide_root_domain"
android:id="@+id/hide_domain"
android:title=""
tools:title="@string/host_val"
android:visible="false" />
@ -59,6 +59,12 @@
android:id="@+id/block_host"
android:title=""
tools:title="@string/host_val" />
<item
android:id="@+id/block_domain"
android:title=""
tools:title="@string/host_val"
android:visible="false" />
</menu>
</item>