├── .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 | 
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