Files
BFR/box/scripts/box.stealth
2026-08-13 11:56:37 +03:00

158 lines
5.6 KiB
Bash

#!/system/bin/sh
# BFR Stealth - hardening without LSPosed (iptables port hide + API lockdown)
if ! /system/bin/sh -n /data/adb/box/settings.ini 2>/dev/null; then
echo "Err: settings.ini syntax error" >&2
exit 1
fi
scripts_dir="${0%/*}"
. /data/adb/box/settings.ini
STEALTH_CHAIN="sbfr_out"
STEALTH_CHAIN6="sbfr_out6"
STEALTH_INPUT="sbfr_in"
STEALTH_INPUT6="sbfr_in6"
buildVersion=$(getprop ro.build.version.release)
IPV="iptables"
IP6V="ip6tables"
buildVersionMajor=${buildVersion%%.*}
if [ "${buildVersionMajor:-0}" -ge 11 ]; then
IPV="iptables -w 100"
IP6V="ip6tables -w 100"
fi
stealth_ports_collect() {
_ports="${tproxy_port} ${redir_port}"
[ -n "${sidecar_listen_port}" ] && _ports="${_ports} ${sidecar_listen_port}"
[ -n "${sidecar_tt_port}" ] && _ports="${_ports} ${sidecar_tt_port}"
[ -n "${sidecar_olc_port}" ] && _ports="${_ports} ${sidecar_olc_port}"
[ -n "${wgobfs_listen_port}" ] && _ports="${_ports} ${wgobfs_listen_port}"
[ -n "${stealth_api_port}" ] && _ports="${_ports} ${stealth_api_port}"
if [ -f "${clash_config}" ]; then
_ec=$(busybox awk '!/^ *#/ && /external-controller:/{print $2}' "${clash_config}" 2>/dev/null | head -n1)
_ec_port=$(echo "${_ec}" | busybox awk -F: '{print $NF}')
[ -n "${_ec_port}" ] && _ports="${_ports} ${_ec_port}"
# Clash mixed/socks ports if present
_socks=$(busybox awk '!/^ *#/ && /socks-port:/{print $2}' "${clash_config}" 2>/dev/null | head -n1)
[ -n "${_socks}" ] && _ports="${_ports} ${_socks}"
_mixed=$(busybox awk '!/^ *#/ && /mixed-port:/{print $2}' "${clash_config}" 2>/dev/null | head -n1)
[ -n "${_mixed}" ] && _ports="${_ports} ${_mixed}"
fi
if [ -f "${sing_config}" ]; then
_sc=$(${box_dir}/bin/yq '.experimental.clash_api.external_controller // ""' "${sing_config}" 2>/dev/null)
_sc_port=$(echo "${_sc}" | busybox awk -F: '{print $NF}')
[ -n "${_sc_port}" ] && _ports="${_ports} ${_sc_port}"
fi
for _p in ${stealth_extra_ports}; do
_ports="${_ports} ${_p}"
done
echo "${_ports}" | tr ' ' '\n' | grep -E '^[0-9]+$' | sort -u
}
resolve_stealth_uids() {
stealth_uid_file="${box_run}/stealth_uids.list"
: > "${stealth_uid_file}"
for pkg in "${stealth_packages_list[@]}"; do
[ -z "${pkg}" ] && continue
uids=$(pm list packages -U --user all 2>/dev/null | grep "package:${pkg} " | busybox awk -F'uid:' '{print $2}' | busybox awk '{print $1}')
for uid in ${uids}; do
[ -n "${uid}" ] && echo "${uid}" >> "${stealth_uid_file}"
done
done
sort -u "${stealth_uid_file}" -o "${stealth_uid_file}" 2>/dev/null
}
stealth_chain_exists() {
${IPV} -L "${STEALTH_CHAIN}" -n >/dev/null 2>&1
}
stealth_disable() {
for ipt in "${IPV}" "${IP6V}"; do
chain="${STEALTH_CHAIN}"
in_chain="${STEALTH_INPUT}"
[ "${ipt}" = "${IP6V}" ] && chain="${STEALTH_CHAIN6}" && in_chain="${STEALTH_INPUT6}"
${ipt} -D OUTPUT -j "${chain}" >/dev/null 2>&1
${ipt} -F "${chain}" >/dev/null 2>&1
${ipt} -X "${chain}" >/dev/null 2>&1
${ipt} -D INPUT -j "${in_chain}" >/dev/null 2>&1
${ipt} -F "${in_chain}" >/dev/null 2>&1
${ipt} -X "${in_chain}" >/dev/null 2>&1
done
}
stealth_enable() {
[ "${stealth_mode}" != "true" ] && return 0
stealth_disable
resolve_stealth_uids
if [ ! -s "${box_run}/stealth_uids.list" ]; then
log Warning "[Stealth] No observer UIDs resolved - add packages to stealth.list.cfg"
return 0
fi
${IPV} -N "${STEALTH_CHAIN}" 2>/dev/null || ${IPV} -F "${STEALTH_CHAIN}"
${IP6V} -N "${STEALTH_CHAIN6}" 2>/dev/null || ${IP6V} -F "${STEALTH_CHAIN6}"
${IPV} -N "${STEALTH_INPUT}" 2>/dev/null || ${IPV} -F "${STEALTH_INPUT}"
${IP6V} -N "${STEALTH_INPUT6}" 2>/dev/null || ${IP6V} -F "${STEALTH_INPUT6}"
while read -r uid; do
[ -z "${uid}" ] && continue
if [ "${stealth_hide_loopback}" = "true" ]; then
${IPV} -A "${STEALTH_CHAIN}" -m owner --uid-owner "${uid}" -d 127.0.0.0/8 -p tcp -j REJECT --reject-with tcp-reset
${IPV} -A "${STEALTH_CHAIN}" -m owner --uid-owner "${uid}" -d 127.0.0.0/8 -p udp -j REJECT --reject-with icmp-port-unreachable
${IP6V} -A "${STEALTH_CHAIN6}" -m owner --uid-owner "${uid}" -d ::1/128 -p tcp -j REJECT --reject-with tcp-reset
${IP6V} -A "${STEALTH_CHAIN6}" -m owner --uid-owner "${uid}" -d ::1/128 -p udp -j REJECT --reject-with icmp6-port-unreachable
fi
done < "${box_run}/stealth_uids.list"
if [ "${stealth_block_external_api}" = "true" ]; then
for port in $(stealth_ports_collect); do
${IPV} -A "${STEALTH_INPUT}" -p tcp --dport "${port}" ! -s 127.0.0.0/8 -j DROP
${IP6V} -A "${STEALTH_INPUT6}" -p tcp --dport "${port}" ! -s ::1/128 -j DROP
done
fi
${IPV} -C OUTPUT -j "${STEALTH_CHAIN}" >/dev/null 2>&1 || ${IPV} -I OUTPUT 1 -j "${STEALTH_CHAIN}"
${IP6V} -C OUTPUT -j "${STEALTH_CHAIN6}" >/dev/null 2>&1 || ${IP6V} -I OUTPUT 1 -j "${STEALTH_CHAIN6}"
${IPV} -C INPUT -j "${STEALTH_INPUT}" >/dev/null 2>&1 || ${IPV} -I INPUT 1 -j "${STEALTH_INPUT}"
${IP6V} -C INPUT -j "${STEALTH_INPUT6}" >/dev/null 2>&1 || ${IP6V} -I INPUT 1 -j "${STEALTH_INPUT6}"
_count=$(wc -l < "${box_run}/stealth_uids.list" 2>/dev/null || echo 0)
log Info "[Stealth] Port-hide active for ${_count} observer UID(s), loopback=${stealth_hide_loopback}"
}
stealth_apply_quic() {
if [ "${stealth_block_quic}" = "true" ]; then
quic="disable"
fi
}
case "$1" in
enable) stealth_enable ;;
disable) stealth_disable ;;
renew) stealth_disable; stealth_enable ;;
status)
if stealth_chain_exists; then
log Info "[Stealth] Rules active"
${IPV} -L "${STEALTH_CHAIN}" -n -v 2>/dev/null | head -n 20
else
log Info "[Stealth] Rules not active"
fi
;;
*)
echo "usage: $0 {enable|disable|renew|status}"
exit 1
;;
esac