#!/bin/sh
[ -n "$IK_AK_NCR" ] || [ ! -r "$0" ] || { tr -d '\r' < "$0" > /tmp/.ikuai-ak.$$ && IK_AK_NCR=1 && export IK_AK_NCR && exec sh /tmp/.ikuai-ak.$$ "$@"; }
# iKuai 全家族通杀 v6 (20260825)
# IKMGR / 旺盟 / helen / libjson / gopher
# 一次性脚本：detect+clean 跑完即结束。不是常驻杀毒，也不能收成 curl|sh。
#
# 下发：
#   推荐启动器（curl/wget 被 null 也能从 CDN 拉本文件）:
#     sendcmd -f ikuai-ak-boot.sh
#     或 curl -kfsSL -A 'ikuai-allkiller/6' https://dl.naixi.net/ikuai/fix/ikuai-ak-boot.sh | tr -d '\r' | sh
#   无 openssl 时整包推: sendcmd -f 本文件
#   SSH 已有本文件: sh ikuai-allkiller.sh clean
#
# 补工具顺序（两边都挂也能试）：
#   1) /etc/mnt/ikuai/.bin-official  2) .evd-orig  3) /tmp/ikuai-fix/curl-x32|x64|arm
#   4) 还活着的 curl/wget-ssl  5) busybox wget(多数固件没有)  6) openssl s_client
#   都失败: tools=0，清理照做。再 sendcmd 推二进制到 /tmp/ikuai-fix/
# CDN 下载会校验 ELF 架构 + SHA256，避免 ARM 包装到 x86。
#
# 不做: 改/重启 dns.sh；杀官方 AC、ikdnsx、/usr/sbin/ik_kernel_event；封 UDP 1234
# 做: 封 606/37810 入站(WAN+LAN，127.0.0.1 放行)。DNS 只删 47.84.83.155/43.98.194.101/92.88.207.254
# 成功: ==> ALL CLEAN OK   同时看 harden=1 wan606=1 wan378=1 enc=0
# tools=0 不挡 CLEAN。升级/重启后 /usr 和 crontab 会被冲掉，再跑一次本脚本。
MODE="${1:-clean}"
CFG=/etc/mnt/ikuai/config.db
PPS=/etc/mnt/ikuai/wpa_ppsk.db
Q=/etc/log/.ikq_$(date +%Y%m%d%H%M%S 2>/dev/null)
[ -n "$Q" ] || Q=/etc/log/.ikq_manual
mkdir -p "$Q" 2>/dev/null || Q=/tmp/.ikq_$(date +%Y%m%d%H%M%S 2>/dev/null)
# /etc/log 写满时改走 /tmp，避免把 /etc/hosts 写空
touch "$Q/.ok" 2>/dev/null || Q=/tmp/.ikq_$(date +%Y%m%d%H%M%S 2>/dev/null)
mkdir -p "$Q" 2>/dev/null
STRIP='s/^\[fastid:[^]]*\][[:space:]]*//'
MAL="47.84.83.155 43.98.194.101 92.88.207.254"
DEF_P="114.114.114.114"
DEF_S="223.5.5.5"
MP=/usr/ikuai/script/utils/monitor_process.sh
HARD=/etc/log/monitor_process.sh.hardened
FIXURL="https://dl.naixi.net/ikuai/fix"
OFF=/etc/mnt/ikuai/.bin-official
STAGE=/tmp/ikuai-fix
AK_UA="ikuai-allkiller/6"

arch_tag() {
  case "$(uname -m)" in
    i386|i486|i586|i686) echo x32 ;;
    x86_64|amd64) echo x64 ;;
    aarch64|arm64) echo arm ;;
    *) echo "" ;;
  esac
}

is_real_bin() {
  [ -e "$1" ] || return 1
  [ -L "$1" ] && [ "$(readlink "$1")" = "/dev/null" ] && return 1
  [ -s "$1" ] || return 1
  return 0
}

is_elf() {
  [ -s "$1" ] || return 1
  case "$(od -An -N4 -tx1 "$1" 2>/dev/null | tr -d ' \n')" in
    7f454c46) return 0 ;;
    *) return 1 ;;
  esac
}

# 防止 ARM 包装到 x86（Q6000 事故）
elf_match() {
  _f=$1; _t=$2
  is_elf "$_f" || return 1
  [ -n "$_t" ] || return 0
  _cls=$(dd if="$_f" bs=1 skip=4 count=1 2>/dev/null | od -An -tx1 | tr -d ' \n')
  _mach=$(dd if="$_f" bs=1 skip=18 count=2 2>/dev/null | od -An -tx1 | tr -d ' \n')
  case "$_t" in
    x32) [ "$_cls" = 01 ] && [ "$_mach" = 0300 ] ;;
    x64) [ "$_cls" = 02 ] && [ "$_mach" = 3e00 ] ;;
    arm) [ "$_cls" = 02 ] && [ "$_mach" = b700 ] ;;
    *) return 1 ;;
  esac
}

tool_broken() {
  is_real_bin "$1" && return 1
  return 0
}

wget_broken() {
  tool_broken /usr/bin/wget-ssl && tool_broken /usr/bin/wget && return 0
  return 1
}

file_sha() {
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" 2>/dev/null | awk '{print $1}'
  elif command -v openssl >/dev/null 2>&1; then
    openssl dgst -sha256 "$1" 2>/dev/null | awk '{print $NF}'
  else
    echo ""
  fi
}

expect_sha() {
  case "$1" in
    curl-x32) echo 9609269148019942f7872d28cf2517af4d142a9f5eeac2bbd01754250ac0311c ;;
    wget-x32) echo 83cf01ac54e391ef76cc104442397d3dcb07c57062c6f829b387199d1ec37d38 ;;
    curl-x64|curl-x86) echo 122794b90e6555a5598f23da18c778cc02354f03e1ce2e51b0afbb11b9ea8e88 ;;
    wget-x64|wget-x86) echo ef942f4469589e4b66c7f9700e1130f1d52df8735d9120db8f10361a882a3eab ;;
    curl-arm) echo 483b7414c6230d4c8af8ab3504e6421ff1dfd0fec980df1fdf76b1141bcccacc ;;
    wget-arm) echo 4600985ed91b78cd4614e95cd2ab3a7bb207ef61f94680d8096c2a3b43b2f1e1 ;;
    *) echo "" ;;
  esac
}

# CDN 下来的才卡哈希；本机官方副本只卡 ELF/架构
cdn_ok() {
  _f=$1; _name=$2; _t=$3
  elf_match "$_f" "$_t" || return 1
  _exp=$(expect_sha "$_name")
  [ -n "$_exp" ] || return 0
  _got=$(file_sha "$_f")
  [ -z "$_got" ] && return 0
  [ "$_got" = "$_exp" ]
}

local_bin_ok() {
  elf_match "$1" "$2"
}

ik_fetch() {
  _u=$1; _o=$2
  rm -f "$_o"
  if is_real_bin /usr/bin/curl; then
    /usr/bin/curl -kfsSL --connect-timeout 20 -A "$AK_UA" -o "$_o" "$_u" 2>/dev/null && is_elf "$_o" && return 0
  fi
  if is_real_bin /usr/bin/wget-ssl; then
    /usr/bin/wget-ssl -q --no-check-certificate -T 20 -U "$AK_UA" -O "$_o" "$_u" 2>/dev/null && is_elf "$_o" && return 0
  fi
  if is_real_bin /usr/bin/wget; then
    /usr/bin/wget -q --no-check-certificate -T 20 -U "$AK_UA" -O "$_o" "$_u" 2>/dev/null && is_elf "$_o" && return 0
  fi
  # /usr/bin/wget 被 null 时，部分固件 busybox 仍带 wget applet（3.x/4.x 多数没有）
  for _bb in /bin/busybox /bin/busybox.nosuid; do
    [ -x "$_bb" ] || continue
    "$_bb" wget --help >/dev/null 2>&1 || continue
    "$_bb" wget -q -T 20 -U "$AK_UA" --no-check-certificate -O "$_o" "$_u" 2>/dev/null && is_elf "$_o" && return 0
    "$_bb" wget -q -T 20 -U "$AK_UA" -O "$_o" "$_u" 2>/dev/null && is_elf "$_o" && return 0
  done
  # 两边都挂：走 openssl，不依赖 curl/wget
  if command -v openssl >/dev/null 2>&1; then
    _host=$(echo "$_u" | sed 's#https://\([^/]*\).*#\1#')
    _path=$(echo "$_u" | sed 's#https://[^/]*##')
    [ -n "$_path" ] || _path=/
    printf 'GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: %s\r\nAccept: */*\r\nConnection: close\r\n\r\n' "$_path" "$_host" "$AK_UA" | \
      openssl s_client -quiet -connect "$_host:443" 2>/dev/null | \
      awk 'BEGIN{h=1} h && ($0=="" || $0=="\r"){h=0; next} !h {print}' > "$_o"
    is_elf "$_o" && return 0
    rm -f "$_o"
  fi
  return 1
}

cache_good_tools() {
  T=$(arch_tag)
  mkdir -p "$OFF" 2>/dev/null
  if is_real_bin /usr/bin/curl && local_bin_ok /usr/bin/curl "$T"; then
    cp -f /usr/bin/curl "$OFF/curl" 2>/dev/null
  fi
  if is_real_bin /usr/bin/wget-ssl && local_bin_ok /usr/bin/wget-ssl "$T"; then
    cp -f /usr/bin/wget-ssl "$OFF/wget" 2>/dev/null
  elif is_real_bin /usr/bin/wget && local_bin_ok /usr/bin/wget "$T"; then
    cp -f /usr/bin/wget "$OFF/wget" 2>/dev/null
  fi
}

restore_tools() {
  T=$(arch_tag)
  [ -n "$T" ] || { echo "tools: skip unknown arch $(uname -m) (mips 无官方包)"; return 0; }
  mkdir -p "$OFF" "$STAGE" 2>/dev/null
  cache_good_tools
  _need=0
  tool_broken /usr/bin/curl && _need=1
  wget_broken && _need=1
  [ "$_need" = 1 ] || { echo "tools: already ok arch=$T (cached $OFF)"; return 0; }
  echo "tools: restoring $T (curl/wget 可能都被 null，先本地后 openssl)"
  for n in curl wget; do
    dest=/usr/bin/$n
    [ "$n" = wget ] && dest=/usr/bin/wget-ssl
    if [ "$n" = wget ]; then
      wget_broken || { ln -sf wget-ssl /usr/bin/wget 2>/dev/null; continue; }
    else
      tool_broken "$dest" || continue
    fi
    src=""
    [ -s "$OFF/$n" ] && local_bin_ok "$OFF/$n" "$T" && src="$OFF/$n"
    [ -z "$src" ] && [ -s /etc/mnt/ikuai/.evd-orig/$n ] && local_bin_ok /etc/mnt/ikuai/.evd-orig/$n "$T" && src=/etc/mnt/ikuai/.evd-orig/$n
    [ -z "$src" ] && [ -s "$STAGE/${n}-${T}" ] && local_bin_ok "$STAGE/${n}-${T}" "$T" && src="$STAGE/${n}-${T}"
    [ -z "$src" ] && [ "$T" = x64 ] && [ -s "$STAGE/${n}-x86" ] && local_bin_ok "$STAGE/${n}-x86" "$T" && src="$STAGE/${n}-x86"
    if [ -z "$src" ] && ik_fetch "$FIXURL/${n}-${T}" "/tmp/${n}.fix" && cdn_ok "/tmp/${n}.fix" "${n}-${T}" "$T"; then
      src="/tmp/${n}.fix"
    fi
    if [ -z "$src" ] && [ "$T" = x64 ] && ik_fetch "$FIXURL/${n}-x86" "/tmp/${n}.fix" && cdn_ok "/tmp/${n}.fix" "${n}-x86" "$T"; then
      src="/tmp/${n}.fix"
    fi
    if [ -z "$src" ]; then
      echo "tools: FAIL $n ($T) 预置 $STAGE/${n}-${T} 或用 sendcmd 推文件"
      continue
    fi
    if [ "$n" = wget ]; then
      rm -f /usr/bin/wget /usr/bin/wget-ssl
      cp -f "$src" /usr/bin/wget-ssl
      chmod 755 /usr/bin/wget-ssl
      ln -sf wget-ssl /usr/bin/wget
      cp -f /usr/bin/wget-ssl "$OFF/wget" 2>/dev/null
    else
      rm -f /usr/bin/curl
      cp -f "$src" /usr/bin/curl
      chmod 755 /usr/bin/curl
      cp -f /usr/bin/curl "$OFF/curl" 2>/dev/null
    fi
    echo "tools: restored $n <- $src"
  done
}

# 攻击者会 chmod -x 官方 ik_rc_client，通杀后 ikvpn/ikvpn4 会一直 remote_offline
restore_rc() {
  [ -f /usr/sbin/ik_rc_client ] || return 0
  if [ ! -x /usr/sbin/ik_rc_client ]; then
    chmod 755 /usr/sbin/ik_rc_client 2>/dev/null
    echo "rc: restored exec bit on ik_rc_client"
  fi
  if ! ps w 2>/dev/null | grep -q '[i]k_rc_client'; then
    /usr/sbin/ik_rc_client >/dev/null 2>&1
    echo "rc: started ik_rc_client (等 10s 再 ikvpn4)"
  fi
}

echo "===IK_AK $MODE BEGIN==="
VER=$(grep '^VERSION=' /etc/release 2>/dev/null | head -1 | cut -d= -f2 | tr -d '"')
GWID=$(grep '^GWID=' /etc/release 2>/dev/null | head -1 | cut -d= -f2)
echo "HOST=$(cat /proc/sys/kernel/hostname 2>/dev/null) VER=$VER GWID=$GWID ARCH=$(uname -m) TAG=$(arch_tag) UP=$(cut -d. -f1 /proc/uptime 2>/dev/null)s"

B=""
epid=$(cat /etc/mnt/ikuai/.cache/eventd.pid 2>/dev/null)
if [ -n "$epid" ]; then
  case "$(readlink /proc/$epid/exe 2>/dev/null)" in *udevd-new*) B="$B gopher_evpid";; esac
fi
for p in /proc/[0-9]*; do
  [ -d "$p" ] || continue
  p=${p#/proc/}
  ex=$(readlink /proc/$p/exe 2>/dev/null)
  case "$ex" in
    /usr/sbin/ik_kernel_event|/usr/sbin/ikdnsx|/usr/sbin/ikdnsd) continue ;;
  esac
  comm=$(cat /proc/$p/comm 2>/dev/null | tr -d '\n')
  cmd=$(tr '\0' ' ' < /proc/$p/cmdline 2>/dev/null)
  case "$ex" in
    *udevd-new*|*/etc/log/udev/*|*/tmp/iktmp/udev/*|*error.log*) B="$B gopher_rat";;
    */etc/log/ik_kernel_event*) B="$B ikmgr_kernel";;
  esac
  case "$cmd" in
    *Edge.server*|*/home/helen/*) B="$B helen";;
    *ikuaidns4mc*|*ikdnsd-worker*|*ikuaidnson*) B="$B wm";;
  esac
  case "$comm" in
    __sched__|cdn-cgi) B="$B json";;
    kworker/u8:0) [ -n "$ex" ] && B="$B gopher_kw";;
  esac
  grep -q 'libaudit.so' /proc/$p/maps 2>/dev/null && B="$B ikmgr_audit"
done 2>/dev/null
{ [ -e /etc/log/udev/udevd-new ] || [ -e /tmp/iktmp/udev/udevd-new ]; } && B="$B gopher_file"
[ -f /etc/mnt/audit/libaudit.so ] && B="$B ikmgr_so"
[ -f /etc/mnt/audit/seed ] && B="$B ikmgr_seed"
[ -e /etc/mnt/ikuai/.sr ] && B="$B wm_sr"
[ -e /etc/log/.wangmeng ] && B="$B wm_wm"
[ -e /etc/log/client_rc_start ] && B="$B helen_rc"
{ [ -e /etc/mnt/ikuai/data/libpptp ] || [ -e /etc/mnt/ikuai/data/ikpptp ]; } && B="$B helen_pptp"
[ -d /home/helen ] && B="$B helen_dir"
[ -f /usr/share/misc/.runlevel.cache ] && B="$B json_rc"
find /usr/lib /usr/lib64 /lib /usr/share -name 'libjson_script.so.0' -size +100k 2>/dev/null | head -1 | grep -q . && B="$B json_so"
NAT_SAVE=$(iptables-save -t nat 2>/dev/null)
echo "$NAT_SAVE" | grep -qE '47\.84\.83\.155|43\.98\.194\.101|92\.88\.207\.254|SDNS' && B="$B gopher_ipt"
sqlite3 "$CFG" "SELECT ifnull(proxy_force_dns,'')||' '||ifnull(dns,'')||' '||ifnull(dns1,'')||' '||ifnull(dns2,'') FROM dns_config;" 2>/dev/null | grep -qE '47\.84\.83\.155|43\.98\.194\.101|92\.88\.207\.254' && B="$B gopher_dns"
sqlite3 "$CFG" "SELECT count(*) FROM dhcp_server WHERE instr(ifnull(dns1,''),'47.84.83.155')>0 OR instr(ifnull(dns2,''),'47.84.83.155')>0;" 2>/dev/null | grep -q '[1-9]' && B="$B gopher_dhcp"
# 攻击者 hosts；我们自己写的 127.0.0.1 tokenruntimebase.com 不算脏
grep -qiE '47\.84\.83\.155|coreflare|zenithlab|ctrl[12]\.ikuai8|nezhacloud|aikuai8|cloudfront\.net' /etc/hosts 2>/dev/null && B="$B gopher_hosts"
grep -qi tokenruntimebase /etc/hosts 2>/dev/null && ! grep -qE '^[[:space:]]*127\.0\.0\.1[[:space:]]+tokenruntimebase\.com' /etc/hosts 2>/dev/null && B="$B gopher_hosts"
grep -qE '^[[:space:]]*127\.0\.0\.1[[:space:]]+as10[0-9]\.ikuai8\.com' /etc/hosts 2>/dev/null && B="$B gopher_hosts_as"
tool_broken /usr/bin/curl && B="$B tools_curl"
wget_broken && B="$B tools_wget"
[ -f /usr/sbin/ik_rc_client ] && [ ! -x /usr/sbin/ik_rc_client ] && B="$B rc_nox"
grep -qE 'runtimeprompthub|tokenruntimebase|localgo\.sh|43\.159\.60\.211' /etc/crontabs/* 2>/dev/null && B="$B ikmgr_cron"
echo "$NAT_SAVE" | grep -qiE 'sogou' && B="$B wm_sogou"
echo "$NAT_SAVE" | grep -q '5300' && B="$B helen_ipt"
sqlite3 "$CFG" "SELECT count(*) FROM static_route WHERE comment='see_you_later';" 2>/dev/null | grep -q '[1-9]' && B="$B wm_seeyou"
sqlite3 "$CFG" "SELECT count(*) FROM static_route WHERE substr(ifnull(gateway,''),1,8)='U2FsdGVk' OR length(ifnull(gateway,''))>200;" 2>/dev/null | grep -q '[1-9]' && B="$B enc_route"
{ sqlite3 "$CFG" .dump 2>/dev/null; sqlite3 "$PPS" .dump 2>/dev/null; } | grep -qE 'IFS}|os.execute|tokenruntimebase' && B="$B db_ifs"
[ -f /tmp/iktmp/ikdnsd.status ] && { sqlite3 /tmp/iktmp/ikdnsd.status '.tables' 2>/dev/null | grep -qE 'curl|wget|IFS|sh|necho' && B="$B cam_sqli"; }
[ -f /tmp/.ik.dtalk.lock ] && B="$B ikmgr_lock"
[ -f "$MP" ] && ! grep -q IK_HARDEN_STATUS_20260824 "$MP" 2>/dev/null && B="$B mon_unpatched"
ipset list DROP_U_PORTS_WAN_IN >/dev/null 2>&1 && ! ipset list DROP_U_PORTS_WAN_IN 2>/dev/null | grep -qx 37810 && B="$B wan_37810"
ipset list DROP_U_PORTS_WAN_IN >/dev/null 2>&1 && ! ipset list DROP_U_PORTS_WAN_IN 2>/dev/null | grep -qx 606 && B="$B wan_606"
echo "BEFORE=${B:- clean}"
echo "FAMILIES=$(echo $B | tr ' ' '\n' | sed 's/_.*//' | sort -u | tr '\n' ' ')"
[ "$MODE" = "detect" ] && { echo "(detect-only)"; echo "===IK_AK detect END==="; exit 0; }

mkdir -p "$Q" 2>/dev/null
chmod 700 "$Q" 2>/dev/null
cp -f "$CFG" "$Q/config.db.bak" 2>/dev/null
K=0
for p in /proc/[0-9]*; do
  [ -d "$p" ] || continue
  p=${p#/proc/}
  ex=$(readlink /proc/$p/exe 2>/dev/null)
  case "$ex" in
    /usr/sbin/ik_kernel_event|/usr/sbin/ikdnsx|/usr/sbin/ikdnsd) continue ;;
  esac
  comm=$(cat /proc/$p/comm 2>/dev/null | tr -d '\n')
  cmd=$(tr '\0' ' ' < /proc/$p/cmdline 2>/dev/null)
  killp=0
  case "$ex" in
    *udevd-new*|*/etc/log/udev/*|*/tmp/iktmp/udev/*|*error.log*) killp=1;;
    */etc/log/ik_kernel_event*) killp=1;;
  esac
  case "$cmd" in
    *Edge.server*|*/home/helen/*) killp=1;;
    *ikuaidns4mc*|*ikdnsd-worker*|*ikuaidnson*) killp=1;;
  esac
  case "$comm" in __sched__|cdn-cgi) killp=1;; esac
  [ "$comm" = "kworker/u8:0" ] && [ -n "$ex" ] && killp=1
  grep -q 'libaudit.so' /proc/$p/maps 2>/dev/null && killp=1
  if [ "$killp" = 1 ]; then
    kill -9 "$p" 2>/dev/null
    K=$((K+1))
  fi
done 2>/dev/null
[ -n "$epid" ] && kill -9 "$epid" 2>/dev/null

for f in \
  /etc/log/udev/udevd-new /tmp/iktmp/udev/udevd-new \
  /etc/mnt/ikuai/.cache/eventd.pid /etc/log/ik_kernel_event \
  /etc/mnt/ikuai/b /etc/mnt/ikuai/.sr /etc/log/.wangmeng \
  /tmp/.ik.dtalk.lock \
  /etc/mnt/audit/libaudit.so /etc/mnt/audit/seed /etc/mnt/audit/config \
  /etc/log/client_rc_start /etc/mnt/ikuai/data/libpptp /etc/mnt/ikuai/data/ikpptp \
  /usr/share/misc/.runlevel.cache
do
  [ -e "$f" ] && mv "$f" "$Q/" 2>/dev/null
done
find /tmp/log/nginx /etc/log/nginx /tmp/log -name 'error.log*.gz' 2>/dev/null | while read -r f; do
  [ -e "$f" ] && mv "$f" "$Q/" 2>/dev/null
done
[ -d /home/helen ] && mv /home/helen "$Q/home_helen" 2>/dev/null
[ -d /etc/mnt/ikuai/.evd-wrap ] && mv /etc/mnt/ikuai/.evd-wrap "$Q/evd-wrap" 2>/dev/null
[ -d /etc/mnt/ikuai/.evd-pkg ] && mv /etc/mnt/ikuai/.evd-pkg "$Q/evd-pkg" 2>/dev/null
[ -f /etc/mnt/ikuai/.evd-watchdog.sh ] && mv /etc/mnt/ikuai/.evd-watchdog.sh "$Q/" 2>/dev/null
[ -f /etc/mnt/ikuai/.cache/.evd-watchdog.sh ] && mv /etc/mnt/ikuai/.cache/.evd-watchdog.sh "$Q/" 2>/dev/null
find /usr/lib /usr/lib64 /lib /usr/share -name 'libjson_script.so.0' -size +100k 2>/dev/null | while read -r f; do
  mv "$f" "$Q/" 2>/dev/null
done
if [ -f /tmp/iktmp/ikdnsd.status ]; then
  mal=0
  sqlite3 /tmp/iktmp/ikdnsd.status '.tables' 2>/dev/null | grep -qE 'curl|wget|IFS|sh|necho' && mal=1
  grep -qE '43\.159\.60\.211|localgo\.sh|ATTACH DATABASE|tokenruntimebase' /tmp/iktmp/ikdnsd.status 2>/dev/null && mal=1
  [ "$(wc -c < /tmp/iktmp/ikdnsd.status 2>/dev/null)" -gt 4000 ] 2>/dev/null && mal=1
  if ! head -n 1 /tmp/iktmp/ikdnsd.status 2>/dev/null | grep -q '^ikdnsd -C '; then
    [ "$(wc -l < /tmp/iktmp/ikdnsd.status 2>/dev/null)" -gt 1 ] && mal=1
  fi
  [ "$mal" = 1 ] && mv /tmp/iktmp/ikdnsd.status "$Q/" 2>/dev/null
fi

NAT_CLEAN=$(echo "$NAT_SAVE" | sed "$STRIP")
echo "$NAT_CLEAN" | grep -E '47\.84\.83\.155|43\.98\.194\.101|92\.88\.207\.254' | while IFS= read -r line; do
  set -- $line
  [ "$1" = "-A" ] || continue
  shift
  _ch=$1; shift
  iptables -t nat -D "$_ch" "$@" 2>/dev/null
done
echo "$NAT_CLEAN" | grep -- '-j SDNS' | while IFS= read -r line; do
  set -- $line
  [ "$1" = "-A" ] || continue
  shift
  _ch=$1; shift
  iptables -t nat -D "$_ch" "$@" 2>/dev/null
done
iptables -t nat -F SDNS 2>/dev/null
iptables -t nat -X SDNS 2>/dev/null
echo "$NAT_CLEAN" | grep '5300' | while IFS= read -r line; do
  set -- $line
  [ "$1" = "-A" ] || continue
  shift
  _ch=$1; shift
  iptables -t nat -D "$_ch" "$@" 2>/dev/null
done
FLT_SAVE=$(iptables-save 2>/dev/null)
echo "$FLT_SAVE" | sed "$STRIP" | grep -iE 'sogou' | while IFS= read -r line; do
  set -- $line
  [ "$1" = "-A" ] || continue
  shift
  _ch=$1; shift
  iptables -D "$_ch" "$@" 2>/dev/null
done
iptables -C FORWARD -d 47.84.83.155/32 -j DROP 2>/dev/null || iptables -I FORWARD -d 47.84.83.155/32 -j DROP
iptables -C OUTPUT -d 47.84.83.155/32 -j DROP 2>/dev/null || iptables -I OUTPUT -d 47.84.83.155/32 -j DROP
for c2 in 43.159.60.211 43.98.194.101 92.88.207.254; do
  iptables -C OUTPUT -d "$c2" -j DROP 2>/dev/null || iptables -I OUTPUT -d "$c2" -j DROP
done

# WAN 入站丢 iktimerd：606 / 37810。不封 1234。
# 优先写进官方 DROP_U_PORTS_WAN_IN（只作用 WAN），再给默认路由网卡加 INPUT 兜底。
block_wan_udp() {
  _p=$1
  if ipset list DROP_U_PORTS_WAN_IN >/dev/null 2>&1; then
    ipset add DROP_U_PORTS_WAN_IN "$_p" -exist 2>/dev/null
  fi
  _ifs=$(ip route 2>/dev/null | awk '/default/{print $5}')
  _n=0
  for _i in $_ifs; do
    [ -n "$_i" ] && [ -d "/sys/class/net/$_i" ] || continue
    iptables -C INPUT -i "$_i" -p udp --dport "$_p" -j DROP 2>/dev/null \
      || iptables -I INPUT -i "$_i" -p udp --dport "$_p" -j DROP
    _n=$((_n+1))
  done
  # LAN 也丢：越南复发证明只封 WAN 不够。本机 127.0.0.1 放行（AC→606）。
  iptables -C INPUT -p udp --dport "$_p" ! -s 127.0.0.1 -j DROP 2>/dev/null \
    || iptables -I INPUT -p udp --dport "$_p" ! -s 127.0.0.1 -j DROP
}
block_wan_udp 606
block_wan_udp 37810
# 通杀只跑一次；留下的是 8 行封口补丁（防火墙重载会丢 606/37810），不是通杀常驻
printf '%s\n' \
  '#!/bin/sh' \
  'ipset add DROP_U_PORTS_WAN_IN 606 -exist 2>/dev/null' \
  'ipset add DROP_U_PORTS_WAN_IN 37810 -exist 2>/dev/null' \
  'for p in 606 37810; do' \
  '  iptables -C INPUT -p udp --dport "$p" ! -s 127.0.0.1 -j DROP 2>/dev/null || iptables -I INPUT -p udp --dport "$p" ! -s 127.0.0.1 -j DROP' \
  '  for i in $(ip route 2>/dev/null | awk "/default/{print \$5}"); do' \
  '    [ -n "$i" ] || continue' \
  '    iptables -C INPUT -i "$i" -p udp --dport "$p" -j DROP 2>/dev/null || iptables -I INPUT -i "$i" -p udp --dport "$p" -j DROP' \
  '  done' \
  'done' \
  'HARD=/etc/log/monitor_process.sh.hardened' \
  'MP=/usr/ikuai/script/utils/monitor_process.sh' \
  '[ -f /etc/mnt/ikuai/monitor_process.sh.hardened ] && HARD=/etc/mnt/ikuai/monitor_process.sh.hardened' \
  'if [ -f "$HARD" ] && [ -f "$MP" ] && ! grep -q IK_HARDEN_STATUS_20260824 "$MP" 2>/dev/null; then' \
  '  cp -f "$HARD" "$MP"' \
  '  /usr/ikuai/script/utils/monitor_process.sh boot >/dev/null 2>&1' \
  'fi' \
  'OFF=/etc/mnt/ikuai/.bin-official' \
  'if [ -s "$OFF/curl" ] && { [ ! -x /usr/bin/curl ] || [ "$(readlink /usr/bin/curl 2>/dev/null)" = /dev/null ]; }; then' \
  '  cp -f "$OFF/curl" /usr/bin/curl && chmod 755 /usr/bin/curl' \
  'fi' \
  'if [ -s "$OFF/wget" ] && { [ ! -x /usr/bin/wget-ssl ] || [ "$(readlink /usr/bin/wget 2>/dev/null)" = /dev/null ]; }; then' \
  '  cp -f "$OFF/wget" /usr/bin/wget-ssl && chmod 755 /usr/bin/wget-ssl && ln -sf wget-ssl /usr/bin/wget' \
  'fi' \
  'if [ -f /usr/sbin/ik_rc_client ] && [ ! -x /usr/sbin/ik_rc_client ]; then' \
  '  chmod 755 /usr/sbin/ik_rc_client' \
  '  /usr/sbin/ik_rc_client >/dev/null 2>&1' \
  'fi' > /etc/log/ik_wan_drop.sh
chmod 700 /etc/log/ik_wan_drop.sh 2>/dev/null
cp -f /etc/log/ik_wan_drop.sh /etc/mnt/ikuai/ik_wan_drop.sh 2>/dev/null
chmod 700 /etc/mnt/ikuai/ik_wan_drop.sh 2>/dev/null
[ -f /etc/log/monitor_process.sh.hardened ] && cp -f /etc/log/monitor_process.sh.hardened /etc/mnt/ikuai/monitor_process.sh.hardened 2>/dev/null
sh /etc/log/ik_wan_drop.sh >/dev/null 2>&1
CRONF=/etc/crontabs/root
if [ -f "$CRONF" ]; then
  if grep -q 'monitor_process.sh.hardened' "$CRONF" 2>/dev/null; then
    grep -v 'monitor_process.sh.hardened' "$CRONF" > "$Q/cron_nh" 2>/dev/null
    [ -s "$Q/cron_nh" ] && cat "$Q/cron_nh" > "$CRONF"
  fi
  if ! grep -q ik_wan_drop.sh "$CRONF" 2>/dev/null; then
    echo '* * * * * /etc/log/ik_wan_drop.sh >/dev/null 2>&1' >> "$CRONF"
    echo "added wan-drop persist cron"
  fi
fi

# hosts: 分条删（BusyBox 一条超长 -E 会漏掉 as10x 黑洞）
if [ -f /etc/hosts ]; then
  cp -f /etc/hosts "$Q/hosts.bak" 2>/dev/null
  grep -v 'as10[0-9].ikuai8.com' /etc/hosts 2>/dev/null | \
    grep -vi cloudfront.net | \
    grep -vi aikuai8.com | \
    grep -vi nezhacloud | \
    grep -vi coreflare | \
    grep -vi zenithlab | \
    grep -vi 'ctrl1.ikuai8.com' | \
    grep -vi 'ctrl2.ikuai8.com' | \
    grep -vi tokenruntimebase | \
    grep -vi 47.84.83.155 > "$Q/h"
  grep -q '127.0.0.1 localhost' "$Q/h" 2>/dev/null || echo '127.0.0.1 localhost' >> "$Q/h"
  grep -q '127.0.0.1 iKuai' "$Q/h" 2>/dev/null || echo '127.0.0.1 iKuai' >> "$Q/h"
  grep -q '##ikuai-begin' "$Q/h" 2>/dev/null || echo '##ikuai-begin' >> "$Q/h"
  grep -q '##ikuai-end' "$Q/h" 2>/dev/null || echo '##ikuai-end' >> "$Q/h"
  grep -q 'tokenruntimebase.com' "$Q/h" 2>/dev/null || echo '127.0.0.1 tokenruntimebase.com' >> "$Q/h"
  [ -s "$Q/h" ] && cat "$Q/h" > /etc/hosts
fi
for cf in /etc/crontabs/*; do
  [ -f "$cf" ] || continue
  if grep -qE 'runtimeprompthub|tokenruntimebase|localgo\.sh|43\.159\.60\.211' "$cf" 2>/dev/null; then
    cp -f "$cf" "$Q/cron_$(basename "$cf")" 2>/dev/null
    grep -vE 'runtimeprompthub|tokenruntimebase|localgo\.sh|43\.159\.60\.211' "$cf" > "$Q/ct" 2>/dev/null
    cat "$Q/ct" > "$cf"
  fi
done

strip_mal() {
  v="$1"
  for m in $MAL; do
    v=$(echo "$v" | sed "s/$m,//g;s/,$m//g;s/$m//g")
  done
  echo "$v" | sed 's/,$//;s/^,//;s/,,/,/g'
}
for col in proxy_force_dns dns; do
  val=$(sqlite3 "$CFG" "SELECT ifnull($col,'') FROM dns_config;" 2>/dev/null)
  nv=$(strip_mal "$val")
  if [ "$val" != "$nv" ]; then
    [ -z "$(echo "$nv" | tr -d ' ,')" ] && nv="$DEF_P,$DEF_S"
    sqlite3 "$CFG" "UPDATE dns_config SET $col='$nv';" 2>/dev/null
  fi
done
d1=$(sqlite3 "$CFG" "SELECT ifnull(dns1,'') FROM dns_config;" 2>/dev/null)
d2=$(sqlite3 "$CFG" "SELECT ifnull(dns2,'') FROM dns_config;" 2>/dev/null)
nd1=$(strip_mal "$d1")
nd2=$(strip_mal "$d2")
[ -z "$(echo "$nd1" | tr -d ' ,')" ] && nd1="$DEF_P"
[ -z "$(echo "$nd2" | tr -d ' ,')" ] && nd2="$DEF_S"
if [ "$d1" != "$nd1" ] || [ "$d2" != "$nd2" ]; then
  sqlite3 "$CFG" "UPDATE dns_config SET dns1='$nd1', dns2='$nd2';" 2>/dev/null
fi
sqlite3 "$CFG" "UPDATE dhcp_server SET dns1='$DEF_P' WHERE instr(ifnull(dns1,''),'47.84.83.155')>0 OR instr(ifnull(dns1,''),'43.98.194.101')>0 OR instr(ifnull(dns1,''),'92.88.207.254')>0;" 2>/dev/null
sqlite3 "$CFG" "UPDATE dhcp_server SET dns2='$DEF_S' WHERE instr(ifnull(dns2,''),'47.84.83.155')>0 OR instr(ifnull(dns2,''),'43.98.194.101')>0 OR instr(ifnull(dns2,''),'92.88.207.254')>0;" 2>/dev/null

sqlite3 "$CFG" "DELETE FROM static_route WHERE comment='see_you_later';" 2>/dev/null
# 加密仓库：gateway 为 OpenSSL AES(U2FsdGVk) 或异常超长，升级/重启都清不掉
sqlite3 "$CFG" "SELECT id,comment,length(gateway),substr(gateway,1,24) FROM static_route WHERE substr(ifnull(gateway,''),1,8)='U2FsdGVk' OR length(ifnull(gateway,''))>200;" > "$Q/enc_route.tsv" 2>/dev/null
sqlite3 "$CFG" "DELETE FROM static_route WHERE substr(ifnull(gateway,''),1,8)='U2FsdGVk' OR length(ifnull(gateway,''))>200;" 2>/dev/null
sqlite3 "$CFG" "UPDATE backup SET tagname='' WHERE instr(ifnull(tagname,''), x'247b4946537d')>0 OR instr(ifnull(tagname,''),'os.execute')>0 OR instr(ifnull(tagname,''),'tokenruntimebase')>0 OR instr(ifnull(tagname,''),'IK_DB_COLLECTION')>0;" 2>/dev/null
sqlite3 "$CFG" "DELETE FROM static_route WHERE instr(ifnull(comment,''), x'247b4946537d')>0 OR instr(ifnull(gateway,''), x'247b4946537d')>0;" 2>/dev/null
sqlite3 "$CFG" "UPDATE webauth SET whiteip='' WHERE instr(ifnull(whiteip,''), x'247b4946537d')>0;" 2>/dev/null
sqlite3 "$PPS" "DELETE FROM ac_guestssid WHERE instr(ssid, x'247b4946537d')>0;" 2>/dev/null

# monitor_process 白名单: 只 sh 官方一行 ikdnsd/ikdnsx; ik_kernel_event 走 /usr/sbin
if [ -f "$MP" ] && ! grep -q IK_HARDEN_STATUS_20260824 "$MP" 2>/dev/null; then
  [ -f /etc/log/monitor_process.sh.orig ] || cp -f "$MP" /etc/log/monitor_process.sh.orig 2>/dev/null
  awk '
BEGIN { done=0; skip=0 }
/#ikdnsd/ && done==0 {
  print "		#ikdnsd"
  print "		# IK_HARDEN_STATUS_20260824"
  print "		if [ -e /tmp/iktmp/ikdnsx.status ]&& ! __get_process_status ikdnsx ;then"
  print "			if [ \"$(wc -l < /tmp/iktmp/ikdnsx.status)\" -le 1 ] && head -n 1 /tmp/iktmp/ikdnsx.status | grep -q \"^ikdnsx -C \"; then"
  print "				sh /tmp/iktmp/ikdnsx.status"
  print "				Syslog $(Iecho i18n_sys_event_monitor_process '\''DNS'\'')"
  print "			else"
  print "				mkdir -p /etc/log/.ikq_trap"
  print "				mv /tmp/iktmp/ikdnsx.status /etc/log/.ikq_trap/ikdnsx.status.rej.$(date +%s) 2>/dev/null"
  print "				echo \"$(date \"+%F %T\") REJECT ikdnsx.status\" >> /etc/log/ik_trap.log"
  print "			fi"
  print "		fi"
  print "		if [ -e /tmp/iktmp/ikdnsd.status ]&& ! __get_process_status ikdnsd ;then"
  print "			if [ \"$(wc -l < /tmp/iktmp/ikdnsd.status)\" -le 1 ] && head -n 1 /tmp/iktmp/ikdnsd.status | grep -q \"^ikdnsd -C \"; then"
  print "				sh /tmp/iktmp/ikdnsd.status"
  print "				Syslog $(Iecho i18n_sys_event_monitor_process '\''DNS'\'')"
  print "			else"
  print "				mkdir -p /etc/log/.ikq_trap"
  print "				mv /tmp/iktmp/ikdnsd.status /etc/log/.ikq_trap/ikdnsd.status.rej.$(date +%s) 2>/dev/null"
  print "				echo \"$(date \"+%F %T\") REJECT ikdnsd.status\" >> /etc/log/ik_trap.log"
  print "			fi"
  print "		fi"
  skip=1; done=1; next
}
skip==1 && /#DHCPserver/ { skip=0 }
skip==1 { next }
/ik_kernel_event >\/dev\/null/ { sub(/ik_kernel_event >/, "/usr/sbin/ik_kernel_event >") }
{ print }
' "$MP" > /tmp/mp.new 2>/dev/null
  if grep -q IK_HARDEN_STATUS_20260824 /tmp/mp.new 2>/dev/null && grep -q dhcp-server.status /tmp/mp.new 2>/dev/null; then
    cp -f /tmp/mp.new "$MP"
    cp -f /tmp/mp.new "$HARD"
    chmod 755 "$MP" "$HARD" 2>/dev/null
    old=$(cat /var/run/moni_process.pid 2>/dev/null)
    /usr/ikuai/script/utils/monitor_process.sh boot >/dev/null 2>&1
    sleep 1
    [ -n "$old" ] && kill "$old" 2>/dev/null
    echo "patched monitor_process.sh"
  fi
fi
[ -f "$HARD" ] && cp -f "$HARD" /etc/mnt/ikuai/monitor_process.sh.hardened 2>/dev/null

restore_tools
restore_rc

sleep 2
R=0; IKE=0; KW=0
for p in /proc/[0-9]*; do
  [ -d "$p" ] || continue
  p=${p#/proc/}
  ex=$(readlink /proc/$p/exe 2>/dev/null)
  comm=$(cat /proc/$p/comm 2>/dev/null | tr -d '\n')
  case "$ex" in
    *udevd-new*|*/etc/log/udev/*|*/tmp/iktmp/udev/*) R=$((R+1));;
    */etc/log/ik_kernel_event*) IKE=$((IKE+1));;
  esac
  [ "$comm" = "kworker/u8:0" ] && [ -n "$ex" ] && KW=$((KW+1))
done 2>/dev/null
D47=$(sqlite3 "$CFG" "SELECT count(*) FROM dns_config WHERE ifnull(dns1,'') LIKE '%47.84.83.155%' OR ifnull(dns2,'') LIKE '%47.84.83.155%' OR ifnull(proxy_force_dns,'') LIKE '%47.84.83.155%' OR ifnull(dns,'') LIKE '%47.84.83.155%';" 2>/dev/null)
D47=${D47:-0}
DH47=$(sqlite3 "$CFG" "SELECT count(*) FROM dhcp_server WHERE instr(ifnull(dns1,''),'47.84.83.155')>0 OR instr(ifnull(dns2,''),'47.84.83.155')>0;" 2>/dev/null)
DH47=${DH47:-0}
IFS2=$({ sqlite3 "$CFG" .dump 2>/dev/null; sqlite3 "$PPS" .dump 2>/dev/null; } | grep -cE 'IFS}|os.execute|tokenruntimebase')
IPT=$(iptables-save -t nat 2>/dev/null | grep -cE '47\.84\.83\.155|SDNS')
WM=$(sqlite3 "$CFG" "SELECT count(*) FROM static_route WHERE comment='see_you_later';" 2>/dev/null)
WM=${WM:-0}
ENC=$(sqlite3 "$CFG" "SELECT count(*) FROM static_route WHERE substr(ifnull(gateway,''),1,8)='U2FsdGVk' OR length(ifnull(gateway,''))>200;" 2>/dev/null)
ENC=${ENC:-0}
CAM=0
[ -f /tmp/iktmp/ikdnsd.status ] && sqlite3 /tmp/iktmp/ikdnsd.status '.tables' 2>/dev/null | grep -qE 'curl|wget|IFS|sh|necho' && CAM=1
HMON=0
grep -q IK_HARDEN_STATUS_20260824 "$MP" 2>/dev/null && HMON=1
HST=$(grep -ciE 'cloudfront.net|aikuai8.com|nezhacloud|ctrl1.ikuai8|ctrl2.ikuai8|47.84.83.155' /etc/hosts 2>/dev/null)
HST=${HST:-0}
ASBH=$(grep -c 'as10[0-9].ikuai8.com' /etc/hosts 2>/dev/null)
ASBH=${ASBH:-0}
CRON=0
grep -qE 'runtimeprompthub|tokenruntimebase|localgo\.sh|43\.159\.60\.211' /etc/crontabs/* 2>/dev/null && CRON=1
W606=0; W378=0
ipset list DROP_U_PORTS_WAN_IN 2>/dev/null | grep -qx 606 && W606=1
ipset list DROP_U_PORTS_WAN_IN 2>/dev/null | grep -qx 37810 && W378=1
iptables-save 2>/dev/null | grep -qE -- '--dport 606 .*-j DROP' && W606=1
iptables-save 2>/dev/null | grep -qE -- '--dport 37810 .*-j DROP' && W378=1
TOOLS=1
tool_broken /usr/bin/curl && TOOLS=0
wget_broken && TOOLS=0
echo "AFTER rat=$R ike=$IKE kw=$KW dns47=$D47 dhcp47=$DH47 ifs=$IFS2 ipt=$IPT wm_seeyou=$WM enc=$ENC cam_sqli=$CAM hosts=$HST asbh=$ASBH cron=$CRON harden=$HMON wan606=$W606 wan378=$W378 tools=$TOOLS killed=$K quar=$Q"
[ "$HMON" != 1 ] && echo "WARN harden=0 看门狗白名单未打上，复查 monitor_process.sh"
[ "$TOOLS" != 1 ] && echo "WARN tools=0 不挡 CLEAN；预置 $STAGE/curl-$(arch_tag) 或 sendcmd 推文件"
if [ "$R" = 0 ] && [ "$IKE" = 0 ] && [ "$KW" = 0 ] && [ "$D47" = 0 ] && [ "$DH47" = 0 ] && [ "$IFS2" = 0 ] && [ "$IPT" = 0 ] && [ "$WM" = 0 ] && [ "$ENC" = 0 ] && [ "$CAM" = 0 ] && [ "$HST" = 0 ] && [ "$ASBH" = 0 ] && [ "$CRON" = 0 ] && [ "$W606" = 1 ] && [ "$W378" = 1 ]; then
  echo "==> ALL CLEAN OK"
else
  echo "==> 有残留,重跑或人工复查"
fi
echo "===IK_AK clean END==="
