-
-
Notifications
You must be signed in to change notification settings - Fork 511
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
When using DoT or DoH opensnitch cannot intercept the dns packets. Therfore the UI always shows IP addresses instead of hostnames. To fix this issue an ebpf (uprobe) filter was created to hook getaddrinfo and gethostbyname calls.
- Loading branch information
Showing
2 changed files
with
303 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,296 @@ | ||
package dns | ||
|
||
import ( | ||
"bytes" | ||
"encoding/binary" | ||
"fmt" | ||
"net" | ||
"os" | ||
"os/signal" | ||
|
||
"github.com/evilsocket/opensnitch/daemon/log" | ||
bpf "github.com/iovisor/gobpf/bcc" | ||
) | ||
|
||
type nameLookupEvent struct { | ||
Mtype uint8 | ||
AddrType uint32 | ||
Host [255]byte | ||
Ip [16]uint8 | ||
} | ||
|
||
const code string = ` | ||
#include <linux/sched.h> | ||
#include <uapi/linux/ptrace.h> | ||
#define MAX_ALIASES 5 | ||
#define MAX_IPS 5 | ||
struct nameLookupEvent { | ||
u8 type; | ||
int addr_type; | ||
char host[255]; | ||
u8 ip[16]; | ||
} __attribute__((packed)); | ||
struct hostent { | ||
char *h_name; /* Official name of host. */ | ||
char **h_aliases; /* Alias list. */ | ||
int h_addrtype; /* Host address type. */ | ||
int h_length; /* Length of address. */ | ||
char **h_addr_list; /* List of addresses from name server. */ | ||
#ifdef __USE_MISC | ||
#define h_addr h_addr_list[0] /* Address, for backward compatibility.*/ | ||
#endif | ||
}; | ||
struct sockaddr { | ||
unsigned short sa_family; | ||
char sa_data[14]; | ||
}; | ||
struct addrinfo { | ||
int ai_flags; /* Input flags. */ | ||
int ai_family; /* Protocol family for socket. */ | ||
int ai_socktype; /* Socket type. */ | ||
int ai_protocol; /* Protocol for socket. */ | ||
size_t ai_addrlen; /* Length of socket address. */ | ||
struct sockaddr *ai_addr; /* Socket address for socket. */ | ||
char *ai_canonname; /* Canonical name for service location. */ | ||
struct addrinfo *ai_next; /* Pointer to next in list. */ | ||
}; | ||
struct in_addr { | ||
uint32_t s_addr; // that's a 32-bit int (4 bytes) | ||
}; | ||
struct sockaddr_in { | ||
short int sin_family; // Address family, AF_INET | ||
unsigned short int sin_port; // Port number | ||
struct in_addr sin_addr; // Internet address | ||
unsigned char sin_zero[8]; // Same size as struct sockaddr | ||
}; | ||
struct in6_addr { | ||
unsigned char s6_addr[16]; // IPv6 address | ||
}; | ||
struct sockaddr_in6 { | ||
u_int16_t sin6_family; // address family, AF_INET6 | ||
u_int16_t sin6_port; // port number, Network Byte Order | ||
u_int32_t sin6_flowinfo; // IPv6 flow information | ||
struct in6_addr sin6_addr; // IPv6 address | ||
u_int32_t sin6_scope_id; // Scope ID | ||
}; | ||
struct addrinfo_args_cache { | ||
struct addrinfo **restrict addrinfo_ptr; | ||
char node[256]; | ||
}; | ||
// define temporary array for data | ||
BPF_HASH(addrinfo_args_hash, u32, struct addrinfo_args_cache); | ||
// BPF output events | ||
BPF_PERF_OUTPUT(events); | ||
/** | ||
* Hooks gethostbyname calls and emits multiple nameLookupEvent events. | ||
* It supports at most MAX_IPS many addresses. | ||
*/ | ||
int ret_hostbyname(struct pt_regs *ctx) { | ||
struct nameLookupEvent data = {0}; | ||
data.type = 1; | ||
if (!PT_REGS_RC(ctx)) | ||
return 0; | ||
struct hostent *host = (struct hostent *)PT_REGS_RC(ctx); | ||
char **ips = host->h_addr_list; | ||
#pragma clang loop unroll(full) | ||
for (int i = 0; i < MAX_IPS; i++) { | ||
if (ips[i] == 0x0) { | ||
data.type = 8; | ||
events.perf_submit(ctx, &data, sizeof(data)); | ||
break; | ||
} | ||
bpf_probe_read_user(&data.host, 8, host->h_name); | ||
bpf_probe_read_user(&data.ip, 16, ips[i]); | ||
data.addr_type = host->h_addrtype; | ||
data.type = 1; | ||
events.perf_submit(ctx, &data, sizeof(data)); | ||
char **alias = host->h_aliases; | ||
#pragma clang loop unroll(full) | ||
for (int j = 0; j < MAX_ALIASES; j++) { | ||
data.type = 4; | ||
if (alias[j] == NULL) { | ||
break; | ||
} | ||
bpf_probe_read_user(&data.host, sizeof(data.host), alias[j]); | ||
events.perf_submit(ctx, &data, sizeof(data)); | ||
} | ||
} | ||
return 0; | ||
} | ||
// capture getaddrinfo call and store the relevant arguments to a hash. | ||
int addrinfo(struct pt_regs *ctx) { | ||
struct addrinfo_args_cache addrinfo_args = {0}; | ||
if (!PT_REGS_PARM1(ctx)) | ||
return 0; | ||
if (!PT_REGS_PARM4(ctx)) | ||
return 0; | ||
u64 pid_tgid = bpf_get_current_pid_tgid(); | ||
u32 tid = (u32)pid_tgid; | ||
addrinfo_args.addrinfo_ptr = (struct addrinfo **)PT_REGS_PARM4(ctx); | ||
bpf_probe_read_user_str(&addrinfo_args.node, sizeof(addrinfo_args.node), | ||
(char *)PT_REGS_PARM1(ctx)); | ||
addrinfo_args_hash.update(&tid, &addrinfo_args); | ||
bpf_trace_printk("Stored %p: %s\n", addrinfo_args.addrinfo_ptr, | ||
addrinfo_args.node); | ||
return 0; | ||
} | ||
int ret_addrinfo(struct pt_regs *ctx) { | ||
struct nameLookupEvent data = {0}; | ||
struct addrinfo_args_cache *addrinfo_args = {0}; | ||
u64 pid_tgid = bpf_get_current_pid_tgid(); | ||
u32 tid = (u32)pid_tgid; | ||
addrinfo_args = addrinfo_args_hash.lookup(&tid); | ||
if (addrinfo_args == 0) { | ||
bpf_trace_printk("missed start"); | ||
return 0; // missed start | ||
} | ||
struct addrinfo **restrict res_p = addrinfo_args->addrinfo_ptr; | ||
#pragma clang loop unroll(full) | ||
for (int i = 0; i < MAX_IPS; i++) { | ||
struct addrinfo *restrict res = *res_p; | ||
if (res == NULL) { | ||
break; | ||
} | ||
data.type = 2; | ||
data.addr_type = res->ai_family; | ||
// AF_INET 2 | ||
if (res->ai_family == 2) { | ||
struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr; | ||
// FIXME possibly leaks memory | ||
bpf_probe_read_user(&data.ip, sizeof(data.ip), &ipv4->sin_addr); | ||
} else { | ||
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr; | ||
bpf_probe_read_user(&data.ip, sizeof(data.ip), &ipv6->sin6_addr); | ||
} | ||
bpf_probe_read_kernel_str(&data.host, sizeof(data.host), | ||
addrinfo_args->node); | ||
events.perf_submit(ctx, &data, sizeof(data)); | ||
res_p = &res->ai_next; | ||
} | ||
return 0; | ||
} | ||
` | ||
|
||
func DnsListenerEbpf() error { | ||
m := bpf.NewModule(code, []string{}) | ||
defer m.Close() | ||
|
||
// Attach Hooks | ||
getaddrinfoUprobe, err := m.LoadUprobe("addrinfo") | ||
if err != nil { | ||
log.Error("Failed to load addrinfo to getaddrinfo: %s\n", err) | ||
return err | ||
} | ||
|
||
err = m.AttachUprobe("c", "getaddrinfo", getaddrinfoUprobe, -1) | ||
if err != nil { | ||
log.Error("Failed to attach addrinfo to getaddrinfo: %s\n", err) | ||
return err | ||
} | ||
fmt.Printf("getaddrinfo\n") | ||
|
||
getaddrinfoUretprobe, err := m.LoadUprobe("ret_addrinfo") | ||
if err != nil { | ||
log.Error("Failed to load ret_addrinfo to getaddrinfo: %s\n", err) | ||
return err | ||
} | ||
|
||
err = m.AttachUretprobe("c", "getaddrinfo", getaddrinfoUretprobe, -1) | ||
if err != nil { | ||
log.Error("Failed to attach ret_addrinfo to getaddrinfo: %s\n", err) | ||
return err | ||
} | ||
|
||
gethostbynameUretprobe, err := m.LoadUprobe("ret_hostbyname") | ||
if err != nil { | ||
log.Error("Failed to load ret_hostbyname to gethostbyname: %s\n", err) | ||
return err | ||
} | ||
|
||
err = m.AttachUretprobe("c", "gethostbyname", gethostbynameUretprobe, -1) | ||
if err != nil { | ||
log.Error("Failed to attach ret_hostbyname to gethostbyname: %s\n", err) | ||
return err | ||
} | ||
|
||
err = m.AttachUretprobe("c", "gethostbyname2", gethostbynameUretprobe, -1) | ||
if err != nil { | ||
log.Error("Failed to attach ret_hostbyname to gethostbyname2: %s\n", err) | ||
return err | ||
} | ||
|
||
// Reading Events | ||
table := bpf.NewTable(m.TableId("events"), m) | ||
|
||
channel := make(chan []byte) | ||
|
||
perfMap, err := bpf.InitPerfMap(table, channel, nil) | ||
if err != nil { | ||
log.Error("Failed to init perf map: %s\n", err) | ||
return err | ||
} | ||
sig := make(chan os.Signal, 1) | ||
signal.Notify(sig, os.Interrupt, os.Kill) | ||
|
||
go func() { | ||
var event nameLookupEvent | ||
for { | ||
data := <-channel | ||
err := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, &event) | ||
if err != nil { | ||
log.Warning("Failed to decode ebpf nameLookupEvent: %s\n", err) | ||
continue | ||
} | ||
// Convert C string (null-terminated) to Go string | ||
host := string(event.Host[:bytes.IndexByte(event.Host[:], 0)]) | ||
var ip net.IP | ||
if event.AddrType == 2 { | ||
ip = net.IP(event.Ip[:4]) | ||
} else { | ||
ip = net.IP(event.Ip[:]) | ||
} | ||
|
||
log.Debug("Tracking Resolved Message: %s -> %s\n", host, ip.String()) | ||
Track(ip.String(), host) | ||
} | ||
}() | ||
|
||
perfMap.Start() | ||
<-sig | ||
perfMap.Stop() | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters