├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── doc ├── 3.9.2.23.md ├── 3.9.5.81.md └── postman.json ├── go_client ├── main.go └── tcpserver │ └── tcpserver.go ├── java_client ├── .gitignore ├── README.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── wxhk │ │ │ ├── WxhkApplication.java │ │ │ ├── constant │ │ │ └── WxMsgType.java │ │ │ ├── controller │ │ │ └── WxMsgController.java │ │ │ ├── infe │ │ │ ├── Resp.java │ │ │ └── SendMsg.java │ │ │ ├── model │ │ │ ├── PrivateChatMsg.java │ │ │ ├── dto │ │ │ │ └── PayoutInformation.java │ │ │ ├── request │ │ │ │ ├── AddFriends.java │ │ │ │ ├── ConfirmThePayment.java │ │ │ │ ├── FindWeChat.java │ │ │ │ ├── ForwardMessages.java │ │ │ │ ├── GetGroupMembers.java │ │ │ │ ├── GetsTheNicknameOfAGroupMember.java │ │ │ │ ├── IncreaseGroupMembership.java │ │ │ │ ├── OpenHook.java │ │ │ │ ├── SendAtText.java │ │ │ │ ├── SendFile.java │ │ │ │ ├── SendImg.java │ │ │ │ ├── SendMsg.java │ │ │ │ ├── SendText.java │ │ │ │ └── ThroughFriends.java │ │ │ └── response │ │ │ │ ├── ContactList.java │ │ │ │ └── GroupMembers.java │ │ │ ├── msg │ │ │ └── WxMsgHandle.java │ │ │ ├── server │ │ │ ├── WxSmgServer.java │ │ │ └── impl │ │ │ │ └── WxSmgServerImpl.java │ │ │ ├── tcp │ │ │ └── vertx │ │ │ │ ├── ArrHandle.java │ │ │ │ ├── InitWeChat.java │ │ │ │ └── VertxTcp.java │ │ │ └── util │ │ │ ├── HttpAsyncUtil.java │ │ │ ├── HttpSendUtil.java │ │ │ └── HttpSyncUtil.java │ └── resources │ │ ├── application.properties │ │ ├── exec │ │ ├── c.exe │ │ └── wxhelper.dll │ │ ├── logback-spring.xml │ │ └── spy.properties │ └── test │ └── java │ └── com │ └── example │ └── wxhk │ ├── WxhkApplicationTests.java │ ├── tcp │ ├── HttpAsyncUtilTest.java │ └── XmlTest.java │ └── util │ └── HttpSendUtilTest.java ├── nodejs_client └── tcp_server.js ├── python ├── 3.9.5.81 │ └── http_client.py ├── client.py ├── decrypt.py ├── http_server.py ├── readme.md └── tcpserver.py ├── source ├── CMakeLists.txt ├── getopt.h └── injector.cc ├── src ├── base64.cpp ├── base64.h ├── config.cc ├── config.h ├── db.cc ├── db.h ├── dllMain.cc ├── export.asm ├── export.h ├── global_context.cc ├── global_context.h ├── hooks.cc ├── hooks.h ├── http_client.cc ├── http_client.h ├── http_server.cc ├── http_server.h ├── http_server_callback.cc ├── http_server_callback.h ├── log.cc ├── log.h ├── lz4.c ├── lz4.h ├── manager.cc ├── manager.h ├── mongoose.c ├── mongoose.h ├── pch.h ├── singleton.h ├── thread_pool.cc ├── thread_pool.h ├── tinyxml2.cpp ├── tinyxml2.h ├── utils.cc ├── utils.h └── wechat_function.h ├── tool └── injector │ ├── ConsoleApplication.exe │ ├── injector.dll │ ├── readme.md │ └── 微信DLL注入器V1.0.3.exe └── weChatHook-java ├── .idea ├── .gitignore ├── compiler.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml └── misc.xml ├── README.md ├── pom.xml └── src └── main ├── java └── com │ └── example │ ├── client │ └── WeChatHookClient.java │ └── service │ └── WeChatHookNettyServer.java └── resources ├── ConsoleInject.exe ├── injector.dll └── wxhelper.dll /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | #*.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | #*.exe 31 | *.out 32 | *.app 33 | CMakePresets.json 34 | .vscode 35 | out -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "spdlog"] 2 | path = spdlog 3 | url = https://github.com/gabime/spdlog 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.0) 2 | # include(ExternalProject) 3 | project(wxhelper VERSION 1.0.0) 4 | enable_language(ASM_MASM) 5 | 6 | 7 | 8 | # SET(CMAKE_ASM_NASM_FLAGS "-w0") 9 | 10 | 11 | set(CMAKE_CXX_STANDARD 17) 12 | set(CMAKE_CXX_STANDARD_REQUIRED True) 13 | set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) 14 | 15 | 16 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D '_UNICODE' /D 'UNICODE' ") 17 | 18 | file(GLOB CPP_FILES ${PROJECT_SOURCE_DIR}/src/*.cc ${PROJECT_SOURCE_DIR}/src/*.cpp ${PROJECT_SOURCE_DIR}/src/*.c ) 19 | 20 | file(GLOB ASM_FILES ${PROJECT_SOURCE_DIR}/src/*.asm ) 21 | 22 | include_directories(${VCPKG_INSTALLED_DIR}/x64-windows/include ${PROJECT_SOURCE_DIR}/spdlog/include ${DETOURS_INCLUDE_DIRS}) 23 | # include_directories(${VCPKG_INSTALLED_DIR}/x64-windows/include ${PROJECT_SOURCE_DIR}/spdlog/include ) 24 | 25 | 26 | 27 | add_subdirectory(spdlog) 28 | add_subdirectory(source) 29 | 30 | # find_package(spdlog CONFIG REQUIRED) 31 | 32 | find_package(nlohmann_json CONFIG REQUIRED) 33 | 34 | find_path(DETOURS_INCLUDE_DIRS "detours/detours.h") 35 | find_library(DETOURS_LIBRARY detours REQUIRED) 36 | 37 | 38 | add_library(wxhelper SHARED ${CPP_FILES} ${ASM_FILES} ) 39 | 40 | 41 | 42 | target_link_libraries(wxhelper PRIVATE nlohmann_json::nlohmann_json) 43 | target_link_libraries(wxhelper PRIVATE spdlog::spdlog spdlog::spdlog_header_only) 44 | target_link_libraries(wxhelper PRIVATE ${DETOURS_LIBRARY}) 45 | 46 | 47 | SET_TARGET_PROPERTIES(wxhelper PROPERTIES LINKER_LANGUAGE C 48 | ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib 49 | LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib 50 | RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib 51 | OUTPUT_NAME "wxhelper" 52 | PREFIX "") 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ttttupup 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wxhelper 2 | wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26, 3.9.0.28, 3.9.2.23,3.9.2.26版本。 3 | #### 免责声明: 4 | 本仓库发布的内容,仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关! 5 | 6 | #### 项目说明: 7 | 本项目是逆向练习项目,可能会造成封号等后果。请自行承担风险。仅用于学习研究,请勿于非法用途。 8 | 9 | #### 实现原理: 10 | 逆向分析PC端微信客户端,定位相关功能关键Call,编写dll调用关键Call。然后将该dll文件注入到微信进程。 11 | dll在注入成功时,创建了一个默认端口为19088的http服务端,然后所有的功能直接可以通过http协议调用。 12 | ``` 13 | 14 | |---------------- 15 | -------------------------- 注入 | WeChat.exe | 16 | | ConsoleApplication.exe |————————> |---------------- -------------- 访问 --------- 17 | | | | wxhelper.dll |————————>| 启动http服务 | <----------| clent | 18 | |-------------------------- |----------------- -------------- -------- 19 | 20 | ``` 21 | #### 使用说明: 22 | 支持的版本3.8.0.41、3.8.1.26、3.9.0.28、3.9.2.23、3.9.2.26 、3.9.5.81。 23 | 源码和主要实现在相应的分支内。 24 | src:主要的dll代码 25 | tool:简单的注入工具,一个是控制台,一个是图形界面。 26 | python: tcpserver.py: 简单的服务器,用以接收消息内容。decrpty.py: 微信数据库解密工具。 http_server.py:http server端。 27 | source: 简单的命令行远程注入源码。 28 | 其他目录:热心作者提供的一些客户端。 29 | 30 | #### 0.首先安装对应的版本的微信,分支名称即代表的是微信对应的版本。dll的前缀都会带有微信版本。 31 | #### 1.使用注入工具注入wxhelper.dll,注入成功后,即可通过postman直接调用对应的接口。 32 | #### 2.可以使用python/clent.py进行简单测试。 33 | 34 | 35 | ##### 特别注意: 36 | 37 | ##### 1.hook相关的接口都需要先调用对应的hook接口,server端才会收到相应消息。 38 | ##### 2.注意个别接口在一些版本没有实现,功能预览里没有的功能就是没有实现。 39 | ##### 3.如果注入不成功,请先检查注入工具,或者使用其他注入工具。 40 | ##### 4.相关功能只在win11环境下进行简单测试,其他环境无法保证。 41 | 42 | 43 | #### 参与项目 44 | 个人精力和水平有限,项目还有许多不足,欢迎提出 issues 或 pr。期待你的贡献。 45 | 46 | 47 | 48 | #### 问题讨论 49 | 个人常用的方法,请参考https://github.com/ttttupup/wxhelper/wiki 50 | 使用上的问题,可查询https://github.com/ttttupup/wxhelper/discussions 51 | 数据库解密,请参考https://github.com/ttttupup/wxhelper/wiki 52 | 个人精力有限,只维护最新版本,旧版本的bug会在新版本中修复,不维护旧版本。 53 | 54 | 55 | #### 编译环境 56 | 57 | Visual Studio 2022(x86) 58 | 59 | Visual Studio code 60 | 61 | cmake 62 | 63 | vcpkg 64 | #### 编译构建 65 | 66 | 先准备好编译环境。 67 | #### 以下是x86环境构建,3.9.5.81是x64环境,具体参考对应分支。 68 | ``` 69 | cd wxhelper 70 | mkdir build 71 | cd build 72 | cmake -DCMAKE_C_COMPILER=cl.exe \ 73 | -DCMAKE_CXX_COMPILER=cl.exe \ 74 | -DCMAKE_BUILD_TYPE=Debug \ 75 | -DCMAKE_INSTALL_PREFIX=C:/other/codeSource/windows/wxhelper/out/install/x86-debug \ 76 | -DCMAKE_TOOLCHAIN_FILE:FILEPATH=C:/vcpkg/scripts/buildsystems/vcpkg.cmake \ 77 | -SC:/wxhelper \ 78 | -BC:/wxhelper/build/x86-debug\ 79 | -G Ninja 80 | 81 | cmake --build .. 82 | ``` 83 | 84 | 以下是在vscode中操作,vs中的操作类似。 85 | 1.安装vcpkg,cmake,vscode 86 | 87 | 2.安装相应的库,如果安装的版本不同,则根据vcpkg安装成功后提示的find_package修改CMakeLists.txt内容即可。或者自己编译。 88 | ``` 89 | vcpkg install mongoose 90 | vcpkg install nlohmann-json 91 | ``` 92 | 3.vscode 配置CMakePresets.json,主要设置CMAKE_C_COMPILER 和CMAKE_CXX_COMPILER 为cl.exe.参考如下 93 | ``` 94 | { 95 | "name": "x86-release", 96 | "displayName": "x86-release", 97 | "description": "Sets Ninja generator, build and install directory", 98 | "generator": "Ninja", 99 | "binaryDir": "${sourceDir}/out/build/${presetName}", 100 | "architecture":{ 101 | "value": "x86", 102 | "strategy": "external" 103 | }, 104 | "cacheVariables": { 105 | "CMAKE_C_COMPILER": "cl.exe", 106 | "CMAKE_CXX_COMPILER": "cl.exe", 107 | "CMAKE_BUILD_TYPE": "Release", 108 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", 109 | "CMAKE_TOOLCHAIN_FILE": { 110 | "value": "C:/soft/vcpkg/scripts/buildsystems/vcpkg.cmake", 111 | "type": "FILEPATH" 112 | } 113 | }, 114 | "environment": { 115 | 116 | } 117 | 118 | } 119 | ``` 120 | 4.执行cmake build vscode中右键configure all projects,在Terminal中点击Run Task,如没有先配置build任务,然后运行即可 121 | 122 | 5.命令行注入工具,注入命令 123 | ``` javascript 124 | //-i 注入程序名 -p 注入dll路径 125 | // -u 卸载程序名 -d 卸载dll名称 126 | // -m pid 关闭微信互斥体,多开微信 127 | // -P port 指定http端口,需要使用 specify-port 分支的生成的dll 128 | // -I 注入程序的pid 129 | //注入 130 | ConsoleInject.exe -i demo.exe -p E:\testInject.dll 131 | //卸载 132 | ConsoleInject.exe -u demo.exe -d testInject.dll 133 | //多开 134 | ConsoleInject.exe -m 1222 135 | // 注入并指定http端口 136 | ConsoleInject.exe -i demo.exe -p E:\testInject.dll -P 18888 137 | // 注入指定pid并关闭多开限制 138 | ConsoleInject.exe -I 15048 -p E:\testInject.dll -m 15048 139 | 140 | ``` 141 | 142 | 6.如果想改变端口,可以在微信目录下创建config.ini配置文件,修改端口即可。不创建则默认端口19088。 143 | ``` shell 144 | [config] 145 | port=19099 146 | 147 | ``` 148 | 149 | #### 更新说明 150 | 2022-12-26 : 增加3.8.1.26版本支持。 151 | 152 | 2022-12-29 : 新增提取文字功能。 153 | 154 | 2023-01-02 : 退出微信登录。 155 | 156 | 2023-01-31 : 新增修改群昵称(仅支持3.8.1.26)。 157 | 158 | 2023-02-01 : 新增拍一拍(仅支持3.8.1.26)。 159 | 160 | 2023-02-04 : 新增群消息置顶和取消置顶。 161 | 162 | 2023-02-06 : 新增确认收款。 163 | 164 | 2023-02-08 : 新增朋友圈消息。 165 | 166 | 2023-02-09 : 新增3.9.0.28版本基础功能。 167 | 168 | 2023-02-13 : 新增群昵称和微信名称。 169 | 170 | 2023-02-17 : 新增通过wxid添加好友,搜索查找微信。 171 | 172 | 2023-03-02 : 新增发送@消息 173 | 174 | 2023-03-04 : 新增消息附件下载 175 | 176 | 2023-03-21 : 新增hook语音 177 | 178 | 2023-03-30 : 新增获取语音文件(推荐使用这个非hook接口) 179 | 180 | 2023-04-08 : 3.9.2.23版本功能更新 181 | 182 | 2023-06-05 :3.9.2.26版本更新 183 | 184 | 2023-07-07 :3.9.5.81版本更新 185 | 186 | #### 功能预览: 187 | 0.检查是否登录 188 | 1.获取登录微信信息 189 | 2.发送文本 190 | 3.发送@文本 191 | 5.发送图片 192 | 6.发送文件 193 | 9.hook消息 194 | 10.取消hook消息 195 | 11.hook图片 196 | 12.取消hook图片 197 | 13.hook语音 198 | 14.取消hook语音 199 | 17.删除好友 200 | 19.通过手机或qq查找微信 201 | 20.通过wxid添加好友 202 | 23.通过好友申请 203 | 25.获取群成员 204 | 26.获取群成员昵称 205 | 27.删除群成员 206 | 28.增加群成员 207 | 31.修改群昵称 208 | 32.获取数据库句柄 209 | 34.查询数据库 210 | 35.hook日志 211 | 36.取消hook日志 212 | 40.转发消息 213 | 44.退出登录 214 | 45.确认收款 215 | 46.联系人列表 216 | 47.获取群详情 217 | 48.获取解密图片 218 | 49.图片提取文字ocr 219 | 50.拍一拍 220 | 51.群消息置顶消息 221 | 52.群消息取消置顶 222 | 53.朋友圈首页 223 | 54.朋友圈下一页 224 | 55.获取联系人或者群名称 225 | 56.获取消息附件(图片,视频,文件) 226 | 57.获取语音文件(silk3格式) 227 | 58.登录二维码 228 | 59.邀请入群 229 | 60.获取群/群成员详情 230 | 61.撤回消息 231 | 62.发送公众号消息 232 | 63.转发公众号消息 233 | 64.发送小程序 234 | 65.退款 235 | 66.下载头像(勿用,没什么用) 236 | #### 感谢 237 | https://github.com/ljc545w/ComWeChatRobot 238 | 239 | https://github.com/NationalSecurityAgency/ghidra 240 | 241 | https://github.com/x64dbg/x64dbg 242 | 243 | #### 讨论组 244 | https://t.me/+LmvAauweyUpjYzJl 245 | -------------------------------------------------------------------------------- /go_client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go_client/tcpserver" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | log.SetFlags(log.LstdFlags | log.Lshortfile) 10 | tcpserver.Listen(19099) 11 | } 12 | -------------------------------------------------------------------------------- /go_client/tcpserver/tcpserver.go: -------------------------------------------------------------------------------- 1 | package tcpserver 2 | 3 | import ( 4 | "bufio" 5 | "log" 6 | "net" 7 | "strconv" 8 | ) 9 | 10 | func Listen(port int) { 11 | p := strconv.Itoa(port) 12 | adress := "127.0.0.1:" + p 13 | ln, err := net.Listen("tcp", adress) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | defer ln.Close() 18 | log.Println("tcp server started") 19 | for { 20 | conn, err := ln.Accept() 21 | if err != nil { 22 | log.Println(err) 23 | continue 24 | } 25 | go handle(conn) 26 | } 27 | } 28 | 29 | func handle(conn net.Conn) { 30 | defer func() { 31 | if err := recover(); err != nil { 32 | log.Println("发生了未处理的异常", err) 33 | } 34 | }() 35 | defer conn.Close() 36 | scanner := bufio.NewScanner(conn) 37 | for scanner.Scan() { 38 | line := scanner.Bytes() 39 | log.Println("收到消息:", string(line)) 40 | } 41 | if err := scanner.Err(); err != nil { 42 | log.Println("错误:", err) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /java_client/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /java_client/README.md: -------------------------------------------------------------------------------- 1 | 环境为jdk17 2 | 执行之后会在当前项目所处磁盘根路径生成一个exec文件夹,然后会把src/main/resources/exec下的文件放在那避免因为路径问题出错 3 | java_client/src/main/resources/exec/c.exe 为注入器,只不过把名字改短了,更新的话换成最新版,改个名字就行, wxhelper.dll同理 4 | 5 | 项目启动之后,会生成一个tcp服务端,用来接受hook信息,然后把接收的信息放在队列中,之后用一个线程去循环处理消息. 6 | 具体实现可以看 7 | ```com.example.wxhk.tcp.vertx```包下的三个文件 8 | 9 | com.example.wxhk.tcp.vertx.VertxTcp 这个是tcp服务端,接受信息 10 | 11 | com.example.wxhk.tcp.vertx.InitWeChat 微信环境初始化 12 | 13 | com.example.wxhk.tcp.vertx.ArrHandle 循环消息处理 14 | 15 | com.example.wxhk.server.WxSmgServer 为消息处理接口,实现其中的方法即可 16 | 17 | ![image](https://github.com/sglmsn/wxhelper/assets/36943585/59d49401-a492-46a9-8ed9-dab7fb1822b4) 18 | 19 | 20 | 21 | 启动项目需要去修改配置文件的微信路径 22 | -------------------------------------------------------------------------------- /java_client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.2 9 | 10 | 11 | com.example 12 | wxhk 13 | 0.0.1-SNAPSHOT 14 | wxhk 15 | wxhk 16 | 17 | 21 18 | 4.5.3 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-mail 24 | 25 | 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-validation 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | io.netty 42 | netty-all 43 | 4.1.105.Final 44 | 45 | 46 | com.squareup.okhttp3 47 | okhttp 48 | 4.11.0 49 | 50 | 51 | 52 | io.vertx 53 | vertx-core 54 | ${vertx-web-client.version} 55 | 56 | 57 | io.vertx 58 | vertx-web 59 | ${vertx-web-client.version} 60 | 61 | 62 | io.vertx 63 | vertx-web-client 64 | ${vertx-web-client.version} 65 | 66 | 67 | io.vertx 68 | vertx-mysql-client 69 | ${vertx-web-client.version} 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-devtools 74 | runtime 75 | true 76 | 77 | 78 | org.springframework.boot 79 | spring-boot-configuration-processor 80 | true 81 | 82 | 83 | org.projectlombok 84 | lombok 85 | true 86 | 87 | 88 | org.dromara.hutool 89 | hutool-all 90 | 6.0.0.M3 91 | 92 | 93 | com.fasterxml.jackson.core 94 | jackson-databind 95 | 2.15.1 96 | 97 | 98 | org.springframework.boot 99 | spring-boot-starter-test 100 | test 101 | 102 | 103 | 104 | 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-jar-plugin 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | **/com/example/wxhk/** 121 | 122 | 123 | 124 | 125 | 126 | 127 | true 128 | lib/ 129 | false 130 | 131 | com.example.wxhk.WxhkApplication 132 | 133 | 134 | 135 | resources/ 136 | 137 | 138 | 139 | ${project.build.directory}/pack/ 140 | 141 | 142 | 143 | org.apache.maven.plugins 144 | maven-dependency-plugin 145 | 146 | 147 | 148 | copy-dependencies 149 | package 150 | 151 | copy-dependencies 152 | 153 | 154 | 155 | ${project.build.directory}/pack/lib 156 | 157 | 158 | 159 | 160 | 161 | maven-resources-plugin 162 | 163 | 164 | 165 | copy-resources 166 | package 167 | 168 | copy-resources 169 | 170 | 171 | 172 | 173 | src/main/resources 174 | 175 | 176 | 177 | ${project.build.directory}/pack/resources 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/WxhkApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk; 2 | 3 | import io.vertx.core.Vertx; 4 | import io.vertx.core.VertxOptions; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | 8 | @SpringBootApplication 9 | public class WxhkApplication { 10 | public static final Vertx vertx; 11 | 12 | static { 13 | vertx = Vertx.vertx(new VertxOptions().setWorkerPoolSize(5).setEventLoopPoolSize(5)); 14 | } 15 | 16 | //ConsoleInject.exe -i WeChat.exe -p D:\wxhelper.dll 17 | //ConsoleApplication.exe -I 4568 -p C:\wxhelper.dll -m 17484 -P 1888 18 | public static void main(String[] args) { 19 | SpringApplication.run(WxhkApplication.class, args); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.constant; 2 | 3 | /** 4 | * 接受到的微信消息类型 5 | * 6 | * @author wt 7 | * @date 2023/05/26 8 | */ 9 | public enum WxMsgType { 10 | 11 | /** 12 | * 13 | */ 14 | 私聊信息(1), 15 | 好友请求(37), 16 | 收到名片(42), 17 | 表情(47), 18 | 转账和收款(49), 19 | 收到转账之后或者文件助手等信息(51), 20 | 21 | 入群(10000), 22 | /** 23 | * 扫码触发,会触发2次, 有一次有编号,一次没有,还有登陆之后也有,很多情况都会调用这个 24 | */ 25 | 扫码触发(10002), 26 | 27 | ; 28 | Integer type; 29 | 30 | WxMsgType(Integer type) { 31 | this.type = type; 32 | } 33 | 34 | public Integer getType() { 35 | return type; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.controller; 2 | 3 | 4 | import org.dromara.hutool.log.Log; 5 | 6 | public class WxMsgController { 7 | 8 | protected static final Log log = Log.get(); 9 | 10 | 11 | void init() { 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/infe/Resp.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.infe; 2 | 3 | /** 4 | * http 响应 5 | * @author wt 6 | * @date 2023/06/01 7 | */ 8 | public interface Resp extends java.io.Serializable{ 9 | 10 | 11 | } 12 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/infe/SendMsg.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.infe; 2 | 3 | import io.vertx.core.json.JsonObject; 4 | 5 | /** 6 | * http接口请求的基础接口 7 | * 8 | * @author wt 9 | * @date 2023/06/01 10 | */ 11 | public interface SendMsg extends java.io.Serializable{ 12 | 13 | default JsonObject toJson(){ 14 | return JsonObject.mapFrom(this); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 私聊 11 | * 12 | * @author wt 13 | * @date 2023/05/26 14 | */ 15 | @Data 16 | @Accessors(chain = true) 17 | @JsonIgnoreProperties(ignoreUnknown = true) 18 | public class PrivateChatMsg implements Serializable { 19 | 20 | String path; 21 | /** 22 | * 内容 23 | */ 24 | 25 | private String content; 26 | /** 27 | * 当是群聊的时候 为群id,否则为微信id 28 | */ 29 | private String fromGroup; 30 | /** 31 | * 微信id 32 | */ 33 | private String fromUser; 34 | private Integer isSendMsg; 35 | /** 36 | * 1通过手机发送 37 | */ 38 | private Integer isSendByPhone; 39 | private Long msgId; 40 | private Integer pid; 41 | private String sign; 42 | private String signature; 43 | private String time; 44 | private Integer timestamp; 45 | 46 | /** 47 | * 对用户,如果是文件助手是filehelper 48 | */ 49 | private String toUser; 50 | /** 51 | * 类型 52 | */ 53 | private Integer type; 54 | } 55 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/dto/PayoutInformation.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.dto; 2 | 3 | import java.math.BigDecimal; 4 | 5 | /** 6 | * 支付信息 7 | * 8 | * @author wt 9 | * @param receiverUsername 付款人 10 | * @param decimal 收款金额 11 | * @param remark 备注 12 | * @param transcationid 13 | * @param transferid 14 | * @date 2023/06/06 15 | */ 16 | public record PayoutInformation(String receiverUsername, BigDecimal decimal, String remark,String transcationid,String transferid) implements java.io.Serializable { 17 | 18 | public PayoutInformation(String receiverUsername, BigDecimal decimal, String remark) { 19 | this(receiverUsername, decimal, remark, null, null); 20 | } 21 | } -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/AddFriends.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import com.example.wxhk.infe.SendMsg; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 添加wxid 好友 9 | * @author wt 10 | * @date 2023/06/01 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class AddFriends implements SendMsg { 15 | String wxid; 16 | /** 17 | * 验证信息 18 | */ 19 | String msg; 20 | } 21 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/ConfirmThePayment.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import com.example.wxhk.infe.SendMsg; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 确认收款 9 | * @author wt 10 | * @date 2023/06/01 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class ConfirmThePayment implements SendMsg { 15 | /** 16 | * 转账人微信id,从hook的消息中获取 17 | */ 18 | String wxid; 19 | /** 20 | * 从hook的消息中获取对应的字段内容 21 | */ 22 | String transcationId; 23 | /** 24 | * 从hook的消息中获取对应的字段内容。 25 | */ 26 | String transferId; 27 | } 28 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/FindWeChat.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import com.example.wxhk.infe.SendMsg; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 通过手机或者qq查找微信 9 | * @author wt 10 | * @date 2023/06/01 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class FindWeChat implements SendMsg { 15 | /** 16 | * 通过 手机或qq查询信息 17 | */ 18 | String keyword; 19 | } 20 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/ForwardMessages.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import com.example.wxhk.infe.SendMsg; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 转发消息 9 | * @author wt 10 | * @date 2023/06/01 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class ForwardMessages implements SendMsg { 15 | /** 16 | * 消息接收人wxid 17 | */ 18 | String wxid; 19 | /** 20 | * 消息id 21 | */ 22 | String msgid; 23 | } 24 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/GetGroupMembers.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import com.example.wxhk.infe.SendMsg; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 获取群成员 9 | * @author wt 10 | * @date 2023/06/01 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class GetGroupMembers implements SendMsg { 15 | String chatRoomId; 16 | } 17 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/GetsTheNicknameOfAGroupMember.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import com.example.wxhk.infe.SendMsg; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 获取群成员昵称 9 | * @author wt 10 | * @date 2023/06/01 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class GetsTheNicknameOfAGroupMember implements SendMsg { 15 | /** 16 | * 聊天室id 17 | */ 18 | String chatRoomId; 19 | /** 20 | * 成员id 21 | */ 22 | String memberId; 23 | } 24 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/IncreaseGroupMembership.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import com.example.wxhk.infe.SendMsg; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 增加群成员 9 | * @author wt 10 | * @date 2023/06/01 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class IncreaseGroupMembership implements SendMsg { 15 | /** 16 | * 聊天室id 17 | */ 18 | String chatRoomId; 19 | /** 20 | * 成员id,以,分割 21 | */ 22 | String memberIds; 23 | } 24 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/OpenHook.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import com.example.wxhk.infe.SendMsg; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 开启hook 9 | * 10 | * @author wt 11 | * @date 2023/06/01 12 | */ 13 | @Data 14 | @Accessors(chain = true) 15 | public class OpenHook implements SendMsg { 16 | String port; 17 | String ip; 18 | /** 19 | * 0/1 :1.启用http 0.不启用http 20 | */ 21 | boolean enableHttp; 22 | /** 23 | * 超时时间,单位ms 24 | */ 25 | String timeout; 26 | 27 | /** 28 | * http的请求地址,enableHttp=1时,不能为空 29 | */ 30 | String url; 31 | } 32 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/SendAtText.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import com.example.wxhk.infe.SendMsg; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 发送at文本 9 | * @author wt 10 | * @date 2023/06/01 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class SendAtText implements SendMsg { 15 | /** 16 | * 聊天室id,群聊用 17 | */ 18 | String chatRoomId; 19 | /** 20 | * 群聊的时候用at多个用逗号隔开,@所有人则是notify@all 21 | */ 22 | String wxids; 23 | 24 | String msg; 25 | } 26 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/SendFile.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import com.example.wxhk.infe.SendMsg; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 发送文件 9 | * 10 | * @author wt 11 | * @date 2023/06/01 12 | */ 13 | @Data 14 | @Accessors(chain = true) 15 | public class SendFile implements SendMsg { 16 | String wxid; 17 | /** 18 | * 发送文件路径 19 | * "filePath": "C:/Users/123.txt" 20 | */ 21 | String filePath; 22 | } 23 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/SendImg.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import com.example.wxhk.infe.SendMsg; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 发送图片 9 | * 10 | * @author wt 11 | * @date 2023/06/01 12 | */ 13 | @Data 14 | @Accessors(chain = true) 15 | public class SendImg implements SendMsg { 16 | String wxid; 17 | /** 18 | * 发送图片接口 19 | * "imagePath": "C:/Users/123.png" 20 | */ 21 | String imagePath; 22 | } 23 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/SendMsg.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import lombok.Data; 4 | import lombok.experimental.Accessors; 5 | 6 | /** 7 | * http请求参数 8 | * 9 | * @author wt 10 | * @date 2023/06/01 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class SendMsg { 15 | /** 16 | * wxid 17 | */ 18 | String wxid; 19 | /** 20 | * 消息内容 21 | */ 22 | String msg; 23 | 24 | /** 25 | * 聊天室id,群聊用 26 | */ 27 | String chatRoomId; 28 | /** 29 | * 成员id 30 | */ 31 | String memberId; 32 | 33 | /** 34 | * 群聊的时候用at多个用逗号隔开,@所有人则是notify@all 35 | */ 36 | String wxids; 37 | /** 38 | * 发送图片接口 39 | * "imagePath": "C:/Users/123.png" 40 | */ 41 | String imagePath; 42 | /** 43 | * 发送文件路径 44 | * "filePath": "C:/Users/123.txt" 45 | */ 46 | String filePath; 47 | /** 48 | * 通过 手机或qq查询信息 49 | */ 50 | String keyword; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/SendText.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import com.example.wxhk.infe.SendMsg; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 发送文本 9 | * 10 | * @author wt 11 | * @date 2023/06/01 12 | */ 13 | @Data 14 | @Accessors(chain = true) 15 | public class SendText implements SendMsg { 16 | String wxid; 17 | String msg; 18 | } 19 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/request/ThroughFriends.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.request; 2 | 3 | import com.example.wxhk.infe.SendMsg; 4 | import lombok.Data; 5 | import lombok.experimental.Accessors; 6 | 7 | /** 8 | * 通过好友请求 9 | * @author wt 10 | * @date 2023/06/01 11 | */ 12 | @Data 13 | @Accessors(chain = true) 14 | public class ThroughFriends implements SendMsg { 15 | /** 16 | * 添加好友消息内容里的encryptusername 17 | */ 18 | String v3; 19 | /** 20 | * 添加好友消息内容里的ticket 21 | */ 22 | String v4; 23 | /** 24 | * 好友权限,0是无限制,1是不让他看我,2是不看他,3是1+2 25 | */ 26 | String permission; 27 | } 28 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/response/ContactList.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.response; 2 | 3 | import com.example.wxhk.infe.Resp; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import lombok.Data; 6 | import lombok.experimental.Accessors; 7 | 8 | import java.io.Serializable; 9 | import java.util.List; 10 | 11 | /** 12 | * 联系人列表 13 | * @author wt 14 | * @date 2023/06/01 15 | */ 16 | @Data 17 | @Accessors(chain = true) 18 | @JsonIgnoreProperties(ignoreUnknown = true) 19 | public class ContactList implements Resp { 20 | 21 | /** 22 | * code : 1 23 | * data : [{"customAccount":"","delFlag":0,"type":1,"userName":"朋友推荐消息","verifyFlag":0,"wxid":"fmessage"},{"customAccount":"tencent_cloud","delFlag":0,"type":3,"userName":"腾讯云助手","verifyFlag":24,"wxid":"gh_a73e2407e0f8"},{"customAccount":"","delFlag":0,"type":1,"userName":"语音记事本","verifyFlag":0,"wxid":"medianote"},{"customAccount":"","delFlag":0,"type":1,"userName":"漂流瓶","verifyFlag":0,"wxid":"floatbottle"},{"customAccount":"jys-wt","delFlag":0,"type":8651011,"userName":"时光似水戏流年","verifyFlag":0,"wxid":"wxid_gf1fogt5a0pq22"},{"customAccount":"wxzhifu","delFlag":0,"type":3,"userName":"微信支付","verifyFlag":24,"wxid":"gh_3dfda90e39d6"},{"customAccount":"dhkzfr","delFlag":0,"type":3,"userName":"阿芙(代发)","verifyFlag":0,"wxid":"wxid_kh16lri40gzj22"},{"customAccount":"","delFlag":0,"type":3,"userName":"文件传输助手","verifyFlag":0,"wxid":"filehelper"},{"customAccount":"","delFlag":0,"type":3,"userName":"fff","verifyFlag":0,"wxid":"24964676359@chatroom"},{"customAccount":"","delFlag":0,"type":2,"userName":"最美阿芙","verifyFlag":0,"wxid":"23793178249@chatroom"},{"customAccount":"afu943344","delFlag":0,"type":2,"userName":"A-阿芙4号-LOL永劫云顶出租-代发","verifyFlag":0,"wxid":"wxid_1gxthknqbmwv22"},{"customAccount":"","delFlag":0,"type":3,"userName":"微信收款助手","verifyFlag":24,"wxid":"gh_f0a92aa7146c"},{"customAccount":"","delFlag":0,"type":0,"userName":"","verifyFlag":0,"wxid":"25984984710827869@openim"}] 24 | * result : OK 25 | */ 26 | 27 | private Integer code; 28 | private String result; 29 | private List data; 30 | 31 | @Data 32 | @Accessors(chain = true) 33 | public static class DataBean implements Serializable { 34 | /** 35 | * customAccount : 36 | * delFlag : 0 37 | * type : 1 38 | * userName : 朋友推荐消息 39 | * verifyFlag : 0 40 | * wxid : fmessage 41 | */ 42 | 43 | private String customAccount; 44 | private Integer delFlag; 45 | private Integer type; 46 | private String userName; 47 | private Integer verifyFlag; 48 | private String wxid; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/model/response/GroupMembers.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.model.response; 2 | 3 | import com.example.wxhk.infe.Resp; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import lombok.Data; 6 | import lombok.experimental.Accessors; 7 | 8 | import java.io.Serializable; 9 | 10 | @Data 11 | @Accessors(chain = true) 12 | @JsonIgnoreProperties(ignoreUnknown = true) 13 | public class GroupMembers implements Resp { 14 | 15 | /** 16 | * code : 1 17 | * data : {"admin":"wxid_gf1fogt5a0pq22","chatRoomId":"24964676359@chatroom","members":"wxid_gf1fogt5a0pq22^Gwxid_4yr8erik0uho22"} 18 | * result : OK 19 | */ 20 | 21 | private Integer code; 22 | private DataBean data; 23 | private String result; 24 | 25 | @Data 26 | public static class DataBean implements Serializable { 27 | /** 28 | * admin : wxid_gf1fogt5a0pq22 29 | * chatRoomId : 24964676359@chatroom 30 | * members : wxid_gf1fogt5a0pq22^Gwxid_4yr8erik0uho22 31 | */ 32 | 33 | private String admin; 34 | private String chatRoomId; 35 | private String members; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.msg; 2 | 3 | import com.example.wxhk.constant.WxMsgType; 4 | import com.example.wxhk.model.PrivateChatMsg; 5 | import com.example.wxhk.model.dto.PayoutInformation; 6 | import com.example.wxhk.server.WxSmgServer; 7 | import com.example.wxhk.tcp.vertx.InitWeChat; 8 | import jakarta.annotation.PostConstruct; 9 | import org.dromara.hutool.core.util.XmlUtil; 10 | import org.dromara.hutool.log.Log; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Component; 13 | import org.w3c.dom.Document; 14 | import org.w3c.dom.Element; 15 | import org.w3c.dom.Node; 16 | import org.w3c.dom.NodeList; 17 | 18 | import java.math.BigDecimal; 19 | import java.util.Iterator; 20 | import java.util.Map; 21 | import java.util.Objects; 22 | import java.util.Set; 23 | import java.util.concurrent.ConcurrentHashMap; 24 | import java.util.concurrent.locks.ReentrantReadWriteLock; 25 | 26 | @Component 27 | public class WxMsgHandle { 28 | public static final ConcurrentHashMap map = new ConcurrentHashMap<>(32); 29 | protected static final Log log = Log.get(); 30 | /** 31 | * 文件传输助手 32 | */ 33 | public static final String FILEHELPER = "filehelper"; 34 | /** 35 | * 收款码缓存 因为有2段信息,一段是交易id,里面可以解析出来源方,二段解析出金额 36 | */ 37 | public static ConcurrentHashMap collection_code_caching = new ConcurrentHashMap<>(); 38 | 39 | 40 | public static WxSmgServer wxSmgServer; 41 | /** 42 | * 看 43 | */ 44 | public static final ReentrantReadWriteLock LOOK = new ReentrantReadWriteLock(); 45 | 46 | @Autowired 47 | public void setWxSmgServer(WxSmgServer wxSmgServer) { 48 | WxMsgHandle.wxSmgServer = wxSmgServer; 49 | } 50 | 51 | @PostConstruct 52 | public void init() { 53 | add(chatMsg -> { 54 | if(Objects.equals(chatMsg.getToUser(), FILEHELPER)){ 55 | wxSmgServer.文件助手(chatMsg); 56 | }else{ 57 | wxSmgServer.私聊(chatMsg); 58 | } 59 | 60 | return null; 61 | }, WxMsgType.私聊信息); 62 | add(chatMsg -> { 63 | if (FILEHELPER.equals(chatMsg.getFromUser())) { 64 | wxSmgServer.文件助手(chatMsg); 65 | } 66 | return 1; 67 | }, WxMsgType.收到转账之后或者文件助手等信息); 68 | add(chatMsg -> { 69 | wxSmgServer.收到名片(chatMsg); 70 | return 1; 71 | }, WxMsgType.收到名片); 72 | add(chatMsg -> { 73 | wxSmgServer.收到好友请求(chatMsg); 74 | return 1; 75 | }, WxMsgType.好友请求);// 好友请求 76 | add(chatMsg -> { 77 | boolean f = 解析扫码支付第二段(chatMsg); 78 | if (f) { 79 | f = 解析收款信息1段(chatMsg); 80 | if (f) { 81 | 解析收款信息2段(chatMsg); 82 | } 83 | } 84 | return null; 85 | }, WxMsgType.转账和收款); 86 | add(chatMsg -> { 87 | boolean f = 解析扫码支付第一段(chatMsg); 88 | return null; 89 | }, WxMsgType.扫码触发); 90 | } 91 | 92 | /** 93 | * 解析扫码支付第一段,得到交易id和微信id 94 | * 95 | * @param chatMsg 96 | * @return boolean 返回true 则继续解析,否则解析成功,不需要解析了 97 | */ 98 | public static boolean 解析扫码支付第一段(PrivateChatMsg chatMsg) { 99 | try { 100 | Document document = XmlUtil.parseXml(chatMsg.getContent()); 101 | Element documentElement = document.getDocumentElement(); 102 | String localName = documentElement.getLocalName(); 103 | if ("sysmsg".equals(localName)) { 104 | String type = documentElement.getAttribute("type"); 105 | if ("paymsg".equals(type)) { 106 | NodeList outtradeno = documentElement.getElementsByTagName("outtradeno"); 107 | if (outtradeno.getLength() > 0) { 108 | String textContent = outtradeno.item(0).getTextContent(); 109 | String textContent1 = documentElement.getElementsByTagName("username").item(0).getTextContent(); 110 | collection_code_caching.put(textContent, textContent1); 111 | return false; 112 | } 113 | } 114 | } 115 | } catch (Exception e) { 116 | log.error(e); 117 | } 118 | return true; 119 | } 120 | 121 | 122 | /** 123 | * 解析扫码支付第二段 124 | * 125 | * @param chatMsg 聊天味精 126 | * @return boolean true 则 继续解析, false则解析成功,不需要再解析了 127 | */ 128 | public static boolean 解析扫码支付第二段(PrivateChatMsg chatMsg) { 129 | try { 130 | Document document = XmlUtil.parseXml(chatMsg.getContent()); 131 | Element documentElement = document.getDocumentElement(); 132 | String localName = documentElement.getLocalName(); 133 | if ("msg".equals(localName)) { 134 | NodeList outtradeno = documentElement.getElementsByTagName("weapp_path"); 135 | if (outtradeno.getLength() > 1) { 136 | String textContent = outtradeno.item(1).getTextContent(); 137 | Set> entries = collection_code_caching.entrySet(); 138 | Iterator> iterator = entries.iterator(); 139 | while (iterator.hasNext()) { 140 | Map.Entry next = iterator.next(); 141 | if (textContent.contains(next.getKey())) { 142 | // 得到了交易信息 143 | NodeList word = documentElement.getElementsByTagName("word"); 144 | String monery = word.item(1).getTextContent(); 145 | String remark = word.item(3).getTextContent(); 146 | if (monery.startsWith("¥")) { 147 | String substring = monery.substring(1); 148 | BigDecimal decimal = new BigDecimal(substring); 149 | log.info("扫码收款:{},付款人:{},付款备注:{}", decimal.stripTrailingZeros().toPlainString(), next.getValue(), remark); 150 | wxSmgServer.扫码收款(new PayoutInformation(next.getValue(),decimal,remark)); 151 | iterator.remove(); 152 | return false; 153 | } 154 | } 155 | } 156 | 157 | 158 | } 159 | } 160 | } catch (Exception e) { 161 | log.error(e); 162 | } 163 | return true; 164 | } 165 | 166 | public static boolean 解析收款信息2段(PrivateChatMsg chatMsg) { 167 | try { 168 | Document document = XmlUtil.parseXml(chatMsg.getContent()); 169 | Element documentElement = document.getDocumentElement(); 170 | String localName = documentElement.getLocalName(); 171 | if ("msg".equals(localName)) { 172 | if (documentElement.getElementsByTagName("transcationid").getLength() > 0) { 173 | String remark = documentElement.getElementsByTagName("pay_memo").item(0).getTextContent(); 174 | String monery = documentElement.getElementsByTagName("feedesc").item(0).getTextContent(); 175 | String receiver_username = documentElement.getElementsByTagName("receiver_username").item(0).getTextContent(); 176 | // 如果是机器人发出的,则跳过解析 177 | if (InitWeChat.WXID_MAP.contains(receiver_username) ) { 178 | return false; 179 | } 180 | if (monery.startsWith("¥")) { 181 | String substring = monery.substring(1); 182 | BigDecimal decimal = new BigDecimal(substring); 183 | log.info("收款:{},付款人:{},付款备注:{}", decimal.stripTrailingZeros().toPlainString(), receiver_username, remark); 184 | wxSmgServer.收款之后(new PayoutInformation(receiver_username, decimal, remark)); 185 | return false; 186 | }; 187 | 188 | } 189 | } 190 | } catch (Exception e) { 191 | log.error(e); 192 | } 193 | return true; 194 | } 195 | 196 | 197 | /** 198 | * 解析收款信息1段 199 | * 会自动进行收款 200 | * 201 | * @param chatMsg 202 | * @return boolean true则 继续解析,false则不需要解析了 203 | */ 204 | public static boolean 解析收款信息1段(PrivateChatMsg chatMsg) { 205 | try { 206 | String content = chatMsg.getContent(); 207 | Document document = XmlUtil.parseXml(content); 208 | NodeList paysubtype1 = document.getElementsByTagName("paysubtype"); 209 | if (paysubtype1.getLength() == 0) { 210 | return true; 211 | } 212 | Node paysubtype = paysubtype1.item(0); 213 | if ("1".equals(paysubtype.getTextContent().trim())) { 214 | // 手机发出去的 215 | String textContent = document.getElementsByTagName("receiver_username").item(0).getTextContent(); 216 | if (!InitWeChat.WXID_MAP.contains(textContent)) { 217 | // 如果不是机器人收款,则认为不需要解析了,大概率是机器人自己发出去的 218 | return false; 219 | } 220 | 221 | String remark = document.getElementsByTagName("pay_memo").item(0).getTextContent(); 222 | String monery = document.getElementsByTagName("feedesc").item(0).getTextContent(); 223 | String receiver_username = document.getElementsByTagName("receiver_username").item(0).getTextContent(); 224 | if (monery.startsWith("¥")) { 225 | String substring = monery.substring(1); 226 | BigDecimal decimal = new BigDecimal(substring); 227 | Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0); 228 | Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0); 229 | wxSmgServer.接到收款(new PayoutInformation(chatMsg.getFromUser(), decimal, remark, transcationid.getTextContent(), transferid.getTextContent())); 230 | return false; 231 | } 232 | } 233 | 234 | } catch (Exception e) { 235 | log.error(e); 236 | } 237 | return true; 238 | } 239 | 240 | public static void exec(PrivateChatMsg chatMsg) { 241 | Handle handle = map.get(chatMsg.getType()); 242 | if (handle != null) { 243 | handle.handle(chatMsg); 244 | } 245 | } 246 | 247 | public void add(Handle handle, WxMsgType... type) { 248 | for (WxMsgType integer : type) { 249 | map.put(integer.getType(), handle); 250 | } 251 | } 252 | 253 | public interface Handle { 254 | Object handle(PrivateChatMsg chatMsg); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/server/WxSmgServer.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.server; 2 | 3 | import com.example.wxhk.model.PrivateChatMsg; 4 | import com.example.wxhk.model.dto.PayoutInformation; 5 | 6 | /** 7 | * 微信消息处理提取 8 | * @author wt 9 | * @date 2023/06/06 10 | */ 11 | public interface WxSmgServer { 12 | /** 13 | * 接到收款 14 | * 15 | * @param payoutInformation 支付信息 16 | */ 17 | void 接到收款(PayoutInformation payoutInformation); 18 | 19 | void 收款之后(PayoutInformation pay); 20 | 21 | void 私聊(PrivateChatMsg chatMsg); 22 | 23 | void 文件助手(PrivateChatMsg chatMsg); 24 | 25 | void 收到名片(PrivateChatMsg chatMsg); 26 | 27 | void 收到好友请求(PrivateChatMsg chatMsg); 28 | 29 | void 扫码收款(PayoutInformation payoutInformation); 30 | } 31 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/server/impl/WxSmgServerImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.server.impl; 2 | 3 | import com.example.wxhk.model.PrivateChatMsg; 4 | import com.example.wxhk.model.dto.PayoutInformation; 5 | import com.example.wxhk.model.request.ConfirmThePayment; 6 | import com.example.wxhk.util.HttpSendUtil; 7 | import org.dromara.hutool.core.text.StrUtil; 8 | import org.dromara.hutool.core.util.XmlUtil; 9 | import org.dromara.hutool.log.Log; 10 | import org.springframework.stereotype.Service; 11 | import org.w3c.dom.Document; 12 | import org.w3c.dom.Element; 13 | 14 | import java.util.Objects; 15 | 16 | @Service 17 | public class WxSmgServerImpl implements com.example.wxhk.server.WxSmgServer { 18 | 19 | protected static final Log log=Log.get(); 20 | 21 | public static final String FILEHELPER = "filehelper"; 22 | @Override 23 | public void 接到收款(PayoutInformation payoutInformation) { 24 | HttpSendUtil.确认收款(new ConfirmThePayment().setWxid(payoutInformation.receiverUsername()).setTranscationId(payoutInformation.transcationid()).setTransferId(payoutInformation.transferid())); 25 | } 26 | @Override 27 | public void 收款之后(PayoutInformation pay) { 28 | HttpSendUtil.发送文本(pay.receiverUsername(), StrUtil.format("收到款项:{},备注:{}", pay.decimal().stripTrailingZeros().toPlainString(), pay.remark())); 29 | } 30 | 31 | @Override 32 | public void 私聊(PrivateChatMsg chatMsg) { 33 | if (Objects.equals(chatMsg.getIsSendMsg(), 1) && Objects.equals(chatMsg.getIsSendByPhone(), 1)) { 34 | log.info("手机端对:{}发出:{}", chatMsg.getFromUser(), chatMsg.getContent()); 35 | }else{ 36 | log.info("收到私聊{}",chatMsg); 37 | } 38 | } 39 | 40 | @Override 41 | public void 文件助手(PrivateChatMsg chatMsg) { 42 | log.info("文件助手:{}",chatMsg); 43 | } 44 | 45 | @Override 46 | public void 收到名片(PrivateChatMsg chatMsg) { 47 | if (FILEHELPER.equals(chatMsg.getFromUser())) { 48 | Document document = XmlUtil.parseXml(chatMsg.getContent()); 49 | Element documentElement = document.getDocumentElement(); 50 | String username = documentElement.getAttribute("username"); 51 | if (StrUtil.isNotBlank(username)) { 52 | HttpSendUtil.发送文本(username); 53 | } 54 | } 55 | } 56 | 57 | @Override 58 | public void 收到好友请求(PrivateChatMsg chatMsg) { 59 | HttpSendUtil.通过好友请求(chatMsg); 60 | } 61 | 62 | @Override 63 | public void 扫码收款(PayoutInformation payoutInformation) { 64 | HttpSendUtil.发送文本(payoutInformation.receiverUsername(), StrUtil.format("扫码收款:{},备注:{}", payoutInformation.decimal().stripTrailingZeros().toPlainString(), payoutInformation.remark())); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.tcp.vertx; 2 | 3 | import com.example.wxhk.model.PrivateChatMsg; 4 | import com.example.wxhk.msg.WxMsgHandle; 5 | import com.example.wxhk.util.HttpSendUtil; 6 | import io.vertx.core.json.JsonObject; 7 | import jakarta.annotation.PostConstruct; 8 | import org.dromara.hutool.core.thread.NamedThreadFactory; 9 | import org.dromara.hutool.log.Log; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.concurrent.LinkedBlockingQueue; 13 | import java.util.concurrent.ThreadPoolExecutor; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | /** 17 | * 消息处理 18 | * 19 | * @author wt 20 | * @date 2023/05/31 21 | */ 22 | @Component 23 | public class ArrHandle { 24 | 25 | /** 26 | * 线程处理消息队列,但是必须保证核心数大于2,其中必定要有一个线程可以单独处理交易队列信息 27 | */ 28 | public static final ThreadPoolExecutor sub = new ThreadPoolExecutor(4, 10, 30, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new NamedThreadFactory("sub", false)); 29 | public static final ThreadLocal chatMsgThreadLocal = new InheritableThreadLocal<>(); 30 | protected static final Log log = Log.get(); 31 | 32 | /** 33 | * 得到当前正在处理的消息 34 | * 35 | * @return {@link PrivateChatMsg} 36 | */ 37 | public static PrivateChatMsg getPriMsg() { 38 | return chatMsgThreadLocal.get(); 39 | } 40 | 41 | @PostConstruct 42 | public void exec() { 43 | for (int i = 0; i < sub.getCorePoolSize()-1; i++) { 44 | sub.submit(() -> { 45 | while (!Thread.currentThread().isInterrupted()) { 46 | try { 47 | JsonObject take = VertxTcp.LINKED_BLOCKING_QUEUE.take(); 48 | log.info("{}", take.encode()); 49 | PrivateChatMsg privateChatMsg = take.mapTo(PrivateChatMsg.class); 50 | chatMsgThreadLocal.set(privateChatMsg); 51 | if ("weixin".equals(privateChatMsg.getFromUser())) { 52 | String s = HttpSendUtil.获取当前登陆微信id(); 53 | InitWeChat.WXID_MAP.add(s); 54 | continue; 55 | } 56 | WxMsgHandle.exec(privateChatMsg); 57 | } catch (Exception e) { 58 | log.error(e); 59 | }finally { 60 | chatMsgThreadLocal.remove(); 61 | } 62 | } 63 | log.error("退出线程了"); 64 | }); 65 | } 66 | sub.submit(() -> { 67 | while (!Thread.currentThread().isInterrupted()) { 68 | try { 69 | JsonObject take = VertxTcp.LINKED_BLOCKING_QUEUE_MON.take(); 70 | log.info("{}", take.encode()); 71 | PrivateChatMsg privateChatMsg = take.mapTo(PrivateChatMsg.class); 72 | chatMsgThreadLocal.set(privateChatMsg); 73 | if ("weixin".equals(privateChatMsg.getFromUser())) { 74 | String s = HttpSendUtil.获取当前登陆微信id(); 75 | InitWeChat.WXID_MAP.add(s); 76 | continue; 77 | } 78 | WxMsgHandle.exec(privateChatMsg); 79 | } catch (Exception e) { 80 | log.error(e); 81 | }finally { 82 | chatMsgThreadLocal.remove(); 83 | } 84 | } 85 | log.error("退出线程了"); 86 | }); 87 | 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.tcp.vertx; 2 | 3 | import com.example.wxhk.util.HttpAsyncUtil; 4 | import com.example.wxhk.util.HttpSyncUtil; 5 | import io.vertx.core.impl.ConcurrentHashSet; 6 | import io.vertx.core.json.JsonObject; 7 | import org.dromara.hutool.core.io.file.FileUtil; 8 | import org.dromara.hutool.core.net.NetUtil; 9 | import org.dromara.hutool.core.text.StrUtil; 10 | import org.dromara.hutool.core.thread.ThreadUtil; 11 | import org.dromara.hutool.log.Log; 12 | import org.dromara.hutool.setting.Setting; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.springframework.beans.factory.annotation.Value; 15 | import org.springframework.boot.CommandLineRunner; 16 | import org.springframework.core.annotation.Order; 17 | import org.springframework.core.io.ClassPathResource; 18 | import org.springframework.stereotype.Component; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | 23 | /** 24 | * 微信注入环境初始化和相关方法 25 | * 26 | * @author wt 27 | * @date 2023/05/16 28 | */ 29 | @Order(-1) 30 | @Component 31 | public class InitWeChat implements CommandLineRunner { 32 | 33 | public final static Log log = Log.get(); 34 | public static final ConcurrentHashSet WXID_MAP = new ConcurrentHashSet<>(); 35 | public static String wxPath; 36 | public static Integer wxPort; 37 | public static Integer vertxPort; 38 | /** 39 | * wxhelper.dll 所在路径 40 | */ 41 | public static File DLL_PATH; 42 | 43 | public static void 注入dll(String wxPid) throws IOException { 44 | String format = StrUtil.format("cmd /C c.exe -I {} -p {}\\wxhelper.dll -m {}", wxPid, DLL_PATH.getAbsolutePath(), wxPid); 45 | Process exec = Runtime.getRuntime().exec(format, null, DLL_PATH); 46 | log.info("注入结果:{}", new String(exec.getInputStream().readAllBytes(), "gbk")); 47 | } 48 | 49 | @NotNull 50 | private static File 环境初始化() { 51 | File target = new File(new File("").getAbsolutePath().split("\\\\")[0] + "\\exec\\"); 52 | try { 53 | File wxPathFile = new File(wxPath); 54 | File config = new File(wxPathFile.getParentFile(), "config.ini"); 55 | Setting setting = new Setting(config.getAbsolutePath()); 56 | setting.getGroupedMap().put("config", "port", String.valueOf(wxPort)); 57 | setting.store(); 58 | ClassPathResource classPathResource = new ClassPathResource("exec"); 59 | File file = classPathResource.getFile(); 60 | target.mkdir(); 61 | for (File listFile : file.listFiles()) { 62 | FileUtil.copy(listFile, target, true); 63 | } 64 | } catch (Exception e) { 65 | log.error(e, "环境初始化失败,请检查"); 66 | } 67 | return target; 68 | } 69 | 70 | /** 71 | * 返回最后一个微信的pid 72 | * 73 | * @return {@link String} 74 | * @throws IOException ioexception 75 | */ 76 | public static String createWx() throws IOException { 77 | Runtime.getRuntime().exec("cmd /C \"" + wxPath + "\""); 78 | return getWxPid(); 79 | } 80 | 81 | @NotNull 82 | private static String getWxPid() throws IOException { 83 | String line = null; 84 | try { 85 | Process exec = Runtime.getRuntime().exec("cmd /C tasklist /FI \"IMAGENAME eq WeChat.exe\" "); 86 | byte[] bytes = exec.getInputStream().readAllBytes(); 87 | line = new String(bytes, "gbk"); 88 | String[] split = line.split("\n"); 89 | if (!line.contains("WeChat.exe")) { 90 | return createWx(); 91 | } 92 | String[] split1 = split[split.length - 1].replaceAll("\\s{2,}", " ").split(" "); 93 | return split1[1]; 94 | } catch (IOException e) { 95 | log.error("获取端口错误:{}", line); 96 | throw e; 97 | } 98 | } 99 | 100 | public static Integer getWxPort() { 101 | return wxPort; 102 | } 103 | 104 | @Value("${wx.port}") 105 | public void setWxPort(Integer wxPort) { 106 | InitWeChat.wxPort = wxPort; 107 | } 108 | 109 | public static String getWxPath() { 110 | return wxPath; 111 | } 112 | 113 | @Value("${wx.path}") 114 | public void setWxPath(String wxPath) { 115 | InitWeChat.wxPath = wxPath; 116 | } 117 | 118 | public static Integer getVertxPort() { 119 | return vertxPort; 120 | } 121 | 122 | @Value("${vertx.port}") 123 | public void setVertxPort(Integer vertxPort) { 124 | InitWeChat.vertxPort = vertxPort; 125 | } 126 | 127 | @Override 128 | public void run(String... args) throws Exception { 129 | //tasklist /FI "IMAGENAME eq WeChat.exe" /m 130 | boolean usableLocalPort = NetUtil.isUsableLocalPort(wxPort); 131 | if (usableLocalPort) { 132 | DLL_PATH = 环境初始化(); 133 | String wxPid = getWxPid(); 134 | 注入dll(wxPid); 135 | } 136 | ThreadUtil.execute(() -> { 137 | while (!Thread.currentThread().isInterrupted()) { 138 | JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.检查微信登陆, new JsonObject()); 139 | if (exec.getInteger("code").equals(1)) { 140 | JsonObject dl = HttpSyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject()); 141 | JsonObject jsonObject = dl.getJsonObject("data"); 142 | String wx = jsonObject.getString("wxid"); 143 | WXID_MAP.add(wx); 144 | if (log.isDebugEnabled()) { 145 | log.debug("检测到微信登陆:{}", wx); 146 | } 147 | break; 148 | } 149 | ThreadUtil.safeSleep(500); 150 | } 151 | 152 | }); 153 | // FIXME: 2023/6/2 程序结束后关闭hook会偶尔出现微信闪退情况,暂时禁用 154 | // Runtime.getRuntime().addShutdownHook(new Thread(HttpSendUtil::关闭hook)); 155 | //netstat -aon|findstr "端口号" 156 | // c.exe -I 4568 -p D:\exec\wxhelper.dll -m 4568 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.tcp.vertx; 2 | 3 | import com.example.wxhk.WxhkApplication; 4 | import com.example.wxhk.constant.WxMsgType; 5 | import com.example.wxhk.model.request.OpenHook; 6 | import com.example.wxhk.util.HttpSendUtil; 7 | import io.vertx.core.AbstractVerticle; 8 | import io.vertx.core.DeploymentOptions; 9 | import io.vertx.core.Future; 10 | import io.vertx.core.Promise; 11 | import io.vertx.core.json.JsonObject; 12 | import io.vertx.core.net.NetServer; 13 | import io.vertx.core.net.NetServerOptions; 14 | import io.vertx.core.parsetools.JsonParser; 15 | import org.dromara.hutool.log.Log; 16 | import org.springframework.boot.CommandLineRunner; 17 | import org.springframework.core.annotation.Order; 18 | import org.springframework.stereotype.Component; 19 | 20 | import java.util.Objects; 21 | import java.util.concurrent.LinkedBlockingQueue; 22 | 23 | /** 24 | * 接受微信hook信息 25 | * 26 | * @author wt 27 | * @date 2023/05/26 28 | */ 29 | @Component 30 | @Order() 31 | public class VertxTcp extends AbstractVerticle implements CommandLineRunner { 32 | public final static LinkedBlockingQueue LINKED_BLOCKING_QUEUE = new LinkedBlockingQueue<>(); 33 | /** 34 | * 这个只保留交易相关的类型 35 | */ 36 | public final static LinkedBlockingQueue LINKED_BLOCKING_QUEUE_MON = new LinkedBlockingQueue<>(); 37 | protected static final Log log = Log.get(); 38 | NetServer netServer; 39 | 40 | @Override 41 | public void start(Promise startPromise) throws Exception { 42 | netServer = vertx.createNetServer(new NetServerOptions() 43 | .setPort(InitWeChat.getVertxPort()) 44 | .setIdleTimeout(0) 45 | .setLogActivity(false) 46 | ); 47 | netServer.connectHandler(socket -> { 48 | JsonParser parser = JsonParser.newParser(); 49 | parser.objectValueMode(); 50 | parser.handler(event -> { 51 | switch (event.type()) { 52 | case START_OBJECT -> { 53 | } 54 | case END_OBJECT -> { 55 | } 56 | case START_ARRAY -> { 57 | } 58 | case END_ARRAY -> { 59 | } 60 | case VALUE -> { 61 | JsonObject entries = event.objectValue(); 62 | 63 | if(Objects.equals(entries.getInteger("type"), WxMsgType.扫码触发.getType()) || 64 | Objects.equals(entries.getInteger("type"), WxMsgType.转账和收款.getType())){ 65 | LINKED_BLOCKING_QUEUE_MON.add(entries); 66 | }else{ 67 | LINKED_BLOCKING_QUEUE.add(entries); 68 | } 69 | } 70 | } 71 | }); 72 | socket.handler(parser); 73 | }); 74 | 75 | Future listen = netServer.listen(); 76 | listen.onComplete(event -> { 77 | boolean succeeded = event.succeeded(); 78 | if (succeeded) { 79 | HttpSendUtil.开启hook(new OpenHook().setPort(InitWeChat.getVertxPort().toString()).setIp("127.0.0.1") 80 | .setEnableHttp(false) 81 | .setTimeout("5000")); 82 | // HttpAsyncUtil.exec(HttpAsyncUtil.Type.开启hook, new JsonObject().put("port", InitWeChat.getVertxPort().toString()).put("ip", "127.0.0.1")); 83 | startPromise.complete(); 84 | } else { 85 | startPromise.fail(event.cause()); 86 | } 87 | 88 | }); 89 | } 90 | 91 | @Override 92 | public void run(String... args) throws Exception { 93 | WxhkApplication.vertx.deployVerticle(this, new DeploymentOptions().setWorkerPoolSize(6)); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.util; 2 | 3 | import com.example.wxhk.WxhkApplication; 4 | import com.example.wxhk.tcp.vertx.InitWeChat; 5 | import io.vertx.core.AsyncResult; 6 | import io.vertx.core.Future; 7 | import io.vertx.core.Handler; 8 | import io.vertx.core.buffer.Buffer; 9 | import io.vertx.core.json.JsonObject; 10 | import io.vertx.ext.web.client.HttpResponse; 11 | import io.vertx.ext.web.client.WebClient; 12 | import io.vertx.ext.web.client.WebClientOptions; 13 | import org.dromara.hutool.log.Log; 14 | 15 | /** 16 | * http异步请求 17 | * 18 | * @author wt 19 | * @date 2023/05/25 20 | */ 21 | public class HttpAsyncUtil { 22 | public static final WebClient client = WebClient.create(WxhkApplication.vertx, new WebClientOptions().setDefaultHost("localhost").setDefaultPort(InitWeChat.wxPort) 23 | .setConnectTimeout(10000).setMaxPoolSize(10).setPoolEventLoopSize(10)); 24 | protected static final Log log = Log.get(); 25 | 26 | public static Future> exec(Type type, JsonObject object) { 27 | return client.post(InitWeChat.wxPort, "localhost", "/api/" + type.getType()) 28 | .sendJsonObject(object) 29 | .onSuccess(event -> 30 | { 31 | if (log.isDebugEnabled()) { 32 | log.debug("type:{},{}", type.getType(), event.bodyAsJsonObject()); 33 | } 34 | } 35 | ); 36 | } 37 | 38 | public static Future> exec(Type type, JsonObject object, Handler>> handler) { 39 | return client.post(InitWeChat.wxPort, "localhost", "/api/" + type.getType()) 40 | .sendJsonObject(object) 41 | .onComplete(handler) 42 | ; 43 | 44 | 45 | } 46 | 47 | public enum Type { 48 | 检查微信登陆("checkLogin"), 49 | 获取登录信息("userInfo"), 50 | 发送文本("sendTextMsg"), 51 | 转发消息("forwardMsg"), 52 | 发送at文本("sendAtText"), 53 | 发送图片("5"), 54 | 发送文件("sendFileMsg"), 55 | 开启hook("hookSyncMsg"), 56 | 关闭hook("unhookSyncMsg"), 57 | 添加好友("20"), 58 | 通过好友申请("23"), 59 | 获取群成员("getMemberFromChatRoom"), 60 | 获取群成员基础信息("getContactProfile"), 61 | 获取群详情("getChatRoomDetailInfo"), 62 | 添加群成员("addMemberToChatRoom"), 63 | 修改群昵称("modifyNickname"), 64 | 删除群成员("delMemberFromChatRoom"), 65 | 置顶群消息("topMsg"), 66 | 取消置顶群消息("removeTopMsg"), 67 | 邀请入群("InviteMemberToChatRoom"), 68 | 确认收款("45"), 69 | 联系人列表("getContactList"), 70 | 查询微信信息("55"), 71 | 下载附件("downloadAttach"), 72 | 解码("decodeImage"), 73 | 74 | 75 | ; 76 | String type; 77 | 78 | Type(String type) { 79 | this.type = type; 80 | } 81 | 82 | public String getType() { 83 | return type; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.util; 2 | 3 | import com.example.wxhk.model.PrivateChatMsg; 4 | import com.example.wxhk.model.request.*; 5 | import com.example.wxhk.model.response.ContactList; 6 | import com.example.wxhk.model.response.GroupMembers; 7 | import com.example.wxhk.tcp.vertx.ArrHandle; 8 | import com.example.wxhk.tcp.vertx.InitWeChat; 9 | import io.vertx.core.json.JsonObject; 10 | import org.dromara.hutool.core.util.XmlUtil; 11 | import org.dromara.hutool.log.Log; 12 | import org.w3c.dom.Document; 13 | import org.w3c.dom.Node; 14 | 15 | /** 16 | * 常见方法 17 | * 18 | * @author wt 19 | * @date 2023/05/29 20 | */ 21 | public class HttpSendUtil { 22 | 23 | protected static final Log log = Log.get(); 24 | 25 | public static JsonObject 通过好友请求(PrivateChatMsg msg) { 26 | Document document = XmlUtil.parseXml(msg.getContent()); 27 | String encryptusername = document.getDocumentElement().getAttribute("encryptusername"); 28 | String ticket = document.getDocumentElement().getAttribute("ticket"); 29 | return HttpSyncUtil.exec(HttpAsyncUtil.Type.通过好友申请, new JsonObject().put("v3", encryptusername).put("v4", ticket).put("permission", "0")); 30 | } 31 | 32 | public static JsonObject 确认收款(PrivateChatMsg msg) { 33 | try { 34 | String content = msg.getContent(); 35 | Document document = XmlUtil.parseXml(content); 36 | Node paysubtype = document.getElementsByTagName("paysubtype").item(0); 37 | if ("1".equals(paysubtype.getTextContent().trim())) { 38 | // 手机发出去的 39 | String textContent = document.getElementsByTagName("receiver_username").item(0).getTextContent(); 40 | if (!InitWeChat.WXID_MAP.contains(textContent)) { 41 | return new JsonObject().put("spick", true); 42 | } 43 | Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0); 44 | Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0); 45 | return HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, new JsonObject().put("wxid", msg.getFromUser()) 46 | .put("transcationId", transcationid.getTextContent()) 47 | .put("transferId", transferid.getTextContent())); 48 | 49 | } 50 | // 如果是确认接受收款,则跳过 51 | return new JsonObject(); 52 | 53 | } catch (Exception e) { 54 | throw new RuntimeException(e); 55 | } 56 | } 57 | 58 | 59 | public static JsonObject 发送文本(String wxid, String msg) { 60 | return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送文本, JsonObject.mapFrom(new SendMsg().setMsg(msg).setWxid(wxid))); 61 | } 62 | 63 | public static JsonObject 发送文本(String msg) { 64 | return 发送文本(ArrHandle.getPriMsg().getFromUser(), msg); 65 | } 66 | 67 | public static JsonObject 发送at文本(String chatRoomId, String wxids, String msg) { 68 | return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送at文本, JsonObject.mapFrom(new SendMsg().setMsg(msg).setWxids(wxids).setChatRoomId(chatRoomId))); 69 | } 70 | 71 | public static JsonObject 发送at文本(String wxids, String msg) { 72 | return 发送at文本(ArrHandle.getPriMsg().getFromGroup(), wxids, msg); 73 | } 74 | 75 | public static JsonObject 发送图片(String wxid, String msg) { 76 | return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送图片, JsonObject.mapFrom(new SendMsg().setImagePath(msg).setWxid(wxid))); 77 | } 78 | 79 | public static JsonObject 发送图片(String msg) { 80 | return 发送图片(ArrHandle.getPriMsg().getFromUser(), msg); 81 | } 82 | 83 | public static JsonObject 发送文件(String wxid, String msg) { 84 | return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送文件, JsonObject.mapFrom(new SendMsg().setFilePath(msg).setWxid(wxid))); 85 | } 86 | 87 | public static JsonObject 发送文件(String msg) { 88 | return 发送文件(ArrHandle.getPriMsg().getFromUser(), msg); 89 | } 90 | 91 | public static JsonObject 添加好友(AddFriends p) { 92 | return HttpSyncUtil.exec(HttpAsyncUtil.Type.添加好友, p.toJson()); 93 | } 94 | 95 | 96 | public static String 获取当前登陆微信id() { 97 | JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject()); 98 | return exec.getJsonObject("data").getString("wxid"); 99 | } 100 | 101 | public static ContactList 联系人列表(){ 102 | JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.联系人列表, new JsonObject()); 103 | return exec.mapTo(ContactList.class); 104 | } 105 | public static JsonObject 开启hook(OpenHook hook){ 106 | JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.开启hook,hook.toJson()); 107 | return exec; 108 | } 109 | public static JsonObject 关闭hook(){ 110 | JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.关闭hook,new JsonObject()); 111 | return exec; 112 | } 113 | 114 | public static GroupMembers 获取群成员(GetGroupMembers p){ 115 | return HttpSyncUtil.exec(HttpAsyncUtil.Type.获取群成员, p.toJson()).mapTo(GroupMembers.class); 116 | 117 | } 118 | 119 | 120 | public static JsonObject 确认收款(ConfirmThePayment payment){ 121 | return HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, payment.toJson()); 122 | } 123 | 124 | 125 | @Deprecated 126 | public static com.example.wxhk.infe.SendMsg of(HttpAsyncUtil.Type type) { 127 | switch (type) { 128 | 129 | case 检查微信登陆 -> { 130 | 131 | } 132 | case 获取登录信息 -> { 133 | } 134 | case 发送文本 -> { 135 | return new SendText(); 136 | } 137 | case 发送at文本 -> { 138 | return new SendAtText(); 139 | } 140 | case 发送图片 -> { 141 | return new SendImg(); 142 | } 143 | case 发送文件 -> { 144 | return new SendFile(); 145 | } 146 | 147 | } 148 | return new SendText(); 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.util; 2 | 3 | import com.example.wxhk.tcp.vertx.InitWeChat; 4 | import io.vertx.core.json.JsonObject; 5 | import org.dromara.hutool.http.client.ClientConfig; 6 | import org.dromara.hutool.http.client.Request; 7 | import org.dromara.hutool.http.client.engine.ClientEngine; 8 | import org.dromara.hutool.http.client.engine.ClientEngineFactory; 9 | import org.dromara.hutool.http.meta.Method; 10 | import org.dromara.hutool.log.Log; 11 | 12 | /** 13 | * http同步请求 14 | * 15 | * @author wt 16 | * @date 2023/05/25 17 | */ 18 | public class HttpSyncUtil { 19 | protected static final Log log = Log.get(); 20 | static final ClientEngine engine; 21 | 22 | static { 23 | ClientConfig clientConfig = ClientConfig.of() 24 | .setTimeout(30 * 1000); 25 | engine = ClientEngineFactory.createEngine(clientConfig); 26 | 27 | } 28 | 29 | public static JsonObject exec(HttpAsyncUtil.Type type, JsonObject obj) { 30 | String post = engine.send(Request.of("http://localhost:" + InitWeChat.wxPort + "/api/" + type.getType()).method(Method.POST).body(obj.encode())).bodyStr(); 31 | if (log.isDebugEnabled()) { 32 | log.debug("type:{},{}", type.getType(), post); 33 | } 34 | return new JsonObject(post); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /java_client/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | wx.path=D:\\Program Files (x86)\\Tencent\\WeChat\\WeChat.exe 2 | wx.port=19088 3 | spring.profiles.active=local 4 | vertx.port=8080 -------------------------------------------------------------------------------- /java_client/src/main/resources/exec/c.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttttupup/wxhelper/fa90a0ea87c9b4ca9c63a1e8385293159167f367/java_client/src/main/resources/exec/c.exe -------------------------------------------------------------------------------- /java_client/src/main/resources/exec/wxhelper.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttttupup/wxhelper/fa90a0ea87c9b4ca9c63a1e8385293159167f367/java_client/src/main/resources/exec/wxhelper.dll -------------------------------------------------------------------------------- /java_client/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ${withLineNumber_debug} 22 | utf-8 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | log_error.log 31 | 32 | 33 | ${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz 34 | 50MB 35 | 100 36 | 37 | 38 | 39 | true 40 | 41 | 42 | 43 | ${file_pattern} 44 | utf-8 45 | 46 | 47 | 48 | 49 | error 50 | ACCEPT 51 | DENY 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | ${CONSOLE_LOG_PATTERN} 79 | utf-8 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | ${LOG_PATH}/log_error.log 88 | 89 | 90 | ${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz 91 | 50MB 92 | 100 93 | 94 | 95 | 96 | true 97 | 98 | 99 | 100 | ${file_pattern} 101 | utf-8 102 | 103 | 104 | 105 | 106 | error 107 | ACCEPT 108 | DENY 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | ${LOG_PATH}/log_total.log 117 | 118 | 119 | ${LOG_PATH}/total/%d{yyyy-MM}/log_total-%d{yyyy-MM-dd}.%i.log.gz 120 | 50MB 121 | 100 122 | 123 | 124 | true 125 | 126 | 127 | ${file_pattern} 128 | utf-8 129 | 130 | 131 | 132 | 133 | 134 | ${LOG_PATH}/log_business.log 135 | 136 | 137 | ${LOG_PATH}/business/%d{yyyy-MM}/log_business-%d{yyyy-MM-dd}.%i.log.gz 138 | 139 | 50MB 140 | 100 141 | 142 | 143 | true 144 | 145 | 146 | ${file_pattern} 147 | utf-8 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /java_client/src/main/resources/spy.properties: -------------------------------------------------------------------------------- 1 | logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat 2 | # 使用Slf4J记录sql 3 | appender=com.p6spy.engine.spy.appender.Slf4JLogger 4 | # 是否开启慢SQL记录 5 | outagedetection=true 6 | # 慢SQL记录标准,单位秒 7 | outagedetectioninterval=2 8 | #日期格式 9 | dateformat=HH:mm:ss 10 | customLogMessageFormat=%(executionTime)|%(category)|connection%(connectionId)|%(sqlSingleLine) -------------------------------------------------------------------------------- /java_client/src/test/java/com/example/wxhk/WxhkApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class WxhkApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.tcp; 2 | 3 | import com.example.wxhk.util.HttpAsyncUtil; 4 | import io.vertx.core.Future; 5 | import io.vertx.core.buffer.Buffer; 6 | import io.vertx.core.json.JsonObject; 7 | import io.vertx.ext.web.client.HttpResponse; 8 | import org.dromara.hutool.core.lang.Console; 9 | import org.dromara.hutool.core.thread.ThreadUtil; 10 | import org.dromara.hutool.log.Log; 11 | import org.junit.jupiter.api.Test; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | 14 | @SpringBootTest 15 | class HttpAsyncUtilTest { 16 | 17 | protected static final Log log = Log.get(); 18 | 19 | 20 | @Test 21 | void exec() { 22 | Future> exec = HttpAsyncUtil.exec(HttpAsyncUtil.Type.联系人列表, new JsonObject()); 23 | exec.onSuccess(event -> { 24 | Console.log(event.bodyAsJsonObject()); 25 | }); 26 | } 27 | @Test 28 | void exec1() { 29 | 30 | for(int i=0;i<10000;i++){ 31 | int finalI = i; 32 | HttpAsyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject(), event -> { 33 | if (event.succeeded()) { 34 | log.info("i:{},{}", finalI,event.result().bodyAsJsonObject()); 35 | }else{ 36 | event.cause().printStackTrace(); 37 | } 38 | }); 39 | log.info("发出请求:{}",i); 40 | } 41 | 42 | ThreadUtil.sync(this); 43 | } 44 | @Test 45 | void exec2() { 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /java_client/src/test/java/com/example/wxhk/util/HttpSendUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.example.wxhk.util; 2 | 3 | import com.example.wxhk.model.request.GetGroupMembers; 4 | import com.example.wxhk.model.response.ContactList; 5 | import com.example.wxhk.model.response.GroupMembers; 6 | import org.dromara.hutool.core.lang.Console; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | import java.time.Duration; 11 | import java.time.LocalDateTime; 12 | import java.util.List; 13 | 14 | @SpringBootTest 15 | class HttpSendUtilTest { 16 | 17 | 18 | @Test 19 | void 获取当前登陆微信id() { 20 | String s = HttpSendUtil.获取当前登陆微信id(); 21 | } 22 | 23 | @Test 24 | void 联系人列表() { 25 | ContactList contactList = HttpSendUtil.联系人列表(); 26 | 27 | List data = contactList.getData(); 28 | for (ContactList.DataBean datum : data) { 29 | Console.log(datum.getWxid(),datum.getUserName()); 30 | } 31 | Console.log(contactList); 32 | } 33 | @Test 34 | void 开启hook() { 35 | 36 | } 37 | @Test 38 | void 关闭ook() { 39 | HttpSendUtil.关闭hook(); 40 | } 41 | 42 | @Test 43 | void 获取群成员() { 44 | GroupMembers 获取群成员 = HttpSendUtil.获取群成员(new GetGroupMembers().setChatRoomId("24964676359@chatroom")); 45 | Console.log(获取群成员); 46 | 47 | Duration between = Duration.between(LocalDateTime.now(), LocalDateTime.now()); 48 | } 49 | } -------------------------------------------------------------------------------- /nodejs_client/tcp_server.js: -------------------------------------------------------------------------------- 1 | const net = require('net') 2 | 3 | const server = net.createServer(socket => { 4 | console.log('New client connected') 5 | 6 | let data = Buffer.from('') 7 | 8 | socket.on('data', data => { 9 | data = Buffer.concat([data, chunk]) 10 | console.log(`Received data: ${data}`) 11 | }) 12 | 13 | socket.on('end', () => { 14 | const decodedData = data.toString('utf8') 15 | console.log(`Received data: ${decodedData}`) 16 | }) 17 | 18 | socket.on('close', () => { 19 | console.log('Client disconnected') 20 | }) 21 | }) 22 | 23 | const port = 19099 24 | server.listen(port, () => { 25 | console.log(`Server listening on port ${port}`) 26 | }) 27 | -------------------------------------------------------------------------------- /python/3.9.5.81/http_client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | 5 | def checkLogin(): 6 | url = "127.0.0.1:19088/api/checkLogin" 7 | payload = {} 8 | headers = {} 9 | response = requests.request("POST", url, headers=headers, data=payload) 10 | print(response.text) 11 | 12 | 13 | def userInfo(): 14 | url = "127.0.0.1:19088/api/userInfo" 15 | payload = {} 16 | headers = {} 17 | response = requests.request("POST", url, headers=headers, data=payload) 18 | print(response.text) 19 | 20 | 21 | def sendTextMsg(): 22 | url = "127.0.0.1:19088/api/sendTextMsg" 23 | payload = json.dumps({ 24 | "wxid": "filehelper", 25 | "msg": "12www" 26 | }) 27 | headers = { 28 | 'Content-Type': 'application/json' 29 | } 30 | response = requests.request("POST", url, headers=headers, data=payload) 31 | print(response.text) 32 | 33 | 34 | def sendImagesMsg(): 35 | url = "127.0.0.1:19088/api/sendImagesMsg" 36 | print("modify imagePath") 37 | raise RuntimeError("modify imagePath then deleted me") 38 | payload = json.dumps({ 39 | "wxid": "filehelper", 40 | "imagePath": "C:\\pic.png" 41 | }) 42 | headers = { 43 | 'Content-Type': 'application/json' 44 | } 45 | 46 | response = requests.request("POST", url, headers=headers, data=payload) 47 | 48 | print(response.text) 49 | 50 | 51 | def sendFileMsg(): 52 | url = "127.0.0.1:19088/api/sendFileMsg" 53 | print("modify filePath") 54 | raise RuntimeError("modify filePath then deleted me") 55 | payload = json.dumps({ 56 | "wxid": "filehelper", 57 | "filePath": "C:\\test.zip" 58 | }) 59 | headers = { 60 | 'Content-Type': 'application/json' 61 | } 62 | response = requests.request("POST", url, headers=headers, data=payload) 63 | print(response.text) 64 | 65 | 66 | def hookSyncMsg(): 67 | url = "127.0.0.1:19088/api/hookSyncMsg" 68 | print("modify ip port url ") 69 | raise RuntimeError("modify ip port url then deleted me") 70 | payload = json.dumps({ 71 | "port": "19099", 72 | "ip": "127.0.0.1", 73 | "url": "http://localhost:8080", 74 | "timeout": "3000", 75 | "enableHttp": "0" 76 | }) 77 | headers = { 78 | 'Content-Type': 'application/json' 79 | } 80 | response = requests.request("POST", url, headers=headers, data=payload) 81 | print(response.text) 82 | 83 | 84 | def unhookSyncMsg(): 85 | url = "127.0.0.1:19088/api/unhookSyncMsg" 86 | payload = {} 87 | headers = {} 88 | response = requests.request("POST", url, headers=headers, data=payload) 89 | print(response.text) 90 | 91 | 92 | def getContactList(): 93 | url = "127.0.0.1:19088/api/getContactList" 94 | payload = {} 95 | headers = {} 96 | response = requests.request("POST", url, headers=headers, data=payload) 97 | print(response.text) 98 | 99 | 100 | def getDBInfo(): 101 | url = "127.0.0.1:19088/api/getDBInfo" 102 | payload = {} 103 | headers = {} 104 | response = requests.request("POST", url, headers=headers, data=payload) 105 | print(response.text) 106 | 107 | 108 | def execSql(): 109 | url = "127.0.0.1:19088/api/execSql" 110 | print("modify dbHandle ") 111 | raise RuntimeError("modify dbHandle then deleted me") 112 | payload = json.dumps({ 113 | "dbHandle": 1713425147584, 114 | "sql": "select * from MSG where localId =100;" 115 | }) 116 | headers = { 117 | 'Content-Type': 'application/json' 118 | } 119 | response = requests.request("POST", url, headers=headers, data=payload) 120 | print(response.text) 121 | 122 | 123 | def getChatRoomDetailInfo(): 124 | url = "127.0.0.1:19088/api/getChatRoomDetailInfo" 125 | print("modify chatRoomId ") 126 | raise RuntimeError("modify chatRoomId then deleted me") 127 | payload = json.dumps({ 128 | "chatRoomId": "123333@chatroom" 129 | }) 130 | headers = { 131 | 'Content-Type': 'application/json' 132 | } 133 | response = requests.request("POST", url, headers=headers, data=payload) 134 | print(response.text) 135 | 136 | 137 | def addMemberToChatRoom(): 138 | url = "127.0.0.1:19088/api/addMemberToChatRoom" 139 | print("modify chatRoomId memberIds ") 140 | raise RuntimeError("modify chatRoomId memberIds then deleted me") 141 | payload = json.dumps({ 142 | "chatRoomId": "123@chatroom", 143 | "memberIds": "wxid_123" 144 | }) 145 | headers = { 146 | 'Content-Type': 'application/json' 147 | } 148 | 149 | response = requests.request("POST", url, headers=headers, data=payload) 150 | 151 | print(response.text) 152 | 153 | 154 | def delMemberFromChatRoom(): 155 | url = "127.0.0.1:19088/api/delMemberFromChatRoom" 156 | print("modify chatRoomId memberIds ") 157 | raise RuntimeError("modify chatRoomId memberIds then deleted me") 158 | payload = json.dumps({ 159 | "chatRoomId": "21363231004@chatroom", 160 | "memberIds": "wxid_123" 161 | }) 162 | headers = { 163 | 'Content-Type': 'application/json' 164 | } 165 | response = requests.request("POST", url, headers=headers, data=payload) 166 | print(response.text) 167 | 168 | 169 | def modifyNickname(): 170 | url = "127.0.0.1:19088/api/modifyNickname" 171 | print("modify chatRoomId wxid nickName") 172 | raise RuntimeError("modify chatRoomId wxid nickName then deleted me") 173 | payload = json.dumps({ 174 | "chatRoomId": "123@chatroom", 175 | "wxid": "wxid_123", 176 | "nickName": "test" 177 | }) 178 | headers = { 179 | 'Content-Type': 'application/json' 180 | } 181 | response = requests.request("POST", url, headers=headers, data=payload) 182 | print(response.text) 183 | 184 | 185 | def getMemberFromChatRoom(): 186 | print("modify chatRoomId ") 187 | raise RuntimeError("modify chatRoomId then deleted me") 188 | url = "127.0.0.1:19088/api/getMemberFromChatRoom" 189 | payload = json.dumps({ 190 | "chatRoomId": "123@chatroom" 191 | }) 192 | headers = { 193 | 'Content-Type': 'application/json' 194 | } 195 | response = requests.request("POST", url, headers=headers, data=payload) 196 | print(response.text) 197 | 198 | 199 | def topMsg(): 200 | print("modify msgId ") 201 | raise RuntimeError("modify msgId then deleted me") 202 | url = "127.0.0.1:19088/api/topMsg" 203 | payload = json.dumps({ 204 | "msgId": 1222222 205 | }) 206 | headers = { 207 | 'Content-Type': 'application/json' 208 | } 209 | response = requests.request("POST", url, headers=headers, data=payload) 210 | print(response.text) 211 | 212 | 213 | def removeTopMsg(): 214 | print("modify msgId chatRoomId ") 215 | raise RuntimeError("modify msgId chatRoomId then deleted me") 216 | 217 | url = "127.0.0.1:19088/api/removeTopMsg" 218 | 219 | payload = json.dumps({ 220 | "chatRoomId": "123@chatroom", 221 | "msgId": 123 222 | }) 223 | headers = { 224 | 'Content-Type': 'application/json' 225 | } 226 | response = requests.request("POST", url, headers=headers, data=payload) 227 | print(response.text) 228 | 229 | 230 | def InviteMemberToChatRoom(): 231 | print("modify memberIds chatRoomId ") 232 | raise RuntimeError("modify memberIds chatRoomId then deleted me") 233 | 234 | url = "127.0.0.1:19088/api/InviteMemberToChatRoom" 235 | 236 | payload = json.dumps({ 237 | "chatRoomId": "123@chatroom", 238 | "memberIds": "wxid_123" 239 | }) 240 | headers = { 241 | 'Content-Type': 'application/json' 242 | } 243 | response = requests.request("POST", url, headers=headers, data=payload) 244 | print(response.text) 245 | 246 | 247 | def hookLog(): 248 | url = "127.0.0.1:19088/api/hookLog" 249 | payload = {} 250 | headers = {} 251 | response = requests.request("POST", url, headers=headers, data=payload) 252 | print(response.text) 253 | 254 | 255 | def unhookLog(): 256 | url = "127.0.0.1:19088/api/unhookLog" 257 | payload = {} 258 | headers = {} 259 | response = requests.request("POST", url, headers=headers, data=payload) 260 | print(response.text) 261 | 262 | 263 | def createChatRoom(): 264 | print("modify memberIds ") 265 | raise RuntimeError("modify memberIds then deleted me") 266 | url = "127.0.0.1:19088/api/createChatRoom" 267 | 268 | payload = json.dumps({ 269 | "memberIds": "wxid_8yn4k908tdqp22,wxid_oyb662qhop4422" 270 | }) 271 | headers = { 272 | 'Content-Type': 'application/json' 273 | } 274 | response = requests.request("POST", url, headers=headers, data=payload) 275 | print(response.text) 276 | 277 | def quitChatRoom(): 278 | print("modify chatRoomId ") 279 | raise RuntimeError("modify chatRoomId then deleted me") 280 | url = "127.0.0.1:19088/api/quitChatRoom" 281 | 282 | payload = json.dumps({ 283 | "chatRoomId": "123@chatroom" 284 | }) 285 | headers = { 286 | 'Content-Type': 'application/json' 287 | } 288 | 289 | response = requests.request("POST", url, headers=headers, data=payload) 290 | print(response.text) 291 | 292 | def forwardMsg(): 293 | print("modify msgId ") 294 | raise RuntimeError("modify msgId then deleted me") 295 | url = "127.0.0.1:19088/api/forwardMsg" 296 | 297 | payload = json.dumps({ 298 | "wxid": "filehelper", 299 | "msgId": "12331" 300 | }) 301 | headers = { 302 | 'Content-Type': 'application/json' 303 | } 304 | response = requests.request("POST", url, headers=headers, data=payload) 305 | print(response.text) 306 | 307 | def getSNSFirstPage(): 308 | url = "127.0.0.1:19088/api/getSNSFirstPage" 309 | 310 | payload = {} 311 | headers = {} 312 | response = requests.request("POST", url, headers=headers, data=payload) 313 | print(response.text) 314 | 315 | def getSNSNextPage(): 316 | print("modify snsId ") 317 | raise RuntimeError("modify snsId then deleted me") 318 | url = "127.0.0.1:19088/api/getSNSNextPage" 319 | 320 | payload = json.dumps({ 321 | "snsId": "" 322 | }) 323 | headers = { 324 | 'Content-Type': 'application/json' 325 | } 326 | 327 | response = requests.request("POST", url, headers=headers, data=payload) 328 | 329 | print(response.text) 330 | 331 | def addFavFromMsg(): 332 | print("modify msgId ") 333 | raise RuntimeError("modify msgId then deleted me") 334 | url = "127.0.0.1:19088/api/addFavFromMsg" 335 | 336 | payload = json.dumps({ 337 | "msgId": "1222222" 338 | }) 339 | headers = { 340 | 'Content-Type': 'application/json' 341 | } 342 | 343 | response = requests.request("POST", url, headers=headers, data=payload) 344 | 345 | print(response.text) 346 | 347 | def addFavFromImage(): 348 | print("modify wxid imagePath ") 349 | raise RuntimeError("modify wxid imagePath then deleted me") 350 | url = "127.0.0.1:19088/api/addFavFromImage" 351 | 352 | payload = json.dumps({ 353 | "wxid": "", 354 | "imagePath": "" 355 | }) 356 | headers = { 357 | 'Content-Type': 'application/json' 358 | } 359 | 360 | response = requests.request("POST", url, headers=headers, data=payload) 361 | 362 | print(response.text) 363 | 364 | def getContactProfile(): 365 | print("modify wxid ") 366 | raise RuntimeError("modify wxid then deleted me") 367 | url = "127.0.0.1:19088/api/getContactProfile" 368 | 369 | payload = json.dumps({ 370 | "wxid": "" 371 | }) 372 | headers = { 373 | 'Content-Type': 'application/json' 374 | } 375 | 376 | response = requests.request("POST", url, headers=headers, data=payload) 377 | print(response.text) 378 | 379 | 380 | def sendAtText(): 381 | print("modify wxids chatRoomId") 382 | raise RuntimeError("modify wxids chatRoomId then deleted me") 383 | url = "127.0.0.1:19088/api/sendAtText" 384 | 385 | payload = json.dumps({ 386 | "wxids": "notify@all", 387 | "chatRoomId": "123@chatroom", 388 | "msg": "你好啊" 389 | }) 390 | headers = { 391 | 'Content-Type': 'application/json' 392 | } 393 | 394 | response = requests.request("POST", url, headers=headers, data=payload) 395 | 396 | print(response.text) 397 | 398 | def forwardPublicMsg(): 399 | print("modify param ") 400 | raise RuntimeError("modify param then deleted me") 401 | url = "127.0.0.1:19088/api/forwardPublicMsg" 402 | 403 | payload = json.dumps({ 404 | "appName": "", 405 | "userName": "", 406 | "title": "", 407 | "url": "", 408 | "thumbUrl": "", 409 | "digest": "", 410 | "wxid": "filehelper" 411 | }) 412 | headers = { 413 | 'Content-Type': 'application/json' 414 | } 415 | 416 | response = requests.request("POST", url, headers=headers, data=payload) 417 | 418 | print(response.text) 419 | 420 | def forwardPublicMsgByMsgId(): 421 | print("modify param ") 422 | raise RuntimeError("modify param then deleted me") 423 | url = "127.0.0.1:19088/api/forwardPublicMsgByMsgId" 424 | 425 | payload = json.dumps({ 426 | "msgId": 123, 427 | "wxid": "filehelper" 428 | }) 429 | headers = { 430 | 'Content-Type': 'application/json' 431 | } 432 | 433 | response = requests.request("POST", url, headers=headers, data=payload) 434 | 435 | print(response.text) 436 | 437 | def downloadAttach(): 438 | print("modify param ") 439 | raise RuntimeError("modify param then deleted me") 440 | url = "127.0.0.1:19088/api/downloadAttach" 441 | 442 | payload = json.dumps({ 443 | "msgId": 123 444 | }) 445 | headers = { 446 | 'Content-Type': 'application/json' 447 | } 448 | 449 | response = requests.request("POST", url, headers=headers, data=payload) 450 | 451 | print(response.text) 452 | 453 | 454 | def decodeImage(): 455 | print("modify param ") 456 | raise RuntimeError("modify param then deleted me") 457 | url = "127.0.0.1:19088/api/decodeImage" 458 | 459 | payload = json.dumps({ 460 | "filePath": "C:\\66664816980131.dat", 461 | "storeDir": "C:\\test" 462 | }) 463 | headers = { 464 | 'Content-Type': 'application/json' 465 | } 466 | 467 | response = requests.request("POST", url, headers=headers, data=payload) 468 | 469 | print(response.text) 470 | 471 | 472 | def getVoiceByMsgId(): 473 | print("modify param ") 474 | raise RuntimeError("modify param then deleted me") 475 | url = "127.0.0.1:19088/api/getVoiceByMsgId" 476 | 477 | payload = json.dumps({ 478 | "msgId": 7880439644200, 479 | "storeDir": "c:\\test" 480 | }) 481 | headers = { 482 | 'Content-Type': 'application/json' 483 | } 484 | 485 | response = requests.request("POST", url, headers=headers, data=payload) 486 | 487 | print(response.text) 488 | 489 | 490 | 491 | if __name__ == '__main__': 492 | checkLogin() 493 | # userInfo() 494 | -------------------------------------------------------------------------------- /python/client.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | 5 | def check_login(): 6 | """ 7 | 0.检查是否登录 8 | :return: 9 | """ 10 | url = "127.0.0.1:19088/api/?type=0" 11 | payload = {} 12 | headers = {} 13 | response = requests.request("POST", url, headers=headers, data=payload) 14 | print(response.text) 15 | 16 | 17 | def user_info(): 18 | """ 19 | 登录用户信息 20 | :return: 21 | """ 22 | url = "127.0.0.1:19088/api/?type=8" 23 | payload = {} 24 | headers = {} 25 | response = requests.request("POST", url, headers=headers, data=payload) 26 | print(response.text) 27 | 28 | 29 | def send_text(): 30 | """ 31 | 发送文本 32 | :return: 33 | """ 34 | url = "127.0.0.1:19088/api/?type=2" 35 | payload = json.dumps({ 36 | "wxid": "filehelper", 37 | "msg": "123" 38 | }) 39 | headers = { 40 | 'Content-Type': 'application/json' 41 | } 42 | response = requests.request("POST", url, headers=headers, data=payload) 43 | print(response.text) 44 | 45 | 46 | def send_at(): 47 | """ 48 | 发送@消息 49 | :return: 50 | """ 51 | url = "127.0.0.1:19088/api/?type=3" 52 | payload = json.dumps({ 53 | "chatRoomId": "12333@chatroom", 54 | "wxids": "notify@all", 55 | "msg": "12333" 56 | }) 57 | headers = { 58 | 'Content-Type': 'application/json' 59 | } 60 | response = requests.request("POST", url, headers=headers, data=payload) 61 | print(response.text) 62 | 63 | 64 | def send_img(): 65 | """ 66 | 发送图片 67 | :return: 68 | """ 69 | url = "127.0.0.1:19088/api/?type=5" 70 | payload = json.dumps({ 71 | "wxid": "filehelper", 72 | "imagePath": "C:/123.png" 73 | }) 74 | headers = { 75 | 'Content-Type': 'application/json' 76 | } 77 | response = requests.request("POST", url, headers=headers, data=payload) 78 | print(response.text) 79 | 80 | 81 | def send_file(): 82 | """ 83 | 发送文件 84 | :return: 85 | """ 86 | url = "127.0.0.1:19088/api/?type=6" 87 | payload = json.dumps({ 88 | "wxid": "filehelper", 89 | "filePath": "C:/test.txt" 90 | }) 91 | headers = { 92 | 'Content-Type': 'application/json' 93 | } 94 | response = requests.request("POST", url, headers=headers, data=payload) 95 | print(response.text) 96 | 97 | 98 | def hook_msg(): 99 | """ 100 | hook 消息 101 | :return: 102 | """ 103 | url = "127.0.0.1:19088/api/?type=9" 104 | payload = json.dumps({ 105 | "port": "19099", 106 | "ip": "127.0.0.1" 107 | }) 108 | headers = { 109 | 'Content-Type': 'application/json' 110 | } 111 | response = requests.request("POST", url, headers=headers, data=payload) 112 | print(response.text) 113 | 114 | 115 | def unhook_msg(): 116 | """ 117 | 取消消息hook 118 | :return: 119 | """ 120 | url = "127.0.0.1:19088/api/?type=10" 121 | payload = {} 122 | headers = {} 123 | response = requests.request("POST", url, headers=headers, data=payload) 124 | print(response.text) 125 | 126 | 127 | def hook_img(): 128 | """ 129 | hook 图片 130 | :return: 131 | """ 132 | url = "127.0.0.1:19088/api/?type=11" 133 | payload = json.dumps({ 134 | "imgDir": "C:\\img" 135 | }) 136 | headers = { 137 | 'Content-Type': 'application/json' 138 | } 139 | response = requests.request("POST", url, headers=headers, data=payload) 140 | print(response.text) 141 | 142 | 143 | def unhook_img(): 144 | """ 145 | 取消hook 图片 146 | :return: 147 | """ 148 | url = "127.0.0.1:19088/api/?type=12" 149 | payload = json.dumps({ 150 | "imgDir": "C:\\img" 151 | }) 152 | headers = { 153 | 'Content-Type': 'application/json' 154 | } 155 | response = requests.request("POST", url, headers=headers, data=payload) 156 | print(response.text) 157 | 158 | 159 | def hook_voice(): 160 | """ 161 | hook 语音 162 | :return: 163 | """ 164 | url = "127.0.0.1:19088/api/?type=56" 165 | payload = json.dumps({ 166 | "msgId": 322456091115784000 167 | }) 168 | headers = { 169 | 'Content-Type': 'application/json' 170 | } 171 | response = requests.request("POST", url, headers=headers, data=payload) 172 | print(response.text) 173 | 174 | 175 | def unhook_voice(): 176 | """ 177 | 取消hook 语音 178 | :return: 179 | """ 180 | url = "127.0.0.1:19088/api/?type=14" 181 | payload = {} 182 | headers = {} 183 | response = requests.request("POST", url, headers=headers, data=payload) 184 | print(response.text) 185 | 186 | 187 | def del_friend(): 188 | """ 189 | 删除好友 190 | :return: 191 | """ 192 | url = "127.0.0.1:19088/api/?type=17" 193 | payload = json.dumps({ 194 | "wxid": "wxid_1124423322" 195 | }) 196 | headers = { 197 | 'Content-Type': 'application/json' 198 | } 199 | response = requests.request("POST", url, headers=headers, data=payload) 200 | print(response.text) 201 | 202 | 203 | def search_friend(): 204 | """ 205 | 网络搜素用户 206 | :return: 207 | """ 208 | url = "127.0.0.1:19088/api/?type=19" 209 | payload = json.dumps({ 210 | "keyword": "13812345678" 211 | }) 212 | headers = { 213 | 'Content-Type': 'application/json' 214 | } 215 | response = requests.request("POST", url, headers=headers, data=payload) 216 | print(response.text) 217 | 218 | 219 | def add_friend(): 220 | """ 221 | 添加好友 222 | :return: 223 | """ 224 | url = "127.0.0.1:19088/api/?type=20" 225 | payload = json.dumps({ 226 | "wxid": "wxid_o11222334422" 227 | }) 228 | headers = { 229 | 'Content-Type': 'application/json' 230 | } 231 | response = requests.request("POST", url, headers=headers, data=payload) 232 | print(response.text) 233 | 234 | 235 | def fetch_chat_room_members(): 236 | """ 237 | 群成员 238 | :return: 239 | """ 240 | url = "127.0.0.1:19088/api/?type=25" 241 | payload = json.dumps({ 242 | "chatRoomId": "2112222004@chatroom" 243 | }) 244 | headers = { 245 | 'Content-Type': 'application/json' 246 | } 247 | response = requests.request("POST", url, headers=headers, data=payload) 248 | print(response.text) 249 | 250 | 251 | def get_member_nickname(): 252 | """ 253 | 群成员昵称 254 | :return: 255 | """ 256 | url = "127.0.0.1:19088/api/?type=26" 257 | payload = json.dumps({ 258 | "chatRoomId": "322333384@chatroom", 259 | "memberId": "wxid_4m1112222u22" 260 | }) 261 | headers = { 262 | 'Content-Type': 'application/json' 263 | } 264 | response = requests.request("POST", url, headers=headers, data=payload) 265 | print(response.text) 266 | 267 | 268 | def del_member(): 269 | """ 270 | 删除群成员 271 | :return: 272 | """ 273 | url = "127.0.0.1:19088/api/?type=27" 274 | payload = json.dumps({ 275 | "chatRoomId": "31122263384@chatroom", 276 | "memberIds": "wxid_12223334422" 277 | }) 278 | headers = { 279 | 'Content-Type': 'application/json' 280 | } 281 | response = requests.request("POST", url, headers=headers, data=payload) 282 | print(response.text) 283 | 284 | 285 | def add_member(): 286 | """ 287 | 增加群成员 288 | :return: 289 | """ 290 | url = "127.0.0.1:19088/api/?type=28" 291 | payload = json.dumps({ 292 | "chatRoomId": "1111163384@chatroom", 293 | "memberIds": "wxid_o12222222" 294 | }) 295 | headers = { 296 | 'Content-Type': 'application/json' 297 | } 298 | response = requests.request("POST", url, headers=headers, data=payload) 299 | print(response.text) 300 | 301 | 302 | def modify_room_name(): 303 | """ 304 | 修改群昵称 305 | :return: 306 | """ 307 | url = "127.0.0.1:19088/api/?type=31" 308 | payload = json.dumps({ 309 | "chatRoomId": "222285428@chatroom", 310 | "wxid": "wxid_222222512", 311 | "nickName": "qqq" 312 | }) 313 | headers = { 314 | 'Content-Type': 'application/json' 315 | } 316 | response = requests.request("POST", url, headers=headers, data=payload) 317 | print(response.text) 318 | 319 | 320 | def get_db_handlers(): 321 | """ 322 | 获取sqlite3的操作句柄 323 | :return: 324 | """ 325 | url = "127.0.0.1:19088/api/?type=32" 326 | payload = {} 327 | headers = {} 328 | response = requests.request("POST", url, headers=headers, data=payload) 329 | print(response.text) 330 | 331 | 332 | def query_db_by_sql(): 333 | """ 334 | 查询数据库 335 | :return: 336 | """ 337 | url = "127.0.0.1:19088/api/?type=34" 338 | payload = json.dumps({ 339 | "dbHandle": 116201928, 340 | "sql": "select localId from MSG where MsgSvrID= 7533111101686156" 341 | }) 342 | headers = { 343 | 'Content-Type': 'application/json' 344 | } 345 | response = requests.request("POST", url, headers=headers, data=payload) 346 | print(response.text) 347 | 348 | 349 | def hook_log(): 350 | """ 351 | hook 日志 352 | :return: 353 | """ 354 | url = "127.0.0.1:19088/api/?type=36" 355 | payload = {} 356 | headers = {} 357 | response = requests.request("POST", url, headers=headers, data=payload) 358 | print(response.text) 359 | 360 | 361 | def unhook_log(): 362 | """ 363 | 取消hook日志 364 | :return: 365 | """ 366 | url = "127.0.0.1:19088/api/?type=37" 367 | payload = {} 368 | headers = {} 369 | response = requests.request("POST", url, headers=headers, data=payload) 370 | print(response.text) 371 | 372 | 373 | def forward(): 374 | """ 375 | 转发消息 376 | :return: 377 | """ 378 | url = "127.0.0.1:19088/api/?type=40" 379 | payload = json.dumps({ 380 | "wxid": "filehelper", 381 | "msgid": "705117679011122708" 382 | }) 383 | headers = { 384 | 'Content-Type': 'application/json' 385 | } 386 | response = requests.request("POST", url, headers=headers, data=payload) 387 | print(response.text) 388 | 389 | 390 | def logout(): 391 | """ 392 | 退出登录 393 | :return: 394 | """ 395 | url = "127.0.0.1:19088/api/?type=44" 396 | payload = {} 397 | headers = {} 398 | response = requests.request("POST", url, headers=headers, data=payload) 399 | print(response.text) 400 | 401 | 402 | def confirm_receipt(): 403 | """ 404 | 确认收款 405 | :return: 406 | """ 407 | url = "127.0.0.1:19088/api/?type=45" 408 | payload = json.dumps({ 409 | "wxid": "wxid_1111112622", 410 | "transcationId": "10000500012312222212243388865912", 411 | "transferId": "100005000120212222173123036" 412 | }) 413 | headers = { 414 | 'Content-Type': 'application/json' 415 | } 416 | response = requests.request("POST", url, headers=headers, data=payload) 417 | print(response.text) 418 | 419 | 420 | def contact_list(): 421 | """ 422 | 好友列表 423 | :return: 424 | """ 425 | url = "127.0.0.1:19088/api/?type=46" 426 | payload = {} 427 | headers = {} 428 | response = requests.request("POST", url, headers=headers, data=payload) 429 | print(response.text) 430 | 431 | 432 | def room_detail(): 433 | """ 434 | 群详情 435 | :return: 436 | """ 437 | url = "127.0.0.1:19088/api/?type=47" 438 | payload = json.dumps({ 439 | "chatRoomId": "199134446111@chatroom" 440 | }) 441 | headers = { 442 | 'Content-Type': 'application/json' 443 | } 444 | response = requests.request("POST", url, headers=headers, data=payload) 445 | print(response.text) 446 | 447 | 448 | def ocr(): 449 | """ 450 | ocr提取文字 451 | :return: 452 | """ 453 | url = "127.0.0.1:19088/api/?type=49" 454 | payload = json.dumps({ 455 | "imagePath": "C:\\WeChat Files\\b23e84997144dd12f21554b0.dat" 456 | }) 457 | headers = { 458 | 'Content-Type': 'application/json' 459 | } 460 | response = requests.request("POST", url, headers=headers, data=payload) 461 | print(response.text) 462 | 463 | 464 | def pat(): 465 | """ 466 | 拍一拍 467 | :return: 468 | """ 469 | url = "127.0.0.1:19088/api/?type=50" 470 | payload = json.dumps({ 471 | "chatRoomId": "211111121004@chatroom", 472 | "wxid": "wxid_111111111422" 473 | }) 474 | headers = { 475 | 'Content-Type': 'application/json' 476 | } 477 | response = requests.request("POST", url, headers=headers, data=payload) 478 | print(response.text) 479 | 480 | 481 | def top_msg(): 482 | """ 483 | 消息置顶 484 | :return: 485 | """ 486 | url = "127.0.0.1:19088/api/?type=51" 487 | payload = json.dumps({ 488 | "wxid": "wxid_o11114422", 489 | "msgid": 3728307145189195000 490 | }) 491 | headers = { 492 | 'Content-Type': 'application/json' 493 | } 494 | response = requests.request("POST", url, headers=headers, data=payload) 495 | print(response.text) 496 | 497 | 498 | def close_top_msg(): 499 | """ 500 | 取消置顶 501 | :return: 502 | """ 503 | url = "127.0.0.1:19088/api/?type=52" 504 | payload = json.dumps({ 505 | "chatRoomId": "213222231004@chatroom", 506 | "msgid": 3728307145189195000 507 | }) 508 | headers = { 509 | 'Content-Type': 'application/json' 510 | } 511 | response = requests.request("POST", url, headers=headers, data=payload) 512 | print(response.text) 513 | 514 | 515 | def sns_first(): 516 | """ 517 | 朋友圈首页 518 | :return: 519 | """ 520 | url = "127.0.0.1:19088/api/?type=53" 521 | payload = {} 522 | headers = {} 523 | response = requests.request("POST", url, headers=headers, data=payload) 524 | print(response.text) 525 | 526 | 527 | def sns_next(): 528 | """ 529 | 朋友圈下一页 530 | :return: 531 | """ 532 | url = "127.0.0.1:19088/api/?type=54" 533 | payload = json.dumps({ 534 | "snsId": "14091988153735844377" 535 | }) 536 | headers = { 537 | 'Content-Type': 'application/json' 538 | } 539 | response = requests.request("POST", url, headers=headers, data=payload) 540 | print(response.text) 541 | 542 | 543 | def query_nickname(): 544 | """ 545 | 查询联系人或群名称 546 | :return: 547 | """ 548 | url = "127.0.0.1:19088/api/?type=55" 549 | 550 | payload = json.dumps({ 551 | "id": "wxid_1112p4422" 552 | }) 553 | headers = { 554 | 'Content-Type': 'application/json' 555 | } 556 | response = requests.request("POST", url, headers=headers, data=payload) 557 | print(response.text) 558 | 559 | 560 | def download_msg_attach(): 561 | """ 562 | 下载消息附件 563 | :return: 564 | """ 565 | url = "127.0.0.1:19088/api/?type=56" 566 | payload = json.dumps({ 567 | "msgId": 6080100336053626000 568 | }) 569 | headers = { 570 | 'Content-Type': 'application/json' 571 | } 572 | response = requests.request("POST", url, headers=headers, data=payload) 573 | print(response.text) 574 | 575 | 576 | def get_member_info(): 577 | """ 578 | 获取群/群成员信息 579 | :return: 580 | """ 581 | url = "127.0.0.1:19088/api/?type=57" 582 | payload = json.dumps({ 583 | "wxid": "wxid_tx8k6tu21112" 584 | }) 585 | headers = { 586 | 'Content-Type': 'application/json' 587 | } 588 | response = requests.request("POST", url, headers=headers, data=payload) 589 | print(response.text) 590 | 591 | 592 | if __name__ == '__main__': 593 | check_login() 594 | user_info() 595 | send_text() -------------------------------------------------------------------------------- /python/decrypt.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import hashlib 3 | import hmac 4 | 5 | # pip install pycryptodome 6 | from Crypto.Cipher import AES 7 | 8 | 9 | def decrypt(password, input_file, out_file): 10 | password = bytes.fromhex(password.replace(' ', '')) 11 | with open(input_file, 'rb') as (f): 12 | blist = f.read() 13 | print(len(blist)) 14 | salt = blist[:16] 15 | key = hashlib.pbkdf2_hmac('sha1', password, salt, DEFAULT_ITER, KEY_SIZE) 16 | first = blist[16:DEFAULT_PAGESIZE] 17 | mac_salt = bytes([x ^ 58 for x in salt]) 18 | mac_key = hashlib.pbkdf2_hmac('sha1', key, mac_salt, 2, KEY_SIZE) 19 | hash_mac = hmac.new(mac_key, digestmod='sha1') 20 | hash_mac.update(first[:-32]) 21 | hash_mac.update(bytes(ctypes.c_int(1))) 22 | if hash_mac.digest() == first[-32:-12]: 23 | print('decrypt success') 24 | else: 25 | print('password error') 26 | return 27 | blist = [blist[i:i + DEFAULT_PAGESIZE] for i in range(DEFAULT_PAGESIZE, len(blist), DEFAULT_PAGESIZE)] 28 | with open(out_file, 'wb') as (f): 29 | f.write(SQLITE_FILE_HEADER) 30 | t = AES.new(key, AES.MODE_CBC, first[-48:-32]) 31 | f.write(t.decrypt(first[:-48])) 32 | f.write(first[-48:]) 33 | for i in blist: 34 | t = AES.new(key, AES.MODE_CBC, i[-48:-32]) 35 | f.write(t.decrypt(i[:-48])) 36 | f.write(i[-48:]) 37 | 38 | 39 | def main(): 40 | password = '565735E30E474DA09250CB5AA047E3940FFA1C6F767C4263B13ABB512933DA49' 41 | input_file = 'C:/var/Applet.db' 42 | out_file = 'c:/var/out/Applet.db' 43 | decrypt(password, input_file, out_file) 44 | 45 | 46 | if __name__ == '__main__': 47 | SQLITE_FILE_HEADER = bytes('SQLite format 3', encoding='ASCII') + bytes(1) 48 | KEY_SIZE = 32 49 | DEFAULT_PAGESIZE = 4096 50 | DEFAULT_ITER = 64000 51 | main() -------------------------------------------------------------------------------- /python/http_server.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Request 2 | 3 | 4 | app = FastAPI() 5 | 6 | # pip install fastapi 7 | # run command :uvicorn test:app --reload 8 | # 127.0.0.1:8000/api 9 | 10 | @app.post("/api") 11 | def create_item(request: Request): 12 | print("recv msg") 13 | return {"code": 0, "msg": "success"} 14 | 15 | 16 | @app.middleware("http") 17 | async def TestCustomMiddleware(request: Request, call_next): 18 | the_headers = request.headers 19 | the_body = await request.json() 20 | 21 | print(the_headers) 22 | print(the_body) 23 | 24 | response = await call_next(request) 25 | 26 | return response -------------------------------------------------------------------------------- /python/readme.md: -------------------------------------------------------------------------------- 1 | ### 常用的一些工具 2 | 3 | 4 | client.py : 快速测试dll的http接口。 5 | 6 | decrpt.py : 微信数据库解密工具。password 为dll个人信息里返回的dbkey。 7 | 8 | http_server.py : 一个简单的http server,用来接收hook的消息。 9 | 10 | tcpserver.py: 一个简单的tcp server,用来接收hook的消息。 -------------------------------------------------------------------------------- /python/tcpserver.py: -------------------------------------------------------------------------------- 1 | import json 2 | import threading 3 | import socketserver 4 | 5 | 6 | class ReceiveMsgSocketServer(socketserver.BaseRequestHandler): 7 | def __init__(self, *args, **kwargs): 8 | super().__init__(*args, **kwargs) 9 | 10 | def handle(self): 11 | conn = self.request 12 | while True: 13 | try: 14 | ptr_data = b"" 15 | while True: 16 | data = conn.recv(1024) 17 | ptr_data += data 18 | if len(data) == 0 or data[-1] == 0xA: 19 | break 20 | 21 | msg = json.loads(ptr_data) 22 | ReceiveMsgSocketServer.msg_callback(msg) 23 | 24 | except OSError: 25 | break 26 | except json.JSONDecodeError: 27 | pass 28 | conn.sendall("200 OK".encode()) 29 | conn.close() 30 | 31 | @staticmethod 32 | def msg_callback(msg): 33 | print(msg) 34 | 35 | 36 | def start_socket_server(port: int = 19099, 37 | request_handler=ReceiveMsgSocketServer, 38 | main_thread: bool = True) -> int or None: 39 | ip_port = ("127.0.0.1", port) 40 | try: 41 | s = socketserver.ThreadingTCPServer(ip_port, request_handler) 42 | if main_thread: 43 | s.serve_forever() 44 | else: 45 | socket_server = threading.Thread(target=s.serve_forever) 46 | socket_server.setDaemon(True) 47 | socket_server.start() 48 | return socket_server.ident 49 | except KeyboardInterrupt: 50 | pass 51 | except Exception as e: 52 | print(e) 53 | return None 54 | 55 | 56 | if __name__ == '__main__': 57 | start_socket_server() 58 | -------------------------------------------------------------------------------- /source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.0) 2 | project(injector VERSION 1.0.0) 3 | 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED True) 7 | 8 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D '_UNICODE' /D 'UNICODE'") 9 | 10 | file(GLOB INJECT_CPP_FILES ${PROJECT_SOURCE_DIR}/*.cc ${PROJECT_SOURCE_DIR}/*.cpp) 11 | 12 | add_executable (injector ${INJECT_CPP_FILES}) 13 | 14 | SET_TARGET_PROPERTIES(injector PROPERTIES LINKER_LANGUAGE C 15 | ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin 16 | LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin 17 | RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin 18 | OUTPUT_NAME "injector" 19 | PREFIX "") 20 | -------------------------------------------------------------------------------- /src/base64.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | /* 3 | base64.cpp and base64.h 4 | 5 | base64 encoding and decoding with C++. 6 | More information at 7 | https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp 8 | 9 | Version: 2.rc.08 (release candidate) 10 | 11 | Copyright (C) 2004-2017, 2020, 2021 Ren�� Nyffenegger 12 | 13 | This source code is provided 'as-is', without any express or implied 14 | warranty. In no event will the author be held liable for any damages 15 | arising from the use of this software. 16 | 17 | Permission is granted to anyone to use this software for any purpose, 18 | including commercial applications, and to alter it and redistribute it 19 | freely, subject to the following restrictions: 20 | 21 | 1. The origin of this source code must not be misrepresented; you must not 22 | claim that you wrote the original source code. If you use this source code 23 | in a product, an acknowledgment in the product documentation would be 24 | appreciated but is not required. 25 | 26 | 2. Altered source versions must be plainly marked as such, and must not be 27 | misrepresented as being the original source code. 28 | 29 | 3. This notice may not be removed or altered from any source distribution. 30 | 31 | Ren�� Nyffenegger rene.nyffenegger@adp-gmbh.ch 32 | 33 | */ 34 | 35 | #include "base64.h" 36 | 37 | #include 38 | #include 39 | 40 | // 41 | // Depending on the url parameter in base64_chars, one of 42 | // two sets of base64 characters needs to be chosen. 43 | // They differ in their last two characters. 44 | // 45 | static const char *base64_chars[2] = { 46 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 47 | "abcdefghijklmnopqrstuvwxyz" 48 | "0123456789" 49 | "+/", 50 | 51 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 52 | "abcdefghijklmnopqrstuvwxyz" 53 | "0123456789" 54 | "-_"}; 55 | 56 | static unsigned int pos_of_char(const unsigned char chr) 57 | { 58 | // 59 | // Return the position of chr within base64_encode() 60 | // 61 | 62 | if (chr >= 'A' && chr <= 'Z') 63 | return chr - 'A'; 64 | else if (chr >= 'a' && chr <= 'z') 65 | return chr - 'a' + ('Z' - 'A') + 1; 66 | else if (chr >= '0' && chr <= '9') 67 | return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; 68 | else if (chr == '+' || chr == '-') 69 | return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters ( 70 | else if (chr == '/' || chr == '_') 71 | return 63; // Ditto for '/' and '_' 72 | else 73 | // 74 | // 2020-10-23: Throw std::exception rather than const char* 75 | //(Pablo Martin-Gomez, https://github.com/Bouska) 76 | // 77 | throw std::runtime_error("Input is not valid base64-encoded data."); 78 | } 79 | 80 | static std::string insert_linebreaks(std::string str, size_t distance) 81 | { 82 | // 83 | // Provided by https://github.com/JomaCorpFX, adapted by me. 84 | // 85 | if (!str.length()) 86 | { 87 | return ""; 88 | } 89 | 90 | size_t pos = distance; 91 | 92 | while (pos < str.size()) 93 | { 94 | str.insert(pos, "\n"); 95 | pos += distance + 1; 96 | } 97 | 98 | return str; 99 | } 100 | 101 | template 102 | static std::string encode_with_line_breaks(String s) 103 | { 104 | return insert_linebreaks(base64_encode(s, false), line_length); 105 | } 106 | 107 | template 108 | static std::string encode_pem(String s) 109 | { 110 | return encode_with_line_breaks(s); 111 | } 112 | 113 | template 114 | static std::string encode_mime(String s) 115 | { 116 | return encode_with_line_breaks(s); 117 | } 118 | 119 | template 120 | static std::string encode(String s, bool url) 121 | { 122 | return base64_encode(reinterpret_cast(s.data()), s.length(), url); 123 | } 124 | 125 | std::string base64_encode(unsigned char const *bytes_to_encode, size_t in_len, bool url) 126 | { 127 | 128 | size_t len_encoded = (in_len + 2) / 3 * 4; 129 | 130 | unsigned char trailing_char = url ? '.' : '='; 131 | 132 | // 133 | // Choose set of base64 characters. They differ 134 | // for the last two positions, depending on the url 135 | // parameter. 136 | // A bool (as is the parameter url) is guaranteed 137 | // to evaluate to either 0 or 1 in C++ therefore, 138 | // the correct character set is chosen by subscripting 139 | // base64_chars with url. 140 | // 141 | const char *base64_chars_ = base64_chars[url]; 142 | 143 | std::string ret; 144 | ret.reserve(len_encoded); 145 | 146 | unsigned int pos = 0; 147 | 148 | while (pos < in_len) 149 | { 150 | ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); 151 | 152 | if (pos + 1 < in_len) 153 | { 154 | ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); 155 | 156 | if (pos + 2 < in_len) 157 | { 158 | ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); 159 | ret.push_back(base64_chars_[bytes_to_encode[pos + 2] & 0x3f]); 160 | } 161 | else 162 | { 163 | ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); 164 | ret.push_back(trailing_char); 165 | } 166 | } 167 | else 168 | { 169 | 170 | ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); 171 | ret.push_back(trailing_char); 172 | ret.push_back(trailing_char); 173 | } 174 | 175 | pos += 3; 176 | } 177 | 178 | return ret; 179 | } 180 | 181 | template 182 | static std::string decode(String encoded_string, bool remove_linebreaks) 183 | { 184 | // 185 | // decode(��) is templated so that it can be used with String = const std::string& 186 | // or std::string_view (requires at least C++17) 187 | // 188 | 189 | if (encoded_string.empty()) 190 | return std::string(); 191 | 192 | if (remove_linebreaks) 193 | { 194 | 195 | std::string copy(encoded_string); 196 | 197 | copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end()); 198 | 199 | return base64_decode(copy, false); 200 | } 201 | 202 | size_t length_of_string = encoded_string.length(); 203 | size_t pos = 0; 204 | 205 | // 206 | // The approximate length (bytes) of the decoded string might be one or 207 | // two bytes smaller, depending on the amount of trailing equal signs 208 | // in the encoded string. This approximation is needed to reserve 209 | // enough space in the string to be returned. 210 | // 211 | size_t approx_length_of_decoded_string = length_of_string / 4 * 3; 212 | std::string ret; 213 | ret.reserve(approx_length_of_decoded_string); 214 | 215 | while (pos < length_of_string) 216 | { 217 | // 218 | // Iterate over encoded input string in chunks. The size of all 219 | // chunks except the last one is 4 bytes. 220 | // 221 | // The last chunk might be padded with equal signs or dots 222 | // in order to make it 4 bytes in size as well, but this 223 | // is not required as per RFC 2045. 224 | // 225 | // All chunks except the last one produce three output bytes. 226 | // 227 | // The last chunk produces at least one and up to three bytes. 228 | // 229 | 230 | size_t pos_of_char_1 = pos_of_char(encoded_string[pos + 1]); 231 | 232 | // 233 | // Emit the first output byte that is produced in each chunk: 234 | // 235 | ret.push_back(static_cast(((pos_of_char(encoded_string[pos + 0])) << 2) + ((pos_of_char_1 & 0x30) >> 4))); 236 | 237 | if ((pos + 2 < length_of_string) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045) 238 | encoded_string[pos + 2] != '=' && 239 | encoded_string[pos + 2] != '.' // accept URL-safe base 64 strings, too, so check for '.' also. 240 | ) 241 | { 242 | // 243 | // Emit a chunk's second byte (which might not be produced in the last chunk). 244 | // 245 | unsigned int pos_of_char_2 = pos_of_char(encoded_string[pos + 2]); 246 | ret.push_back(static_cast(((pos_of_char_1 & 0x0f) << 4) + ((pos_of_char_2 & 0x3c) >> 2))); 247 | 248 | if ((pos + 3 < length_of_string) && 249 | encoded_string[pos + 3] != '=' && 250 | encoded_string[pos + 3] != '.') 251 | { 252 | // 253 | // Emit a chunk's third byte (which might not be produced in the last chunk). 254 | // 255 | ret.push_back(static_cast(((pos_of_char_2 & 0x03) << 6) + pos_of_char(encoded_string[pos + 3]))); 256 | } 257 | } 258 | 259 | pos += 4; 260 | } 261 | 262 | return ret; 263 | } 264 | 265 | std::string base64_decode(std::string const &s, bool remove_linebreaks) 266 | { 267 | return decode(s, remove_linebreaks); 268 | } 269 | 270 | std::string base64_encode(std::string const &s, bool url) 271 | { 272 | return encode(s, url); 273 | } 274 | 275 | std::string base64_encode_pem(std::string const &s) 276 | { 277 | return encode_pem(s); 278 | } 279 | 280 | std::string base64_encode_mime(std::string const &s) 281 | { 282 | return encode_mime(s); 283 | } 284 | 285 | #if __cplusplus >= 201703L 286 | // 287 | // Interface with std::string_view rather than const std::string& 288 | // Requires C++17 289 | // Provided by Yannic Bonenberger (https://github.com/Yannic) 290 | // 291 | 292 | std::string base64_encode(std::string_view s, bool url) 293 | { 294 | return encode(s, url); 295 | } 296 | 297 | std::string base64_encode_pem(std::string_view s) 298 | { 299 | return encode_pem(s); 300 | } 301 | 302 | std::string base64_encode_mime(std::string_view s) 303 | { 304 | return encode_mime(s); 305 | } 306 | 307 | std::string base64_decode(std::string_view s, bool remove_linebreaks) 308 | { 309 | return decode(s, remove_linebreaks); 310 | } 311 | 312 | #endif // __cplusplus >= 201703L -------------------------------------------------------------------------------- /src/base64.h: -------------------------------------------------------------------------------- 1 | // 2 | // base64 encoding and decoding with C++. 3 | // Version: 2.rc.08 (release candidate) 4 | // 5 | #pragma once 6 | #ifndef BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A 7 | #define BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A 8 | 9 | #include 10 | 11 | #if __cplusplus >= 201703L 12 | #include 13 | #endif // __cplusplus >= 201703L 14 | 15 | std::string base64_encode(std::string const &s, bool url = false); 16 | std::string base64_encode_pem(std::string const &s); 17 | std::string base64_encode_mime(std::string const &s); 18 | 19 | std::string base64_decode(std::string const &s, bool remove_linebreaks = false); 20 | std::string base64_encode(unsigned char const *, size_t len, bool url = false); 21 | 22 | #if __cplusplus >= 201703L 23 | // 24 | // Interface with std::string_view rather than const std::string& 25 | // Requires C++17 26 | // Provided by Yannic Bonenberger (https://github.com/Yannic) 27 | // 28 | std::string base64_encode(std::string_view s, bool url = false); 29 | std::string base64_encode_pem(std::string_view s); 30 | std::string base64_encode_mime(std::string_view s); 31 | 32 | std::string base64_decode(std::string_view s, bool remove_linebreaks = false); 33 | #endif // __cplusplus >= 201703L 34 | 35 | #endif /* BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A */ -------------------------------------------------------------------------------- /src/config.cc: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "config.h" 3 | 4 | namespace wxhelper { 5 | Config::Config(/* args */) {} 6 | 7 | Config::~Config() {} 8 | 9 | void Config::Initialize() { 10 | port_ = GetPrivateProfileInt("config", "Port", 19088, "./config.ini"); 11 | } 12 | int Config::GetPort() { return port_; } 13 | 14 | } // namespace wxhelper -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef WXHELPER_CONFIG_H_ 2 | #define WXHELPER_CONFIG_H_ 3 | 4 | namespace wxhelper { 5 | 6 | class Config { 7 | public: 8 | Config(); 9 | ~Config(); 10 | void Initialize(); 11 | int GetPort(); 12 | 13 | private: 14 | int port_; 15 | }; 16 | } // namespace wxhelper 17 | #endif -------------------------------------------------------------------------------- /src/db.h: -------------------------------------------------------------------------------- 1 | #ifndef WXHELPER_DB_H_ 2 | #define WXHELPER_DB_H_ 3 | #include 4 | #include 5 | 6 | #include "wechat_function.h" 7 | #include "windows.h" 8 | #include "singleton.h" 9 | namespace wxhelper { 10 | class DB :public Singleton{ 11 | public: 12 | void init(UINT64 base); 13 | int ExecuteSQL(UINT64 db, const char *sql, UINT64 callback, void *data); 14 | 15 | int Select(UINT64 db_hanle, const char *sql, 16 | std::vector> &query_result); 17 | 18 | std::vector GetDbHandles(); 19 | UINT64 GetDbHandleByDbName(wchar_t *dbname); 20 | INT64 GetLocalIdByMsgId(ULONG64 msgid, INT64 &dbIndex); 21 | std::vector GetChatMsgByMsgId(ULONG64 msgid); 22 | 23 | std::string GetVoiceBuffByMsgId(ULONG64 msgid); 24 | 25 | std::string GetPublicMsgCompressContentByMsgId(ULONG64 msgid); 26 | 27 | private: 28 | int SelectDataInner(UINT64 db, const char *sql, 29 | std::vector> &data); 30 | 31 | private: 32 | std::map dbmap_; 33 | std::vector dbs_; 34 | UINT64 base_addr_; 35 | }; 36 | 37 | } // namespace wxhelper 38 | 39 | #endif -------------------------------------------------------------------------------- /src/dllMain.cc: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "global_context.h" 3 | #include "utils.h" 4 | 5 | 6 | using namespace wxhelper; 7 | 8 | BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, 9 | LPVOID lpReserved) { 10 | switch (ul_reason_for_call) { 11 | case DLL_PROCESS_ATTACH: { 12 | DisableThreadLibraryCalls(hModule); 13 | GlobalContext::GetInstance().initialize(hModule); 14 | break; 15 | } 16 | case DLL_THREAD_ATTACH: { 17 | break; 18 | } 19 | case DLL_THREAD_DETACH: { 20 | break; 21 | } 22 | case DLL_PROCESS_DETACH: { 23 | GlobalContext::GetInstance().finally(); 24 | break; 25 | } 26 | } 27 | return TRUE; 28 | } -------------------------------------------------------------------------------- /src/export.asm: -------------------------------------------------------------------------------- 1 | ;################################################################# 2 | 3 | .code 4 | 5 | 6 | ;################################################################# 7 | 8 | 9 | ;################################################################# 10 | 11 | ;检测wechat登录状态 12 | ;param: get_account_service_addr 函数地址 13 | 14 | ;################################################################# 15 | _GetAccountService PROC, 16 | get_account_service_addr:QWORD ; 函数地址 17 | sub rsp,28h 18 | call rcx 19 | add rsp,28h 20 | ret 21 | _GetAccountService ENDP 22 | 23 | ;################################################################# 24 | 25 | ;获取wechat数据保存路径 26 | ;param: addr 函数地址 27 | ;return:路径地址 28 | 29 | ;################################################################# 30 | _GetDataSavePath PROC, 31 | get_data_path_addr:QWORD, ; 函数地址 32 | out_path:QWORD ; 输出 33 | sub rsp,40h 34 | mov rax,rcx 35 | mov rcx,rdx 36 | call rax 37 | add rsp,40h 38 | ret 39 | _GetDataSavePath ENDP 40 | 41 | 42 | ;################################################################# 43 | 44 | ;获取wechat当前数据保存路径 45 | ;param: addr 函数地址 46 | ;return:路径地址 47 | 48 | ;################################################################# 49 | _GetCurrentDataPath PROC, 50 | get_current_path_addr: QWORD, ; 函数地址 51 | out_path: QWORD ; 输出 52 | sub rsp,28h 53 | mov rax,rcx 54 | mov rcx,rdx 55 | call rax 56 | add rsp,28h 57 | ret 58 | _GetCurrentDataPath ENDP 59 | 60 | 61 | END -------------------------------------------------------------------------------- /src/export.h: -------------------------------------------------------------------------------- 1 | #ifndef WXHELPER_EXPORT_H_ 2 | #define WXHELPER_EXPORT_H_ 3 | 4 | extern "C" UINT64 _GetAccountService(UINT64 addr); 5 | extern "C" UINT64 _GetDataSavePath(UINT64 addr,ULONG_PTR out); 6 | extern "C" UINT64 _GetCurrentDataPath(UINT64 addr,ULONG_PTR out); 7 | extern "C" UINT64 _SendTextMsg(UINT64 mgr_addr,UINT64 send_text_addr,UINT64 free_addr,UINT64 receiver,UINT64 msg,UINT64 chat_msg); 8 | #endif -------------------------------------------------------------------------------- /src/global_context.cc: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "global_context.h" 3 | #include "thread_pool.h" 4 | #include "db.h" 5 | 6 | namespace wxhelper { 7 | 8 | GlobalContext::~GlobalContext() { 9 | if (config.has_value()) { 10 | config.reset(); 11 | } 12 | if (log.has_value()) { 13 | log.reset(); 14 | } 15 | 16 | } 17 | void GlobalContext::initialize(HMODULE module) { 18 | state =GlobalContextState::INITIALIZING; 19 | module_ = module; 20 | #ifndef _DEBUG 21 | Utils::Hide(module); 22 | #endif 23 | UINT64 base = Utils::GetWeChatWinBase(); 24 | config.emplace(); 25 | config->Initialize(); 26 | log.emplace(); 27 | log->Initialize(); 28 | http_server = std::unique_ptr( new HttpServer(config->GetPort())); 29 | http_server->HttpStart(); 30 | ThreadPool::GetInstance().Create(2, 8); 31 | mgr = std::unique_ptr(new Manager(base)); 32 | DB::GetInstance().init(base); 33 | state =GlobalContextState::INITIALIZED; 34 | } 35 | 36 | void GlobalContext::finally() { 37 | if (http_server) { 38 | http_server->HttpClose(); 39 | } 40 | } 41 | } // namespace wxhelper -------------------------------------------------------------------------------- /src/global_context.h: -------------------------------------------------------------------------------- 1 | #ifndef GLOBAL_CONTEXT_H_ 2 | #define GLOBAL_CONTEXT_H_ 3 | #include "config.h" 4 | #include "http_server.h" 5 | #include "log.h" 6 | #include "singleton.h" 7 | #include "manager.h" 8 | 9 | namespace wxhelper { 10 | 11 | enum class GlobalContextState { NOT_INITIALIZED, INITIALIZING, INITIALIZED }; 12 | 13 | class GlobalContext : public Singleton { 14 | friend class Singleton; 15 | ~GlobalContext(); 16 | 17 | public: 18 | void initialize(HMODULE module); 19 | void finally(); 20 | 21 | public: 22 | std::optional config; 23 | std::optional log; 24 | std::unique_ptr http_server; 25 | std::unique_ptr mgr; 26 | 27 | GlobalContextState state = GlobalContextState::NOT_INITIALIZED; 28 | 29 | private: 30 | HMODULE module_; 31 | }; 32 | 33 | } // namespace wxhelper 34 | #endif -------------------------------------------------------------------------------- /src/hooks.cc: -------------------------------------------------------------------------------- 1 |  2 | #include "pch.h" 3 | #include "hooks.h" 4 | #include "thread_pool.h" 5 | #include "wechat_function.h" 6 | #include 7 | #include "base64.h" 8 | #include "http_client.h" 9 | 10 | namespace offset = wxhelper::V3_9_5_81::offset; 11 | namespace common = wxhelper::common; 12 | namespace wxhelper { 13 | namespace hooks { 14 | 15 | static int kServerPort = 19099; 16 | static bool kMsgHookFlag = false; 17 | static char kServerIp[16] = "127.0.0.1"; 18 | static bool kEnableHttp = false; 19 | static bool kLogHookFlag = false; 20 | 21 | static bool kSnsFinishHookFlag = false; 22 | 23 | 24 | 25 | static UINT64 (*R_DoAddMsg)(UINT64, UINT64, UINT64) = (UINT64(*)( 26 | UINT64, UINT64, UINT64))(Utils::GetWeChatWinBase() + offset::kDoAddMsg); 27 | 28 | static UINT64 (*R_Log)(UINT64, UINT64, UINT64, UINT64, UINT64, UINT64, UINT64, 29 | UINT64, UINT64, UINT64, UINT64, UINT64) = 30 | (UINT64(*)(UINT64, UINT64, UINT64, UINT64, UINT64, UINT64, UINT64, UINT64, 31 | UINT64, UINT64, UINT64, 32 | UINT64))(Utils::GetWeChatWinBase() + offset::kHookLog); 33 | 34 | static UINT64 (*R_OnSnsTimeLineSceneFinish)(UINT64, UINT64, UINT64) = 35 | (UINT64(*)(UINT64, UINT64, UINT64))(Utils::GetWeChatWinBase() + 36 | offset::kOnSnsTimeLineSceneFinish); 37 | 38 | VOID CALLBACK SendMsgCallback(PTP_CALLBACK_INSTANCE instance, PVOID context, 39 | PTP_WORK Work) { 40 | common::InnerMessageStruct *msg = (common::InnerMessageStruct *)context; 41 | if (msg == NULL) { 42 | SPDLOG_INFO("add work:msg is null"); 43 | return; 44 | } 45 | std::unique_ptr sms(msg); 46 | nlohmann::json j_msg = 47 | nlohmann::json::parse(msg->buffer, msg->buffer + msg->length, nullptr, false); 48 | if (j_msg.is_discarded() == true) { 49 | return; 50 | } 51 | std::string jstr = j_msg.dump() + "\n"; 52 | 53 | if (kServerPort == 0) { 54 | SPDLOG_ERROR("http server port error :{}", kServerPort); 55 | return; 56 | } 57 | WSADATA was_data = {0}; 58 | int ret = WSAStartup(MAKEWORD(2, 2), &was_data); 59 | if (ret != 0) { 60 | SPDLOG_ERROR("WSAStartup failed:{}", ret); 61 | return; 62 | } 63 | 64 | SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 65 | if (client_socket < 0) { 66 | SPDLOG_ERROR("socket init fail"); 67 | return; 68 | } 69 | BOOL status = false; 70 | sockaddr_in client_addr; 71 | memset(&client_addr, 0, sizeof(client_addr)); 72 | client_addr.sin_family = AF_INET; 73 | client_addr.sin_port = htons((u_short)kServerPort); 74 | InetPtonA(AF_INET, kServerIp, &client_addr.sin_addr.s_addr); 75 | if (connect(client_socket, reinterpret_cast(&client_addr), 76 | sizeof(sockaddr)) < 0) { 77 | SPDLOG_ERROR("socket connect fail"); 78 | goto clean; 79 | } 80 | char recv_buf[1024] = {0}; 81 | ret = send(client_socket, jstr.c_str(), static_cast(jstr.size()) , 0); 82 | if (ret < 0) { 83 | SPDLOG_ERROR("socket send fail ,ret:{}", ret); 84 | goto clean; 85 | } 86 | ret = shutdown(client_socket, SD_SEND); 87 | if (ret == SOCKET_ERROR) { 88 | SPDLOG_ERROR("shutdown failed with erro:{}", ret); 89 | goto clean; 90 | } 91 | ret = recv(client_socket, recv_buf, sizeof(recv_buf), 0); 92 | if (ret < 0) { 93 | SPDLOG_ERROR("socket recv fail ,ret:{}", ret); 94 | goto clean; 95 | } 96 | clean: 97 | closesocket(client_socket); 98 | WSACleanup(); 99 | return; 100 | } 101 | 102 | VOID CALLBACK SendHttpMsgCallback(PTP_CALLBACK_INSTANCE instance, PVOID context, 103 | PTP_WORK Work) { 104 | common::InnerMessageStruct *msg = (common::InnerMessageStruct *)context; 105 | if (msg == NULL) { 106 | SPDLOG_INFO("http msg is null"); 107 | return; 108 | } 109 | 110 | std::unique_ptr sms(msg); 111 | nlohmann::json j_msg = 112 | nlohmann::json::parse(msg->buffer, msg->buffer + msg->length, nullptr, false); 113 | if (j_msg.is_discarded() == true) { 114 | return; 115 | } 116 | std::string jstr = j_msg.dump() + "\n"; 117 | HttpClient::GetInstance().SendRequest(jstr); 118 | } 119 | 120 | void HandleSyncMsg(INT64 param1, INT64 param2, INT64 param3) { 121 | nlohmann::json msg; 122 | 123 | msg["pid"] = GetCurrentProcessId(); 124 | msg["fromUser"] = Utils::ReadSKBuiltinString(*(INT64 *)(param2 + 0x18)); 125 | msg["toUser"] = Utils::ReadSKBuiltinString(*(INT64 *)(param2 + 0x28)); 126 | msg["content"] = Utils::ReadSKBuiltinString(*(INT64 *)(param2 + 0x30)); 127 | msg["signature"] = Utils::ReadWeChatStr(*(INT64 *)(param2 + 0x48)); 128 | msg["msgId"] = *(INT64 *)(param2 + 0x60); 129 | msg["msgSequence"] = *(DWORD *)(param2 + 0x5C); 130 | msg["createTime"] = *(DWORD *)(param2 + 0x58); 131 | msg["displayFullContent"] = Utils::ReadWeChatStr(*(INT64 *)(param2 + 0x50)); 132 | DWORD type = *(DWORD *)(param2 + 0x24); 133 | msg["type"] = type; 134 | if (type == 3) { 135 | int a = 1; 136 | std::string img = 137 | Utils::ReadSKBuiltinBuffer(*(INT64 *)(param2 + 0x40)); 138 | SPDLOG_INFO("encode size:{}",img.size()); 139 | msg["base64Img"] = base64_encode(img); 140 | a = 2; 141 | } 142 | std::string jstr = msg.dump() + '\n'; 143 | common::InnerMessageStruct *inner_msg = new common::InnerMessageStruct; 144 | inner_msg->buffer = new char[jstr.size() + 1]; 145 | memcpy(inner_msg->buffer, jstr.c_str(), jstr.size() + 1); 146 | inner_msg->length = jstr.size(); 147 | if(kEnableHttp){ 148 | bool add = ThreadPool::GetInstance().AddWork(SendHttpMsgCallback,inner_msg); 149 | SPDLOG_INFO("add http msg work:{}",add); 150 | }else{ 151 | bool add = ThreadPool::GetInstance().AddWork(SendMsgCallback,inner_msg); 152 | SPDLOG_INFO("add msg work:{}",add); 153 | } 154 | R_DoAddMsg(param1,param2,param3); 155 | } 156 | 157 | UINT64 HandlePrintLog(UINT64 param1, UINT64 param2, UINT64 param3, UINT64 param4, 158 | UINT64 param5, UINT64 param6, UINT64 param7, UINT64 param8, 159 | UINT64 param9, UINT64 param10, UINT64 param11, 160 | UINT64 param12) { 161 | UINT64 p = R_Log(param1, param2, param3, param4, param5, param6, param7, param8, param9, 162 | param10, param11, param12); 163 | if(p== 0 || p == 1){ 164 | return p; 165 | } 166 | char *msg = (char *)p; 167 | if (msg != NULL) { 168 | // INT64 size = *(INT64 *)(p - 0x8); 169 | std::string str(msg); 170 | std::wstring ws = Utils::UTF8ToWstring(str); 171 | std::string out = Utils::WstringToAnsi(ws, CP_ACP); 172 | spdlog::info("wechat log:{}", out); 173 | } 174 | return p; 175 | } 176 | 177 | void HandleSNSMsg(INT64 param1, INT64 param2, INT64 param3) { 178 | nlohmann::json j_sns; 179 | INT64 begin_addr = *(INT64 *)(param2 + 0x30); 180 | INT64 end_addr = *(INT64 *)(param2 + 0x38); 181 | if (begin_addr == 0) { 182 | j_sns = {{"data", nlohmann::json::array()}}; 183 | } else { 184 | while (begin_addr < end_addr) { 185 | nlohmann::json j_item; 186 | j_item["snsId"] = *(UINT64 *)(begin_addr); 187 | j_item["createTime"] = *(DWORD *)(begin_addr + 0x38); 188 | j_item["senderId"] = Utils::ReadWstringThenConvert(begin_addr + 0x18); 189 | j_item["content"] = Utils::ReadWstringThenConvert(begin_addr + 0x48); 190 | j_item["xml"] = Utils::ReadWstringThenConvert(begin_addr + 0x580); 191 | j_sns["data"].push_back(j_item); 192 | begin_addr += 0x11E0; 193 | } 194 | } 195 | std::string jstr = j_sns.dump() + '\n'; 196 | common::InnerMessageStruct *inner_msg = new common::InnerMessageStruct; 197 | inner_msg->buffer = new char[jstr.size() + 1]; 198 | memcpy(inner_msg->buffer, jstr.c_str(), jstr.size() + 1); 199 | inner_msg->length = jstr.size(); 200 | if (kEnableHttp) { 201 | bool add = ThreadPool::GetInstance().AddWork(SendHttpMsgCallback, inner_msg); 202 | SPDLOG_INFO("hook sns add http msg work:{}", add); 203 | } else { 204 | bool add = ThreadPool::GetInstance().AddWork(SendMsgCallback, inner_msg); 205 | SPDLOG_INFO("hook sns add msg work:{}", add); 206 | } 207 | R_OnSnsTimeLineSceneFinish(param1, param2, param3); 208 | } 209 | 210 | int HookSyncMsg(std::string client_ip, int port, std::string url, 211 | uint64_t timeout, bool enable) { 212 | if (kMsgHookFlag) { 213 | SPDLOG_INFO("recv msg hook already called"); 214 | return 2; 215 | } 216 | kEnableHttp = enable; 217 | if (kEnableHttp) { 218 | HttpClient::GetInstance().SetConfig(url, timeout); 219 | } 220 | if (client_ip.size() < 1) { 221 | return -2; 222 | } 223 | 224 | kServerPort = port; 225 | strcpy_s(kServerIp, client_ip.c_str()); 226 | UINT64 base = Utils::GetWeChatWinBase(); 227 | if (!base) { 228 | SPDLOG_INFO("base addr is null"); 229 | return -1; 230 | } 231 | 232 | // DetourRestoreAfterWith(); 233 | DetourTransactionBegin(); 234 | DetourUpdateThread(GetCurrentThread()); 235 | DetourAttach(&(PVOID&)R_DoAddMsg, &HandleSyncMsg); 236 | LONG ret = DetourTransactionCommit(); 237 | if(ret == NO_ERROR){ 238 | kMsgHookFlag = true; 239 | } 240 | SPDLOG_INFO("hook sync {}",ret); 241 | DetourTransactionBegin(); 242 | DetourUpdateThread(GetCurrentThread()); 243 | DetourAttach(&(PVOID&)R_OnSnsTimeLineSceneFinish, &HandleSNSMsg); 244 | ret = DetourTransactionCommit(); 245 | if(ret == NO_ERROR){ 246 | kSnsFinishHookFlag = true; 247 | } 248 | SPDLOG_INFO("hook sns {}",ret); 249 | return ret; 250 | } 251 | 252 | int UnHookSyncMsg() { 253 | if (!kMsgHookFlag) { 254 | kMsgHookFlag = false; 255 | kEnableHttp = false; 256 | strcpy_s(kServerIp, "127.0.0.1"); 257 | SPDLOG_INFO("hook sync msg reset"); 258 | return NO_ERROR; 259 | } 260 | UINT64 base = Utils::GetWeChatWinBase(); 261 | DetourTransactionBegin(); 262 | DetourUpdateThread(GetCurrentThread()); 263 | DetourDetach(&(PVOID&)R_DoAddMsg, &HandleSyncMsg); 264 | LONG ret = DetourTransactionCommit(); 265 | if (ret == NO_ERROR) { 266 | kMsgHookFlag = false; 267 | kEnableHttp = false; 268 | strcpy_s(kServerIp, "127.0.0.1"); 269 | } 270 | return ret; 271 | } 272 | 273 | int HookLog() { 274 | if (kLogHookFlag) { 275 | SPDLOG_INFO("log hook already called"); 276 | return 2; 277 | } 278 | 279 | UINT64 base = Utils::GetWeChatWinBase(); 280 | if (!base) { 281 | SPDLOG_INFO("base addr is null"); 282 | return -1; 283 | } 284 | 285 | // DetourRestoreAfterWith(); 286 | DetourTransactionBegin(); 287 | DetourUpdateThread(GetCurrentThread()); 288 | UINT64 do_add_msg_addr = base + offset::kHookLog; 289 | DetourAttach(&(PVOID &)R_Log, &HandlePrintLog); 290 | LONG ret = DetourTransactionCommit(); 291 | if (ret == NO_ERROR) { 292 | kLogHookFlag = true; 293 | } 294 | return ret; 295 | } 296 | 297 | int UnHookLog() { 298 | if (!kLogHookFlag) { 299 | kLogHookFlag = false; 300 | SPDLOG_INFO("hook log reset"); 301 | return NO_ERROR; 302 | } 303 | UINT64 base = Utils::GetWeChatWinBase(); 304 | DetourTransactionBegin(); 305 | DetourUpdateThread(GetCurrentThread()); 306 | UINT64 do_add_msg_addr = base + offset::kHookLog; 307 | DetourDetach(&(PVOID &)R_Log, &HandlePrintLog); 308 | LONG ret = DetourTransactionCommit(); 309 | if (ret == NO_ERROR) { 310 | kLogHookFlag = false; 311 | } 312 | return ret; 313 | } 314 | 315 | } // namespace hooks 316 | } // namespace wxhelper -------------------------------------------------------------------------------- /src/hooks.h: -------------------------------------------------------------------------------- 1 | #ifndef WXHELPER_HOOKS_H_ 2 | #define WXHELPER_HOOKS_H_ 3 | #include "Windows.h" 4 | #include "wechat_function.h" 5 | namespace wxhelper { 6 | namespace hooks { 7 | 8 | int HookSyncMsg(std::string client_ip, int port, std::string url, uint64_t timeout, 9 | bool enable); 10 | 11 | int UnHookSyncMsg(); 12 | 13 | int HookLog(); 14 | 15 | int UnHookLog(); 16 | 17 | } // namespace hooks 18 | } // namespace wxhelper 19 | #endif -------------------------------------------------------------------------------- /src/http_client.cc: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "http_client.h" 3 | namespace wxhelper { 4 | 5 | void HttpClient::SendRequest(std::string content) { 6 | struct mg_mgr mgr; 7 | Data data ; 8 | data.done = false; 9 | data.post_data = content; 10 | mg_mgr_init(&mgr); 11 | mg_http_connect(&mgr, url_.c_str(), OnHttpEvent, &data); 12 | while (!data.done){ 13 | mg_mgr_poll(&mgr, 500); 14 | } 15 | mg_mgr_free(&mgr); 16 | } 17 | 18 | 19 | void HttpClient::OnHttpEvent(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { 20 | const char * s_url = GetInstance().url_.c_str(); 21 | Data data = *(Data*)fn_data; 22 | if (ev == MG_EV_OPEN) { 23 | // Connection created. Store connect expiration time in c->data 24 | *(uint64_t *) c->data = mg_millis() + GetInstance().timeout_; 25 | } else if (ev == MG_EV_POLL) { 26 | if (mg_millis() > *(uint64_t *) c->data && 27 | (c->is_connecting || c->is_resolving)) { 28 | mg_error(c, "Connect timeout"); 29 | } 30 | } else if (ev == MG_EV_CONNECT) { 31 | struct mg_str host = mg_url_host(s_url); 32 | if (mg_url_is_ssl(s_url)) { 33 | // no implement 34 | } 35 | 36 | // Send request 37 | size_t content_length = data.post_data.size(); 38 | mg_printf(c, 39 | "POST %s HTTP/1.0\r\n" 40 | "Host: %.*s\r\n" 41 | "Content-Type: application/json\r\n" 42 | "Content-Length: %d\r\n" 43 | "\r\n", 44 | mg_url_uri(s_url), (int) host.len, 45 | host.ptr, content_length); 46 | mg_send(c, data.post_data.c_str(), content_length); 47 | } else if (ev == MG_EV_HTTP_MSG) { 48 | // Response is received. Print it 49 | #ifdef _DEBUG 50 | struct mg_http_message *hm = (struct mg_http_message *) ev_data; 51 | printf("%.*s", (int) hm->message.len, hm->message.ptr); 52 | #endif 53 | c->is_closing = 1; // Tell mongoose to close this connection 54 | data.done = true; // Tell event loop to stops 55 | } else if (ev == MG_EV_ERROR) { 56 | data.done = true; // Error, tell event loop to stop 57 | } 58 | } 59 | 60 | void HttpClient::SetConfig(std::string url,uint64_t timeout){ 61 | url_=url; 62 | timeout_=timeout; 63 | } 64 | 65 | } // namespace wxhelper -------------------------------------------------------------------------------- /src/http_client.h: -------------------------------------------------------------------------------- 1 | #ifndef WXHELPER_HTTP_CLIENT_H_ 2 | #define WXHELPER_HTTP_CLIENT_H_ 3 | #include "mongoose.h" 4 | #include "singleton.h" 5 | 6 | namespace wxhelper { 7 | struct Data { 8 | bool done; 9 | std::string post_data; 10 | }; 11 | class HttpClient : public Singleton { 12 | public: 13 | void SendRequest(std::string content); 14 | void SetConfig(std::string url,uint64_t timeout); 15 | 16 | static void OnHttpEvent(struct mg_connection *c, int ev, void *ev_data, 17 | void *fn_data); 18 | private: 19 | std::string url_; 20 | uint64_t timeout_; 21 | }; 22 | 23 | } // namespace wxhelper 24 | #endif -------------------------------------------------------------------------------- /src/http_server.cc: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "http_server_callback.h" 3 | #include "http_server.h" 4 | 5 | namespace wxhelper { 6 | 7 | HttpServer::HttpServer(int port) { 8 | port_ = port; 9 | running_ = false; 10 | mg_mgr_init(&mgr_); 11 | } 12 | 13 | HttpServer::~HttpServer() { 14 | if (thread_ != nullptr) { 15 | CloseHandle(thread_); 16 | } 17 | mg_mgr_free(&mgr_); 18 | } 19 | 20 | bool HttpServer::HttpStart() { 21 | if (running_) { 22 | return true; 23 | } 24 | #ifdef _DEBUG 25 | Utils::CreateConsole(); 26 | #endif 27 | running_ = true; 28 | thread_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)StartHttpServer, this, 29 | NULL, 0); 30 | return true; 31 | } 32 | 33 | bool HttpServer::HttpClose() { 34 | if (!running_) { 35 | return true; 36 | } 37 | #ifdef _DEBUG 38 | Utils::CloseConsole(); 39 | #endif 40 | running_ = false; 41 | if (thread_) { 42 | WaitForSingleObject(thread_, -1); 43 | CloseHandle(thread_); 44 | thread_ = NULL; 45 | } 46 | return true; 47 | } 48 | 49 | int HttpServer::GetPort() { return port_; } 50 | bool HttpServer::GetRunning() { return running_; } 51 | 52 | const mg_mgr* HttpServer::GetMgr() { return &mgr_; } 53 | 54 | } // namespace wxhelper -------------------------------------------------------------------------------- /src/http_server.h: -------------------------------------------------------------------------------- 1 | #ifndef WXHELPER_HTTP_SERVER_H_ 2 | #define WXHELPER_HTTP_SERVER_H_ 3 | 4 | #include "mongoose.h" 5 | 6 | namespace wxhelper { 7 | class HttpServer { 8 | public: 9 | explicit HttpServer(int port); 10 | HttpServer(const HttpServer&) = delete; 11 | HttpServer(HttpServer &&)=delete; 12 | HttpServer& operator=(const HttpServer&) = delete; 13 | ~HttpServer(); 14 | 15 | bool HttpStart(); 16 | bool HttpClose(); 17 | int GetPort(); 18 | bool GetRunning(); 19 | const mg_mgr* GetMgr(); 20 | 21 | private: 22 | int port_; 23 | bool running_; 24 | struct mg_mgr mgr_; 25 | HANDLE thread_; 26 | }; 27 | } // namespace wxhelper 28 | 29 | #endif -------------------------------------------------------------------------------- /src/http_server_callback.h: -------------------------------------------------------------------------------- 1 | #ifndef WXHELPER_HTTP_SERVER_CALLBACK_H_ 2 | #define WXHELPER_HTTP_SERVER_CALLBACK_H_ 3 | #include 4 | 5 | #include "http_server.h" 6 | #include "mongoose.h" 7 | 8 | 9 | void StartHttpServer(wxhelper::HttpServer *server); 10 | 11 | void EventHandler(struct mg_connection *c, int ev, void *ev_data, 12 | void *fn_data); 13 | void HandleHttpRequest(struct mg_connection *c, void *ev_data); 14 | void HandleWebsocketRequest(struct mg_connection *c, void *ev_data); 15 | std::string HttpDispatch(struct mg_connection *c, struct mg_http_message *hm); 16 | 17 | #endif -------------------------------------------------------------------------------- /src/log.cc: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "log.h" 3 | 4 | 5 | #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO 6 | namespace wxhelper { 7 | Log::Log() {} 8 | 9 | Log::~Log() {} 10 | 11 | void Log::Initialize() { 12 | auto logger = 13 | spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 23, 59); 14 | logger->flush_on(spdlog::level::err); 15 | spdlog::set_default_logger(logger); 16 | spdlog::flush_every(std::chrono::seconds(3)); 17 | spdlog::set_level(spdlog::level::debug); 18 | spdlog::set_pattern("%Y-%m-%d %H:%M:%S [%l] [%t] - <%s>|<%#>|<%!>,%v"); 19 | } 20 | 21 | } // namespace wxhelper -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #ifndef WXHELPER_LOG_H_ 2 | #define WXHELPER_LOG_H_ 3 | namespace wxhelper { 4 | class Log { 5 | private: 6 | 7 | public: 8 | Log(); 9 | ~Log(); 10 | void Initialize(); 11 | }; 12 | 13 | } // namespace wxhelper 14 | 15 | #endif -------------------------------------------------------------------------------- /src/manager.h: -------------------------------------------------------------------------------- 1 | #ifndef WXHELPER_MANAGER_H_ 2 | #define WXHELPER_MANAGER_H_ 3 | #include "Windows.h" 4 | #include "wechat_function.h" 5 | namespace wxhelper { 6 | class Manager { 7 | public: 8 | explicit Manager(UINT64 base); 9 | ~Manager(); 10 | INT64 CheckLogin(); 11 | INT64 GetSelfInfo(common::SelfInfoInner& out); 12 | INT64 SendTextMsg(const std::wstring& wxid, const std::wstring& msg); 13 | INT64 SendImageMsg(const std::wstring& wxid, const std::wstring& image_path); 14 | INT64 SendFileMsg(const std::wstring& wxid, const std::wstring& file_path); 15 | INT64 GetContacts(std::vector &vec); 16 | INT64 GetChatRoomDetailInfo(const std::wstring& room_id, 17 | common::ChatRoomInfoInner& room_info); 18 | INT64 AddMemberToChatRoom(const std::wstring& room_id, 19 | const std::vector& members); 20 | 21 | INT64 ModChatRoomMemberNickName(const std::wstring& room_id, 22 | const std::wstring& wxid, 23 | const std::wstring& nickname); 24 | INT64 DelMemberFromChatRoom(const std::wstring& room_id, 25 | const std::vector& members); 26 | INT64 GetMemberFromChatRoom(const std::wstring& room_id, 27 | common::ChatRoomMemberInner& member); 28 | INT64 SetTopMsg(ULONG64 msg_id); 29 | INT64 RemoveTopMsg(const std::wstring& room_id,ULONG64 msg_id); 30 | INT64 InviteMemberToChatRoom(const std::wstring& room_id, 31 | const std::vector& wxids); 32 | INT64 CreateChatRoom(const std::vector& wxids); 33 | INT64 QuitChatRoom(const std::wstring& room_id); 34 | INT64 ForwardMsg(UINT64 msg_id, const std::wstring& wxid); 35 | INT64 GetSNSFirstPage(); 36 | INT64 GetSNSNextPage(UINT64 sns_id); 37 | INT64 AddFavFromMsg(UINT64 msg_id); 38 | INT64 AddFavFromImage(const std::wstring& wxid, 39 | const std::wstring& image_path); 40 | INT64 SendAtText(const std::wstring& room_id, 41 | const std::vector& wxids, 42 | const std::wstring& msg); 43 | std::wstring GetContactOrChatRoomNickname(const std::wstring& wxid); 44 | INT64 GetContactByWxid(const std::wstring& wxid, 45 | common::ContactProfileInner& profile); 46 | INT64 DoDownloadTask(UINT64 msg_id); 47 | INT64 ForwardPublicMsg(const std::wstring& wxid, const std::wstring& title, 48 | const std::wstring& url, const std::wstring& thumb_url, 49 | const std::wstring& sender_id, 50 | const std::wstring& sender_name, 51 | const std::wstring& digest); 52 | INT64 ForwardPublicMsgByMsgId(const std::wstring& wxid, UINT64 msg_id); 53 | 54 | INT64 DecodeImage(const std::wstring& file_path, 55 | const std::wstring& save_dir); 56 | INT64 GetVoiceByDB(ULONG64 msg_id, const std::wstring& dir); 57 | INT64 SendCustomEmotion(const std::wstring& file_path, 58 | const std::wstring& wxid); 59 | INT64 SendApplet( 60 | const std::wstring& recv_wxid, const std::wstring& waid_suff, 61 | const std::wstring& waid_w, const std::string& waid_s, 62 | const std::string& wa_wxid, const std::string& json_param, 63 | const std::string& head_image, const std::string& big_image, 64 | const std::string& index_page); 65 | INT64 SendPatMsg(const std::wstring& room_id, const std::wstring& wxid); 66 | INT64 DoOCRTask(const std::wstring& img_path, std::string &result); 67 | INT64 Test(); 68 | private: 69 | UINT64 base_addr_; 70 | UINT64 js_api_addr_; 71 | }; 72 | 73 | } // namespace wxhelper 74 | #endif -------------------------------------------------------------------------------- /src/pch.h: -------------------------------------------------------------------------------- 1 | #ifndef PCH_H 2 | #define PCH_H 3 | 4 | 5 | #define GLOG_NO_ABBREVIATED_SEVERITIES 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "Windows.h" 17 | #include "utils.h" 18 | #include 19 | #include "spdlog/spdlog.h" 20 | #include "spdlog/sinks/rotating_file_sink.h" 21 | #include "spdlog/sinks/daily_file_sink.h" 22 | #include "spdlog/sinks/stdout_color_sinks.h" 23 | #include 24 | #include 25 | 26 | #endif // PCH_H 27 | 28 | -------------------------------------------------------------------------------- /src/singleton.h: -------------------------------------------------------------------------------- 1 | #ifndef WXHELPER_SINGLETON_H_ 2 | #define WXHELPER_SINGLETON_H_ 3 | template 4 | class Singleton { 5 | protected: 6 | Singleton() {} 7 | ~Singleton() {} 8 | 9 | Singleton(const Singleton&) = delete; 10 | Singleton& operator=(const Singleton&) = delete; 11 | 12 | Singleton(Singleton&&) = delete; 13 | Singleton& operator=(Singleton&&) = delete; 14 | 15 | public: 16 | static T& GetInstance() { 17 | static T instance{}; 18 | return instance; 19 | } 20 | }; 21 | #endif -------------------------------------------------------------------------------- /src/thread_pool.cc: -------------------------------------------------------------------------------- 1 |  2 | #include "pch.h" 3 | #include "thread_pool.h" 4 | 5 | #include "Windows.h" 6 | 7 | namespace wxhelper { 8 | ThreadPool::~ThreadPool() { 9 | if(cleanup_group_){ 10 | CloseThreadpoolCleanupGroupMembers(cleanup_group_, true, NULL); 11 | CloseThreadpoolCleanupGroup(cleanup_group_); 12 | } 13 | DestroyThreadpoolEnvironment(&env_); 14 | if (pool_){ 15 | CloseThreadpool(pool_); 16 | } 17 | } 18 | 19 | bool ThreadPool::Create(unsigned long min, unsigned long max) { 20 | InitializeThreadpoolEnvironment(&env_); 21 | pool_ = CreateThreadpool(NULL); 22 | if (NULL == pool_) { 23 | return false; 24 | } 25 | SetThreadpoolThreadMaximum(pool_, max); 26 | BOOL ret = SetThreadpoolThreadMinimum(pool_, min); 27 | if (FALSE == ret) { 28 | return false; 29 | } 30 | cleanup_group_ = CreateThreadpoolCleanupGroup(); 31 | if (NULL == cleanup_group_) { 32 | return false; 33 | } 34 | SetThreadpoolCallbackPool(&env_, pool_); 35 | SetThreadpoolCallbackCleanupGroup(&env_, cleanup_group_, NULL); 36 | return true; 37 | } 38 | 39 | bool ThreadPool::AddWork(PTP_WORK_CALLBACK callback,PVOID opt) { 40 | PTP_WORK work = CreateThreadpoolWork(callback, opt, &env_); 41 | if (NULL == work) { 42 | return false; 43 | } 44 | SubmitThreadpoolWork(work); 45 | return true; 46 | } 47 | 48 | } // namespace wxhelper -------------------------------------------------------------------------------- /src/thread_pool.h: -------------------------------------------------------------------------------- 1 | #ifndef WXHELPER_THREAD_POOL_H_ 2 | #define WXHELPER_THREAD_POOL_H_ 3 | #include "Windows.h" 4 | #include "singleton.h" 5 | namespace wxhelper { 6 | 7 | class ThreadPool :public Singleton{ 8 | public: 9 | ~ThreadPool(); 10 | 11 | bool Create(unsigned long min = 1, unsigned long max = 4); 12 | 13 | bool AddWork(PTP_WORK_CALLBACK callback,PVOID opt); 14 | 15 | private: 16 | PTP_POOL pool_; 17 | PTP_CLEANUP_GROUP cleanup_group_; 18 | TP_CALLBACK_ENVIRON env_; 19 | }; 20 | 21 | } // namespace wxhelper 22 | 23 | #endif -------------------------------------------------------------------------------- /src/utils.cc: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "utils.h" 3 | #include 4 | #include 5 | #define BUFSIZE 1024 6 | #define JPEG0 0xFF 7 | #define JPEG1 0xD8 8 | #define JPEG2 0xFF 9 | #define PNG0 0x89 10 | #define PNG1 0x50 11 | #define PNG2 0x4E 12 | #define BMP0 0x42 13 | #define BMP1 0x4D 14 | #define GIF0 0x47 15 | #define GIF1 0x49 16 | #define GIF2 0x46 17 | 18 | namespace wxhelper { 19 | std::wstring Utils::UTF8ToWstring(const std::string &str) { 20 | return Utils::AnsiToWstring(str, CP_UTF8); 21 | } 22 | 23 | std::string Utils::WstringToUTF8(const std::wstring &str) { 24 | return Utils::WstringToAnsi(str, CP_UTF8); 25 | } 26 | 27 | std::wstring Utils::AnsiToWstring(const std::string &input, INT64 locale) { 28 | int wchar_len = MultiByteToWideChar(locale, 0, input.c_str(), -1, NULL, 0); 29 | if (wchar_len > 0) { 30 | std::vector temp(wchar_len); 31 | MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, &temp[0], wchar_len); 32 | return std::wstring(&temp[0]); 33 | } 34 | 35 | return std::wstring(); 36 | } 37 | 38 | std::string Utils::WstringToAnsi(const std::wstring &input, INT64 locale) { 39 | int char_len = WideCharToMultiByte(locale, 0, input.c_str(), -1, 0, 0, 0, 0); 40 | if (char_len > 0) { 41 | std::vector temp(char_len); 42 | WideCharToMultiByte(locale, 0, input.c_str(), -1, &temp[0], char_len, 0, 0); 43 | return std::string(&temp[0]); 44 | } 45 | return std::string(); 46 | } 47 | 48 | UINT64 Utils::GetWeChatWinBase() { 49 | return (UINT64)GetModuleHandleA("WeChatWin.dll"); 50 | } 51 | 52 | bool Utils::CreateConsole() { 53 | if (AllocConsole()) { 54 | AttachConsole(GetCurrentProcessId()); 55 | FILE *retStream; 56 | freopen_s(&retStream, "CONOUT$", "w", stdout); 57 | if (!retStream) throw std::runtime_error("Stdout redirection failed."); 58 | freopen_s(&retStream, "CONOUT$", "w", stderr); 59 | if (!retStream) throw std::runtime_error("Stderr redirection failed."); 60 | return false; 61 | } 62 | return true; 63 | } 64 | 65 | void Utils::CloseConsole() { 66 | fclose(stdin); 67 | fclose(stdout); 68 | fclose(stderr); 69 | FreeConsole(); 70 | } 71 | 72 | std::string Utils::EncodeHexString(const std::string &str) { 73 | const std::string hex_table = "0123456789abcdef"; 74 | std::string sb; 75 | for (int i = 0; i < str.length(); i++) { 76 | sb += hex_table.at((str[i] & 0xf0) >> 4); 77 | sb += hex_table.at((str[i] & 0x0f) >> 0); 78 | } 79 | return sb; 80 | } 81 | 82 | std::string Utils::Hex2String(const std::string &hex_str) { 83 | std::string ret; 84 | const std::string hex_table = "0123456789abcdef"; 85 | for (int i = 0; i < hex_str.length(); i += 2) { 86 | ret += BYTE(hex_table.find(hex_str.at(i)) << 4 | 87 | hex_table.find(hex_str.at(i + 1))); 88 | } 89 | return ret; 90 | } 91 | 92 | std::string Utils::Bytes2Hex(const BYTE *bytes, const int length) { 93 | if (bytes == NULL) { 94 | return ""; 95 | } 96 | std::string buff; 97 | const int len = length; 98 | for (int j = 0; j < len; j++) { 99 | int high = bytes[j] / 16, low = bytes[j] % 16; 100 | buff += (high < 10) ? ('0' + high) : ('a' + high - 10); 101 | buff += (low < 10) ? ('0' + low) : ('a' + low - 10); 102 | } 103 | return buff; 104 | } 105 | 106 | void Utils::Hex2Bytes(const std::string &hex, BYTE *bytes) { 107 | int byte_len = hex.length() / 2; 108 | std::string str; 109 | unsigned int n; 110 | for (int i = 0; i < byte_len; i++) { 111 | str = hex.substr(i * 2, 2); 112 | sscanf_s(str.c_str(), "%x", &n); 113 | bytes[i] = n; 114 | } 115 | } 116 | 117 | bool Utils::IsDigit(std::string str) { 118 | if (str.length() == 0) { 119 | return false; 120 | } 121 | for (auto it : str) { 122 | if (it < '0' || it > '9') { 123 | return false; 124 | } 125 | } 126 | return true; 127 | } 128 | 129 | bool Utils::FindOrCreateDirectoryW(const wchar_t *path) { 130 | WIN32_FIND_DATAW fd; 131 | HANDLE hFind = ::FindFirstFileW(path, &fd); 132 | if (hFind != INVALID_HANDLE_VALUE && 133 | (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { 134 | FindClose(hFind); 135 | return true; 136 | } 137 | 138 | if (!::CreateDirectoryW(path, NULL)) { 139 | return false; 140 | } 141 | return true; 142 | } 143 | 144 | void Utils::HookAnyAddress(DWORD hook_addr, LPVOID jmp_addr, char *origin) { 145 | BYTE jmp_code[5] = {0}; 146 | jmp_code[0] = 0xE9; 147 | *(DWORD *)&jmp_code[1] = (DWORD)jmp_addr - hook_addr - 5; 148 | DWORD old_protext = 0; 149 | VirtualProtect((LPVOID)hook_addr, 5, PAGE_EXECUTE_READWRITE, &old_protext); 150 | ReadProcessMemory(GetCurrentProcess(), (LPVOID)hook_addr, origin, 5, 0); 151 | memcpy((void *)hook_addr, jmp_code, 5); 152 | VirtualProtect((LPVOID)hook_addr, 5, old_protext, &old_protext); 153 | } 154 | 155 | void Utils::UnHookAnyAddress(DWORD hook_addr, char *origin) { 156 | DWORD old_protext = 0; 157 | VirtualProtect((LPVOID)hook_addr, 5, PAGE_EXECUTE_READWRITE, &old_protext); 158 | WriteProcessMemory(GetCurrentProcess(), (LPVOID)hook_addr, origin, 5, 0); 159 | VirtualProtect((LPVOID)hook_addr, 5, old_protext, &old_protext); 160 | } 161 | 162 | std::wstring Utils::GetTimeW(long long timestamp) { 163 | wchar_t *wstr = new wchar_t[20]; 164 | memset(wstr, 0, 20 * 2); 165 | tm tm_out; 166 | localtime_s(&tm_out, ×tamp); 167 | swprintf_s(wstr, 20, L"%04d-%02d-%02d %02d:%02d:%02d", 1900 + tm_out.tm_year, 168 | tm_out.tm_mon + 1, tm_out.tm_mday, tm_out.tm_hour, tm_out.tm_min, 169 | tm_out.tm_sec); 170 | std::wstring str_time(wstr); 171 | delete[] wstr; 172 | return str_time; 173 | } 174 | 175 | std::string Utils::WCharToUTF8(wchar_t *wstr) { 176 | int c_size = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, FALSE); 177 | if (c_size > 0) { 178 | char *buffer = new char[c_size]; 179 | WideCharToMultiByte(CP_UTF8, 0, wstr, -1, buffer, c_size, NULL, FALSE); 180 | std::string str(buffer); 181 | delete[] buffer; 182 | buffer = NULL; 183 | return str; 184 | } 185 | return std::string(); 186 | } 187 | 188 | bool Utils::IsTextUtf8(const char *str,INT64 length) { 189 | char endian = 1; 190 | bool littlen_endian = (*(char *)&endian == 1); 191 | 192 | size_t i; 193 | int bytes_num; 194 | unsigned char chr; 195 | 196 | i = 0; 197 | bytes_num = 0; 198 | while (i < length) { 199 | if (littlen_endian) { 200 | chr = *(str + i); 201 | } else { // Big Endian 202 | chr = (*(str + i) << 8) | *(str + i + 1); 203 | i++; 204 | } 205 | 206 | if (bytes_num == 0) { 207 | if ((chr & 0x80) != 0) { 208 | while ((chr & 0x80) != 0) { 209 | chr <<= 1; 210 | bytes_num++; 211 | } 212 | if ((bytes_num < 2) || (bytes_num > 6)) { 213 | return false; 214 | } 215 | bytes_num--; 216 | } 217 | } else { 218 | if ((chr & 0xC0) != 0x80) { 219 | return false; 220 | } 221 | bytes_num--; 222 | } 223 | i++; 224 | } 225 | return (bytes_num == 0); 226 | } 227 | 228 | void Utils::Hide(HMODULE module) { 229 | PPEB peb = (PPEB)__readgsqword(0x60); 230 | PPEB_LDR_DATA ldr = peb->Ldr; 231 | 232 | void *cur_ptr = *((void **)((unsigned char *)ldr + 0x18)); 233 | void *next_ptr = cur_ptr; 234 | do { 235 | void *next = *((void **)((unsigned char *)next_ptr)); 236 | void *last = *((void **)((unsigned char *)next_ptr + 0x8)); 237 | void *base_addr = *((void **)((unsigned char *)next_ptr + 0x30)); 238 | if (base_addr == module) { 239 | *((void **)((unsigned char *)last)) = next; 240 | *((void **)((unsigned char *)next + 0x8)) = last; 241 | cur_ptr = next; 242 | } 243 | next_ptr = *((void **)next_ptr); 244 | } while (cur_ptr != next_ptr); 245 | } 246 | 247 | std::string Utils::ReadSKBuiltinString(INT64 addr) { 248 | INT64 inner_string = *(INT64 *)(addr + 0x8); 249 | if (inner_string == 0) { 250 | return std::string(); 251 | } 252 | return ReadWeChatStr(inner_string); 253 | } 254 | 255 | std::string Utils::ReadSKBuiltinBuffer(INT64 addr) { 256 | INT64 len = *(INT64 *)(addr + 0x10); 257 | if (len == 0) { 258 | return std::string(); 259 | } 260 | INT64 inner_string = *(INT64 *)(addr + 0x8); 261 | if (inner_string == 0) { 262 | return std::string(); 263 | } 264 | return ReadWeChatStr(inner_string); 265 | } 266 | 267 | std::string Utils::ReadWeChatStr(INT64 addr) { 268 | INT64 len = *(INT64 *)(addr + 0x10); 269 | if (len == 0) { 270 | return std::string(); 271 | } 272 | INT64 max_len = *(INT64 *)(addr + 0x18); 273 | if ((max_len | 0xF) == 0xF) { 274 | return std::string((char *)addr, len); 275 | } 276 | char *char_from_user = *(char **)(addr); 277 | return std::string(char_from_user, len); 278 | } 279 | 280 | std::string Utils::ImageXor(std::string buf){ 281 | const char *origin = buf.c_str(); 282 | short key = 0; 283 | if ((*origin ^ JPEG0) == (*(origin + 1) ^ JPEG1)) { 284 | key = *origin ^ JPEG0; 285 | } else if ((*origin ^ PNG1) == (*(origin + 1) ^ PNG2)) { 286 | key = *origin ^ PNG1; 287 | } else if ((*origin ^ GIF0) == (*(origin + 1) ^ GIF1)) { 288 | key = *origin ^ GIF0; 289 | } else if ((*origin ^ BMP0) == (*(origin + 1) ^ BMP1)) { 290 | key = *origin ^ BMP0; 291 | } else { 292 | key = -1; 293 | } 294 | if (key > 0) { 295 | char *img_buf = new char[buf.size()]; 296 | for (unsigned int i = 0; i < buf.size(); i++) { 297 | img_buf[i] = *(origin + i) ^ key; 298 | } 299 | std::string str(img_buf); 300 | delete[] img_buf; 301 | img_buf = NULL; 302 | return str; 303 | } 304 | return std::string(); 305 | } 306 | 307 | std::wstring Utils::ReadWstring(INT64 addr){ 308 | DWORD len = *(DWORD *)(addr + 0x8); 309 | if (len == 0) { 310 | return std::wstring(); 311 | } 312 | wchar_t * str = *(wchar_t **)(addr); 313 | if (str == NULL) { 314 | return std::wstring(); 315 | } 316 | return std::wstring(str, len); 317 | 318 | } 319 | std::string Utils::ReadWstringThenConvert(INT64 addr){ 320 | std::wstring wstr = ReadWstring(addr); 321 | return WstringToUTF8(wstr); 322 | } 323 | 324 | INT64 Utils::DecodeImage(const wchar_t* file_path,const wchar_t* save_dir){ 325 | std::wstring save_path(save_dir); 326 | std::wstring orign_file_path(file_path); 327 | if (!Utils::FindOrCreateDirectoryW(save_path.c_str())) { 328 | return 0; 329 | } 330 | 331 | INT64 pos_begin = orign_file_path.find_last_of(L"\\") + 1; 332 | INT64 pos_end = orign_file_path.find_last_of(L"."); 333 | std::wstring file_name = 334 | orign_file_path.substr(pos_begin, pos_end - pos_begin); 335 | HANDLE h_origin_file = 336 | CreateFileW(file_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, 337 | FILE_ATTRIBUTE_NORMAL, NULL); 338 | char buffer[BUFSIZE] = {0}; 339 | DWORD bytes_read = 0; 340 | DWORD bytes_write = 0; 341 | unsigned char magic_head[4] = {0}; 342 | std::wstring suffix; 343 | short key = 0; 344 | if (ReadFile(h_origin_file, buffer, BUFSIZE, &bytes_read, NULL)) { 345 | memcpy(magic_head, buffer, 3); 346 | } else { 347 | CloseHandle(h_origin_file); 348 | return 0; 349 | } 350 | if ((magic_head[0] ^ JPEG0) == (magic_head[1] ^ JPEG1)) { 351 | key = magic_head[0] ^ JPEG0; 352 | suffix = L".jpg"; 353 | } else if ((magic_head[0] ^ PNG1) == (magic_head[1] ^ PNG2)) { 354 | key = magic_head[0] ^ PNG1; 355 | suffix = L".png"; 356 | } else if ((magic_head[0] ^ GIF0) == (magic_head[1] ^ GIF1)) { 357 | key = magic_head[0] ^ GIF0; 358 | suffix = L".gif"; 359 | } else if ((magic_head[0] ^ BMP0) == (magic_head[1] ^ BMP1)) { 360 | key = magic_head[0] ^ BMP0; 361 | suffix = L".bmp"; 362 | } else { 363 | key = -1; 364 | suffix = L".dat"; 365 | } 366 | std::wstring save_img_path = save_path + L"\\" + file_name + suffix; 367 | HANDLE save_img = CreateFileW(save_img_path.c_str(), GENERIC_ALL, 0, NULL, 368 | CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 369 | if (save_img == INVALID_HANDLE_VALUE) { 370 | return 0; 371 | } 372 | if (key > 0) { 373 | for (unsigned int i = 0; i < bytes_read; i++) { 374 | buffer[i] ^= key; 375 | } 376 | } 377 | if (!WriteFile(save_img, (LPCVOID)buffer, bytes_read, &bytes_write, 0)) { 378 | CloseHandle(h_origin_file); 379 | CloseHandle(save_img); 380 | return 0; 381 | } 382 | 383 | do { 384 | if (ReadFile(h_origin_file, buffer, BUFSIZE, &bytes_read, NULL)) { 385 | if (key > 0) { 386 | for (unsigned int i = 0; i < bytes_read; i++) { 387 | buffer[i] ^= key; 388 | } 389 | } 390 | if (!WriteFile(save_img, (LPCVOID)buffer, bytes_read, &bytes_write, 0)) { 391 | CloseHandle(h_origin_file); 392 | CloseHandle(save_img); 393 | return 0; 394 | } 395 | } 396 | } while (bytes_read == BUFSIZE); 397 | CloseHandle(h_origin_file); 398 | CloseHandle(save_img); 399 | return 1; 400 | } 401 | 402 | std::vector Utils::QWordScan(INT64 value, int align, 403 | const wchar_t *module) { 404 | MODULEINFO module_info; 405 | std::vector result; 406 | if (GetModuleInformation(GetCurrentProcess(), GetModuleHandleW(module), 407 | &module_info, sizeof(module_info))) { 408 | auto start = static_cast(module_info.lpBaseOfDll); 409 | const auto end = start + module_info.SizeOfImage - 0x8; 410 | 411 | auto current_addr = start; 412 | while (current_addr < end) { 413 | if (*(INT64*)current_addr == value) { 414 | result.push_back(reinterpret_cast(current_addr)); 415 | } 416 | start += align; 417 | current_addr = start; 418 | } 419 | } 420 | return result; 421 | } 422 | 423 | std::vector Utils::QWordScan(INT64 value, INT64 start,int align) { 424 | SYSTEM_INFO sys_info; 425 | GetSystemInfo(&sys_info); 426 | std::vector result; 427 | INT64 min_addr = 428 | reinterpret_cast(sys_info.lpMinimumApplicationAddress); 429 | INT64 max_addr = 430 | reinterpret_cast(sys_info.lpMaximumApplicationAddress); 431 | const INT64 page_size = sys_info.dwPageSize; 432 | min_addr = min_addr > start ? min_addr : start; 433 | 434 | auto current_addr = min_addr; 435 | MEMORY_BASIC_INFORMATION mem_info = {}; 436 | HANDLE handle = GetCurrentProcess(); 437 | while (current_addr < max_addr) { 438 | VirtualQueryEx(handle, reinterpret_cast(current_addr), &mem_info, 439 | sizeof(MEMORY_BASIC_INFORMATION)); 440 | 441 | if ((INT64)mem_info.RegionSize <= 0) { 442 | break; 443 | } 444 | INT64 region_size = mem_info.RegionSize; 445 | if ((mem_info.State & MEM_COMMIT) == MEM_COMMIT && 446 | (mem_info.Protect & PAGE_GUARD) != PAGE_GUARD && 447 | (mem_info.Protect & PAGE_NOACCESS) != PAGE_NOACCESS) { 448 | for (INT64 i = 0; i < region_size; i += align) { 449 | if (value == *(INT64 *)current_addr) { 450 | result.push_back(current_addr); 451 | } 452 | current_addr += align; 453 | } 454 | } else { 455 | current_addr += region_size; 456 | } 457 | } 458 | return result; 459 | } 460 | } // namespace wxhelper -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef WXHELPER_UTILS_H_ 2 | #define WXHELPER_UTILS_H_ 3 | #include 4 | 5 | #include 6 | #include 7 | #define STRING2INT(str) (Utils::IsDigit(str) ? stoi(str) : 0) 8 | #define WS2LPWS(wstr) (LPWSTR) wstr.c_str() 9 | 10 | 11 | namespace wxhelper { 12 | 13 | class Utils { 14 | public: 15 | static std::wstring UTF8ToWstring(const std::string &str); 16 | 17 | static std::string WstringToUTF8(const std::wstring &str); 18 | 19 | static std::wstring AnsiToWstring(const std::string &input, 20 | INT64 locale = CP_ACP); 21 | 22 | static std::string WstringToAnsi(const std::wstring &input, 23 | INT64 locale = CP_ACP); 24 | 25 | static UINT64 GetWeChatWinBase(); 26 | 27 | static bool CreateConsole(); 28 | 29 | static void CloseConsole(); 30 | 31 | static std::string EncodeHexString(const std::string &str); 32 | 33 | static std::string Hex2String(const std::string &hex_str); 34 | 35 | static std::string Bytes2Hex(const BYTE *bytes, const int length); 36 | 37 | static void Hex2Bytes(const std::string &hex, BYTE *bytes); 38 | 39 | static bool IsDigit(std::string str); 40 | 41 | static bool FindOrCreateDirectoryW(const wchar_t *path); 42 | 43 | static void HookAnyAddress(DWORD hook_addr, LPVOID jmp_addr, char *origin); 44 | static void UnHookAnyAddress(DWORD hook_addr, char *origin); 45 | static std::wstring GetTimeW(long long timestamp); 46 | 47 | static std::string WCharToUTF8(wchar_t *wstr); 48 | 49 | static bool IsTextUtf8(const char *str, INT64 length); 50 | 51 | static void Hide(HMODULE module); 52 | 53 | static std::string ReadSKBuiltinString(INT64 addr); 54 | static std::string ReadSKBuiltinBuffer(INT64 addr); 55 | static std::string ReadWeChatStr(INT64 addr); 56 | 57 | static std::string ImageXor(std::string buf); 58 | static std::wstring ReadWstring(INT64 addr); 59 | static std::string ReadWstringThenConvert(INT64 addr); 60 | 61 | static INT64 DecodeImage(const wchar_t* file_path,const wchar_t* save_dir); 62 | 63 | static std::vector QWordScan(INT64 value, int align, 64 | const wchar_t *module); 65 | 66 | static std::vector QWordScan(INT64 value, INT64 start,int align); 67 | template 68 | static std::vector split(T1 str, T2 letter) { 69 | std::vector arr; 70 | size_t pos; 71 | while ((pos = str.find_first_of(letter)) != T1::npos) { 72 | T1 str1 = str.substr(0, pos); 73 | arr.push_back(str1); 74 | str = str.substr(pos + 1, str.length() - pos - 1); 75 | } 76 | arr.push_back(str); 77 | return arr; 78 | } 79 | 80 | template 81 | static T1 replace(T1 source, T2 replaced, T1 replaceto) { 82 | std::vector v_arr = split(source, replaced); 83 | if (v_arr.size() < 2) return source; 84 | T1 temp; 85 | for (unsigned int i = 0; i < v_arr.size() - 1; i++) { 86 | temp += v_arr[i]; 87 | temp += replaceto; 88 | } 89 | temp += v_arr[v_arr.size() - 1]; 90 | return temp; 91 | } 92 | 93 | template 94 | static T *WxHeapAlloc(size_t n) { 95 | return (T *)HeapAlloc(GetProcessHeap(), 0, n); 96 | } 97 | }; 98 | 99 | } // namespace wxhelper 100 | #endif -------------------------------------------------------------------------------- /tool/injector/ConsoleApplication.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttttupup/wxhelper/fa90a0ea87c9b4ca9c63a1e8385293159167f367/tool/injector/ConsoleApplication.exe -------------------------------------------------------------------------------- /tool/injector/injector.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttttupup/wxhelper/fa90a0ea87c9b4ca9c63a1e8385293159167f367/tool/injector/injector.dll -------------------------------------------------------------------------------- /tool/injector/readme.md: -------------------------------------------------------------------------------- 1 | ## 可以使用对应分支下的注入工具,或者自己编译一下 source目录下的注入程序。 2 | 3 | 1.ConsoleApplication.exe 4 | 编译好的x64版本的注入器 5 | 命令行注入工具,注入命令 6 | ``` javascript 7 | //-i 注入程序名 -p 注入dll路径 8 | // -u 卸载程序名 -d 卸载dll名称 9 | //注入 10 | ConsoleInject.exe -i demo.exe -p E:\wxhelper.dll 11 | //卸载 12 | ConsoleInject.exe -u demo.exe -d wxhelper.dll 13 | 14 | ``` -------------------------------------------------------------------------------- /tool/injector/微信DLL注入器V1.0.3.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttttupup/wxhelper/fa90a0ea87c9b4ca9c63a1e8385293159167f367/tool/injector/微信DLL注入器V1.0.3.exe -------------------------------------------------------------------------------- /weChatHook-java/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /weChatHook-java/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /weChatHook-java/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /weChatHook-java/.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /weChatHook-java/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /weChatHook-java/README.md: -------------------------------------------------------------------------------- 1 | #maven打包 2 | ```aidl 3 | 进入weChatHook-java项目根目录执行如下,命令进行打包 4 | 5 | mvn package 6 | 7 | 打包完成后在target目录下找到 8 | weChatHook-java-1.0-jar-with-dependencies.jar 9 | 文件就可以直接启动了 10 | ``` 11 | #启动命令 12 | ####命令参数说明 13 | ###port:监听的端口 默认端口19077 14 | ###hookApi:消息转发的接口 为空不转发 15 | ```aidl 16 | java -jar .\weChatHook-java-1.0-jar-with-dependencies.jar --port=9999 --hookApi=http://localhost:29099/api/demo/msg 17 | ``` 18 | #java接收hook消息示例 19 | ```aidl 20 | @RequestMapping("/api/demo") 21 | public class DemoController { 22 | @PostMapping("/msg") 23 | public void getMsg(String msg){ 24 | JSONObject jsonObject = JSON.parseObject(msg); 25 | jsonObject.forEach((k,v)->{ 26 | System.out.println(k+":"+v); 27 | }); 28 | } 29 | ``` -------------------------------------------------------------------------------- /weChatHook-java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.example 8 | weChatHook-java 9 | 1.0 10 | 11 | 12 | 8 13 | 8 14 | 15 | 16 | 17 | 18 | org.jsoup 19 | jsoup 20 | 1.14.3 21 | 22 | 23 | 24 | com.alibaba 25 | fastjson 26 | 1.2.83 27 | 28 | 29 | 30 | org.projectlombok 31 | lombok 32 | 1.18.24 33 | 34 | 35 | 36 | 37 | io.netty 38 | netty-all 39 | 4.1.51.Final 40 | 41 | 42 | junit 43 | junit 44 | 4.13.2 45 | test 46 | 47 | 48 | 49 | 50 | 51 | org.apache.maven.plugins 52 | maven-assembly-plugin 53 | 54 | 55 | 56 | true 57 | com.example.service.WeChatHookNettyServer 58 | 59 | 60 | 61 | jar-with-dependencies 62 | 63 | 64 | 65 | 66 | make-assembly 67 | package 68 | 69 | single 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /weChatHook-java/src/main/java/com/example/service/WeChatHookNettyServer.java: -------------------------------------------------------------------------------- 1 | package com.example.service; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.example.client.WeChatHookClient; 6 | import io.netty.bootstrap.ServerBootstrap; 7 | import io.netty.channel.Channel; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.ChannelInitializer; 10 | import io.netty.channel.ChannelOption; 11 | import io.netty.channel.SimpleChannelInboundHandler; 12 | import io.netty.channel.nio.NioEventLoopGroup; 13 | import io.netty.channel.socket.SocketChannel; 14 | import io.netty.channel.socket.nio.NioServerSocketChannel; 15 | import io.netty.handler.codec.DelimiterBasedFrameDecoder; 16 | import io.netty.handler.codec.Delimiters; 17 | import io.netty.handler.codec.string.StringDecoder; 18 | import io.netty.handler.codec.string.StringEncoder; 19 | import io.netty.util.CharsetUtil; 20 | import io.netty.util.internal.StringUtil; 21 | 22 | import java.io.*; 23 | import java.net.InetSocketAddress; 24 | import java.util.*; 25 | 26 | /** 27 | * @PACKAGE_NAME: com.example.service 28 | * @NAME: WeChatHookNettyServer 29 | * @AUTHOR: wxs 30 | * @DATE: 2023/5/31 15:07 31 | * @PROJECT_NAME: WeChatHook-java 32 | **/ 33 | public class WeChatHookNettyServer { 34 | 35 | /** 36 | * 直接启动main方法 37 | * 38 | * @param args 39 | */ 40 | public static void main(String[] args) { 41 | 42 | Integer serverPort = 19077; 43 | 44 | String hookApi = null; 45 | 46 | for (String arg : args) { 47 | System.out.println(arg); 48 | if (arg.startsWith("--port")) { 49 | serverPort = Integer.valueOf(arg.split("=")[1]); 50 | } 51 | if (arg.startsWith("--hookApi")) { 52 | hookApi = arg.split("=")[1]; 53 | } 54 | } 55 | 56 | //1、注入 57 | inject(); 58 | 59 | //2、开启hook 60 | try { 61 | JSONObject result = WeChatHookClient.hook_msg("127.0.0.1", serverPort.toString()); 62 | } catch (Exception e) { 63 | System.out.println("hook 失败,请检查微信是否登录"); 64 | return; 65 | } 66 | //3、启动服务 67 | start(serverPort, hookApi); 68 | } 69 | 70 | /** 71 | * 执行注入命令 72 | */ 73 | public static void inject() { 74 | // 75 | File consoleInjectTemp = null; 76 | File wxhelperTemp = null; 77 | try { 78 | consoleInjectTemp = createTempFile("ConsoleInject", ".exe"); 79 | wxhelperTemp = createTempFile("wxhelper", ".dll"); 80 | 81 | String ConsoleInject = consoleInjectTemp.getAbsolutePath(); 82 | 83 | String wxhelper = wxhelperTemp.getAbsolutePath(); 84 | 85 | //ConsoleInject.exe -i WeChat.exe -p C:\Users\DELL\Desktop\injector\wxhelper.dll 86 | String command = ConsoleInject + " -i WeChat.exe -p " + wxhelper; 87 | 88 | //重试3次 89 | int retryCount = 3; 90 | do { 91 | retryCount--; 92 | try { 93 | //检查登录状态 94 | JSONObject jsonObject = WeChatHookClient.check_login(); 95 | //如果已登录不需要注入 96 | if (jsonObject.getInteger("code").equals(1)) { 97 | return; 98 | } 99 | } catch (Exception e) { 100 | System.out.println(e.getMessage() + "请确认微信已登录"); 101 | } 102 | //执行注入命令 103 | excuteShell(command); 104 | 105 | } while (retryCount >= 0); 106 | 107 | } catch (Exception e) { 108 | e.printStackTrace(); 109 | return; 110 | } finally { 111 | ////如果不为空删除临时文件 112 | if (Objects.nonNull(consoleInjectTemp)) { 113 | consoleInjectTemp.delete(); 114 | } 115 | //如果不为空删除临时文件 116 | if (Objects.nonNull(wxhelperTemp)) { 117 | wxhelperTemp.delete(); 118 | } 119 | } 120 | 121 | } 122 | 123 | /** 124 | * 创建临时文件 125 | * 126 | * @param fileName 127 | * @param suffix 128 | * @return 129 | */ 130 | private static File createTempFile(String fileName, String suffix) { 131 | InputStream inputStream = WeChatHookNettyServer.class.getResourceAsStream("/" + fileName + suffix); 132 | 133 | FileOutputStream outputStream = null; 134 | try { 135 | File tempFile = File.createTempFile(fileName, suffix); 136 | outputStream = new FileOutputStream(tempFile); 137 | byte[] buffer = new byte[4096]; 138 | int bytesRead; 139 | while ((bytesRead = inputStream.read(buffer)) != -1) { 140 | outputStream.write(buffer, 0, bytesRead); 141 | } 142 | return tempFile; 143 | } catch (IOException e) { 144 | e.printStackTrace(); 145 | } finally { 146 | if (Objects.nonNull(outputStream)) { 147 | try { 148 | outputStream.close(); 149 | } catch (IOException e) { 150 | e.printStackTrace(); 151 | } 152 | } 153 | 154 | } 155 | return null; 156 | } 157 | 158 | /** 159 | * 启动服务 160 | * 161 | * @param port 162 | */ 163 | public static void start(Integer port, String hookApi) { 164 | NioEventLoopGroup bossGroup = new NioEventLoopGroup(); 165 | NioEventLoopGroup workerGroup = new NioEventLoopGroup(); 166 | try { 167 | ServerBootstrap serverBootstrap = new ServerBootstrap(); 168 | serverBootstrap.group(bossGroup, workerGroup) 169 | .channel(NioServerSocketChannel.class) 170 | .localAddress(new InetSocketAddress(port)) 171 | .childHandler(new ChannelInitializer() { 172 | @Override 173 | protected void initChannel(SocketChannel ch) { 174 | ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024 * 100, Delimiters.lineDelimiter())); 175 | ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8)); 176 | ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8)); 177 | ch.pipeline().addLast(new ReceiveMsgHandler(hookApi)); 178 | } 179 | }) 180 | .option(ChannelOption.SO_BACKLOG, 128) 181 | .childOption(ChannelOption.SO_KEEPALIVE, true); 182 | 183 | Channel channel = serverBootstrap.bind().sync().channel(); 184 | System.out.println("服务启动成功 端口号 " + port); 185 | channel.closeFuture().sync(); 186 | } catch (InterruptedException e) { 187 | e.printStackTrace(); 188 | } finally { 189 | bossGroup.shutdownGracefully(); 190 | workerGroup.shutdownGracefully(); 191 | } 192 | } 193 | 194 | private static class ReceiveMsgHandler extends SimpleChannelInboundHandler { 195 | 196 | private String hookApi; 197 | 198 | public ReceiveMsgHandler(String hookApi) { 199 | this.hookApi = hookApi; 200 | } 201 | 202 | @Override 203 | protected void channelRead0(ChannelHandlerContext ctx, String msg) { 204 | JSONObject jsonObject = JSON.parseObject(msg); 205 | 206 | jsonObject.forEach((k, v) -> { 207 | System.out.println(k + " = " + v); 208 | }); 209 | 210 | String fromGroup = jsonObject.getString("fromGroup"); 211 | String fromUser = jsonObject.getString("fromUser"); 212 | String from; 213 | if (fromGroup.equals(fromUser)) { 214 | JSONObject fromGroupJson = WeChatHookClient.query_nickname(jsonObject.getString("fromGroup")); 215 | String groupNname = fromGroupJson.getString("name"); 216 | from = "消息来自:" + groupNname; 217 | jsonObject.put("fromUserName", groupNname); 218 | } else { 219 | JSONObject fromGroupJson = WeChatHookClient.query_nickname(jsonObject.getString("fromGroup")); 220 | String groupNname = fromGroupJson.getString("name"); 221 | 222 | JSONObject fromUserJson = WeChatHookClient.query_nickname(jsonObject.getString("fromUser")); 223 | String fromUserName = fromUserJson.getString("name"); 224 | jsonObject.put("fromUserName", fromUserName); 225 | 226 | from = "消息来自:" + groupNname + "->" + fromUserName; 227 | } 228 | System.out.println("----------" + from + "----------"); 229 | //消息转发 230 | if (StringUtil.isNullOrEmpty(hookApi)) { 231 | return; 232 | } 233 | //检查api接口是否是通的 234 | //转发消息 235 | try { 236 | WeChatHookClient.hook(hookApi, msg); 237 | } catch (Exception e) { 238 | //请检查hookApi服务是否正常 239 | System.err.println("--》消息转发失败,请检查hookApi服务是否正常"); 240 | } 241 | 242 | } 243 | 244 | @Override 245 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 246 | cause.printStackTrace(); 247 | ctx.close(); 248 | } 249 | 250 | } 251 | 252 | /** 253 | * 执行shell 命令 254 | * 255 | * @param command 256 | */ 257 | public static void excuteShell(String command) { 258 | try { 259 | Process process = Runtime.getRuntime().exec(command); 260 | 261 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 262 | String line; 263 | while ((line = reader.readLine()) != null) { 264 | System.out.println(line); 265 | } 266 | int exitCode = process.waitFor(); 267 | System.out.println("Exit Code: " + exitCode); 268 | } catch (IOException | InterruptedException e) { 269 | e.printStackTrace(); 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /weChatHook-java/src/main/resources/ConsoleInject.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttttupup/wxhelper/fa90a0ea87c9b4ca9c63a1e8385293159167f367/weChatHook-java/src/main/resources/ConsoleInject.exe -------------------------------------------------------------------------------- /weChatHook-java/src/main/resources/injector.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttttupup/wxhelper/fa90a0ea87c9b4ca9c63a1e8385293159167f367/weChatHook-java/src/main/resources/injector.dll -------------------------------------------------------------------------------- /weChatHook-java/src/main/resources/wxhelper.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ttttupup/wxhelper/fa90a0ea87c9b4ca9c63a1e8385293159167f367/weChatHook-java/src/main/resources/wxhelper.dll --------------------------------------------------------------------------------