增加特征码自动寻址机制,兼容性更强

This commit is contained in:
zhangyang131 2026-06-04 13:18:55 +08:00
parent e1047bcc8b
commit 608934165d
2 changed files with 368 additions and 38 deletions

View File

@ -4,14 +4,15 @@ macOS 微信防撤回工具持续更新欢迎star。
## 最新版本 ## 最新版本
**支持微信 4.1.9 ~ 4.1.10**,适配微信全新 C++ 架构,通过 DYLD 运行时注入实现防撤回。 **支持微信 4.1.x 系列**,适配微信全新 C++ 架构,通过 DYLD 运行时注入实现防撤回。内置**特征码自动寻址**机制,应对微信动态更新。
### 功能 ### 功能
- 对方撤回的消息保留可见 - 对方撤回的消息保留可见
- 自己撤回正常工作 - 自己撤回正常工作
- 撤回时弹出 macOS 系统通知(显示谁撤回了消息) - 撤回时弹出 macOS 系统通知(显示谁撤回了消息,含用户昵称
- 通知开关可随时切换 - 通知开关可随时切换
- 微信动态更新后自动适配(特征码搜索)
### 原理 ### 原理
@ -22,11 +23,16 @@ macOS 微信防撤回工具持续更新欢迎star。
通过读取当前登录用户 ID完整字符串匹配精确区分自己与对方。 通过读取当前登录用户 ID完整字符串匹配精确区分自己与对方。
Hook 安装采用三级查找策略:
1. **快速路径**:硬编码地址 + 完整 5 条指令特征码验证
2. **特征码搜索**:扫描 `wechat.dylib __TEXT` 段,自动定位新版本函数地址
3. **fallback**4.1.9 dispatch slot 写入
### 适用范围 ### 适用范围
- macOS 微信 4.1.9、4.1.10 - macOS 微信 4.1.x 系列4.1.9 / 4.1.10 已验证,更新版本依赖运行时自动寻址)
- Apple Siliconarm64及 Intelx86_64 - Apple Siliconarm64及 Intelx86_64
- macOS Sequoia / Sonoma / Ventura 等(自动处理 provenance 限制) - macOS Sequoia / Sonoma / Ventura / Tahoe 等(自动处理 provenance 限制)
### 使用 ### 使用
@ -79,6 +85,37 @@ macOS 系统自带工具,无需额外安装:
cat /tmp/antirevoke_debug.log # 查看运行时日志 cat /tmp/antirevoke_debug.log # 查看运行时日志
``` ```
### 微信版本更新后
微信支持动态更新(无需经过 App Store更新后 build 号变化可能导致 hook 失效。
脚本会自动应对:
1. **特征码动态搜索**:当硬编码地址失效时,自动扫描 `wechat.dylib``__TEXT` 段查找 `isRevokeMessage` 函数特征码。微信小版本更新(仅函数地址改变、实现不变)的情况下,**自动适配,无需任何操作**。
2. **大版本校验放宽**:脚本只校验 `CFBundleShortVersionString` 是否为 `4.1.x`,不再依赖精确 build 号。新 build 号(如 4.1.11)也能正常安装,运行时通过特征码自动寻址。
3. **失败提示**:如果特征码也匹配不到(微信改了实现),系统通知会弹出提示:
- `WeChatIntercept 需更新` — 微信版本号变化(如 4.1.10 → 4.1.11),需要更新脚本
- `WeChatIntercept 异常` — 已适配的 build 号但仍失败(极罕见)
4. **消息对象偏移失效**:如果 `msg+0x18` 偏移读取的 sender ID 持续出现非可打印 ASCII 内容,累计达到阈值后会弹出 `"快去催 WeChatIntercept 作者更新适配"` 通知。日常对方撤回的多次调用不会误触发。
### 排查步骤
如果防撤回功能不生效:
1. 查看运行时日志:`cat /tmp/antirevoke_debug.log`
2. 关键日志说明:
- `[已适配]` — build 号在已知列表,安装应该成功
- `[未适配]` — build 号未知,靠运行时自动寻址
- `快速路径命中` — 硬编码地址有效,正常工作
- `特征码搜索找到` — 自动找到新地址,正常工作(说明微信地址变了但实现没变)
- `hook 安装失败` — 需要更新脚本,请前往 GitHub 检查最新版本或提交 issue
3. 提交 issue 时附带:
- 微信版本:`defaults read /Applications/WeChat.app/Contents/Info.plist CFBundleShortVersionString` 与 `CFBundleVersion`
- 调试日志:`/tmp/antirevoke_debug.log`
### 已知限制 ### 已知限制
- **聊天框内无撤回提示**:由于微信 4.x 的架构限制C++ 实现 + 符号 strip + 数据库加密),无法在聊天界面内插入系统消息。替代方案为 macOS 系统通知。 - **聊天框内无撤回提示**:由于微信 4.x 的架构限制C++ 实现 + 符号 strip + 数据库加密),无法在聊天界面内插入系统消息。替代方案为 macOS 系统通知。
@ -89,11 +126,11 @@ cat /tmp/antirevoke_debug.log # 查看运行时日志
### 风险说明 ### 风险说明
1.微信每次升级后,地址、结构体字段、运行时行为都可能变化,补丁可能立即失效。 1. 微信每次升级后,地址、结构体字段、运行时行为都可能变化,补丁可能立即失效。脚本内置特征码自动寻址机制可应对函数地址变化,但无法应对函数实现/消息对象结构的根本变化。
2.本项目只承诺仓库内标明的支持版本,不承诺自动兼容未来版本 2. 本项目仅承诺仓库内标明的已验证版本4.1.9 / 4.1.104.1.x 系列其他 build 号通过运行时自动寻址尽力支持,但不保证 100% 兼容
3.本项目仅用于技术研究与兼容性分析,请自行承担使用风险。 3. 本项目仅用于技术研究与兼容性分析,请自行承担使用风险。
--- ---

355
patch.sh
View File

@ -33,15 +33,11 @@ WECHAT_BIN="$WECHAT_APP/Contents/MacOS/WeChat"
DYLIB_DST="$WECHAT_APP/Contents/Resources/WeChatAntiRevoke.dylib" DYLIB_DST="$WECHAT_APP/Contents/Resources/WeChatAntiRevoke.dylib"
DYLIB_INSTALL_NAME="@executable_path/../Resources/WeChatAntiRevoke.dylib" DYLIB_INSTALL_NAME="@executable_path/../Resources/WeChatAntiRevoke.dylib"
# 支持的版本列表
VERSION_419="268602"
VERSION_4110="268824"
print_banner() { print_banner() {
echo "" echo ""
echo "==============================" echo "=============================="
echo " 微信防撤回安装工具" echo " 微信防撤回安装工具"
echo " 适用: macOS / 微信 4.1.9 & 4.1.10" echo " 适用: macOS / 微信 4.1.9+"
echo " 支持: Apple Silicon + Intel" echo " 支持: Apple Silicon + Intel"
echo "==============================" echo "=============================="
echo "" echo ""
@ -53,12 +49,28 @@ check_environment() {
exit 1 exit 1
fi fi
SHORT_VER=$(defaults read "$WECHAT_APP/Contents/Info.plist" CFBundleShortVersionString 2>/dev/null)
VERSION=$(defaults read "$WECHAT_APP/Contents/Info.plist" CFBundleVersion 2>/dev/null) VERSION=$(defaults read "$WECHAT_APP/Contents/Info.plist" CFBundleVersion 2>/dev/null)
if [ "$VERSION" != "$VERSION_419" ] && [ "$VERSION" != "$VERSION_4110" ]; then
echo "[ERROR] 不支持的微信版本 (实际 $VERSION,支持 $VERSION_419 / $VERSION_4110)" if [ -z "$SHORT_VER" ]; then
echo "[ERROR] 无法读取微信版本号,请检查 /Applications/WeChat.app 是否完整"
exit 1 exit 1
fi fi
# 大版本校验:仅支持 4.1.x 系列C++ 架构)
case "$SHORT_VER" in
4.1.*)
echo "[INFO] 微信版本: $SHORT_VER ($VERSION)"
;;
*)
echo "[ERROR] 不支持的微信大版本: $SHORT_VER"
echo " 本工具仅支持 4.1.x 系列"
echo " 旧版 3.x 请使用 Install.sh"
echo " 如果你认为这是误判,请提交 issue"
exit 1
;;
esac
if ! command -v clang &>/dev/null; then if ! command -v clang &>/dev/null; then
echo "[ERROR] 未找到 clang请安装 Xcode Command Line Tools:" echo "[ERROR] 未找到 clang请安装 Xcode Command Line Tools:"
echo " xcode-select --install" echo " xcode-select --install"
@ -99,10 +111,12 @@ compile_dylib() {
echo "[INFO] 编译 hook 动态库..." echo "[INFO] 编译 hook 动态库..."
# 内嵌 hook.m 源码 # 内嵌 hook.m 源码
local SRC_FILE=$(mktemp /tmp/hook_XXXXXX.m) local SRC_FILE="/tmp/antirevoke_hook_src.m"
rm -f "$SRC_FILE"
cat > "$SRC_FILE" << 'HOOK_SOURCE' cat > "$SRC_FILE" << 'HOOK_SOURCE'
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <mach-o/dyld.h> #import <mach-o/dyld.h>
#import <mach-o/loader.h>
#import <mach/mach.h> #import <mach/mach.h>
#import <sys/mman.h> #import <sys/mman.h>
#import <libkern/OSCacheControl.h> #import <libkern/OSCacheControl.h>
@ -233,6 +247,18 @@ static void send_notification(const char *text) {
}); });
} }
// ── 检查 sender 偏移是否仍有效 ───────────────────────────────
// 仅检查前 4 字节是否为可打印 ASCII微信 ID 总以可打印字符开头)
// 缩小检查范围避免误判撤回流程中的二次调用(其 sender 可能是 std::string 元数据)
static _Bool is_valid_sender(const char *s) {
if (s[0] == '\0') return 1; // 空字符串 = 自己撤回确认
for (int i = 0; i < 4; i++) {
unsigned char c = (unsigned char)s[i];
if (c < 0x20 || c > 0x7E) return 0; // 非可打印字符
}
return 1;
}
// ── hook 函数 ──────────────────────────────────────────────── // ── hook 函数 ────────────────────────────────────────────────
__attribute__((visibility("default"))) __attribute__((visibility("default")))
_Bool hook_isRevokeMessage(void *msg) { _Bool hook_isRevokeMessage(void *msg) {
@ -245,6 +271,32 @@ _Bool hook_isRevokeMessage(void *msg) {
const char *sender = (const char *)((uint8_t *)msg + 0x18); const char *sender = (const char *)((uint8_t *)msg + 0x18);
// 检查 sender 偏移是否仍有效(前 4 字节必须可打印 ASCII
// 失效时静默放行return 1避免影响撤回流程的内部状态
// 真正的偏移失效会持续触发,达到阈值时弹一次"催更新"通知
if (!is_valid_sender(sender)) {
ARLOG("WARN: sender 区域非可打印 ASCII跳过此次调用");
// 累计失效次数,达到阈值时弹通知催更新(仅一次)
static int g_invalid_count = 0;
static _Bool g_warned = 0;
g_invalid_count++;
if (g_invalid_count >= 5 && !g_warned) {
g_warned = 1;
char *cmd = (char *)malloc(1024);
if (cmd) {
snprintf(cmd, 1024,
"osascript -e 'display notification \"sender 偏移已失效,快去催 WeChatIntercept 作者更新适配\" "
"with title \"WeChatIntercept 需更新\"' &");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
system(cmd);
free(cmd);
});
}
}
return 1; // 静默放行,不影响业务流程
}
// 自己撤回 → 放行 // 自己撤回 → 放行
if (sender[0] == '\0') return 1; if (sender[0] == '\0') return 1;
if (g_my_id[0] != '\0' && strncmp(sender, g_my_id, strlen(g_my_id)) == 0) return 1; if (g_my_id[0] != '\0' && strncmp(sender, g_my_id, strlen(g_my_id)) == 0) return 1;
@ -285,11 +337,12 @@ _Bool hook_isRevokeMessage(void *msg) {
return 0; return 0;
} }
// ── 查找 wechat.dylib 的 ASLR slide ───────────────────────── // ── 查找 wechat.dylib 的 ASLR slide 和 mach_header ───────────
// 优先匹配 Resources/wechat.dylib核心库Frameworks/ 为 stub 不可用 // 优先匹配 Resources/wechat.dylib核心库Frameworks/ 为 stub 不可用
static uintptr_t find_wechat_slide(void) { static uintptr_t find_wechat_slide(const struct mach_header **out_header) {
uint32_t count = _dyld_image_count(); uint32_t count = _dyld_image_count();
uintptr_t fallback = 0; uintptr_t fallback = 0;
const struct mach_header *fallback_header = NULL;
size_t resLen = strlen(kDylibSuffix_Resources); size_t resLen = strlen(kDylibSuffix_Resources);
size_t fwLen = strlen(kDylibSuffix_Frameworks); size_t fwLen = strlen(kDylibSuffix_Frameworks);
@ -297,14 +350,133 @@ static uintptr_t find_wechat_slide(void) {
const char *name = _dyld_get_image_name(i); const char *name = _dyld_get_image_name(i);
if (!name) continue; if (!name) continue;
size_t len = strlen(name); size_t len = strlen(name);
if (len >= resLen && strcmp(name + len - resLen, kDylibSuffix_Resources) == 0) if (len >= resLen && strcmp(name + len - resLen, kDylibSuffix_Resources) == 0) {
if (out_header) *out_header = _dyld_get_image_header(i);
return (uintptr_t)_dyld_get_image_vmaddr_slide(i); return (uintptr_t)_dyld_get_image_vmaddr_slide(i);
if (len >= fwLen && strcmp(name + len - fwLen, kDylibSuffix_Frameworks) == 0) }
if (len >= fwLen && strcmp(name + len - fwLen, kDylibSuffix_Frameworks) == 0) {
fallback = (uintptr_t)_dyld_get_image_vmaddr_slide(i); fallback = (uintptr_t)_dyld_get_image_vmaddr_slide(i);
fallback_header = _dyld_get_image_header(i);
}
} }
if (out_header) *out_header = fallback_header;
return fallback; return fallback;
} }
// ── 解析 wechat.dylib 的 __TEXT 段范围 ───────────────────────
// 返回 1 = 成功0 = 失败
static _Bool find_text_segment(const struct mach_header *header, uintptr_t slide,
uintptr_t *out_start, size_t *out_size) {
if (!header) return 0;
const uint8_t *p = (const uint8_t *)header;
uint32_t ncmds;
if (header->magic == MH_MAGIC_64) {
p += sizeof(struct mach_header_64);
ncmds = ((const struct mach_header_64 *)header)->ncmds;
} else if (header->magic == MH_MAGIC) {
p += sizeof(struct mach_header);
ncmds = header->ncmds;
} else {
return 0;
}
for (uint32_t i = 0; i < ncmds; i++) {
const struct load_command *lc = (const struct load_command *)p;
if (lc->cmd == LC_SEGMENT_64) {
const struct segment_command_64 *seg = (const struct segment_command_64 *)p;
if (strcmp(seg->segname, "__TEXT") == 0) {
*out_start = (uintptr_t)seg->vmaddr + slide;
*out_size = (size_t)seg->vmsize;
return 1;
}
} else if (lc->cmd == LC_SEGMENT) {
const struct segment_command *seg = (const struct segment_command *)p;
if (strcmp(seg->segname, "__TEXT") == 0) {
*out_start = (uintptr_t)seg->vmaddr + slide;
*out_size = (size_t)seg->vmsize;
return 1;
}
}
p += lc->cmdsize;
}
return 0;
}
// ── 特征码搜索:在 __TEXT 段中查找 isRevokeMessage 函数 ──────
// arm64 特征5 条指令的 isRevokeMessage无 dispatch slot 的 4.1.10 形态)
// LDR W8, [X0, #0xC]; MOV W9, #0x2712; CMP W8, W9; CSET W0, EQ; RET
// 返回函数 VAslide + offset未找到返回 0
static uintptr_t scan_isRevokeMessage_arm64(uintptr_t text_start, size_t text_size) {
static const uint32_t pattern[5] = {
0xB9400C08u, 0x5284E249u, 0x6B09011Fu, 0x1A9F17E0u, 0xD65F03C0u
};
const uint32_t *base = (const uint32_t *)text_start;
size_t count = text_size / 4;
if (count < 5) return 0;
for (size_t i = 0; i + 5 <= count; i++) {
if (base[i] == pattern[0] &&
base[i+1] == pattern[1] &&
base[i+2] == pattern[2] &&
base[i+3] == pattern[3] &&
base[i+4] == pattern[4]) {
return text_start + i * 4;
}
}
return 0;
}
// x86_64 特征完整函数16 字节)
// 55 48 89 E5 (push rbp; mov rbp,rsp)
// 81 7F 0C 12 27 00 00 (cmp [rdi+0xC], 0x2712)
// 0F 94 C0 (sete al)
// 5D C3 (pop rbp; ret)
static uintptr_t scan_isRevokeMessage_x86_64(uintptr_t text_start, size_t text_size) {
static const uint8_t pattern[] = {
0x55, 0x48, 0x89, 0xE5,
0x81, 0x7F, 0x0C, 0x12, 0x27, 0x00, 0x00,
0x0F, 0x94, 0xC0,
0x5D, 0xC3
};
const uint8_t *base = (const uint8_t *)text_start;
if (text_size < sizeof(pattern)) return 0;
for (size_t i = 0; i + sizeof(pattern) <= text_size; i++) {
if (base[i] == pattern[0] &&
memcmp(base + i, pattern, sizeof(pattern)) == 0) {
return text_start + i;
}
}
return 0;
}
// ── 版本检测 ─────────────────────────────────────────────────
// 已知支持的 build4.1.9 (268602)、4.1.10 (268824)
static const char *kKnownBuilds[] = { "268602", "268824", NULL };
static _Bool is_known_build(const char *build) {
if (!build) return 0;
for (int i = 0; kKnownBuilds[i]; i++) {
if (strcmp(build, kKnownBuilds[i]) == 0) return 1;
}
return 0;
}
// 读取 Info.plist 中的 CFBundleVersion + CFBundleShortVersionString
static void read_wechat_version(char *short_ver, size_t short_sz,
char *build, size_t build_sz) {
short_ver[0] = '\0';
build[0] = '\0';
@autoreleasepool {
NSDictionary *info = [[NSBundle bundleWithPath:@"/Applications/WeChat.app"] infoDictionary];
NSString *sv = info[@"CFBundleShortVersionString"];
NSString *bv = info[@"CFBundleVersion"];
if (sv) strncpy(short_ver, [sv UTF8String], short_sz - 1);
if (bv) strncpy(build, [bv UTF8String], build_sz - 1);
}
}
// ── 内存保护工具 ───────────────────────────────────────────── // ── 内存保护工具 ─────────────────────────────────────────────
static kern_return_t make_rw(uintptr_t addr, size_t len) { static kern_return_t make_rw(uintptr_t addr, size_t len) {
uintptr_t page = addr & ~(uintptr_t)0x3FFF; uintptr_t page = addr & ~(uintptr_t)0x3FFF;
@ -360,6 +532,49 @@ static _Bool install_x86_64_trampoline(uintptr_t func_addr, uintptr_t hook_addr)
return 1; return 1;
} }
// ── Hook 安装失败时通知用户 ─────────────────────────────────
static void notify_install_failed(const char *short_ver, const char *build, _Bool known_build) {
if (!is_notify_enabled()) return;
char *cmd = (char *)malloc(2048);
if (!cmd) return;
char title[64];
char body[512];
if (known_build) {
// 已知 build 但仍失败(极罕见)
snprintf(title, sizeof(title), "WeChatIntercept 异常");
snprintf(body, sizeof(body),
"已知版本 %s (%s) hook 安装失败,请查看 /tmp/antirevoke_debug.log",
short_ver, build);
} else {
// 未知 build可能是版本变化或仅 build 号变化
snprintf(title, sizeof(title), "WeChatIntercept 需更新");
snprintf(body, sizeof(body),
"微信版本 %s (build %s) 未适配,防撤回功能已失效。请前往 GitHub 获取最新脚本",
short_ver, build);
}
// 转义 body 中的双引号和反斜杠
char escaped[1024];
int j = 0;
for (int i = 0; body[i] && j < (int)sizeof(escaped) - 2; i++) {
if (body[i] == '"' || body[i] == '\\') escaped[j++] = '\\';
escaped[j++] = body[i];
}
escaped[j] = '\0';
snprintf(cmd, 2048,
"osascript -e 'display notification \"%s\" with title \"%s\"' &",
escaped, title);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
system(cmd);
free(cmd);
});
}
// ── 主 constructor ─────────────────────────────────────────── // ── 主 constructor ───────────────────────────────────────────
__attribute__((constructor)) __attribute__((constructor))
static void hook_init(void) { static void hook_init(void) {
@ -371,43 +586,125 @@ static void hook_init(void) {
init_config_path(); init_config_path();
ARLOG("hook_init 启动"); ARLOG("hook_init 启动");
uintptr_t slide = find_wechat_slide(); // 读取微信版本
if (slide == 0) { ARLOG("ERROR: 未找到 wechat.dylib"); return; } char short_ver[32] = {0};
char build[32] = {0};
read_wechat_version(short_ver, sizeof(short_ver), build, sizeof(build));
_Bool known_build = is_known_build(build);
ARLOG("微信版本: %s (build %s) %s", short_ver, build,
known_build ? "[已适配]" : "[未适配]");
const struct mach_header *header = NULL;
uintptr_t slide = find_wechat_slide(&header);
if (slide == 0) {
ARLOG("ERROR: 未找到 wechat.dylib");
notify_install_failed(short_ver, build, known_build);
return;
}
// 解析 __TEXT 段范围(用于特征码搜索)
uintptr_t text_start = 0;
size_t text_size = 0;
_Bool has_text = find_text_segment(header, slide, &text_start, &text_size);
ARLOG("slide=0x%lx __TEXT=[0x%lx, +0x%zx) found=%d",
(unsigned long)slide, (unsigned long)text_start, text_size, has_text);
uintptr_t hook = (uintptr_t)&hook_isRevokeMessage; uintptr_t hook = (uintptr_t)&hook_isRevokeMessage;
ARLOG("slide=0x%lx hook=0x%lx", (unsigned long)slide, (unsigned long)hook); _Bool installed = 0;
#if defined(__arm64__) || defined(__aarch64__) #if defined(__arm64__) || defined(__aarch64__)
// 1. 先尝试硬编码地址(快速路径)
uintptr_t func_addr = 0;
uintptr_t func_4110 = slide + k4110_FuncVA_arm64; uintptr_t func_4110 = slide + k4110_FuncVA_arm64;
uint32_t head_insn = *(volatile uint32_t *)func_4110; uint32_t head_insn = *(volatile uint32_t *)func_4110;
if (head_insn == 0xB9400C08u) { if (head_insn == 0xB9400C08u) {
if (install_arm64_trampoline(func_4110, hook)) // 进一步验证完整 5 条指令特征码(避免误判)
ARLOG("4.1.10 arm64 trampoline 安装成功"); uint32_t *p = (uint32_t *)func_4110;
if (p[1] == 0x5284E249u && p[2] == 0x6B09011Fu &&
p[3] == 0x1A9F17E0u && p[4] == 0xD65F03C0u) {
func_addr = func_4110;
ARLOG("快速路径命中: 0x%lx", (unsigned long)func_addr);
}
}
// 2. 快速路径失败 → 尝试 4.1.9 slot
if (func_addr == 0) {
void **slot = (void **)(slide + k419_SlotVA_arm64);
// 简单验证:检查 slot 周围是否在 __DATA 段(不严格)
// 先记录,后面如果特征码搜索也失败再尝试 slot
}
// 3. 特征码搜索(兜底)
if (func_addr == 0 && has_text) {
ARLOG("快速路径未命中,开始特征码搜索...");
uintptr_t found = scan_isRevokeMessage_arm64(text_start, text_size);
if (found) {
func_addr = found;
ARLOG("特征码搜索找到: 0x%lx (offset 0x%lx)",
(unsigned long)func_addr, (unsigned long)(func_addr - slide));
}
}
// 4. 安装 trampoline
if (func_addr != 0) {
if (install_arm64_trampoline(func_addr, hook)) {
ARLOG("arm64 trampoline 安装成功");
installed = 1;
}
} else { } else {
// 5. 最后尝试 4.1.9 slot 方式
void **slot = (void **)(slide + k419_SlotVA_arm64); void **slot = (void **)(slide + k419_SlotVA_arm64);
uintptr_t page = (uintptr_t)slot & ~(uintptr_t)0x3FFF; uintptr_t page = (uintptr_t)slot & ~(uintptr_t)0x3FFF;
vm_protect(mach_task_self(), (vm_address_t)page, 0x4000, 0, VM_PROT_READ | VM_PROT_WRITE); kern_return_t kr = vm_protect(mach_task_self(), (vm_address_t)page, 0x4000,
*slot = (void *)hook; 0, VM_PROT_READ | VM_PROT_WRITE);
ARLOG("4.1.9 arm64 slot 写入成功"); if (kr == KERN_SUCCESS) {
*slot = (void *)hook;
ARLOG("4.1.9 arm64 slot 写入fallback");
installed = 1;
}
} }
#elif defined(__x86_64__) #elif defined(__x86_64__)
uintptr_t func_addr = 0;
uintptr_t func_4110_x86 = slide + k4110_FuncVA_x86_64; uintptr_t func_4110_x86 = slide + k4110_FuncVA_x86_64;
uintptr_t func_419_x86 = slide + k419_FuncVA_x86_64; uintptr_t func_419_x86 = slide + k419_FuncVA_x86_64;
const uint32_t kFuncHead = 0xE5894855u; const uint32_t kFuncHead = 0xE5894855u;
// 1. 快速路径
if (*(volatile uint32_t *)func_4110_x86 == kFuncHead) { if (*(volatile uint32_t *)func_4110_x86 == kFuncHead) {
if (install_x86_64_trampoline(func_4110_x86, hook)) func_addr = func_4110_x86;
ARLOG("4.1.10 x86_64 trampoline 安装成功");
} else if (*(volatile uint32_t *)func_419_x86 == kFuncHead) { } else if (*(volatile uint32_t *)func_419_x86 == kFuncHead) {
if (install_x86_64_trampoline(func_419_x86, hook)) func_addr = func_419_x86;
ARLOG("4.1.9 x86_64 trampoline 安装成功"); }
} else {
ARLOG("ERROR: x86_64 函数地址未匹配"); // 2. 特征码搜索
if (func_addr == 0 && has_text) {
ARLOG("快速路径未命中,开始特征码搜索...");
uintptr_t found = scan_isRevokeMessage_x86_64(text_start, text_size);
if (found) {
func_addr = found;
ARLOG("特征码搜索找到: 0x%lx (offset 0x%lx)",
(unsigned long)func_addr, (unsigned long)(func_addr - slide));
}
}
// 3. 安装 trampoline
if (func_addr != 0) {
if (install_x86_64_trampoline(func_addr, hook)) {
ARLOG("x86_64 trampoline 安装成功");
installed = 1;
}
} }
#endif #endif
ARLOG("就绪,等待撤回消息...");
if (!installed) {
ARLOG("ERROR: hook 安装失败 - 微信版本 %s (build %s) 未适配",
short_ver, build);
notify_install_failed(short_ver, build, known_build);
} else {
ARLOG("就绪,等待撤回消息...");
}
}); });
} }
HOOK_SOURCE HOOK_SOURCE
@ -654,10 +951,6 @@ do_install() {
print_banner print_banner
check_environment check_environment
VERSION=$(defaults read "$WECHAT_APP/Contents/Info.plist" CFBundleVersion 2>/dev/null)
SHORT_VER=$(defaults read "$WECHAT_APP/Contents/Info.plist" CFBundleShortVersionString 2>/dev/null)
echo "[INFO] 微信版本: $SHORT_VER ($VERSION)"
# 检查是否已安装 # 检查是否已安装
if [ -f "$DYLIB_DST" ]; then if [ -f "$DYLIB_DST" ]; then
echo "[INFO] 检测到已安装,将重新安装..." echo "[INFO] 检测到已安装,将重新安装..."