Detecting Advanced Persistent Threats (APTs) in Financial Systems with eBPF

Explore how eBPF enhances security in Kubernetes for financial systems by detecting Advanced Persistent Threats (APTs). Learn about network monitoring, system call tracking, and application behaviour analysis to protect sensitive data with deep visibility and robust protection.

Introduction

Advanced Persistent Threats (APTs) represent a significant challenge for the financial industry. These sophisticated cyberattacks are characterized by their stealthy and prolonged nature, often targeting sensitive financial data and infrastructure. Early detection and mitigation of APTs are crucial to preventing substantial damage and ensuring the security of financial institutions.

In this blog post, we'll explore how eBPF (Extended Berkeley Packet Filter) can be utilized to enhance the security of Kubernetes environments by providing advanced monitoring and detection capabilities. We'll delve into the specifics of APTs, the features of eBPF, and how it can be effectively implemented to detect and mitigate these threats in financial systems.

Understanding APTs

What are APTs?

APTs are sophisticated cyberattacks that infiltrate networks to steal data or disrupt operations (ex. APT38). Unlike typical cyberattacks, these are characterized by their extended duration, stealth, and persistence. These attacks are often carried out by well-funded and highly skilled adversaries.

Why are APTs a significant threat to financial institutions?

APTs pose a substantial threat to financial institutions due to their ability to evade traditional security measures and maintain a presence within networks for extended periods. The potential damage includes financial loss, reputational harm, and regulatory penalties. As financial institutions handle vast amounts of sensitive data, the need for advanced detection mechanisms is critical.

Lets take a Real-world scenario of this..

In 2016, APT38, a North Korean hacking group, infiltrated Bangladesh Bank via spear-phishing, compromising its SWIFT system. They attempted to steal $1 billion, successfully transferring $81 million before being detected. This incident showcases the severe financial and reputation threats posed by APTs to financial institutions.

What is eBPF?

Extended Berkeley Packet Filter (eBPF) is a powerful technology that allows the execution of user-defined programs in the Linux kernel. Initially developed for packet filtering, eBPF has evolved to provide a wide range of capabilities, including monitoring, security, and performance profiling.

Why use eBPF in Kubernetes for security?

eBPF offers several advantages over traditional security tools:

  • Deep visibility: eBPF can capture detailed information about system and network activities.
  • Low overhead: eBPF operates with minimal performance impact, making it suitable for production environments.
  • Flexibility: eBPF programs can be tailored to specific security needs and continuously updated to adapt to new threats.

Using eBPF to Detect APTs in Kubernetes

Network Monitoring

eBPF can monitor network traffic in real-time, detecting suspicious activities indicative of APTs. By capturing detailed packet data and analyzing network patterns, eBPF can identify unusual communication behaviour, such as data exfiltration or command-and-control (C2) traffic.

System Call Monitoring

System calls are a critical interaction point between user applications and the kernel. By using eBPF to monitor system calls, we can detect abnormal behaviour and identify common tactics, techniques, and procedures (TTPs) used by APTs, such as unauthorized file access or privilege escalation attempts.

Application Behaviour Analysis

Monitoring application behaviour with eBPF provides insights into how applications interact with the system. By establishing a baseline of normal behaviour, eBPF can detect deviations that may indicate an ongoing APT, such as unusual process creation or unexpected network connections.

Let's Dive into Implementing a Basic eBPF Solution for APT Detection

Setting up eBPF in a Kubernetes environment

To get started with eBPF in Kubernetes, you'll need to:

  1. Ensure your Kubernetes nodes are running a compatible Linux kernel version (5.4)
  2. Install the necessary eBPF tools and libraries, such as bpftrace or libbpf.
  3. Deploy eBPF programs to monitor desired activities, leveraging Kubernetes DaemonSets for cluster-wide coverage.

Creating eBPF Programs for APT Detection

Lets create an eBPF program to monitor DNS Requests

In the below example, this eBPF program could monitor DNS requests to detect potential unauthorized communication, from a C2 point of view:

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"log"
	"os"
	"strings"
	"time"
	"unsafe"

	"github.com/cilium/ebpf"
	"github.com/cilium/ebpf/link"
	"github.com/cilium/ebpf/perf"
	"golang.org/x/sys/unix"
)

// Define the eBPF program
const prog = `
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>

BPF_HASH(dns_queries, u64, u32);
BPF_PERF_OUTPUT(events);

struct dns_event_t {
    u64 pid;
    char comm[16];
    char query[256];
};

int dns_monitor(struct pt_regs *ctx, struct sock *sk) {
    u64 pid = bpf_get_current_pid_tgid() >> 32;
    struct dns_event_t event = {};
    event.pid = pid;
    bpf_get_current_comm(&event.comm, sizeof(event.comm));

    // Reading DNS query (simplified example)
    bpf_probe_read_user(&event.query, sizeof(event.query), sk->sk_daddr);

    dns_queries.increment(pid);
    events.perf_submit(ctx, &event, sizeof(event));

    return 0;
}
`

// Structure to hold the event data
type DnsEvent struct {
	PID      uint64
	Comm     [16]byte
	Query    [256]byte
}

var dnsQueryCounts = make(map[string]int)
var queryThreshold = 100 // Example threshold for query rate

func main() {
	// Load the eBPF program
	spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader([]byte(prog)))
	if err != nil {
		log.Fatalf("failed to load eBPF program: %v", err)
	}

	coll, err := ebpf.NewCollection(spec)
	if err != nil {
		log.Fatalf("failed to create eBPF collection: %v", err)
	}
	defer coll.Close()

	// Attach the program to the appropriate network hook
	kprobe, err := link.Kprobe("dns_monitor", coll.Programs["dns_monitor"])
	if err != nil {
		log.Fatalf("failed to attach kprobe: %v", err)
	}
	defer kprobe.Close()

	// Open the perf event map
	rd, err := perf.NewReader(coll.Maps["events"], os.Getpagesize())
	if err != nil {
		log.Fatalf("failed to create perf reader: %v", err)
	}
	defer rd.Close()

	fmt.Println("Monitoring DNS queries for anomalous behavior...")

	// Read events from the perf buffer
	for {
		record, err := rd.Read()
		if err != nil {
			if perf.IsClosed(err) {
				break
			}
			log.Printf("error reading from perf buffer: %v", err)
			continue
		}

		if record.LostSamples != 0 {
			log.Printf("lost %d samples", record.LostSamples)
			continue
		}

		var event DnsEvent
		if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil {
			log.Printf("error parsing event: %v", err)
			continue
		}

		// Convert comm and query to strings
		comm := strings.TrimRight(string(event.Comm[:]), "\x00")
		query := strings.TrimRight(string(event.Query[:]), "\x00")

		// Count DNS queries for anomaly detection
		dnsQueryCounts[query]++
		if dnsQueryCounts[query] > queryThreshold {
			fmt.Printf("Anomalous DNS query detected! PID: %d, Command: %s, Query: %s, Count: %d\n",
				event.PID, comm, query, dnsQueryCounts[query])
		} else {
			fmt.Printf("DNS query detected. PID: %d, Command: %s, Query: %s\n", event.PID, comm, query)
		}
	}
}

In the above code, we attach an eBPF program to the dns_monitor function to monitor DNS queries. The program captures DNS query events and retrieves the process ID, command name, and the DNS query itself. These details are stored in a hash map and sent to a perf buffer. The Go program then reads the data from the perf buffer, processes it, and displays the information. Additionally, it counts the number of DNS queries for each unique query and checks if any query exceeds a predefined threshold, which helps in detecting anomalous DNS activity.



Another example of an eBPF Program for System Call Monitoring

That could be used to monitor file access system calls to detect unauthorized file access:

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"log"
	"os"
	"time"
	"unsafe"

	"github.com/cilium/ebpf"
	"github.com/cilium/ebpf/link"
	"github.com/cilium/ebpf/perf"
	"golang.org/x/sys/unix"
)

// Define the eBPF program
const prog = `
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
#include <linux/fs.h>

BPF_HASH(file_access, u64, u32);
BPF_PERF_OUTPUT(events);

struct event_t {
    u64 pid;
    char filename[256];
};

int syscall__open(struct pt_regs *ctx, const char __user *filename, int flags) {
    u64 pid = bpf_get_current_pid_tgid();
    struct event_t event = {};
    event.pid = pid;
    bpf_probe_read_user(&event.filename, sizeof(event.filename), filename);
    events.perf_submit(ctx, &event, sizeof(event));
    return 0;
}
`

// Structure to hold the event data
type Event struct {
	PID      uint64
	Filename [256]byte
}

func main() {
	// Load the eBPF program
	spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader([]byte(prog)))
	if err != nil {
		log.Fatalf("failed to load eBPF program: %v", err)
	}

	coll, err := ebpf.NewCollection(spec)
	if err != nil {
		log.Fatalf("failed to create eBPF collection: %v", err)
	}
	defer coll.Close()

	// Attach the program to the open syscall
	kprobe, err := link.Kprobe("sys_enter_open", coll.Programs["syscall__open"])
	if err != nil {
		log.Fatalf("failed to attach kprobe: %v", err)
	}
	defer kprobe.Close()

	// Open the perf event map
	rd, err := perf.NewReader(coll.Maps["events"], os.Getpagesize())
	if err != nil {
		log.Fatalf("failed to create perf reader: %v", err)
	}
	defer rd.Close()

	fmt.Println("Monitoring file access...")

	// Authorized files map
	authorizedFiles := map[string]bool{
		"/home/user/authorized_file.txt": true,
		"/home/user/another_file.txt":    true,
		// Add more authorized files or directories here
	}

	// Read events from the perf buffer
	for {
		record, err := rd.Read()
		if err != nil {
			if perf.IsClosed(err) {
				break
			}
			log.Printf("error reading from perf buffer: %v", err)
			continue
		}

		if record.LostSamples != 0 {
			log.Printf("lost %d samples", record.LostSamples)
			continue
		}

		var event Event
		if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil {
			log.Printf("error parsing event: %v", err)
			continue
		}

		// Convert filename to string and check if it's unauthorized
		filename := string(event.Filename[:bytes.IndexByte(event.Filename[:], 0)])
		if _, authorized := authorizedFiles[filename]; !authorized {
			fmt.Printf("Unauthorized file access detected! PID: %d, Filename: %s\n", event.PID, filename)
		} else {
			fmt.Printf("Authorized file access. PID: %d, Filename: %s\n", event.PID, filename)
		}
		time.Sleep(1 * time.Second)
	}
}

In the above code we attach an eBPF program to the sys_enter_open syscall to count the number of file accesses by each process. The counts are stored in an eBPF hash map, and the data is read from the perf buffer and displayed.

Just a heads-up: this is not production-ready code... yet 😉


How to Deploy This in Kubernetes?

To deploy these eBPF programs in Kubernetes, you can use a DaemonSet to ensure that the eBPF program runs on every node in the cluster. Here's a step-by-step approach:

  1. Containerize the Application: Build the eBPF program and containerize the Go application that interacts with it.
  2. Create a DaemonSet: Define a DaemonSet configuration to deploy the container on each node in the cluster.
  3. Configure Permissions: In the DaemonSet, specify the necessary permissions, such as setting appropriate security contexts. This often includes privileged access to allow the eBPF program to interact with the kernel.

Here is an example DaemonSet configuration:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: ebpf-monitor
  labels:
    app: ebpf-monitor
spec:
  selector:
    matchLabels:
      app: ebpf-monitor
  template:
    metadata:
      labels:
        app: ebpf-monitor
    spec:
      containers:
      - name: ebpf-monitor
        image: your-docker-repo/ebpf-monitor:latest
        securityContext:
          privileged: true
        volumeMounts:
        - name: bpf-fs
          mountPath: /sys/fs/bpf
        - name: modules
          mountPath: /lib/modules
        - name: config
          mountPath: /etc/config
      volumes:
      - name: bpf-fs
        hostPath:
          path: /sys/fs/bpf
      - name: modules
        hostPath:
          path: /lib/modules
      - name: config
        configMap:
          name: ebpf-config
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet

Monitoring and Logging: Ensure that the application logs and metrics are collected and accessible. This might involve setting up a logging and monitoring stack (e.g., ELK Stack, Prometheus, Grafana) to visualize and analyze the data collected by the eBPF programs.

By following these steps, you can effectively deploy and manage eBPF programs for APT detection across your Kubernetes cluster.

Conclusion

Detecting APTs are crucial for the financial industry, given the high stakes involved. eBPF offers a powerful solution for enhancing security in Kubernetes environments, providing advanced monitoring and detection capabilities with minimal performance impact. Outside of this, additional security controls such as network policies, pod security policies, role-based access control (RBAC), and continuous vulnerability scanning are essential.

Implementing network policies ensures proper segmentation and traffic control between pods. Pod security policies enforce security standards at the pod level, while RBAC restricts access based on user roles. Continuous vulnerability scanning identifies and addresses potential security flaws in container images and configurations. Combining eBPF with these controls ensures comprehensive protection for applications running in Kubernetes, safeguarding against a wide array of threats.

If you are part of a financial institution, consider exploring and implementing eBPF in your Kubernetes environments to bolster your security posture. Stay ahead of sophisticated threats and ensure the safety of your sensitive data.

Additional Resources

Subscribe to Kubernetes Logs

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe