Skip to content

Commit

Permalink
Add ebpf based dns lookup hooks
Browse files Browse the repository at this point in the history
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
calesanz committed Jan 9, 2022
1 parent 568ce69 commit 3b461a9
Show file tree
Hide file tree
Showing 2 changed files with 303 additions and 0 deletions.
296 changes: 296 additions & 0 deletions daemon/dns/ebpfhook.go
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
}
7 changes: 7 additions & 0 deletions daemon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,13 @@ func main() {
}
}

go func() {
err := dns.DnsListenerEbpf()
if err != nil {
log.Warning("Unable to attach ebpf listener.")
}
}()

log.Info("Running on netfilter queue #%d ...", queueNum)
for {
select {
Expand Down

0 comments on commit 3b461a9

Please sign in to comment.