#!/bin/sh
# iKuai eval-injection + IKMGR persist hotfix - BusyBox ash (iKuai 3.x / 4.x)
# Cloud-pushable, idempotent. Safe to run via curl|sh.
#
# P0-2  ac_wechat_auth.sh           eval old_*$id="$ssid" -> eval "old_*$id=\$ssid"
# P0-1  ac_scan_nearby_{ap,sig}.sh  eval SSID=...         -> strip metachar + printf %b
# P1-1  ap_load.sh / load_config.sh eval echo $x          -> eval "printf '%s\n' $x"
# P1-3  webauth.sh                  neutralize awk whiteip interpolation
#
# IKMGR persist is NOT an LKM. Stock ik_audit_client dlopens
# /etc/mnt/audit/libaudit.so (plugin_init). Malware replaces that .so
# and drops /etc/mnt/audit/seed + config (aikuai8 / CloudFront C2).
# Do NOT delete stock /usr/sbin/ik_kernel_event or /usr/bin/ik_audit_*.
# Do NOT pkill bash -s (cloud push itself may be curl|sh).
#
# Usage: sh ikuai-eval-hotfix.sh
# Exit 0 = success. Non-zero = a required patch failed.

VER=20260813.5
BAK=/etc/log/ikuai-eval-hotfix-bak-$VER
MARKER=/etc/log/.ikuai-eval-hotfix
QDIR=/etc/log/quarantine-eval-hotfix
CFG=/etc/mnt/ikuai/config.db
PSK=/etc/mnt/ikuai/wpa_ppsk.db
WECHAT=/usr/ikuai/script/ac_wechat_auth.sh
NEAR_AP=/usr/ikuai/script/ac_scan_nearby_ap.sh
NEAR_SIG=/usr/ikuai/script/ac_scan_nearby_sig.sh
AP_LOAD=/usr/ikuai/script/utils/ap_load.sh
LOAD_CFG=/usr/ikuai/ac/patch/include/load_config.sh
WEBAUTH=/usr/ikuai/script/webauth.sh
AUDIT_DIR=/etc/mnt/audit

ok=0
fail=0
skip=0

log() { echo "hotfix[$VER] $*"; }

bak() {
	_f=$1
	[ -f "$_f" ] || return 1
	mkdir -p "$BAK" 2>/dev/null || return 1
	_b="$BAK/$(echo "$_f" | sed 's#/#_#g')"
	[ -f "$_b" ] || cp -a "$_f" "$_b"
	return 0
}

wechat_is_patched() {
	grep -qF 'eval "old_intranet_ssid$id=\$ssid"' "$1" \
	&& grep -qF 'eval "old_guestnet_ssid$id=\$ssid"' "$1"
}

patch_wechat() {
	_f=$WECHAT
	if [ ! -f "$_f" ]; then
		log "SKIP $_f (missing)"
		skip=$((skip + 1))
		return 0
	fi
	if wechat_is_patched "$_f"; then
		log "OK   P0-2 $_f already patched"
		ok=$((ok + 1))
		return 0
	fi
	bak "$_f" || { log "FAIL $_f not writable"; fail=$((fail + 1)); return 1; }
	sed -i 's/eval old_intranet_ssid[$]id="[$]ssid"/eval "old_intranet_ssid$id=\\$ssid"/' "$_f"
	sed -i 's/eval old_guestnet_ssid[$]id="[$]ssid"/eval "old_guestnet_ssid$id=\\$ssid"/' "$_f"
	if wechat_is_patched "$_f"; then
		log "FIX  P0-2 $_f"
		ok=$((ok + 1))
	else
		log "FAIL P0-2 $_f did not apply"
		fail=$((fail + 1))
		return 1
	fi
}

patch_nearby() {
	_f=$1
	if [ ! -f "$_f" ]; then
		log "SKIP $_f (missing)"
		skip=$((skip + 1))
		return 0
	fi
	if grep -qF 'ikuai-eval-hotfix P0-1' "$_f"; then
		log "OK   P0-1 $_f already patched"
		ok=$((ok + 1))
		return 0
	fi
	if ! grep -q 'eval SSID=' "$_f"; then
		log "SKIP $_f (no eval SSID)"
		skip=$((skip + 1))
		return 0
	fi
	bak "$_f" || { log "FAIL $_f not writable"; fail=$((fail + 1)); return 1; }
	_sed=/tmp/ikuai-eval-p01.$$.sed
	cat > "$_sed" << 'SED'
/eval SSID=/c\
# ikuai-eval-hotfix P0-1\
SSID=$(printf "%s" "$SSID" | sed 's/[;`$|&]//g')\
SSID=$(printf "%b" "$SSID")
SED
	sed -i -f "$_sed" "$_f"
	rm -f "$_sed"
	if grep -q 'eval SSID=' "$_f"; then
		log "FAIL P0-1 $_f still has eval SSID"
		fail=$((fail + 1))
		return 1
	fi
	log "FIX  P0-1 $_f"
	ok=$((ok + 1))
}

patch_eval_echo() {
	_f=$1
	if [ ! -f "$_f" ]; then
		log "SKIP $_f (missing)"
		skip=$((skip + 1))
		return 0
	fi
	if grep -qF 'eval "printf' "$_f"; then
		log "OK   P1-1 $_f already patched"
		ok=$((ok + 1))
		return 0
	fi
	if ! grep -qF 'eval echo $x' "$_f"; then
		log "SKIP $_f (no eval echo \$x)"
		skip=$((skip + 1))
		return 0
	fi
	bak "$_f" || { log "FAIL $_f not writable"; fail=$((fail + 1)); return 1; }
	sed -i 's/eval echo [$]x/eval "printf '\''%s\\n'\'' \$x"/' "$_f"
	if grep -qF 'eval "printf' "$_f"; then
		log "FIX  P1-1 $_f"
		ok=$((ok + 1))
	else
		log "FAIL P1-1 $_f did not apply"
		fail=$((fail + 1))
		return 1
	fi
}

patch_webauth() {
	_f=$WEBAUTH
	if [ ! -f "$_f" ]; then
		log "SKIP $_f (missing)"
		skip=$((skip + 1))
		return 0
	fi
	if grep -qF 'ikuai-eval-hotfix P1-3' "$_f"; then
		log "OK   P1-3 $_f already patched"
		ok=$((ok + 1))
		return 0
	fi
	if ! grep -q "whiteip=\"'\$whiteip'\"" "$_f"; then
		log "SKIP $_f (no awk whiteip interpolation)"
		skip=$((skip + 1))
		return 0
	fi
	bak "$_f" || { log "FAIL $_f not writable"; fail=$((fail + 1)); return 1; }
	sed -i 's/whiteip="'\''$whiteip'\''"/whiteip=""/g' "$_f"
	sed -i '1a\
# ikuai-eval-hotfix P1-3 neutralized awk whiteip interpolation
' "$_f"
	if grep -q "whiteip=\"'\$whiteip'\"" "$_f"; then
		log "FAIL P1-3 $_f interpolation remains"
		fail=$((fail + 1))
		return 1
	fi
	log "FIX  P1-3 $_f"
	ok=$((ok + 1))
}

audit_is_ikmgr() {
	[ -d "$AUDIT_DIR" ] || return 1
	grep -q IKMGR "$AUDIT_DIR/libaudit.so" 2>/dev/null && return 0
	grep -q '/etc/mnt/audit/seed' "$AUDIT_DIR/libaudit.so" 2>/dev/null && return 0
	grep -qE 'aikuai8|IKMGR|cloudfront.net/api/client/bootstrap' "$AUDIT_DIR/config" "$AUDIT_DIR/seed" 2>/dev/null && return 0
	if [ -f "$AUDIT_DIR/seed" ] && grep -q IKMGR_FORCE_RESTART "$AUDIT_DIR/seed" 2>/dev/null; then
		return 0
	fi
	return 1
}

kill_pid() {
	_pid=$1
	_why=$2
	[ -n "$_pid" ] && [ -d "/proc/$_pid" ] || return 0
	log "KILL pid=$_pid $_why"
	kill -9 "$_pid" 2>/dev/null
}

proc_comm() {
	_c=
	IFS= read -r _c < "$1/comm" 2>/dev/null || _c=$(tr -d '\n\r' < "$1/comm" 2>/dev/null)
	printf '%s' "$_c"
}

is_stock_ikuai_exe() {
	case "$1" in
	/usr/sbin/*|/usr/bin/*)
		return 0
		;;
	esac
	return 1
}

ikmgr_evidence() {
	audit_is_ikmgr && return 0
	[ -f /tmp/ikmgr-once-debug.log ] && grep -qE "aikuai8|cloudfront.net/api/client|ikmgr_runner" /tmp/ikmgr-once-debug.log 2>/dev/null && return 0
	ls /etc/log/quarantine-eval-hotfix/mnt-audit.*/libaudit.so >/dev/null 2>&1 && return 0
	ls /etc/log/quarantine-20260813/.ikmgr-runner >/dev/null 2>&1 && return 0
	return 1
}

stub_ikaudit_update() {
	_f=/usr/ikuai/script/ikaudit_update.sh
	if [ ! -f "$_f" ]; then
		log "SKIP $_f (missing)"
		skip=$((skip + 1))
		return 0
	fi
	if grep -qF "ikuai-eval-hotfix audit-plugin-block" "$_f"; then
		log "OK   ikaudit_update.sh already stubbed"
		ok=$((ok + 1))
		return 0
	fi
	bak "$_f" || { log "FAIL $_f not writable"; fail=$((fail + 1)); return 1; }
	cat > "$_f" << "STUB"
#!/bin/sh
# ikuai-eval-hotfix audit-plugin-block
# Stock updater pulls /etc/mnt/audit/libaudit.so from audit.ikuai8.com
# then SIGUSR2 ik_audit_client. That channel delivered the IKMGR plugin.
# pidof must keep succeeding so monitor_process.sh does not respawn the real updater.
while :; do sleep 3600; done
STUB
	chmod +x "$_f"
	pkill -f /usr/ikuai/script/ikaudit_update.sh 2>/dev/null
	sleep 1
	start-stop-daemon -S -x "$_f" -b >/dev/null 2>&1 || ( "$_f" >/dev/null 2>&1 & )
	log "FIX  stubbed ikaudit_update.sh (block plugin re-fetch)"
	ok=$((ok + 1))
}

kill_ikmgr_live() {
	# 1) stock ik_audit_client that already dlopened the malware plugin
	for _m in /proc/[0-9]*/maps; do
		[ -f "$_m" ] || continue
		grep -q '/etc/mnt/audit/libaudit.so' "$_m" 2>/dev/null || continue
		_pid=${_m#/proc/}
		_pid=${_pid%/maps}
		kill_pid "$_pid" "maps libaudit.so"
	done
	# 2) open fds / deleted captures
	for _d in /proc/[0-9]*; do
		_pid=${_d#/proc/}
		[ -d "$_d/fd" ] || continue
		for _fd in "$_d"/fd/*; do
			_lk=$(readlink "$_fd" 2>/dev/null) || continue
			case "$_lk" in
			*ikmgr*|*nezha-agent*|*ikmgr-runner-capture*|*ikmgr-bootstrap*)
				kill_pid "$_pid" "fd $_lk"
				break
				;;
			esac
		done
	done
	# 3) IKMGR cover names. /proc/pid/comm has a trailing newline — strip it.
	#    Never kill stock /usr/sbin/ik_kernel_event or /usr/bin/ik_audit_*.
	for _d in /proc/[0-9]*; do
		_pid=${_d#/proc/}
		_comm=$(proc_comm "$_d")
		case "$_comm" in
		ik_kernel_event|ik_audit_publis|ikmgr*)
			;;
		*)
			continue
			;;
		esac
		_exe=$(readlink "$_d/exe" 2>/dev/null)
		if is_stock_ikuai_exe "$_exe"; then
			continue
		fi
		kill_pid "$_pid" "cover comm=$_comm exe=$_exe"
	done
	# 4) named helpers (not bash -s)
	pkill -9 -f '/etc/mnt/audit/seed' 2>/dev/null
	pkill -9 -f ac-seed 2>/dev/null
	pkill -9 -f nezha-agent 2>/dev/null
	pkill -9 -f ikmgr 2>/dev/null
}

quarantine_ikmgr() {
	mkdir -p "$QDIR" 2>/dev/null || return 0
	if audit_is_ikmgr; then
		log "QUAR $AUDIT_DIR -> $QDIR (IKMGR plugin persist)"
		mv "$AUDIT_DIR" "$QDIR/mnt-audit.$$" 2>/dev/null
		mkdir -p "$AUDIT_DIR" 2>/dev/null
	else
		log "OK   $AUDIT_DIR has no IKMGR fingerprint"
	fi
	mv /etc/log/.ikmgr-runner "$QDIR/" 2>/dev/null
	mv /etc/log/nezha "$QDIR/" 2>/dev/null
	mv /tmp/.ikmgr-runner-proc "$QDIR/" 2>/dev/null
	mv /tmp/ikmgr-once-debug.log "$QDIR/" 2>/dev/null
	mv /tmp/.ikmgr-bootstrap.* "$QDIR/" 2>/dev/null
	mv /tmp/.ikmgr-runner-capture.* "$QDIR/" 2>/dev/null
	mv /tmp/iktmp/.ikmgr-dns-kernel-start.err "$QDIR/" 2>/dev/null
	rm -f /tmp/.ikmgr-runner-capture.* 2>/dev/null
	log "QUAR ikmgr/nezha leftovers -> $QDIR"
}

scrub_ikdnsd() {
	_f=/tmp/iktmp/ikdnsd.static.conf
	[ -f "$_f" ] || { log "SKIP ikdnsd.static.conf (missing)"; return 0; }
	if grep -qE 'huorong|360safe|aikuai8|ikmgr' "$_f" 2>/dev/null; then
		mkdir -p "$QDIR" 2>/dev/null
		cp -a "$_f" "$QDIR/ikdnsd.static.conf.$$" 2>/dev/null
		: > "$_f"
		log "FIX  wiped IKMGR sinkholes in $_f"
	else
		log "OK   ikdnsd.static.conf has no IKMGR sinkholes"
	fi
}

scrub_db() {
	if [ ! -f "$CFG" ]; then
		log "SKIP $CFG (missing)"
		return 0
	fi
	if [ ! -x /usr/bin/sqlite3 ] && ! command -v sqlite3 >/dev/null 2>&1; then
		log "SKIP sqlite3 not found"
		return 0
	fi
	log "SCRUB sqlite rows containing the IFS token"
	sqlite3 "$CFG" "UPDATE webauth SET whiteip=substr('a',0,0) WHERE whiteip LIKE '%IFS%' OR whiteip LIKE '%ikmgr%' OR whiteip='127.0.0.1' OR whiteip LIKE '127.0.0.1;%';" 2>/dev/null
	if [ -f "$PSK" ]; then
		sqlite3 "$PSK" "DELETE FROM ac_guestssid WHERE ssid LIKE '%IFS%' OR ssid LIKE '%IKMGR%' OR ssid LIKE '%nezha%' OR ssid LIKE '%aikuai8%' OR ssid LIKE '%;%';" 2>/dev/null
		sqlite3 "$PSK" "DELETE FROM ac_intrassid WHERE ssid LIKE '%IFS%' OR ssid LIKE '%IKMGR%' OR ssid LIKE '%;%';" 2>/dev/null
		sqlite3 "$PSK" "VACUUM;" 2>/dev/null
	fi
	sqlite3 "$CFG" "VACUUM;" 2>/dev/null
	_c=$(sqlite3 "$CFG" .dump 2>/dev/null | grep -c IFS)
	_p=0
	[ -f "$PSK" ] && _p=$(sqlite3 "$PSK" .dump 2>/dev/null | grep -c IFS)
	log "SCRUB dump-hits IFS config=$_c psk=$_p (0=clean)"
}

log "start"
if [ ! -w /usr/ikuai/script ]; then
	log "WARN /usr/ikuai/script not writable; /usr is tmpfs and patches vanish on reboot"
fi
# Patch first so a dying agent cannot re-arm via eval.
patch_wechat
patch_nearby "$NEAR_AP"
patch_nearby "$NEAR_SIG"
patch_eval_echo "$AP_LOAD"
patch_eval_echo "$LOAD_CFG"
patch_webauth
# Drop in-memory wechat_auth loops that already ingested payload.
pkill -f ac_wechat_auth.sh 2>/dev/null
kill_ikmgr_live
_need_stub=0
ikmgr_evidence && _need_stub=1
quarantine_ikmgr
ikmgr_evidence && _need_stub=1
if [ "$_need_stub" -eq 1 ]; then
	stub_ikaudit_update
	# Drop in-memory plugin; monitor_process restarts stock client.
	killall -9 ik_audit_client 2>/dev/null
	pkill -9 -f "cloudfront.net/api/client" 2>/dev/null
	pkill -9 -f "scripts/once/" 2>/dev/null
fi
# Race: agent may rewrite sqlite once more while dying.
scrub_db
kill_ikmgr_live
scrub_db
scrub_ikdnsd
if [ "$IKUAI_HOTFIX_FREEZE_DB" = 1 ]; then
	chattr +i "$CFG" 2>/dev/null
	[ -f "$PSK" ] && chattr +i "$PSK" 2>/dev/null
	log "FIX  chattr +i sqlite (undo: chattr -i $CFG $PSK)"
fi
echo "$VER" > "$MARKER" 2>/dev/null
if [ -f /tmp/ikmgr-once-debug.log ]; then
	log "WARN debug log still present after quarantine"
fi
if audit_is_ikmgr; then
	log "WARN IKMGR fingerprint still in $AUDIT_DIR"
	fail=$((fail + 1))
fi
log "done ok=$ok skip=$skip fail=$fail"
[ "$fail" -eq 0 ]
