GAME=com.tencent.tmgp.dfm
KP=/data/adb/ksu/bin/ksud
MOD=marchkpm
KPM_FILE=/data/adb/kpm/marchkpm.kpm
# 功能表: 名字|libUE4.so 偏移|patch 字节|长度
FEATS='无后|0xA81B95C|003d40bd|4
范围|0x8BA62A0|0070271e1f2003d5|8
内透|0x9D1E308|3f000014|4'
f_name() { echo "$1" | cut -d'|' -f1; }
f_off() { echo "$1" | cut -d'|' -f2; }
f_patch() { echo "$1" | cut -d'|' -f3; }
f_len() { echo "$1" | cut -d'|' -f4; }
CTL() { $KP kpm control $MOD "$1"; }
# 64 位十六进制加法 -> 走驱动 resolve (内核 64 位计算, 支持 0x 前缀)
# resolve -> dmesg: resolve_dec: %llu (0x%llx)
hexadd() {
dmesg -c >/dev/null 2>&1
CTL "resolve $1 $2" >/dev/null 2>&1
sleep 0.2
dmesg 2>/dev/null | grep 'resolve_dec:' | tail -1 | sed 's/.*(0x//; s/).*//'
}
find_pid() {
for p in $(pidof $GAME); do
if grep -q libUE4.so /proc/$p/maps 2>/dev/null; then
echo $p
return
fi
done
}
find_base() {
grep libUE4.so /proc/$1/maps 2>/dev/null | grep 'r-xp' | head -1 | cut -d- -f1
}
get_pid_base() {
PID=$(find_pid)
[ -z "$PID" ] && { echo "[-] 游戏未运行"; return 1; }
BASE=$(find_base $PID)
[ -z "$BASE" ] && { echo "[-] 未找到 libUE4.so 基址"; return 1; }
echo "[*] 进程ID=$PID 基址=0x$BASE"
return 0
}
ensure_kpm() {
if $KP kpm list 2>/dev/null | grep -q "$MOD"; then
return 0
fi
if [ -f "$KPM_FILE" ]; then
$KP kpm load "$KPM_FILE" >/dev/null 2>&1
sleep 1
fi
$KP kpm list 2>/dev/null | grep -q "$MOD" || { echo "[-] 驱动未就绪"; return 1; }
}
# 部署单个功能 (shadow + 该页 sechide)
apply_one() {
NAME=$1; OFF=$2; PATCH=$3; LEN=$4
ADDR=$(hexadd 0x$BASE $OFF)
[ -z "$ADDR" ] && { echo " [失败] $NAME: 地址计算失败"; return 1; }
OUT=$(CTL "shadow $PID 0x$ADDR $PATCH $LEN" 2>&1)
if echo "$OUT" | grep -qi 'Failed\|error\|Unknown'; then
echo " [失败] $NAME: shadow 安装失败"
return 1
fi
# 段级读隐藏: 登记该地址所在页 (4K), TP 读页内任意偏移都返回原字节
PAGE=$(echo "$ADDR" | sed 's/...$/000/')
PAGEEND=$(hexadd 0x$PAGE 0x1000)
CTL "sechide $PID 0x$PAGE 0x$PAGEEND" >/dev/null 2>&1
echo " [生效] $NAME"
return 0
}
ON() {
EXECONLY=1
[ "$2" = "noexec" ] && EXECONLY=0
ensure_kpm || exit 1
get_pid_base || exit 1
# 先清旧状态
CTL "restoreall $PID" >/dev/null 2>&1
CTL "sechide $PID 0 0" >/dev/null 2>&1
CTL "uhide 0" >/dev/null 2>&1
CTL "shadowcfg 12 0" >/dev/null 2>&1
ALLOK=1
for f in $FEATS; do
apply_one "$(f_name "$f")" "$(f_off "$f")" "$(f_patch "$f")" "$(f_len "$f")" || ALLOK=0
done
CTL "uhide 1" >/dev/null 2>&1
if [ "$EXECONLY" = "1" ]; then
CTL "shadowcfg 12 1" >/dev/null 2>&1
fi
if [ "$ALLOK" = "1" ]; then
echo "[+] 全部功能已开启"
else
echo "[-] 部分功能开启失败"
fi
echo ""
VERIFY
}
check_one() {
NAME=$1; OFF=$2; PATCH=$3; LEN=$4
ADDR=$(hexadd 0x$BASE $OFF)
[ -z "$ADDR" ] && { echo " [失败] $NAME: 地址计算失败"; return 2; }
# vread = 物理直读 (绕读路径) -> 应读到 patch 值 = 功能生效
dmesg -c >/dev/null 2>&1
CTL "vread $PID 0x$ADDR $LEN" >/dev/null 2>&1
sleep 0.2
VHEX=$(dmesg 2>/dev/null | grep 'vread' | tail -1 | sed 's/.*B: //; s/ //g')
# hread = 走 access_remote_vm (被 uhide/sechide 拦截) -> 应读到原字节 = 隐藏生效
dmesg -c >/dev/null 2>&1
CTL "hread $PID 0x$ADDR $LEN" >/dev/null 2>&1
sleep 0.2
HHEX=$(dmesg 2>/dev/null | grep 'hread' | tail -1 | sed 's/.*B: //; s/ //g')
PATCH_CMP=$(echo "$PATCH" | tr 'A-F' 'a-f')
if [ -z "$VHEX" ] || [ -z "$HHEX" ]; then
echo " [失败] $NAME: 自检读取无输出"
return 2
fi
if [ "$VHEX" != "$PATCH_CMP" ]; then
echo " [失效] $NAME: 功能未生效"
return 1
fi
if [ "$HHEX" = "$PATCH_CMP" ]; then
echo " [暴露] $NAME: 读路径未隐藏"
return 1
fi
echo " [正常] $NAME"
return 0
}
VERIFY() {
ensure_kpm || exit 1
get_pid_base || exit 1
PASS=1
echo "[*] 运行状态:"
for f in $FEATS; do
check_one "$(f_name "$f")" "$(f_off "$f")" "$(f_patch "$f")" "$(f_len "$f")"
[ $? -eq 0 ] || PASS=0
done
echo ""
if [ "$PASS" = "1" ]; then
echo "[+] HIDE OK"
else
echo "[-] HIDE FAIL (有功能失效或暴露)"
fi
}
OFF() {
ensure_kpm || exit 1
get_pid_base || exit 1
CTL "restoreall $PID" >/dev/null 2>&1
CTL "sechide $PID 0 0" >/dev/null 2>&1
CTL "uhide 0" >/dev/null 2>&1
CTL "shadowcfg 12 0" >/dev/null 2>&1
echo "[+] 已全部还原"
}
case "$1" in
on|"") ON "$@" ;;
check) VERIFY ;;
off) OFF ;;
*) echo "用法: sh df_delta.sh on [noexec] | check | off" ;;
esac