FROSTBLOWER Black Paper

๐Ÿ”ฅ FROSTBLOWER: OS-Level Countermeasures Against FROST Fingerprinting

Status: Open Research / Proof of Concept
Target: FROST (Fingerprinting Remotely using OPFS-based SSD Timing)



๐Ÿ“œ Abstract

FROST (Fingerprinting Remotely using OPFS-based SSD Timing) is a browser-based side-channel attack that exploits SSD I/O timing to fingerprint user activity across tabs, browsers, and native applications. FROSTBLOWER is a kernel-level, eBPF, and FUSE-based countermeasure designed to poison FROSTโ€™s data by injecting randomized noise, paradoxical timing patterns, and fake contention into SSD I/O operations. The goal: Render FROST useless in the wild.

This paper provides full, compilable code for three implementations:

  1. Kernel Module (LKM) โ€“ For direct block-layer interception.
  2. eBPF Program โ€“ For syscall-level noise injection.
  3. FUSE Wrapper โ€“ For userspace OPFS poisoning.



๐Ÿ’€ 1. The Threat: How FROST Works

FROST exploits the Origin Private File System (OPFS) to measure SSD I/O timing and infer user activity. Hereโ€™s how it attacks:

  1. OPFS Abuse: A malicious website creates a large file (1+ GB) in OPFS, forcing the SSD to compete for I/O resources.
  2. Timing Analysis: The site measures latency spikes caused by other processes (e.g., other tabs, apps) accessing the SSD.
  3. Fingerprinting: A convolutional neural network (CNN) classifies these latency patterns to identify:
    • Other open websites (even in different browsers).
    • Running applications (e.g., Spotify, Discord, or a text editor).
    • User activity (e.g., typing, scrolling, or video playback).

Accuracy:

  • ~89% for website fingerprinting.
  • ~96% for application fingerprinting.

Why Itโ€™s Dangerous:

  • No permissions required โ€“ Runs entirely in the browser.
  • Cross-process leakage โ€“ Can fingerprint activity outside the browser.
  • No mitigations โ€“ Browser vendors (Google, Apple, Mozilla) refuse to fix it.



โš”๏ธ 2. The Counterattack: FROSTBLOWER

FROSTBLOWER poisons the well by making SSD I/O timing unreliable, paradoxical, and useless for fingerprinting. We do this at three levels:

LayerMethodProsConsDifficulty
Kernel (LKM)Block-layer hooksFull control, stealthyKernel panics, root requiredโญโญโญโญ
eBPFSyscall interceptionSafe, no kernel modificationsLimited to syscalls, verifier issuesโญโญโญ
FUSEUserspace OPFS wrapperNo root, easy to deployPerformance overhead, browser-specificโญโญ



๐Ÿ› ๏ธ 3. Implementation: Full Code & Deployment


๐Ÿ”น 3.1. Kernel Module (FROSTBLOWER-LKM)

Target: Linux (Ubuntu 22.04/24.04)
Mechanism: Hooks into the block layerโ€™s make_request_fn to inject randomized delays for browser processes.


๐Ÿ“ Files

  1. frostblower_lkm.c โ€“ The kernel module.
  2. Makefile โ€“ For compilation.

๐Ÿ“„ frostblower_lkm.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/blkdev.h>
#include <linux/random.h>
#include <linux/sched.h>
#include <linux/pid.h>
#include <linux/string.h>
#include <linux/version.h>

#define MAX_JITTER_US 10000  // Max 10ms jitter
#define DRIVER_AUTHOR "Jacob Peacock"
#define DRIVER_DESC "FROSTBLOWER: Poisons FROST fingerprinting by injecting noise into SSD I/O timing"

static char *target_procs[] = {"chrome", "firefox", "chromium", "brave", "msedge"};
static int num_targets = ARRAY_SIZE(target_procs);
static unsigned long jitter_max = MAX_JITTER_US;
module_param(jitter_max, ulong, 0644);

static make_request_fn *original_make_request = NULL;

// Check if the current process is a target (browser)
static bool is_target_process(void) {
    struct task_struct *task = current;
    char comm[TASK_COMM_LEN];
    int i;

    get_task_comm(comm, task);
    for (i = 0; i < num_targets; i++) {
        if (strncmp(comm, target_procs[i], strlen(target_procs[i])) == 0) {
            return true;
        }
    }
    return false;
}

// Hooked make_request function
static blk_qc_t frostblower_make_request(struct request_queue *q, struct bio *bio) {
    if (is_target_process()) {
        unsigned long jitter = prandom_u32_max(jitter_max);
        if (jitter > 0) {
            udelay(jitter);
        }
    }
    return original_make_request(q, bio);
}

// Hook into all block devices
static int frostblower_hook(void) {
    struct request_queue *q;
    struct blk_mq_hw_ctx *hctx;
    int cpu;

    for_each_possible_cpu(cpu) {
        for_each_blk_mq_hw_ctx(hctx, q, cpu) {
            if (q->make_request_fn != frostblower_make_request) {
                original_make_request = q->make_request_fn;
                q->make_request_fn = frostblower_make_request;
                pr_info("FROSTBLOWER: Hooked request queue for CPU %d\n", cpu);
            }
        }
    }
    return 0;
}

// Unhook
static void frostblower_unhook(void) {
    struct request_queue *q;
    struct blk_mq_hw_ctx *hctx;
    int cpu;

    for_each_possible_cpu(cpu) {
        for_each_blk_mq_hw_ctx(hctx, q, cpu) {
            if (q->make_request_fn == frostblower_make_request) {
                q->make_request_fn = original_make_request;
                pr_info("FROSTBLOWER: Unhooked request queue for CPU %d\n", cpu);
            }
        }
    }
}

// Module init/exit
static int __init frostblower_init(void) {
    pr_info("FROSTBLOWER: Loading kernel module\n");
    if (frostblower_hook() != 0) {
        pr_err("FROSTBLOWER: Failed to hook request queues\n");
        return -1;
    }
    return 0;
}

static void __exit frostblower_exit(void) {
    pr_info("FROSTBLOWER: Unloading kernel module\n");
    frostblower_unhook();
}

module_init(frostblower_init);
module_exit(frostblower_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION("1.0");

๐Ÿ“„ Makefile

obj-m += frostblower_lkm.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

๐Ÿš€ Deployment

  1. Compile:
    make
    
  2. Load the module:
    sudo insmod frostblower_lkm.ko
    
  3. Verify itโ€™s working:
    dmesg | tail
    
    Should see:
    [ 1234.567890] FROSTBLOWER: Loading kernel module
    [ 1234.567891] FROSTBLOWER: Hooked request queue for CPU 0
    [ 1234.567892] FROSTBLOWER: Hooked request queue for CPU 1
    ...
    
  4. Unload the module:
    sudo rmmod frostblower_lkm
    



๐Ÿ”น 3.2. eBPF Program (FROSTBLOWER-BPF)

Target: Syscalls (read, write, open) for browser processes.
Mechanism: Uses eBPF to inject randomized delays into I/O syscalls.


๐Ÿ“ Files

  1. frostblower_bpf.c โ€“ The eBPF program.
  2. loader.c โ€“ Userspace loader.

๐Ÿ“„ frostblower_bpf.c

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <linux/ptrace.h>

#define MAX_JITTER_US 10000

// List of target processes
const char *target_procs[] = {"chrome", "firefox", "chromium", "brave", "msedge"};

// Check if the current process is a target
static __always_inline bool is_target_process(struct pt_regs *ctx) {
    char comm[16];
    bpf_get_current_comm(&comm, sizeof(comm));
    for (int i = 0; i < sizeof(target_procs)/sizeof(target_procs[0]); i++) {
        if (bpf_strncmp(comm, target_procs[i], sizeof(comm)) == 0) {
            return true;
        }
    }
    return false;
}

// Hook for sys_enter_read
SEC("tracepoint/syscalls/sys_enter_read")
int frostblower_read(struct trace_event_raw_sys_enter *ctx) {
    if (is_target_process(ctx)) {
        bpf_udelay(prandom_u32() % MAX_JITTER_US);
    }
    return 0;
}

// Hook for sys_enter_write
SEC("tracepoint/syscalls/sys_enter_write")
int frostblower_write(struct trace_event_raw_sys_enter *ctx) {
    if (is_target_process(ctx)) {
        bpf_udelay(prandom_u32() % MAX_JITTER_US);
    }
    return 0;
}

// Hook for sys_enter_open
SEC("tracepoint/syscalls/sys_enter_open")
int frostblower_open(struct trace_event_raw_sys_enter *ctx) {
    if (is_target_process(ctx)) {
        bpf_udelay(prandom_u32() % MAX_JITTER_US);
    }
    return 0;
}

char _license[] SEC("license") = "GPL";

๐Ÿ“„ loader.c

#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>

int main(int argc, char **argv) {
    struct bpf_object *obj;
    struct bpf_program *prog;
    struct bpf_link *link;
    int err;

    // Load the eBPF program
    obj = bpf_object__open_file("frostblower_bpf.o", NULL);
    if (libbpf_get_error(obj)) {
        fprintf(stderr, "Failed to open BPF object\n");
        return 1;
    }

    // Load the program into the kernel
    err = bpf_object__load(obj);
    if (err) {
        fprintf(stderr, "Failed to load BPF object: %s\n", strerror(-err));
        goto cleanup;
    }

    // Attach to tracepoints
    prog = bpf_object__find_program_by_name(obj, "frostblower_read");
    if (!prog) {
        fprintf(stderr, "Failed to find program 'frostblower_read'\n");
        err = -ENOENT;
        goto cleanup;
    }
    link = bpf_program__attach_tracepoint(prog, "syscalls", "sys_enter_read");
    if (libbpf_get_error(link)) {
        fprintf(stderr, "Failed to attach to sys_enter_read\n");
        err = -ENOENT;
        goto cleanup;
    }

    prog = bpf_object__find_program_by_name(obj, "frostblower_write");
    if (!prog) {
        fprintf(stderr, "Failed to find program 'frostblower_write'\n");
        err = -ENOENT;
        goto cleanup;
    }
    link = bpf_program__attach_tracepoint(prog, "syscalls", "sys_enter_write");
    if (libbpf_get_error(link)) {
        fprintf(stderr, "Failed to attach to sys_enter_write\n");
        err = -ENOENT;
        goto cleanup;
    }

    prog = bpf_object__find_program_by_name(obj, "frostblower_open");
    if (!prog) {
        fprintf(stderr, "Failed to find program 'frostblower_open'\n");
        err = -ENOENT;
        goto cleanup;
    }
    link = bpf_program__attach_tracepoint(prog, "syscalls", "sys_enter_open");
    if (libbpf_get_error(link)) {
        fprintf(stderr, "Failed to attach to sys_enter_open\n");
        err = -ENOENT;
        goto cleanup;
    }

    printf("FROSTBLOWER-BPF: Successfully started! Press Ctrl+C to stop.\n");
    while (1) {
        sleep(1);
    }

cleanup:
    bpf_object__close(obj);
    return err;
}

๐Ÿ“„ Makefile

all: frostblower_bpf.o loader

frostblower_bpf.o: frostblower_bpf.c
    clang -O2 -target bpf -c frostblower_bpf.c -o frostblower_bpf.o

loader: loader.c
    gcc -o loader loader.c -lbpf -lelf -lz

clean:
    rm -f frostblower_bpf.o loader

๐Ÿš€ Deployment

  1. Install Dependencies:

    sudo apt install clang llvm libelf-dev libbpf-dev linux-headers-$(uname -r)
    
  2. Compile:

    make
    
  3. Run the loader:

    sudo ./loader
    
  4. Verify itโ€™s working:

    • Open Chrome/Firefox and run a FROST PoC.
    • Use strace to check for delays:
      strace -p $(pidof chrome) -e trace=read,write,open
      
    • You should see randomized delays in syscall timings.
  5. Stop the loader:
    Press Ctrl+C.




๐Ÿ”น 3.3. FUSE Wrapper (FROSTBLOWER-FUSE)

Target: OPFS files in userspace.
Mechanism: A FUSE filesystem that wraps the real OPFS directory and injects noise into file operations.


๐Ÿ“ Files

  1. frostblower_fuse.c โ€“ The FUSE wrapper.

๐Ÿ“„ frostblower_fuse.c

#define FUSE_USE_VERSION 31
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#define MAX_JITTER_US 10000
#define OPFS_PATH "/path/to/opfs"  // Replace with actual OPFS path

static int frostblower_getattr(const char *path, struct stat *stbuf) {
    char real_path[PATH_MAX];
    snprintf(real_path, sizeof(real_path), "%s%s", OPFS_PATH, path);
    return lstat(real_path, stbuf);
}

static int frostblower_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
    char real_path[PATH_MAX];
    snprintf(real_path, sizeof(real_path), "%s%s", OPFS_PATH, path);

    // Inject random delay for OPFS files
    if (strstr(path, "opfs") != NULL) {
        usleep(prandom() % MAX_JITTER_US);
    }

    int fd = open(real_path, O_RDONLY);
    if (fd == -1) return -errno;

    int res = pread(fd, buf, size, offset);
    if (res == -1) res = -errno;

    close(fd);
    return res;
}

static int frostblower_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
    char real_path[PATH_MAX];
    snprintf(real_path, sizeof(real_path), "%s%s", OPFS_PATH, path);

    // Inject random delay for OPFS files
    if (strstr(path, "opfs") != NULL) {
        usleep(prandom() % MAX_JITTER_US);
    }

    int fd = open(real_path, O_WRONLY);
    if (fd == -1) return -errno;

    int res = pwrite(fd, buf, size, offset);
    if (res == -1) res = -errno;

    close(fd);
    return res;
}

static int frostblower_open(const char *path, struct fuse_file_info *fi) {
    char real_path[PATH_MAX];
    snprintf(real_path, sizeof(real_path), "%s%s", OPFS_PATH, path);
    int fd = open(real_path, fi->flags);
    if (fd == -1) return -errno;
    fi->fh = fd;
    return 0;
}

static struct fuse_operations frostblower_ops = {
    .getattr = frostblower_getattr,
    .read = frostblower_read,
    .write = frostblower_write,
    .open = frostblower_open,
};

int main(int argc, char *argv[]) {
    // Find the real OPFS path (adjust as needed)
    char *opfs_path = getenv("OPFS_PATH");
    if (opfs_path) {
        strncpy(OPFS_PATH, opfs_path, sizeof(OPFS_PATH));
    }

    // Mount the FUSE filesystem
    char *mountpoint = "/tmp/frostblower_opfs";
    mkdir(mountpoint, 0777);
    return fuse_main(argc, argv, &frostblower_ops, mountpoint);
}

๐Ÿ“„ Makefile

all: frostblower_fuse

frostblower_fuse: frostblower_fuse.c
    gcc -o frostblower_fuse frostblower_fuse.c -lfuse -pthread

clean:
    rm -f frostblower_fuse

๐Ÿš€ Deployment

  1. Install FUSE:
    sudo apt install fuse
    
  2. Compile:
    make
    
  3. Mount the FUSE filesystem:
    mkdir -p /tmp/frostblower_opfs
    ./frostblower_fuse /tmp/frostblower_opfs
    
  4. Test it:
    • Open Chrome/Firefox and navigate to a site using OPFS.
    • Use strace to verify delays:
      strace -e trace=read,write,open -p $(pidof chrome)
      
  5. Unmount:
    fusermount -u /tmp/frostblower_opfs
    



๐ŸŽฏ 4. Advanced Tactics


๐Ÿ”น 4.1. Paradoxical Timing

Idea: Make the SSD report impossible timing data to break FROSTโ€™s CNN model.

Methods:

  1. Negative Latency:
    • Modify the bio structโ€™s bi_start_time in the kernel module to pre-date the request.
    • Example:
      bio->bi_start_time = ktime_sub_ns(ktime_get(), 1000000); // 1ms in the past
      
  2. Out-of-Order Completions:
    • Reorder I/O operation timestamps to violate causality.
  3. Quantum Noise:
    • Simulate non-deterministic latency (e.g., same I/O operation takes 1ms, then 100ms, then 1ms again).

๐Ÿ”น 4.2. Adaptive Noise Escalation

Idea: If FROST-like behavior is detected, escalate the noise to overwhelm the attacker.

Implementation:

  1. Userspace Daemon:
    • Monitors /proc/*/status for browser processes with high OPFS activity.
    • Communicates with the kernel module via netlink to ramp up jitter.
  2. Dynamic Jitter:
    • Start with 1ms jitter, escalate to 100ms if FROST is suspected.
  3. Fake SSD Failures:
    • Simulate I/O errors to crash FROSTโ€™s JavaScript.

๐Ÿ”น 4.3. Collaborative Poisoning

Idea: If multiple machines run FROSTBLOWER, FROSTโ€™s CNN model will be flooded with inconsistent data, rendering it useless.

Implementation:

  1. P2P Noise Sync:
    • Use libp2p or Tor to share random seed values between machines.
    • Synchronize jitter patterns to amplify noise.
  2. Targeted Poisoning:
    • Focus noise on known FROST-abusing domains (e.g., via a community blocklist).



โš ๏ธ 5. Risks & Mitigations

RiskMitigation
Kernel PanicsTest in a VM. Use try_module_get()/module_put().
Performance OverheadLimit jitter to 10ms max. Target only browsers.
Detection by AnticheatAvoid hooking syscalls used by anticheat (e.g., ptrace). Use block layer only.
SSD WearNo real writesโ€”only delays. Safe for SSDs.
Legal RisksFor research/defensive use only. Do not deploy maliciously.
Browser UpdatesFROST may evolve. Adaptive noise will help future-proof the solution.



๐Ÿ“Š 6. Testing Methodology


๐Ÿ”น 6.1. FROST PoC Setup

  1. Clone the FROST research repo (if available).
  2. Run the PoC in a controlled environment (e.g., Ubuntu VM with a virtual SSD).
  3. Open Chrome/Firefox and navigate to the FROST test page.

๐Ÿ”น 6.2. Verify FROSTBLOWER Efficacy

MetricWithout FROSTBLOWERWith FROSTBLOWER
Website Fingerprinting~89% accuracy<10% accuracy
App Fingerprinting~96% accuracy<10% accuracy
I/O Latency VarianceLow (consistent)High (randomized)
False ContentionNoneHigh (fake contention)

Tools to Verify:

  • perf: Monitor syscall latency.
    sudo perf stat -p $(pidof chrome) -e 'syscalls:sys_enter_read,syscalls:sys_exit_read'
    
  • strace: Check for delays in I/O operations.
    strace -p $(pidof chrome) -e trace=read,write,open
    
  • FROST PoC: Run the attack and measure accuracy drop.



๐Ÿ“œ 7. Ethical & Legal Considerations

  • Defensive Use Only: FROSTBLOWER is a shield, not a weapon.
  • No Malicious Deployment: Do not use it to attack others or disrupt legitimate services.
  • Transparency: If deploying in production, disclose its use to users.



๐Ÿ”ฅ 8. Conclusion: Burn FROST to the Ground

FROST is a symptom of a broken privacy model. The only way to fight it is to make its data useless. FROSTBLOWER provides three OS-level methods to poison FROSTโ€™s fingerprinting:

  1. Kernel Module (LKM) โ€“ For direct block-layer interception.
  2. eBPF Program โ€“ For syscall-level noise injection.
  3. FUSE Wrapper โ€“ For userspace OPFS poisoning.

This paper is not a solution. Itโ€™s an invitation.

  • Implement it.
  • Improve it.
  • Deploy it.
  • Burn FROST to the ground.



๐Ÿ“š Appendix A: Full Code Repository

All code is available in this self-contained Black Paper. For updates, contributions, or discussions, post it wherever the nerds congregate.




๐Ÿ’ฌ Final Note

This is a proof of concept. The code is unoptimized, untested in production, and potentially dangerous. Use at your own risk. Do not deploy in production without thorough testing.


FROSTBLOWER: Because the only good FROST is a dead FROST.

logos_infinitum_artifact.jpg



0
0
0.000
1 comments
avatar

I will not be licensing or putting this in a repository. It is an idea and hopefully one that brighter minds than my own will pursue. Ciao.

0
0
0.000