#!/bin/bash # ───────────────────────────────────────────────────────────────────── # KSMBD-012 Test Environment Setup # ───────────────────────────────────────────────────────────────────── # # Builds a QEMU-bootable kernel (6.19.x) with ksmbd and an initramfs # that auto-starts a vulnerable ksmbd server on boot. # # Current Ubuntu/Debian LTS kernels (<=6.11) do not include ksmbd # durable handle support (merged in 6.12-rc1, commit c8efcc786146), # so a QEMU environment is required. # # Prerequisites: # sudo apt install build-essential flex bison bc libelf-dev libssl-dev \ # libglib2.0-dev libnl-3-dev libnl-genl-3-dev libtool autoconf \ # qemu-system-x86 busybox-static cpio python3-pip # pip3 install impacket # # Usage: # ./setup.sh # downloads kernel, builds everything # ./setup.sh /path/to/source # use existing kernel source tree # # After setup completes, run: # ./run.sh # boots QEMU, ksmbd starts automatically # python3 exploit.py acl-bypass ... # from another terminal # ───────────────────────────────────────────────────────────────────── set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" WORK="$SCRIPT_DIR/.build" KERNEL_VER="${KERNEL_VER:-6.19.11}" KSMBD_TOOLS_REPO="https://github.com/cifsd-team/ksmbd-tools.git" RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' info() { echo -e "${GREEN}[*]${NC} $*"; } warn() { echo -e "${YELLOW}[!]${NC} $*"; } err() { echo -e "${RED}[-]${NC} $*" >&2; exit 1; } # ── Check prerequisites ─────────────────────────────────────────── for cmd in make gcc qemu-system-x86_64 cpio gzip git autoconf; do command -v "$cmd" &>/dev/null || err "Missing: $cmd" done BUSYBOX="$(command -v busybox 2>/dev/null || true)" [ -n "$BUSYBOX" ] && file "$BUSYBOX" | grep -q "statically linked" || \ err "Need statically-linked busybox (apt install busybox-static)" mkdir -p "$WORK" # ── Step 1: Kernel source ───────────────────────────────────────── if [ -n "${1:-}" ] && [ -f "$1/Makefile" ]; then KSRC="$(cd "$1" && pwd)" info "Using existing kernel source: $KSRC" else KSRC="$WORK/linux-$KERNEL_VER" if [ -f "$KSRC/Makefile" ]; then info "Kernel source already present at $KSRC" else TARBALL="$WORK/linux-$KERNEL_VER.tar.xz" if [ ! -f "$TARBALL" ]; then MAJOR="${KERNEL_VER%%.*}" URL="https://cdn.kernel.org/pub/linux/kernel/v${MAJOR}.x/linux-${KERNEL_VER}.tar.xz" info "Downloading kernel $KERNEL_VER..." curl -L -o "$TARBALL" "$URL" fi info "Extracting kernel source..." tar -xf "$TARBALL" -C "$WORK" fi fi # ── Step 2: Build ksmbd-tools ───────────────────────────────────── TOOLS="$WORK/ksmbd-tools" if [ ! -f "$TOOLS/tools/ksmbd.tools" ]; then info "Building ksmbd-tools..." [ -d "$TOOLS" ] || git clone --depth=1 "$KSMBD_TOOLS_REPO" "$TOOLS" (cd "$TOOLS" && autoreconf -i && \ ./configure --prefix=/ --sysconfdir=/etc --localstatedir=/var && \ make -j"$(nproc)") fi # Verify durable handle support if ! strings "$TOOLS/tools/ksmbd.tools" | grep "durable handles" >/dev/null 2>&1; then err "ksmbd-tools lacks durable handle support" fi info "ksmbd-tools ready ($(cd "$TOOLS" && ./tools/ksmbd.tools --version 2>&1 | head -1 || true))" # ── Step 3: Configure and build kernel ───────────────────────────── info "Configuring kernel..." cd "$KSRC" if [ ! -f .config ]; then make defconfig fi scripts/config \ -e CONFIG_SMB_SERVER \ -e CONFIG_INET -e CONFIG_NET \ -e CONFIG_NLS -e CONFIG_NLS_UTF8 -e CONFIG_UNICODE \ -e CONFIG_CRYPTO_MD5 -e CONFIG_CRYPTO_HMAC -e CONFIG_CRYPTO_SHA256 \ -e CONFIG_CRYPTO_SHA512 -e CONFIG_CRYPTO_CMAC -e CONFIG_CRYPTO_AES \ -e CONFIG_CRYPTO_ECB -e CONFIG_CRYPTO_DES -e CONFIG_CRYPTO_CCM \ -e CONFIG_CRYPTO_GCM -e CONFIG_CRYPTO_CRC32 \ -e CONFIG_VIRTIO -e CONFIG_VIRTIO_NET -e CONFIG_VIRTIO_PCI \ -e CONFIG_DEVTMPFS -e CONFIG_DEVTMPFS_MOUNT -e CONFIG_TMPFS \ -e CONFIG_PROC_FS -e CONFIG_SERIAL_8250 -e CONFIG_SERIAL_8250_CONSOLE \ -e CONFIG_BLK_DEV_INITRD make olddefconfig grep -q "CONFIG_SMB_SERVER=y" .config || err "CONFIG_SMB_SERVER not enabled" info "Building kernel ($(nproc) jobs)..." make -j"$(nproc)" bzImage BZIMAGE="$KSRC/arch/x86/boot/bzImage" [ -f "$BZIMAGE" ] || err "bzImage not found" # ── Step 4: Build initramfs ──────────────────────────────────────── info "Building initramfs..." INITRD="$WORK/initramfs" rm -rf "$INITRD" mkdir -p "$INITRD"/{bin,sbin,lib,lib64,etc/ksmbd,proc,sys,dev,tmp/smbtest,run,var/run} cp "$BUSYBOX" "$INITRD/bin/busybox" (cd "$INITRD/bin" && for c in sh ls cat echo mkdir mount umount sleep ip ln \ mknod chmod chown kill rm grep; do ln -sf busybox "$c"; done) cp "$TOOLS/tools/ksmbd.tools" "$INITRD/sbin/" (cd "$INITRD/sbin" && ln -sf ksmbd.tools ksmbd.mountd && ln -sf ksmbd.tools ksmbd.adduser) for lib in $(ldd "$TOOLS/tools/ksmbd.tools" 2>/dev/null | grep -o '/[^ ]*' | sort -u); do [ -f "$lib" ] && { mkdir -p "$INITRD$(dirname "$lib")"; cp "$lib" "$INITRD$(dirname "$lib")/"; } done cp /lib64/ld-linux-x86-64.so.2 "$INITRD/lib64/" 2>/dev/null || true # NSS plugin for getpwnam_r — dlopen()ed at runtime, not visible to ldd. # Without this, ksmbd-tools cannot resolve victim/attacker → POSIX UIDs and # every SMB user falls back to uid 0 (root), defeating the ACL boundary. for libdir in /usr/lib/x86_64-linux-gnu /lib/x86_64-linux-gnu /usr/lib64 /lib64; do if [ -f "$libdir/libnss_files.so.2" ]; then mkdir -p "$INITRD$libdir" cp "$libdir/libnss_files.so.2" "$INITRD$libdir/" break fi done cat > "$INITRD/init" << 'INITSCRIPT' #!/bin/sh export PATH=/bin:/sbin mount -t proc proc /proc; mount -t sysfs sysfs /sys mount -t devtmpfs devtmpfs /dev; mount -t tmpfs tmpfs /tmp; mount -t tmpfs tmpfs /run mknod -m 666 /dev/null c 1 3 2>/dev/null ip link set lo up; ip addr add 127.0.0.1/8 dev lo for iface in eth0 ens0; do ip link set $iface up 2>/dev/null && { ip addr add 10.0.2.15/24 dev $iface; break; } done mkdir -p /tmp/smbtest /etc/ksmbd /var/run # POSIX users — ksmbd-tools resolves SMB account → uid via getpwnam_r, # so without these every SMB user maps to uid 0 and the ACL boundary # we want to demonstrate disappears. cat > /etc/passwd << 'EOF' root:x:0:0:root:/root:/bin/sh victim:x:1000:1000:victim:/tmp:/bin/sh attacker:x:1001:1001:attacker:/tmp:/bin/sh EOF cat > /etc/group << 'EOF' root:x:0: victim:x:1000: attacker:x:1001: EOF cat > /etc/nsswitch.conf << 'EOF' passwd: files group: files shadow: files EOF # Pre-stage the share with a victim-owned 0600 file so the ACL bypass # is provable. Default file name matches `python3 exploit.py acl-bypass --file`. # Share dir must be victim-writable: SMB DESIRED_ACCESS=DELETE on a file # requires +w on the parent, otherwise victim's own CREATE is rejected. chown 1000:1000 /tmp/smbtest chmod 0700 /tmp/smbtest echo 'placeholder - will be overwritten by victim' > /tmp/smbtest/secret_0600.txt chown 1000:1000 /tmp/smbtest/secret_0600.txt chmod 0600 /tmp/smbtest/secret_0600.txt ksmbd.adduser -a -p Victim1 victim ksmbd.adduser -a -p Attacker2 attacker cat > /etc/ksmbd/ksmbd.conf << 'EOF' [global] server signing = disabled smb3 encryption = disabled server min protocol = SMB2_10 server max protocol = SMB3_11 # SMB2_10 = 2.1 (allows the dialect-regression test); SMB3_11 = 3.1.1. durable handles = yes [share] path = /tmp/smbtest read only = no guest ok = no valid users = victim, attacker oplocks = yes EOF ksmbd.mountd -n & sleep 2 echo "" echo "=== ksmbd ready ===" echo "Users: victim/Victim1 (uid 1000), attacker/Attacker2 (uid 1001)" echo "Share: share -> /tmp/smbtest" echo "Target: /tmp/smbtest/secret_0600.txt (0600 victim:victim)" echo "" exec /bin/sh INITSCRIPT chmod +x "$INITRD/init" (cd "$INITRD" && find . | cpio -o -H newc 2>/dev/null | gzip > "$WORK/initramfs.cpio.gz") # ── Step 5: Create run script ───────────────────────────────────── cat > "$SCRIPT_DIR/run.sh" << RUNEOF #!/bin/bash # Boot the vulnerable ksmbd QEMU environment. # Once you see "=== ksmbd ready ===" run from another terminal: # # python3 exploit.py acl-bypass \\ # --target 127.0.0.1 --port 44500 --share share \\ # --user victim --password Victim1 \\ # --user2 attacker --password2 Attacker2 \\ # --file secret_0600.txt # # Kill QEMU: Ctrl-A X DIR="\$(cd "\$(dirname "\$0")" && pwd)" exec qemu-system-x86_64 \\ -kernel "$BZIMAGE" \\ -initrd "$WORK/initramfs.cpio.gz" \\ -append "console=ttyS0 nokaslr nopti" \\ -m 512M -nographic -no-reboot \\ -netdev user,id=net0,hostfwd=tcp::\${PORT:-44500}-:445 \\ -device virtio-net-pci,netdev=net0 RUNEOF chmod +x "$SCRIPT_DIR/run.sh" # ── Done ─────────────────────────────────────────────────────────── echo "" info "Setup complete." info "" info " Kernel: $BZIMAGE" info " Initrd: $WORK/initramfs.cpio.gz" info "" info " Terminal 1: ./run.sh" info " Terminal 2: python3 exploit.py acl-bypass \\" info " --target 127.0.0.1 --port 44500 --share share \\" info " --user victim --password Victim1 \\" info " --user2 attacker --password2 Attacker2 \\" info " --file secret_0600.txt" info "" info " Server-side check (in QEMU console after the exploit runs):" info " ls -l /tmp/smbtest/secret_0600.txt" info " cat /tmp/smbtest/secret_0600.txt"