467 lines
15 KiB
Go
467 lines
15 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"syscall"
|
||
"time"
|
||
"unsafe"
|
||
|
||
"golang.org/x/sys/windows/registry"
|
||
)
|
||
|
||
// 进程相关的Windows API常量和结构
|
||
const (
|
||
TH32CS_SNAPPROCESS = 0x00000002
|
||
)
|
||
|
||
// getUserWxWorkVersion 获取企业微信版本
|
||
func getUserWxWorkVersion(funcAddr uintptr) (string, error) {
|
||
// 首先检查函数指针是否有效
|
||
if funcAddr == 0 {
|
||
return "", fmt.Errorf("GetUserWxWorkVersion函数指针无效")
|
||
}
|
||
|
||
// 调用GetUserWxWorkVersion函数前添加defer recover,防止程序崩溃
|
||
done := false
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
if !done {
|
||
globalLogger.Error("调用GetUserWxWorkVersion时发生panic: %v", r)
|
||
}
|
||
}
|
||
}()
|
||
|
||
// 调用GetUserWxWorkVersion函数
|
||
versionPtr, _, callErr := syscall.Syscall(
|
||
funcAddr,
|
||
0,
|
||
0,
|
||
0,
|
||
0,
|
||
)
|
||
|
||
if versionPtr == 0 || callErr != 0 {
|
||
done = true
|
||
return "", fmt.Errorf("调用GetUserWxWorkVersion失败: 返回值=%d, 错误=%v", versionPtr, callErr)
|
||
}
|
||
|
||
// 安全地将指针转换为Go字符串
|
||
version := ""
|
||
if versionPtr != 0 {
|
||
// 使用defer recover保护指针转换操作
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
done = true
|
||
globalLogger.Error("转换版本字符串时发生panic: %v", r)
|
||
version = ""
|
||
}
|
||
}()
|
||
version = syscall.UTF16ToString((*[1024]uint16)(unsafe.Pointer(versionPtr))[:])
|
||
}
|
||
done = true
|
||
return version, nil
|
||
}
|
||
|
||
// findProcessByName 根据进程名查找进程ID
|
||
func findProcessByName(name string) ([]uint32, error) {
|
||
globalLogger.Info("[辅助程序] 查找进程: %s", name)
|
||
|
||
// 打开系统快照
|
||
hSnapshot, err := syscall.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建进程快照失败: %v", err)
|
||
}
|
||
defer syscall.CloseHandle(hSnapshot)
|
||
|
||
// 初始化进程信息结构 - 使用syscall包中的ProcessEntry32
|
||
var pe32 syscall.ProcessEntry32
|
||
pe32.Size = uint32(unsafe.Sizeof(pe32))
|
||
|
||
// 获取第一个进程
|
||
if err := syscall.Process32First(hSnapshot, &pe32); err != nil {
|
||
return nil, fmt.Errorf("获取第一个进程失败: %v", err)
|
||
}
|
||
|
||
// 存储找到的进程ID
|
||
var processIDs []uint32
|
||
|
||
// 遍历所有进程
|
||
for {
|
||
// 将进程名从UTF-16转换为Go字符串
|
||
processName := syscall.UTF16ToString(pe32.ExeFile[:])
|
||
|
||
// 只匹配完整的进程名,不区分大小写
|
||
if strings.EqualFold(processName, name) {
|
||
|
||
globalLogger.Info("[辅助程序] 找到进程: %s, 进程ID: %d", processName, pe32.ProcessID)
|
||
processIDs = append(processIDs, pe32.ProcessID)
|
||
}
|
||
|
||
// 获取下一个进程
|
||
if err := syscall.Process32Next(hSnapshot, &pe32); err != nil {
|
||
// 如果没有更多进程,跳出循环
|
||
if err == syscall.ERROR_NO_MORE_FILES {
|
||
break
|
||
}
|
||
return nil, fmt.Errorf("获取下一个进程失败: %v", err)
|
||
}
|
||
}
|
||
|
||
if len(processIDs) == 0 {
|
||
return []uint32{}, fmt.Errorf("未找到进程: %s", name)
|
||
}
|
||
|
||
return processIDs, nil
|
||
}
|
||
|
||
// getWxWorkInstallPath 从注册表获取企业微信安装路径
|
||
func getWxWorkInstallPath() (string, error) {
|
||
// 打开注册表项
|
||
key, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\WXWork",
|
||
registry.QUERY_VALUE)
|
||
if err != nil {
|
||
return "", fmt.Errorf("无法打开注册表: %v", err)
|
||
}
|
||
defer key.Close()
|
||
|
||
// 读取安装路径
|
||
installPath, _, err := key.GetStringValue("InstallLocation")
|
||
if err != nil {
|
||
return "", fmt.Errorf("无法获取安装路径: %v", err)
|
||
}
|
||
|
||
// 验证路径是否存在
|
||
exePath := filepath.Join(installPath, "WXWork.exe")
|
||
if _, err := os.Stat(exePath); os.IsNotExist(err) {
|
||
return "", fmt.Errorf("企业微信可执行文件不存在: %s", exePath)
|
||
}
|
||
|
||
return exePath, nil
|
||
}
|
||
|
||
// startWxWorkInstance 启动企业微信新实例
|
||
func startWxWorkInstance(exePath string) (uint32, error) {
|
||
// 创建进程信息
|
||
var si syscall.StartupInfo
|
||
var pi syscall.ProcessInformation
|
||
defer syscall.CloseHandle(pi.Thread)
|
||
defer syscall.CloseHandle(pi.Process)
|
||
|
||
// 准备命令行参数
|
||
cmdLine, err := syscall.UTF16PtrFromString(exePath + " --multi-instance")
|
||
if err != nil {
|
||
return 0, fmt.Errorf("转换命令行参数失败: %v", err)
|
||
}
|
||
|
||
// 启动进程
|
||
err = syscall.CreateProcess(nil, cmdLine, nil, nil, false,
|
||
syscall.CREATE_NEW_PROCESS_GROUP, nil, nil, &si, &pi)
|
||
if err != nil {
|
||
return 0, fmt.Errorf("启动进程失败: %v", err)
|
||
}
|
||
|
||
globalLogger.Info("成功启动企业微信实例,进程ID: %d", pi.ProcessId)
|
||
return pi.ProcessId, nil
|
||
}
|
||
|
||
// InjectWxWork 智能多开并注入DLL
|
||
// 参数1: WxWorkHelper.dll路径
|
||
// 参数2: WXWork.exe路径,传空
|
||
// 返回值: 成功返回企业微信进程ID,失败返回0
|
||
// 函数原型: DWORD __stdcall InjectWxWork(IN LPCSTR szDllPath,IN LPCSTR szWxWorkExePath);
|
||
func InjectWxWork(szDllPath, szWxWorkExePath string) uint32 {
|
||
// 1. 检查DLL文件是否存在
|
||
if _, err := os.Stat(szDllPath); os.IsNotExist(err) {
|
||
globalLogger.Error("DLL文件不存在: %s", szDllPath)
|
||
return 0
|
||
}
|
||
|
||
// 2. 加载Loader DLL获取注入函数
|
||
dllBundle := resolveDLLBundle()
|
||
loaderPath := dllBundle.LoaderPath
|
||
if loaderPath == "" {
|
||
globalLogger.Error("未找到可用Loader DLL: %s", dllBundle.Message)
|
||
return 0
|
||
}
|
||
loaderDLL, err := loadDLL(loaderPath)
|
||
if err != nil {
|
||
globalLogger.Error("加载Loader DLL失败: %v", err)
|
||
return 0
|
||
}
|
||
defer syscall.FreeLibrary(loaderDLL)
|
||
|
||
// 3. 获取Loader函数指针
|
||
loaderFuncs, err := getLoaderFunctions(loaderDLL)
|
||
if err != nil || loaderFuncs.InjectWxWork == 0 {
|
||
globalLogger.Error("获取InjectWxWork函数指针失败: %v", err)
|
||
return 0
|
||
}
|
||
|
||
// 4. 将参数转换为ANSI字符串指针(LPCSTR),以匹配InjectWxWork函数的参数要求
|
||
dllPathPtr, err := syscall.BytePtrFromString(szDllPath)
|
||
if err != nil {
|
||
globalLogger.Error("转换DLL路径失败: %v", err)
|
||
return 0
|
||
}
|
||
|
||
// 企业微信可执行文件路径参数,传空时使用空指针
|
||
var wxWorkExePathPtr *byte
|
||
if szWxWorkExePath != "" {
|
||
wxWorkExePathPtr, err = syscall.BytePtrFromString(szWxWorkExePath)
|
||
if err != nil {
|
||
globalLogger.Error("转换企业微信路径失败: %v", err)
|
||
return 0
|
||
}
|
||
}
|
||
|
||
// 5. 直接使用syscall.Syscall调用InjectWxWork函数进行智能多开并注入DLL
|
||
globalLogger.Info("准备调用InjectWxWork函数,DLL路径: %s,企业微信路径: %s,函数地址: 0x%X",
|
||
szDllPath, szWxWorkExePath, loaderFuncs.InjectWxWork)
|
||
ret, _, callErr := syscall.Syscall(
|
||
loaderFuncs.InjectWxWork, // 函数地址
|
||
2, // 参数数量
|
||
uintptr(unsafe.Pointer(dllPathPtr)), // 参数1: DLL路径 (WxWorkHelper.dll)
|
||
uintptr(unsafe.Pointer(wxWorkExePathPtr)), // 参数2: 企业微信路径 (传空时为NULL)
|
||
0, // 保留参数
|
||
)
|
||
|
||
// 6. 检查函数调用结果
|
||
if ret == 0 {
|
||
// 调用失败,记录详细错误信息
|
||
globalLogger.Error("调用InjectWxWork函数失败,返回值: %d, 错误代码: %v", ret, callErr)
|
||
return 0
|
||
}
|
||
|
||
globalLogger.Info("InjectWxWork函数调用成功,返回进程ID: %d", ret)
|
||
return uint32(ret)
|
||
}
|
||
|
||
func startAdditionalWxWorkInstance(helperDLLPath string) (map[string]interface{}, error) {
|
||
if strings.TrimSpace(helperDLLPath) == "" {
|
||
return nil, fmt.Errorf("helper DLL path is empty")
|
||
}
|
||
if _, err := os.Stat(helperDLLPath); err != nil {
|
||
return nil, fmt.Errorf("helper DLL is unavailable: %w", err)
|
||
}
|
||
|
||
loaderFuncsMutex.Lock()
|
||
loaderFuncs := globalLoaderFuncs
|
||
loaderFuncsMutex.Unlock()
|
||
if loaderFuncs == nil {
|
||
return nil, fmt.Errorf("loader functions are not initialized")
|
||
}
|
||
|
||
if loaderFuncs.InjectWxWorkMultiOpen != 0 {
|
||
if pid, err := injectWxWorkMultiOpen(loaderFuncs.InjectWxWorkMultiOpen, helperDLLPath, ""); err == nil && pid != 0 {
|
||
setInjectionStatus(injectionStatusConnected, pid, "")
|
||
go injectAllWxWorkProcesses(helperDLLPath, pid)
|
||
return map[string]interface{}{
|
||
"success": true,
|
||
"message": "new WeCom instance requested by multi-open",
|
||
"method": "InjectWxWorkMultiOpen",
|
||
"processId": pid,
|
||
"recognizedClientCount": recognizedClientCount(),
|
||
"usableClientCount": usableClientCount(),
|
||
"connectionCount": connectedClientCount(),
|
||
}, nil
|
||
} else if err != nil {
|
||
globalLogger.Warn("[辅助程序] InjectWxWorkMultiOpen failed, fallback to process start: %v", err)
|
||
}
|
||
}
|
||
|
||
exePath, err := getWxWorkInstallPath()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
pid, err := startWxWorkInstance(exePath)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if loaderFuncs.InjectWxWorkPid != 0 {
|
||
if ok, injectErr := injectIntoProcess(loaderFuncs.InjectWxWorkPid, pid, helperDLLPath); injectErr != nil {
|
||
globalLogger.Warn("[辅助程序] fallback instance started but InjectWxWorkPid failed: %v", injectErr)
|
||
} else if ok {
|
||
markPIDInjected(pid)
|
||
}
|
||
}
|
||
go injectAllWxWorkProcesses(helperDLLPath, pid)
|
||
setInjectionStatus(injectionStatusConnected, pid, "")
|
||
return map[string]interface{}{
|
||
"success": true,
|
||
"message": "new WeCom instance started by fallback",
|
||
"method": "CreateProcess+InjectWxWorkPid",
|
||
"processId": pid,
|
||
"recognizedClientCount": recognizedClientCount(),
|
||
"usableClientCount": usableClientCount(),
|
||
"connectionCount": connectedClientCount(),
|
||
}, nil
|
||
}
|
||
|
||
func injectWxWorkMultiOpen(funcAddr uintptr, dllPath string, wxWorkExePath string) (uint32, error) {
|
||
dllPathPtr, err := syscall.BytePtrFromString(dllPath)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
var wxWorkExePathPtr *byte
|
||
if strings.TrimSpace(wxWorkExePath) != "" {
|
||
wxWorkExePathPtr, err = syscall.BytePtrFromString(wxWorkExePath)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
}
|
||
ret, _, callErr := syscall.Syscall(
|
||
funcAddr,
|
||
2,
|
||
uintptr(unsafe.Pointer(dllPathPtr)),
|
||
uintptr(unsafe.Pointer(wxWorkExePathPtr)),
|
||
0,
|
||
)
|
||
if ret == 0 {
|
||
return 0, fmt.Errorf("InjectWxWorkMultiOpen returned 0: %v", callErr)
|
||
}
|
||
return uint32(ret), nil
|
||
}
|
||
|
||
// injectAllWxWorkProcesses tries to inject the helper DLL into every WXWork.exe
|
||
// process. Enterprise WeChat often keeps several WXWork.exe processes alive; the
|
||
// PID returned by InjectWxWork is not always the one that owns message callbacks.
|
||
func injectAllWxWorkProcesses(dllPath string, primaryPID uint32) {
|
||
time.Sleep(1500 * time.Millisecond)
|
||
|
||
loaderFuncsMutex.Lock()
|
||
loaderFuncs := globalLoaderFuncs
|
||
loaderFuncsMutex.Unlock()
|
||
if loaderFuncs == nil || loaderFuncs.InjectWxWorkPid == 0 {
|
||
globalLogger.Warn("[辅助程序] 跳过多进程注入,InjectWxWorkPid函数不可用")
|
||
return
|
||
}
|
||
|
||
processIDs, err := findProcessByName("WXWork.exe")
|
||
if err != nil {
|
||
globalLogger.Warn("[辅助程序] 多进程注入时未找到WXWork.exe: %v", err)
|
||
return
|
||
}
|
||
globalLogger.Info("[辅助程序] 准备对 %d 个WXWork.exe进程尝试注入,主PID: %d", len(processIDs), primaryPID)
|
||
|
||
successCount := 0
|
||
for _, pid := range processIDs {
|
||
if !markPIDInjected(pid) {
|
||
globalLogger.Info("[辅助程序] WXWork进程 %d 已注入过,跳过", pid)
|
||
continue
|
||
}
|
||
ok, injectErr := injectIntoProcess(loaderFuncs.InjectWxWorkPid, pid, dllPath)
|
||
if injectErr != nil {
|
||
unmarkPIDInjected(pid)
|
||
globalLogger.Warn("[辅助程序] WXWork进程 %d 注入失败: %v", pid, injectErr)
|
||
continue
|
||
}
|
||
if ok {
|
||
successCount++
|
||
}
|
||
}
|
||
globalLogger.Info("[辅助程序] WXWork多进程注入完成,成功: %d/%d", successCount, len(processIDs))
|
||
}
|
||
|
||
// injectIntoProcess 向指定进程注入DLL
|
||
// injectIntoProcess 调用InjectWxWorkPid函数将DLL注入到指定的企业微信进程
|
||
// 参数:
|
||
// - funcAddr: InjectWxWorkPid函数地址
|
||
// - pid: 目标企业微信进程ID
|
||
// - dllPath: DLL文件路径
|
||
// 返回值:
|
||
// - 成功与否的布尔值
|
||
// - 错误信息
|
||
func injectIntoProcess(funcAddr uintptr, pid uint32, dllPath string) (bool, error) {
|
||
fmt.Printf("找到要注入的DLL文件: %s\n", dllPath)
|
||
fmt.Printf("目标进程ID: %d\n", pid)
|
||
|
||
// 验证DLL文件是否存在
|
||
if _, err := os.Stat(dllPath); os.IsNotExist(err) {
|
||
globalLogger.Error("[辅助程序] DLL文件不存在: %s", dllPath)
|
||
return false, fmt.Errorf("DLL文件不存在: %s", dllPath)
|
||
}
|
||
|
||
// 检查函数地址是否有效
|
||
if funcAddr == 0 {
|
||
globalLogger.Error("[辅助程序] InjectWxWorkPid函数地址无效")
|
||
return false, fmt.Errorf("InjectWxWorkPid函数地址无效")
|
||
}
|
||
|
||
// 验证目标进程是否存在
|
||
systemProcess, err := os.FindProcess(int(pid))
|
||
if err != nil {
|
||
globalLogger.Error("[辅助程序] 找不到进程ID %d: %v", pid, err)
|
||
return false, fmt.Errorf("找不到目标进程: %v", err)
|
||
}
|
||
systemProcess.Release() // 只是检查进程是否存在,不保留句柄
|
||
|
||
// 将DLL路径转换为ANSI字符串指针(LPCSTR),以匹配InjectWxWorkPid函数的参数要求
|
||
dllPathPtr, err := syscall.BytePtrFromString(dllPath)
|
||
if err != nil {
|
||
fmt.Printf("错误: 无法转换DLL路径: %v\n", err)
|
||
globalLogger.Error("[辅助程序] 转换DLL路径失败: %v", err)
|
||
return false, fmt.Errorf("转换DLL路径失败: %v", err)
|
||
}
|
||
|
||
globalLogger.Info("[辅助程序] 准备注入DLL,路径: %s,目标进程ID: %d,函数地址: 0x%X", dllPath, pid, funcAddr)
|
||
fmt.Printf("函数地址: 0x%X, 参数1(pid): %d, 参数2(dllPath): %s\n", funcAddr, pid, dllPath)
|
||
|
||
// Windows API权限常量
|
||
const (
|
||
PROCESS_QUERY_INFORMATION = 0x0400
|
||
PROCESS_VM_OPERATION = 0x0008
|
||
PROCESS_VM_READ = 0x0010
|
||
PROCESS_VM_WRITE = 0x0020
|
||
// Windows错误代码常量
|
||
ERROR_ACCESS_DENIED = 5
|
||
ERROR_INVALID_PARAMETER = 87
|
||
ERROR_NOT_FOUND = 1168
|
||
)
|
||
|
||
// 检查DLL文件大小
|
||
dllInfo, err := os.Stat(dllPath)
|
||
if err == nil {
|
||
globalLogger.Info("[辅助程序] DLL文件大小: %d 字节", dllInfo.Size())
|
||
}
|
||
|
||
// 调用InjectWxWorkPid函数(DWORD __stdcall InjectWxWorkPid(IN DWORD dwPid, IN LPCSTR szDllPath))
|
||
// 使用与成功示例完全一致的syscall.Syscall调用方式
|
||
fmt.Println("开始调用函数...")
|
||
globalLogger.Info("[辅助程序] 开始调用InjectWxWorkPid函数,函数地址: 0x%X, 进程ID: %d, DLL路径: %s", funcAddr, pid, dllPath)
|
||
ret, _, callErr := syscall.Syscall(
|
||
funcAddr, // 函数地址
|
||
2, // 参数数量
|
||
uintptr(pid), // 参数1: 进程ID
|
||
uintptr(unsafe.Pointer(dllPathPtr)), // 参数2: DLL路径
|
||
0, // 保留参数
|
||
)
|
||
|
||
// 检查返回值和错误信息
|
||
if ret == 0 {
|
||
// 注入失败,记录详细错误信息
|
||
fmt.Printf("注入DLL失败,返回值: %d, 错误代码: %v\n", ret, callErr)
|
||
globalLogger.Error("[辅助程序] 注入DLL失败,返回值: %d, 错误代码: %v", ret, callErr)
|
||
|
||
// 提供详细的错误分析和可能的解决方案
|
||
possibleSolutions := "可能的解决方案:\n"
|
||
possibleSolutions += "1. 确认以管理员权限运行程序\n"
|
||
possibleSolutions += "2. 检查目标进程是否正在运行且未被保护\n"
|
||
possibleSolutions += "3. 确保DLL文件未被占用且路径正确\n"
|
||
possibleSolutions += "4. 确认DLL与目标进程的位数匹配(32位/64位)\n"
|
||
possibleSolutions += "5. 检查企业微信版本是否受支持(3.0.0.1001及以上)"
|
||
fmt.Println(possibleSolutions)
|
||
globalLogger.Info("%s", possibleSolutions)
|
||
|
||
return false, fmt.Errorf("注入DLL失败: 返回值=%d, 错误=%v\n%s", ret, callErr, possibleSolutions)
|
||
} else {
|
||
// 注入成功
|
||
fmt.Printf("向进程ID %d 注入DLL成功,返回值: %d\n", pid, ret)
|
||
globalLogger.Info("[辅助程序] 向进程ID %d 注入DLL成功,返回值: %d", pid, ret)
|
||
return true, nil
|
||
}
|
||
}
|