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