mirror of
https://github.com/ttttupup/wxhelper.git
synced 2024-11-22 10:19:23 +08:00
Merge branch 'ttttupup:main' into main
This commit is contained in:
commit
bdf19fe4e4
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- Browser [e.g. chrome, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Smartphone (please complete the following information):**
|
||||||
|
- Device: [e.g. iPhone6]
|
||||||
|
- OS: [e.g. iOS8.1]
|
||||||
|
- Browser [e.g. stock browser, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
@ -10,13 +10,17 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D '_UNICODE' /D 'UNICODE'")
|
|||||||
file(GLOB CPP_FILES ${PROJECT_SOURCE_DIR}/src/*.cc ${PROJECT_SOURCE_DIR}/src/*.cpp)
|
file(GLOB CPP_FILES ${PROJECT_SOURCE_DIR}/src/*.cc ${PROJECT_SOURCE_DIR}/src/*.cpp)
|
||||||
|
|
||||||
|
|
||||||
include_directories(${VCPKG_INSTALLED_DIR}/x86-windows/include)
|
|
||||||
|
include_directories(c:/soft/vcpkg/installed/x86-windows/include)
|
||||||
|
|
||||||
# add_subdirectory(3rd)
|
# add_subdirectory(3rd)
|
||||||
|
# add_subdirectory(source)
|
||||||
|
|
||||||
find_package(nlohmann_json CONFIG REQUIRED)
|
find_package(nlohmann_json CONFIG REQUIRED)
|
||||||
find_package(unofficial-mongoose CONFIG REQUIRED)
|
find_package(unofficial-mongoose CONFIG REQUIRED)
|
||||||
|
# find_package(spdlog CONFIG REQUIRED)
|
||||||
|
# find_package(minhook CONFIG REQUIRED)
|
||||||
|
|
||||||
|
|
||||||
add_library(wxhelper SHARED ${CPP_FILES} )
|
add_library(wxhelper SHARED ${CPP_FILES} )
|
||||||
|
|
||||||
@ -25,6 +29,8 @@ add_library(wxhelper SHARED ${CPP_FILES} )
|
|||||||
|
|
||||||
target_link_libraries(wxhelper PRIVATE nlohmann_json::nlohmann_json)
|
target_link_libraries(wxhelper PRIVATE nlohmann_json::nlohmann_json)
|
||||||
target_link_libraries(wxhelper PRIVATE unofficial::mongoose::mongoose)
|
target_link_libraries(wxhelper PRIVATE unofficial::mongoose::mongoose)
|
||||||
|
# target_link_libraries(wxhelper PRIVATE spdlog::spdlog spdlog::spdlog_header_only)
|
||||||
|
# target_link_libraries(wxhelper PRIVATE minhook::minhook)
|
||||||
|
|
||||||
SET_TARGET_PROPERTIES(wxhelper PROPERTIES LINKER_LANGUAGE C
|
SET_TARGET_PROPERTIES(wxhelper PROPERTIES LINKER_LANGUAGE C
|
||||||
ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
|
ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
|
||||||
|
58
README.md
58
README.md
@ -1,5 +1,5 @@
|
|||||||
# wxhelper
|
# wxhelper
|
||||||
wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26, 3.9.0.28, 3.9.2.23版本。
|
wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26, 3.9.0.28, 3.9.2.23,3.9.2.26版本。
|
||||||
#### 免责声明:
|
#### 免责声明:
|
||||||
本仓库发布的内容,仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关!
|
本仓库发布的内容,仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关!
|
||||||
|
|
||||||
@ -19,13 +19,13 @@ dll在注入成功时,创建了一个默认端口为19088的http服务端,
|
|||||||
|
|
||||||
```
|
```
|
||||||
#### 使用说明:
|
#### 使用说明:
|
||||||
支持的版本3.8.0.41,3.8.1.26,3.9.0.28。
|
支持的版本3.8.0.41、3.8.1.26、3.9.0.28、3.9.2.23、3.9.2.26 。
|
||||||
源码和主要实现在相应的分支内。
|
源码和主要实现在相应的分支内。
|
||||||
src:主要的dll代码
|
src:主要的dll代码
|
||||||
tool:简单的注入工具,一个是控制台,一个是图形界面。
|
tool:简单的注入工具,一个是控制台,一个是图形界面。
|
||||||
python: tcpserver.py: 简单的服务器,用以接收消息内容。decrpty.py: 微信数据库解密工具。
|
python: tcpserver.py: 简单的服务器,用以接收消息内容。decrpty.py: 微信数据库解密工具。 http_server.py:http server端。
|
||||||
source: 简单的命令行远程注入源码。
|
source: 简单的命令行远程注入源码。
|
||||||
|
其他目录:热心作者提供的一些客户端。
|
||||||
|
|
||||||
#### 0.首先安装对应的版本的微信,分支名称即代表的是微信对应的版本。dll的前缀都会带有微信版本。
|
#### 0.首先安装对应的版本的微信,分支名称即代表的是微信对应的版本。dll的前缀都会带有微信版本。
|
||||||
#### 1.使用注入工具注入wxhelper.dll,注入成功后,即可通过postman直接调用对应的接口。
|
#### 1.使用注入工具注入wxhelper.dll,注入成功后,即可通过postman直接调用对应的接口。
|
||||||
@ -49,7 +49,7 @@ source: 简单的命令行远程注入源码。
|
|||||||
个人常用的方法,请参考https://github.com/ttttupup/wxhelper/wiki
|
个人常用的方法,请参考https://github.com/ttttupup/wxhelper/wiki
|
||||||
使用上的问题,可查询https://github.com/ttttupup/wxhelper/discussions
|
使用上的问题,可查询https://github.com/ttttupup/wxhelper/discussions
|
||||||
数据库解密,请参考https://github.com/ttttupup/wxhelper/wiki
|
数据库解密,请参考https://github.com/ttttupup/wxhelper/wiki
|
||||||
|
个人精力有限,只维护最新版本,旧版本的bug会在新版本中修复,不维护旧版本。
|
||||||
|
|
||||||
|
|
||||||
#### 编译环境
|
#### 编译环境
|
||||||
@ -63,6 +63,21 @@ cmake
|
|||||||
vcpkg
|
vcpkg
|
||||||
#### 编译构建
|
#### 编译构建
|
||||||
先准备好编译环境。
|
先准备好编译环境。
|
||||||
|
```
|
||||||
|
cd wxhelper
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_C_COMPILER=cl.exe \
|
||||||
|
-DCMAKE_CXX_COMPILER=cl.exe \
|
||||||
|
-DCMAKE_BUILD_TYPE=Debug \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=C:/other/codeSource/windows/wxhelper/out/install/x86-debug \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=C:/vcpkg/scripts/buildsystems/vcpkg.cmake \
|
||||||
|
-SC:/wxhelper \
|
||||||
|
-BC:/wxhelper/build/x86-debug\
|
||||||
|
-G Ninja
|
||||||
|
|
||||||
|
cmake --build ..
|
||||||
|
```
|
||||||
|
|
||||||
以下是在vscode中操作,vs中的操作类似。
|
以下是在vscode中操作,vs中的操作类似。
|
||||||
1.安装vcpkg,cmake,vscode
|
1.安装vcpkg,cmake,vscode
|
||||||
@ -100,7 +115,7 @@ vcpkg
|
|||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
4.vscode中右键configure all projects,在Terminal中点击Run Task,如没有先配置build任务,然后运行即可
|
4.执行cmake build vscode中右键configure all projects,在Terminal中点击Run Task,如没有先配置build任务,然后运行即可
|
||||||
|
|
||||||
5.命令行注入工具,注入命令
|
5.命令行注入工具,注入命令
|
||||||
``` javascript
|
``` javascript
|
||||||
@ -108,6 +123,7 @@ vcpkg
|
|||||||
// -u 卸载程序名 -d 卸载dll名称
|
// -u 卸载程序名 -d 卸载dll名称
|
||||||
// -m pid 关闭微信互斥体,多开微信
|
// -m pid 关闭微信互斥体,多开微信
|
||||||
// -P port 指定http端口,需要使用 specify-port 分支的生成的dll
|
// -P port 指定http端口,需要使用 specify-port 分支的生成的dll
|
||||||
|
// -I 注入程序的pid
|
||||||
//注入
|
//注入
|
||||||
ConsoleInject.exe -i demo.exe -p E:\testInject.dll
|
ConsoleInject.exe -i demo.exe -p E:\testInject.dll
|
||||||
//卸载
|
//卸载
|
||||||
@ -116,6 +132,16 @@ vcpkg
|
|||||||
ConsoleInject.exe -m 1222
|
ConsoleInject.exe -m 1222
|
||||||
// 注入并指定http端口
|
// 注入并指定http端口
|
||||||
ConsoleInject.exe -i demo.exe -p E:\testInject.dll -P 18888
|
ConsoleInject.exe -i demo.exe -p E:\testInject.dll -P 18888
|
||||||
|
// 注入指定pid并关闭多开限制
|
||||||
|
ConsoleInject.exe -I 15048 -p E:\testInject.dll -m 15048
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
6.如果想改变端口,可以在微信目录下创建config.ini配置文件,修改端口即可。不创建则默认端口19088。
|
||||||
|
``` shell
|
||||||
|
[config]
|
||||||
|
port=19099
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 更新说明
|
#### 更新说明
|
||||||
@ -147,6 +173,12 @@ vcpkg
|
|||||||
|
|
||||||
2023-03-21 : 新增hook语音
|
2023-03-21 : 新增hook语音
|
||||||
|
|
||||||
|
2023-03-30 : 新增获取语音文件(推荐使用这个非hook接口)
|
||||||
|
|
||||||
|
2023-04-08 : 3.9.2.23版本功能更新
|
||||||
|
|
||||||
|
2023-06-05 :3.9.2.26版本更新
|
||||||
|
|
||||||
#### 功能预览:
|
#### 功能预览:
|
||||||
0.检查是否登录
|
0.检查是否登录
|
||||||
1.获取登录微信信息
|
1.获取登录微信信息
|
||||||
@ -163,6 +195,7 @@ vcpkg
|
|||||||
17.删除好友
|
17.删除好友
|
||||||
19.通过手机或qq查找微信
|
19.通过手机或qq查找微信
|
||||||
20.通过wxid添加好友
|
20.通过wxid添加好友
|
||||||
|
23.通过好友申请
|
||||||
25.获取群成员
|
25.获取群成员
|
||||||
26.获取群成员昵称
|
26.获取群成员昵称
|
||||||
27.删除群成员
|
27.删除群成员
|
||||||
@ -186,9 +219,22 @@ vcpkg
|
|||||||
54.朋友圈下一页
|
54.朋友圈下一页
|
||||||
55.获取联系人或者群名称
|
55.获取联系人或者群名称
|
||||||
56.获取消息附件(图片,视频,文件)
|
56.获取消息附件(图片,视频,文件)
|
||||||
|
57.获取语音文件(silk3格式)
|
||||||
|
58.登录二维码
|
||||||
|
59.邀请入群
|
||||||
|
60.获取群/群成员详情
|
||||||
|
61.撤回消息
|
||||||
|
62.发送公众号消息
|
||||||
|
63.转发公众号消息
|
||||||
|
64.发送小程序
|
||||||
|
65.退款
|
||||||
|
66.下载头像(勿用,没什么用)
|
||||||
#### 感谢
|
#### 感谢
|
||||||
https://github.com/ljc545w/ComWeChatRobot
|
https://github.com/ljc545w/ComWeChatRobot
|
||||||
|
|
||||||
https://github.com/NationalSecurityAgency/ghidra
|
https://github.com/NationalSecurityAgency/ghidra
|
||||||
|
|
||||||
https://github.com/x64dbg/x64dbg
|
https://github.com/x64dbg/x64dbg
|
||||||
|
|
||||||
|
#### 讨论组
|
||||||
|
https://t.me/+LmvAauweyUpjYzJl
|
||||||
|
1633
doc/3.9.2.23.md
Normal file
1633
doc/3.9.2.23.md
Normal file
File diff suppressed because it is too large
Load Diff
33
java_client/.gitignore
vendored
Normal file
33
java_client/.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
HELP.md
|
||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
21
java_client/README.md
Normal file
21
java_client/README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
环境为jdk17
|
||||||
|
执行之后会在当前项目所处磁盘根路径生成一个exec文件夹,然后会把src/main/resources/exec下的文件放在那避免因为路径问题出错
|
||||||
|
java_client/src/main/resources/exec/c.exe 为注入器,只不过把名字改短了,更新的话换成最新版,改个名字就行, wxhelper.dll同理
|
||||||
|
|
||||||
|
项目启动之后,会生成一个tcp服务端,用来接受hook信息,然后把接收的信息放在队列中,之后用一个线程去循环处理消息.
|
||||||
|
具体实现可以看
|
||||||
|
```com.example.wxhk.tcp.vertx```包下的三个文件
|
||||||
|
|
||||||
|
com.example.wxhk.tcp.vertx.VertxTcp 这个是tcp服务端,接受信息
|
||||||
|
|
||||||
|
com.example.wxhk.tcp.vertx.InitWeChat 微信环境初始化
|
||||||
|
|
||||||
|
com.example.wxhk.tcp.vertx.ArrHandle 循环消息处理
|
||||||
|
|
||||||
|
com.example.wxhk.server.WxSmgServer 为消息处理接口,实现其中的方法即可
|
||||||
|
|
||||||
|
![image](https://github.com/sglmsn/wxhelper/assets/36943585/59d49401-a492-46a9-8ed9-dab7fb1822b4)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
启动项目需要去修改配置文件的微信路径
|
184
java_client/pom.xml
Normal file
184
java_client/pom.xml
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
<groupId>com.example</groupId>
|
||||||
|
<artifactId>wxhk</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>wxhk</name>
|
||||||
|
<description>wxhk</description>
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||||
|
<!-- <artifactId>spring-boot-starter-thymeleaf</artifactId>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||||
|
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-all</artifactId>
|
||||||
|
<version>4.1.92.Final</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
|
<artifactId>okhttp</artifactId>
|
||||||
|
<version>4.11.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.vertx</groupId>
|
||||||
|
<artifactId>vertx-core</artifactId>
|
||||||
|
<version>4.4.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.vertx</groupId>
|
||||||
|
<artifactId>vertx-web</artifactId>
|
||||||
|
<version>4.4.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.vertx</groupId>
|
||||||
|
<artifactId>vertx-web-client</artifactId>
|
||||||
|
<version>4.4.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.vertx</groupId>
|
||||||
|
<artifactId>vertx-mysql-client</artifactId>
|
||||||
|
<version>4.4.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>6.0.0.M3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>2.15.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<!-- 排除文件配置 -->
|
||||||
|
<!-- <excludes> -->
|
||||||
|
<!-- <exclude>*.**</exclude> -->
|
||||||
|
<!-- <exclude>*/**.xml</exclude> -->
|
||||||
|
<!-- </excludes> -->
|
||||||
|
|
||||||
|
<!-- 包含文件配置,现在只打包 com 文件夹 -->
|
||||||
|
<includes>
|
||||||
|
<include>
|
||||||
|
**/com/example/wxhk/**
|
||||||
|
</include>
|
||||||
|
</includes>
|
||||||
|
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<!-- 配置加入依赖包 -->
|
||||||
|
<addClasspath>true</addClasspath>
|
||||||
|
<classpathPrefix>lib/</classpathPrefix>
|
||||||
|
<useUniqueVersions>false</useUniqueVersions>
|
||||||
|
<!-- Spring Boot 启动类(自行修改) -->
|
||||||
|
<mainClass>com.example.wxhk.WxhkApplication</mainClass>
|
||||||
|
</manifest>
|
||||||
|
<manifestEntries>
|
||||||
|
<!-- 外部资源路径加入 manifest.mf 的 Class-Path -->
|
||||||
|
<Class-Path>resources/</Class-Path>
|
||||||
|
</manifestEntries>
|
||||||
|
</archive>
|
||||||
|
<!-- jar 输出目录 -->
|
||||||
|
<outputDirectory>${project.build.directory}/pack/</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<!-- 复制依赖 -->
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-dependencies</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<!-- 依赖包 输出目录 -->
|
||||||
|
<outputDirectory>${project.build.directory}/pack/lib</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
|
<!-- 复制资源 -->
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-resources</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-resources</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<!-- 资源文件 输出目录 -->
|
||||||
|
<outputDirectory>${project.build.directory}/pack/resources</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.example.wxhk;
|
||||||
|
|
||||||
|
import io.vertx.core.Vertx;
|
||||||
|
import io.vertx.core.VertxOptions;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class WxhkApplication {
|
||||||
|
public static final Vertx vertx;
|
||||||
|
|
||||||
|
static {
|
||||||
|
vertx = Vertx.vertx(new VertxOptions().setWorkerPoolSize(5).setEventLoopPoolSize(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
//ConsoleInject.exe -i WeChat.exe -p D:\wxhelper.dll
|
||||||
|
//ConsoleApplication.exe -I 4568 -p C:\wxhelper.dll -m 17484 -P 1888
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(WxhkApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.example.wxhk.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接受到的微信消息类型
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/26
|
||||||
|
*/
|
||||||
|
public enum WxMsgType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
私聊信息(1),
|
||||||
|
好友请求(37),
|
||||||
|
收到名片(42),
|
||||||
|
表情(47),
|
||||||
|
转账和收款(49),
|
||||||
|
收到转账之后或者文件助手等信息(51),
|
||||||
|
|
||||||
|
入群(10000),
|
||||||
|
/**
|
||||||
|
* 扫码触发,会触发2次, 有一次有编号,一次没有,还有登陆之后也有,很多情况都会调用这个
|
||||||
|
*/
|
||||||
|
扫码触发(10002),
|
||||||
|
|
||||||
|
;
|
||||||
|
Integer type;
|
||||||
|
|
||||||
|
WxMsgType(Integer type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.example.wxhk.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
|
||||||
|
public class WxMsgController {
|
||||||
|
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
11
java_client/src/main/java/com/example/wxhk/infe/Resp.java
Normal file
11
java_client/src/main/java/com/example/wxhk/infe/Resp.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package com.example.wxhk.infe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http 响应
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
public interface Resp extends java.io.Serializable{
|
||||||
|
|
||||||
|
|
||||||
|
}
|
17
java_client/src/main/java/com/example/wxhk/infe/SendMsg.java
Normal file
17
java_client/src/main/java/com/example/wxhk/infe/SendMsg.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package com.example.wxhk.infe;
|
||||||
|
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http接口请求的基础接口
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
public interface SendMsg<T> extends java.io.Serializable{
|
||||||
|
|
||||||
|
default JsonObject toJson(){
|
||||||
|
return JsonObject.mapFrom(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package com.example.wxhk.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 私聊
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/26
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class PrivateChatMsg implements Serializable {
|
||||||
|
|
||||||
|
String path;
|
||||||
|
/**
|
||||||
|
* 内容
|
||||||
|
*/
|
||||||
|
|
||||||
|
private String content;
|
||||||
|
/**
|
||||||
|
* 当是群聊的时候 为群id,否则为微信id
|
||||||
|
*/
|
||||||
|
private String fromGroup;
|
||||||
|
/**
|
||||||
|
* 微信id
|
||||||
|
*/
|
||||||
|
private String fromUser;
|
||||||
|
private Integer isSendMsg;
|
||||||
|
/**
|
||||||
|
* 1通过手机发送
|
||||||
|
*/
|
||||||
|
private Integer isSendByPhone;
|
||||||
|
private Long msgId;
|
||||||
|
private Integer pid;
|
||||||
|
private String sign;
|
||||||
|
private String signature;
|
||||||
|
private String time;
|
||||||
|
private Integer timestamp;
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
*/
|
||||||
|
private Integer type;
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.example.wxhk.model.dto;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付信息
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @param receiverUsername 付款人
|
||||||
|
* @param decimal 收款金额
|
||||||
|
* @param remark 备注
|
||||||
|
* @param transcationid
|
||||||
|
* @param transferid
|
||||||
|
* @date 2023/06/06
|
||||||
|
*/
|
||||||
|
public record PayoutInformation(String receiverUsername, BigDecimal decimal, String remark,String transcationid,String transferid) implements java.io.Serializable {
|
||||||
|
|
||||||
|
public PayoutInformation(String receiverUsername, BigDecimal decimal, String remark) {
|
||||||
|
this(receiverUsername, decimal, remark, null, null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.SendMsg;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加wxid 好友
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class AddFriends implements SendMsg<AddFriends> {
|
||||||
|
String wxid;
|
||||||
|
/**
|
||||||
|
* 验证信息
|
||||||
|
*/
|
||||||
|
String msg;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.SendMsg;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认收款
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class ConfirmThePayment implements SendMsg<ConfirmThePayment> {
|
||||||
|
/**
|
||||||
|
* 转账人微信id,从hook的消息中获取
|
||||||
|
*/
|
||||||
|
String wxid;
|
||||||
|
/**
|
||||||
|
* 从hook的消息中获取对应的字段内容
|
||||||
|
*/
|
||||||
|
String transcationId;
|
||||||
|
/**
|
||||||
|
* 从hook的消息中获取对应的字段内容。
|
||||||
|
*/
|
||||||
|
String transferId;
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.SendMsg;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过手机或者qq查找微信
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class FindWeChat implements SendMsg<FindWeChat> {
|
||||||
|
/**
|
||||||
|
* 通过 手机或qq查询信息
|
||||||
|
*/
|
||||||
|
String keyword;
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.SendMsg;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转发消息
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class ForwardMessages implements SendMsg<ForwardMessages> {
|
||||||
|
/**
|
||||||
|
* 消息接收人wxid
|
||||||
|
*/
|
||||||
|
String wxid;
|
||||||
|
/**
|
||||||
|
* 消息id
|
||||||
|
*/
|
||||||
|
String msgid;
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.SendMsg;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取群成员
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class GetGroupMembers implements SendMsg<GetGroupMembers> {
|
||||||
|
String chatRoomId;
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.SendMsg;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取群成员昵称
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class GetsTheNicknameOfAGroupMember implements SendMsg<GetsTheNicknameOfAGroupMember> {
|
||||||
|
/**
|
||||||
|
* 聊天室id
|
||||||
|
*/
|
||||||
|
String chatRoomId;
|
||||||
|
/**
|
||||||
|
* 成员id
|
||||||
|
*/
|
||||||
|
String memberId;
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.SendMsg;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加群成员
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class IncreaseGroupMembership implements SendMsg<IncreaseGroupMembership> {
|
||||||
|
/**
|
||||||
|
* 聊天室id
|
||||||
|
*/
|
||||||
|
String chatRoomId;
|
||||||
|
/**
|
||||||
|
* 成员id,以,分割
|
||||||
|
*/
|
||||||
|
String memberIds;
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.SendMsg;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开启hook
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class OpenHook implements SendMsg<OpenHook> {
|
||||||
|
String port;
|
||||||
|
String ip;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.SendMsg;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送at文本
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class SendAtText implements SendMsg<SendAtText> {
|
||||||
|
/**
|
||||||
|
* 聊天室id,群聊用
|
||||||
|
*/
|
||||||
|
String chatRoomId;
|
||||||
|
/**
|
||||||
|
* 群聊的时候用at多个用逗号隔开,@所有人则是<b>notify@all</b>
|
||||||
|
*/
|
||||||
|
String wxids;
|
||||||
|
|
||||||
|
String msg;
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.SendMsg;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送文件
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class SendFile implements SendMsg<SendFile> {
|
||||||
|
String wxid;
|
||||||
|
/**
|
||||||
|
* 发送文件路径
|
||||||
|
* "filePath": "C:/Users/123.txt"
|
||||||
|
*/
|
||||||
|
String filePath;
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.SendMsg;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送图片
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class SendImg implements SendMsg<SendImg> {
|
||||||
|
String wxid;
|
||||||
|
/**
|
||||||
|
* 发送图片接口
|
||||||
|
* "imagePath": "C:/Users/123.png"
|
||||||
|
*/
|
||||||
|
String imagePath;
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http请求参数
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class SendMsg {
|
||||||
|
/**
|
||||||
|
* wxid
|
||||||
|
*/
|
||||||
|
String wxid;
|
||||||
|
/**
|
||||||
|
* 消息内容
|
||||||
|
*/
|
||||||
|
String msg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天室id,群聊用
|
||||||
|
*/
|
||||||
|
String chatRoomId;
|
||||||
|
/**
|
||||||
|
* 成员id
|
||||||
|
*/
|
||||||
|
String memberId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群聊的时候用at多个用逗号隔开,@所有人则是<b>notify@all</b>
|
||||||
|
*/
|
||||||
|
String wxids;
|
||||||
|
/**
|
||||||
|
* 发送图片接口
|
||||||
|
* "imagePath": "C:/Users/123.png"
|
||||||
|
*/
|
||||||
|
String imagePath;
|
||||||
|
/**
|
||||||
|
* 发送文件路径
|
||||||
|
* "filePath": "C:/Users/123.txt"
|
||||||
|
*/
|
||||||
|
String filePath;
|
||||||
|
/**
|
||||||
|
* 通过 手机或qq查询信息
|
||||||
|
*/
|
||||||
|
String keyword;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.SendMsg;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送文本
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class SendText implements SendMsg<SendText> {
|
||||||
|
String wxid;
|
||||||
|
String msg;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.example.wxhk.model.request;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.SendMsg;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过好友请求
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class ThroughFriends implements SendMsg<ThroughFriends> {
|
||||||
|
/**
|
||||||
|
* 添加好友消息内容里的encryptusername
|
||||||
|
*/
|
||||||
|
String v3;
|
||||||
|
/**
|
||||||
|
* 添加好友消息内容里的ticket
|
||||||
|
*/
|
||||||
|
String v4;
|
||||||
|
/**
|
||||||
|
* 好友权限,0是无限制,1是不让他看我,2是不看他,3是1+2
|
||||||
|
*/
|
||||||
|
String permission;
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package com.example.wxhk.model.response;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.Resp;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 联系人列表
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/01
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class ContactList implements Resp {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* code : 1
|
||||||
|
* 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"}]
|
||||||
|
* result : OK
|
||||||
|
*/
|
||||||
|
|
||||||
|
private Integer code;
|
||||||
|
private String result;
|
||||||
|
private List<DataBean> data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public static class DataBean implements Serializable {
|
||||||
|
/**
|
||||||
|
* customAccount :
|
||||||
|
* delFlag : 0
|
||||||
|
* type : 1
|
||||||
|
* userName : 朋友推荐消息
|
||||||
|
* verifyFlag : 0
|
||||||
|
* wxid : fmessage
|
||||||
|
*/
|
||||||
|
|
||||||
|
private String customAccount;
|
||||||
|
private Integer delFlag;
|
||||||
|
private Integer type;
|
||||||
|
private String userName;
|
||||||
|
private Integer verifyFlag;
|
||||||
|
private String wxid;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.example.wxhk.model.response;
|
||||||
|
|
||||||
|
import com.example.wxhk.infe.Resp;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class GroupMembers implements Resp {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* code : 1
|
||||||
|
* data : {"admin":"wxid_gf1fogt5a0pq22","chatRoomId":"24964676359@chatroom","members":"wxid_gf1fogt5a0pq22^Gwxid_4yr8erik0uho22"}
|
||||||
|
* result : OK
|
||||||
|
*/
|
||||||
|
|
||||||
|
private Integer code;
|
||||||
|
private DataBean data;
|
||||||
|
private String result;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class DataBean implements Serializable {
|
||||||
|
/**
|
||||||
|
* admin : wxid_gf1fogt5a0pq22
|
||||||
|
* chatRoomId : 24964676359@chatroom
|
||||||
|
* members : wxid_gf1fogt5a0pq22^Gwxid_4yr8erik0uho22
|
||||||
|
*/
|
||||||
|
|
||||||
|
private String admin;
|
||||||
|
private String chatRoomId;
|
||||||
|
private String members;
|
||||||
|
}
|
||||||
|
}
|
250
java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java
Normal file
250
java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
package com.example.wxhk.msg;
|
||||||
|
|
||||||
|
import com.example.wxhk.constant.WxMsgType;
|
||||||
|
import com.example.wxhk.model.PrivateChatMsg;
|
||||||
|
import com.example.wxhk.model.dto.PayoutInformation;
|
||||||
|
import com.example.wxhk.server.WxSmgServer;
|
||||||
|
import com.example.wxhk.tcp.vertx.InitWeChat;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import org.dromara.hutool.core.util.XmlUtil;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WxMsgHandle {
|
||||||
|
public static final ConcurrentHashMap<Integer, Handle> map = new ConcurrentHashMap<>(32);
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
/**
|
||||||
|
* 文件传输助手
|
||||||
|
*/
|
||||||
|
public static final String FILEHELPER = "filehelper";
|
||||||
|
/**
|
||||||
|
* 收款码缓存 因为有2段信息,一段是交易id,里面可以解析出来源方,二段解析出金额
|
||||||
|
*/
|
||||||
|
public static ConcurrentHashMap<String, String> collection_code_caching = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
public static WxSmgServer wxSmgServer;
|
||||||
|
/**
|
||||||
|
* 看
|
||||||
|
*/
|
||||||
|
public static final ReentrantReadWriteLock LOOK = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setWxSmgServer(WxSmgServer wxSmgServer) {
|
||||||
|
WxMsgHandle.wxSmgServer = wxSmgServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
add(chatMsg -> {
|
||||||
|
wxSmgServer.私聊(chatMsg);
|
||||||
|
return null;
|
||||||
|
}, WxMsgType.私聊信息);
|
||||||
|
add(chatMsg -> {
|
||||||
|
if (FILEHELPER.equals(chatMsg.getFromUser())) {
|
||||||
|
wxSmgServer.文件助手(chatMsg);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}, WxMsgType.收到转账之后或者文件助手等信息);
|
||||||
|
add(chatMsg -> {
|
||||||
|
wxSmgServer.收到名片(chatMsg);
|
||||||
|
return 1;
|
||||||
|
}, WxMsgType.收到名片);
|
||||||
|
add(chatMsg -> {
|
||||||
|
wxSmgServer.收到好友请求(chatMsg);
|
||||||
|
return 1;
|
||||||
|
}, WxMsgType.好友请求);// 好友请求
|
||||||
|
add(chatMsg -> {
|
||||||
|
boolean f = 解析扫码支付第二段(chatMsg);
|
||||||
|
if (f) {
|
||||||
|
f = 解析收款信息1段(chatMsg);
|
||||||
|
if (f) {
|
||||||
|
解析收款信息2段(chatMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, WxMsgType.转账和收款);
|
||||||
|
add(chatMsg -> {
|
||||||
|
boolean f = 解析扫码支付第一段(chatMsg);
|
||||||
|
return null;
|
||||||
|
}, WxMsgType.扫码触发);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析扫码支付第一段,得到交易id和微信id
|
||||||
|
*
|
||||||
|
* @param chatMsg
|
||||||
|
* @return boolean 返回true 则继续解析,否则解析成功,不需要解析了
|
||||||
|
*/
|
||||||
|
public static boolean 解析扫码支付第一段(PrivateChatMsg chatMsg) {
|
||||||
|
try {
|
||||||
|
Document document = XmlUtil.parseXml(chatMsg.getContent());
|
||||||
|
Element documentElement = document.getDocumentElement();
|
||||||
|
String localName = documentElement.getLocalName();
|
||||||
|
if ("sysmsg".equals(localName)) {
|
||||||
|
String type = documentElement.getAttribute("type");
|
||||||
|
if ("paymsg".equals(type)) {
|
||||||
|
NodeList outtradeno = documentElement.getElementsByTagName("outtradeno");
|
||||||
|
if (outtradeno.getLength() > 0) {
|
||||||
|
String textContent = outtradeno.item(0).getTextContent();
|
||||||
|
String textContent1 = documentElement.getElementsByTagName("username").item(0).getTextContent();
|
||||||
|
collection_code_caching.put(textContent, textContent1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析扫码支付第二段
|
||||||
|
*
|
||||||
|
* @param chatMsg 聊天味精
|
||||||
|
* @return boolean true 则 继续解析, false则解析成功,不需要再解析了
|
||||||
|
*/
|
||||||
|
public static boolean 解析扫码支付第二段(PrivateChatMsg chatMsg) {
|
||||||
|
try {
|
||||||
|
Document document = XmlUtil.parseXml(chatMsg.getContent());
|
||||||
|
Element documentElement = document.getDocumentElement();
|
||||||
|
String localName = documentElement.getLocalName();
|
||||||
|
if ("msg".equals(localName)) {
|
||||||
|
NodeList outtradeno = documentElement.getElementsByTagName("weapp_path");
|
||||||
|
if (outtradeno.getLength() > 1) {
|
||||||
|
String textContent = outtradeno.item(1).getTextContent();
|
||||||
|
Set<Map.Entry<String, String>> entries = collection_code_caching.entrySet();
|
||||||
|
Iterator<Map.Entry<String, String>> iterator = entries.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Map.Entry<String, String> next = iterator.next();
|
||||||
|
if (textContent.contains(next.getKey())) {
|
||||||
|
// 得到了交易信息
|
||||||
|
NodeList word = documentElement.getElementsByTagName("word");
|
||||||
|
String monery = word.item(1).getTextContent();
|
||||||
|
String remark = word.item(3).getTextContent();
|
||||||
|
if (monery.startsWith("¥")) {
|
||||||
|
String substring = monery.substring(1);
|
||||||
|
BigDecimal decimal = new BigDecimal(substring);
|
||||||
|
log.info("扫码收款:{},付款人:{},付款备注:{}", decimal.stripTrailingZeros().toPlainString(), next.getValue(), remark);
|
||||||
|
wxSmgServer.扫码收款(new PayoutInformation(next.getValue(),decimal,remark));
|
||||||
|
iterator.remove();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean 解析收款信息2段(PrivateChatMsg chatMsg) {
|
||||||
|
try {
|
||||||
|
Document document = XmlUtil.parseXml(chatMsg.getContent());
|
||||||
|
Element documentElement = document.getDocumentElement();
|
||||||
|
String localName = documentElement.getLocalName();
|
||||||
|
if ("msg".equals(localName)) {
|
||||||
|
if (documentElement.getElementsByTagName("transcationid").getLength() > 0) {
|
||||||
|
String remark = documentElement.getElementsByTagName("pay_memo").item(0).getTextContent();
|
||||||
|
String monery = documentElement.getElementsByTagName("feedesc").item(0).getTextContent();
|
||||||
|
String receiver_username = documentElement.getElementsByTagName("receiver_username").item(0).getTextContent();
|
||||||
|
// 如果是机器人发出的,则跳过解析
|
||||||
|
if (InitWeChat.WXID_MAP.contains(receiver_username) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (monery.startsWith("¥")) {
|
||||||
|
String substring = monery.substring(1);
|
||||||
|
BigDecimal decimal = new BigDecimal(substring);
|
||||||
|
log.info("收款:{},付款人:{},付款备注:{}", decimal.stripTrailingZeros().toPlainString(), chatMsg.getFromUser(), remark);
|
||||||
|
wxSmgServer.收款之后(new PayoutInformation(chatMsg.getFromUser(), decimal, remark));
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析收款信息1段
|
||||||
|
* <b>会自动进行收款</b>
|
||||||
|
*
|
||||||
|
* @param chatMsg
|
||||||
|
* @return boolean true则 继续解析,false则不需要解析了
|
||||||
|
*/
|
||||||
|
public static boolean 解析收款信息1段(PrivateChatMsg chatMsg) {
|
||||||
|
try {
|
||||||
|
String content = chatMsg.getContent();
|
||||||
|
Document document = XmlUtil.parseXml(content);
|
||||||
|
NodeList paysubtype1 = document.getElementsByTagName("paysubtype");
|
||||||
|
if (paysubtype1.getLength() == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Node paysubtype = paysubtype1.item(0);
|
||||||
|
if ("1".equals(paysubtype.getTextContent().trim())) {
|
||||||
|
// 手机发出去的
|
||||||
|
String textContent = document.getElementsByTagName("receiver_username").item(0).getTextContent();
|
||||||
|
if (!InitWeChat.WXID_MAP.contains(textContent)) {
|
||||||
|
// 如果不是机器人收款,则认为不需要解析了,大概率是机器人自己发出去的
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String remark = document.getElementsByTagName("pay_memo").item(0).getTextContent();
|
||||||
|
String monery = document.getElementsByTagName("feedesc").item(0).getTextContent();
|
||||||
|
String receiver_username = document.getElementsByTagName("receiver_username").item(0).getTextContent();
|
||||||
|
if (monery.startsWith("¥")) {
|
||||||
|
String substring = monery.substring(1);
|
||||||
|
BigDecimal decimal = new BigDecimal(substring);
|
||||||
|
Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0);
|
||||||
|
Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0);
|
||||||
|
wxSmgServer.接到收款(new PayoutInformation(chatMsg.getFromUser(), decimal, remark, transcationid.getTextContent(), transferid.getTextContent()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void exec(PrivateChatMsg chatMsg) {
|
||||||
|
Handle handle = map.get(chatMsg.getType());
|
||||||
|
if (handle != null) {
|
||||||
|
handle.handle(chatMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Handle handle, WxMsgType... type) {
|
||||||
|
for (WxMsgType integer : type) {
|
||||||
|
map.put(integer.getType(), handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Handle {
|
||||||
|
Object handle(PrivateChatMsg chatMsg);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.example.wxhk.server;
|
||||||
|
|
||||||
|
import com.example.wxhk.model.PrivateChatMsg;
|
||||||
|
import com.example.wxhk.model.dto.PayoutInformation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信消息处理提取
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/06/06
|
||||||
|
*/
|
||||||
|
public interface WxSmgServer {
|
||||||
|
/**
|
||||||
|
* 接到收款
|
||||||
|
*
|
||||||
|
* @param payoutInformation 支付信息
|
||||||
|
*/
|
||||||
|
void 接到收款(PayoutInformation payoutInformation);
|
||||||
|
|
||||||
|
void 收款之后(PayoutInformation pay);
|
||||||
|
|
||||||
|
void 私聊(PrivateChatMsg chatMsg);
|
||||||
|
|
||||||
|
void 文件助手(PrivateChatMsg chatMsg);
|
||||||
|
|
||||||
|
void 收到名片(PrivateChatMsg chatMsg);
|
||||||
|
|
||||||
|
void 收到好友请求(PrivateChatMsg chatMsg);
|
||||||
|
|
||||||
|
void 扫码收款(PayoutInformation payoutInformation);
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package com.example.wxhk.server.impl;
|
||||||
|
|
||||||
|
import com.example.wxhk.model.PrivateChatMsg;
|
||||||
|
import com.example.wxhk.model.dto.PayoutInformation;
|
||||||
|
import com.example.wxhk.model.request.ConfirmThePayment;
|
||||||
|
import com.example.wxhk.util.HttpSendUtil;
|
||||||
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
|
import org.dromara.hutool.core.util.XmlUtil;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class WxSmgServerImpl implements com.example.wxhk.server.WxSmgServer {
|
||||||
|
|
||||||
|
protected static final Log log=Log.get();
|
||||||
|
|
||||||
|
public static final String FILEHELPER = "filehelper";
|
||||||
|
@Override
|
||||||
|
public void 接到收款(PayoutInformation payoutInformation) {
|
||||||
|
HttpSendUtil.确认收款(new ConfirmThePayment().setWxid(payoutInformation.receiverUsername()).setTranscationId(payoutInformation.transcationid()).setTransferId(payoutInformation.transferid()));
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void 收款之后(PayoutInformation pay) {
|
||||||
|
HttpSendUtil.发送文本(pay.receiverUsername(), StrUtil.format("收到款项:{},备注:{}", pay.decimal().stripTrailingZeros().toPlainString(), pay.remark()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void 私聊(PrivateChatMsg chatMsg) {
|
||||||
|
if (Objects.equals(chatMsg.getIsSendMsg(), 1) && Objects.equals(chatMsg.getIsSendByPhone(), 1)) {
|
||||||
|
log.info("手机端对:{}发出:{}", chatMsg.getFromUser(), chatMsg.getContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void 文件助手(PrivateChatMsg chatMsg) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void 收到名片(PrivateChatMsg chatMsg) {
|
||||||
|
if (FILEHELPER.equals(chatMsg.getFromUser())) {
|
||||||
|
Document document = XmlUtil.parseXml(chatMsg.getContent());
|
||||||
|
Element documentElement = document.getDocumentElement();
|
||||||
|
String username = documentElement.getAttribute("username");
|
||||||
|
if (StrUtil.isNotBlank(username)) {
|
||||||
|
HttpSendUtil.发送文本(username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void 收到好友请求(PrivateChatMsg chatMsg) {
|
||||||
|
HttpSendUtil.通过好友请求(chatMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void 扫码收款(PayoutInformation payoutInformation) {
|
||||||
|
HttpSendUtil.发送文本(payoutInformation.receiverUsername(), StrUtil.format("扫码收款:{},备注:{}", payoutInformation.decimal().stripTrailingZeros().toPlainString(), payoutInformation.remark()));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package com.example.wxhk.tcp.vertx;
|
||||||
|
|
||||||
|
import com.example.wxhk.model.PrivateChatMsg;
|
||||||
|
import com.example.wxhk.msg.WxMsgHandle;
|
||||||
|
import com.example.wxhk.util.HttpSendUtil;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import org.dromara.hutool.core.thread.NamedThreadFactory;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息处理
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/31
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class ArrHandle {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 线程处理消息队列,但是必须保证核心数大于2,其中必定要有一个线程可以单独处理交易队列信息
|
||||||
|
*/
|
||||||
|
public static final ThreadPoolExecutor sub = new ThreadPoolExecutor(4, 10, 30, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new NamedThreadFactory("sub", false));
|
||||||
|
public static final ThreadLocal<PrivateChatMsg> chatMsgThreadLocal = new InheritableThreadLocal<>();
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 得到当前正在处理的消息
|
||||||
|
*
|
||||||
|
* @return {@link PrivateChatMsg}
|
||||||
|
*/
|
||||||
|
public static PrivateChatMsg getPriMsg() {
|
||||||
|
return chatMsgThreadLocal.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void exec() {
|
||||||
|
for (int i = 0; i < sub.getCorePoolSize()-1; i++) {
|
||||||
|
sub.submit(() -> {
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
try {
|
||||||
|
JsonObject take = VertxTcp.LINKED_BLOCKING_QUEUE.take();
|
||||||
|
log.info("{}", take.encode());
|
||||||
|
PrivateChatMsg privateChatMsg = take.mapTo(PrivateChatMsg.class);
|
||||||
|
chatMsgThreadLocal.set(privateChatMsg);
|
||||||
|
if ("weixin".equals(privateChatMsg.getFromUser())) {
|
||||||
|
String s = HttpSendUtil.获取当前登陆微信id();
|
||||||
|
InitWeChat.WXID_MAP.add(s);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
WxMsgHandle.exec(privateChatMsg);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e);
|
||||||
|
}finally {
|
||||||
|
chatMsgThreadLocal.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.error("退出线程了");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sub.submit(() -> {
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
try {
|
||||||
|
JsonObject take = VertxTcp.LINKED_BLOCKING_QUEUE_MON.take();
|
||||||
|
log.info("{}", take.encode());
|
||||||
|
PrivateChatMsg privateChatMsg = take.mapTo(PrivateChatMsg.class);
|
||||||
|
chatMsgThreadLocal.set(privateChatMsg);
|
||||||
|
if ("weixin".equals(privateChatMsg.getFromUser())) {
|
||||||
|
String s = HttpSendUtil.获取当前登陆微信id();
|
||||||
|
InitWeChat.WXID_MAP.add(s);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
WxMsgHandle.exec(privateChatMsg);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e);
|
||||||
|
}finally {
|
||||||
|
chatMsgThreadLocal.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.error("退出线程了");
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,158 @@
|
|||||||
|
package com.example.wxhk.tcp.vertx;
|
||||||
|
|
||||||
|
import com.example.wxhk.util.HttpAsyncUtil;
|
||||||
|
import com.example.wxhk.util.HttpSyncUtil;
|
||||||
|
import io.vertx.core.impl.ConcurrentHashSet;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import org.dromara.hutool.core.io.file.FileUtil;
|
||||||
|
import org.dromara.hutool.core.net.NetUtil;
|
||||||
|
import org.dromara.hutool.core.text.StrUtil;
|
||||||
|
import org.dromara.hutool.core.thread.ThreadUtil;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
import org.dromara.hutool.setting.Setting;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信注入环境初始化和相关方法
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/16
|
||||||
|
*/
|
||||||
|
@Order(-1)
|
||||||
|
@Component
|
||||||
|
public class InitWeChat implements CommandLineRunner {
|
||||||
|
|
||||||
|
public final static Log log = Log.get();
|
||||||
|
public static final ConcurrentHashSet<String> WXID_MAP = new ConcurrentHashSet<>();
|
||||||
|
public static String wxPath;
|
||||||
|
public static Integer wxPort;
|
||||||
|
public static Integer vertxPort;
|
||||||
|
/**
|
||||||
|
* wxhelper.dll 所在路径
|
||||||
|
*/
|
||||||
|
public static File DLL_PATH;
|
||||||
|
|
||||||
|
public static void 注入dll(String wxPid) throws IOException {
|
||||||
|
String format = StrUtil.format("cmd /C c.exe -I {} -p {}\\wxhelper.dll -m {}", wxPid, DLL_PATH.getAbsolutePath(), wxPid);
|
||||||
|
Process exec = Runtime.getRuntime().exec(format, null, DLL_PATH);
|
||||||
|
log.info("注入结果:{}", new String(exec.getInputStream().readAllBytes(), "gbk"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static File 环境初始化() {
|
||||||
|
File target = new File(new File("").getAbsolutePath().split("\\\\")[0] + "\\exec\\");
|
||||||
|
try {
|
||||||
|
File wxPathFile = new File(wxPath);
|
||||||
|
File config = new File(wxPathFile.getParentFile(), "config.ini");
|
||||||
|
Setting setting = new Setting(config.getAbsolutePath());
|
||||||
|
setting.getGroupedMap().put("config", "port", String.valueOf(wxPort));
|
||||||
|
setting.store();
|
||||||
|
ClassPathResource classPathResource = new ClassPathResource("exec");
|
||||||
|
File file = classPathResource.getFile();
|
||||||
|
target.mkdir();
|
||||||
|
for (File listFile : file.listFiles()) {
|
||||||
|
FileUtil.copy(listFile, target, true);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e, "环境初始化失败,请检查");
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回最后一个微信的pid
|
||||||
|
*
|
||||||
|
* @return {@link String}
|
||||||
|
* @throws IOException ioexception
|
||||||
|
*/
|
||||||
|
public static String createWx() throws IOException {
|
||||||
|
Runtime.getRuntime().exec("cmd /C \"" + wxPath + "\"");
|
||||||
|
return getWxPid();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static String getWxPid() throws IOException {
|
||||||
|
String line = null;
|
||||||
|
try {
|
||||||
|
Process exec = Runtime.getRuntime().exec("cmd /C tasklist /FI \"IMAGENAME eq WeChat.exe\" ");
|
||||||
|
byte[] bytes = exec.getInputStream().readAllBytes();
|
||||||
|
line = new String(bytes, "gbk");
|
||||||
|
String[] split = line.split("\n");
|
||||||
|
if (!line.contains("WeChat.exe")) {
|
||||||
|
return createWx();
|
||||||
|
}
|
||||||
|
String[] split1 = split[split.length - 1].replaceAll("\\s{2,}", " ").split(" ");
|
||||||
|
return split1[1];
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("获取端口错误:{}", line);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Integer getWxPort() {
|
||||||
|
return wxPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value("${wx.port}")
|
||||||
|
public void setWxPort(Integer wxPort) {
|
||||||
|
InitWeChat.wxPort = wxPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getWxPath() {
|
||||||
|
return wxPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value("${wx.path}")
|
||||||
|
public void setWxPath(String wxPath) {
|
||||||
|
InitWeChat.wxPath = wxPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Integer getVertxPort() {
|
||||||
|
return vertxPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value("${vertx.port}")
|
||||||
|
public void setVertxPort(Integer vertxPort) {
|
||||||
|
InitWeChat.vertxPort = vertxPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(String... args) throws Exception {
|
||||||
|
//tasklist /FI "IMAGENAME eq WeChat.exe" /m
|
||||||
|
boolean usableLocalPort = NetUtil.isUsableLocalPort(wxPort);
|
||||||
|
if (usableLocalPort) {
|
||||||
|
DLL_PATH = 环境初始化();
|
||||||
|
String wxPid = getWxPid();
|
||||||
|
注入dll(wxPid);
|
||||||
|
}
|
||||||
|
ThreadUtil.execute(() -> {
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.检查微信登陆, new JsonObject());
|
||||||
|
if (exec.getInteger("code").equals(1)) {
|
||||||
|
JsonObject dl = HttpSyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject());
|
||||||
|
JsonObject jsonObject = dl.getJsonObject("data");
|
||||||
|
String wx = jsonObject.getString("wxid");
|
||||||
|
WXID_MAP.add(wx);
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("检测到微信登陆:{}", wx);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ThreadUtil.safeSleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
// FIXME: 2023/6/2 程序结束后关闭hook会偶尔出现微信闪退情况,暂时禁用
|
||||||
|
// Runtime.getRuntime().addShutdownHook(new Thread(HttpSendUtil::关闭hook));
|
||||||
|
//netstat -aon|findstr "端口号"
|
||||||
|
// c.exe -I 4568 -p D:\exec\wxhelper.dll -m 4568
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
package com.example.wxhk.tcp.vertx;
|
||||||
|
|
||||||
|
import com.example.wxhk.WxhkApplication;
|
||||||
|
import com.example.wxhk.constant.WxMsgType;
|
||||||
|
import com.example.wxhk.util.HttpAsyncUtil;
|
||||||
|
import io.vertx.core.AbstractVerticle;
|
||||||
|
import io.vertx.core.DeploymentOptions;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.Promise;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.core.net.NetServer;
|
||||||
|
import io.vertx.core.net.NetServerOptions;
|
||||||
|
import io.vertx.core.parsetools.JsonParser;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接受微信hook信息
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/26
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Order()
|
||||||
|
public class VertxTcp extends AbstractVerticle implements CommandLineRunner {
|
||||||
|
public final static LinkedBlockingQueue<JsonObject> LINKED_BLOCKING_QUEUE = new LinkedBlockingQueue<>();
|
||||||
|
/**
|
||||||
|
* 这个只保留交易相关的类型
|
||||||
|
*/
|
||||||
|
public final static LinkedBlockingQueue<JsonObject> LINKED_BLOCKING_QUEUE_MON = new LinkedBlockingQueue<>();
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
NetServer netServer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Promise<Void> startPromise) throws Exception {
|
||||||
|
netServer = vertx.createNetServer(new NetServerOptions()
|
||||||
|
.setPort(InitWeChat.getVertxPort())
|
||||||
|
.setIdleTimeout(0)
|
||||||
|
.setLogActivity(false)
|
||||||
|
);
|
||||||
|
netServer.connectHandler(socket -> {
|
||||||
|
JsonParser parser = JsonParser.newParser();
|
||||||
|
parser.objectValueMode();
|
||||||
|
parser.handler(event -> {
|
||||||
|
switch (event.type()) {
|
||||||
|
case START_OBJECT -> {
|
||||||
|
}
|
||||||
|
case END_OBJECT -> {
|
||||||
|
}
|
||||||
|
case START_ARRAY -> {
|
||||||
|
}
|
||||||
|
case END_ARRAY -> {
|
||||||
|
}
|
||||||
|
case VALUE -> {
|
||||||
|
JsonObject entries = event.objectValue();
|
||||||
|
|
||||||
|
if(Objects.equals(entries.getInteger("type"), WxMsgType.扫码触发.getType()) ||
|
||||||
|
Objects.equals(entries.getInteger("type"), WxMsgType.转账和收款.getType())){
|
||||||
|
LINKED_BLOCKING_QUEUE_MON.add(entries);
|
||||||
|
}else{
|
||||||
|
LINKED_BLOCKING_QUEUE.add(entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.handler(parser);
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<NetServer> listen = netServer.listen();
|
||||||
|
listen.onComplete(event -> {
|
||||||
|
boolean succeeded = event.succeeded();
|
||||||
|
if (succeeded) {
|
||||||
|
HttpAsyncUtil.exec(HttpAsyncUtil.Type.开启hook, new JsonObject().put("port", InitWeChat.getVertxPort().toString()).put("ip", "127.0.0.1"));
|
||||||
|
startPromise.complete();
|
||||||
|
} else {
|
||||||
|
startPromise.fail(event.cause());
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(String... args) throws Exception {
|
||||||
|
WxhkApplication.vertx.deployVerticle(this, new DeploymentOptions().setWorkerPoolSize(6));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package com.example.wxhk.util;
|
||||||
|
|
||||||
|
import com.example.wxhk.WxhkApplication;
|
||||||
|
import com.example.wxhk.tcp.vertx.InitWeChat;
|
||||||
|
import io.vertx.core.AsyncResult;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.Handler;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.client.HttpResponse;
|
||||||
|
import io.vertx.ext.web.client.WebClient;
|
||||||
|
import io.vertx.ext.web.client.WebClientOptions;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http异步请求
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/25
|
||||||
|
*/
|
||||||
|
public class HttpAsyncUtil {
|
||||||
|
public static final WebClient client = WebClient.create(WxhkApplication.vertx, new WebClientOptions().setDefaultHost("localhost").setDefaultPort(InitWeChat.wxPort)
|
||||||
|
.setConnectTimeout(10000).setMaxPoolSize(10).setPoolEventLoopSize(10));
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
|
||||||
|
public static Future<HttpResponse<Buffer>> exec(Type type, JsonObject object) {
|
||||||
|
return client.post(InitWeChat.wxPort, "localhost", "/api/?type=" + type.getType())
|
||||||
|
.sendJsonObject(object)
|
||||||
|
.onSuccess(event ->
|
||||||
|
{
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("type:{},{}", type.getType(), event.bodyAsJsonObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Future<HttpResponse<Buffer>> exec(Type type, JsonObject object, Handler<AsyncResult<HttpResponse<Buffer>>> handler) {
|
||||||
|
return client.post(InitWeChat.wxPort, "localhost", "/api/?type=" + type.getType())
|
||||||
|
.sendJsonObject(object)
|
||||||
|
.onComplete(handler)
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
检查微信登陆("0"),
|
||||||
|
获取登录信息("1"),
|
||||||
|
发送文本("2"),
|
||||||
|
发送at文本("3"),
|
||||||
|
发送图片("5"),
|
||||||
|
发送文件("6"),
|
||||||
|
开启hook("9"),
|
||||||
|
关闭hook("10"),
|
||||||
|
添加好友("20"),
|
||||||
|
通过好友申请("23"),
|
||||||
|
获取群成员("25"),
|
||||||
|
获取群成员昵称("26"),
|
||||||
|
删除群成员("27"),
|
||||||
|
确认收款("45"),
|
||||||
|
联系人列表("46"),
|
||||||
|
查询微信信息("55"),
|
||||||
|
|
||||||
|
|
||||||
|
;
|
||||||
|
String type;
|
||||||
|
|
||||||
|
Type(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,151 @@
|
|||||||
|
package com.example.wxhk.util;
|
||||||
|
|
||||||
|
import com.example.wxhk.model.PrivateChatMsg;
|
||||||
|
import com.example.wxhk.model.request.*;
|
||||||
|
import com.example.wxhk.model.response.ContactList;
|
||||||
|
import com.example.wxhk.model.response.GroupMembers;
|
||||||
|
import com.example.wxhk.tcp.vertx.ArrHandle;
|
||||||
|
import com.example.wxhk.tcp.vertx.InitWeChat;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import org.dromara.hutool.core.util.XmlUtil;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 常见方法
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/29
|
||||||
|
*/
|
||||||
|
public class HttpSendUtil {
|
||||||
|
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
|
||||||
|
public static JsonObject 通过好友请求(PrivateChatMsg msg) {
|
||||||
|
Document document = XmlUtil.parseXml(msg.getContent());
|
||||||
|
String encryptusername = document.getDocumentElement().getAttribute("encryptusername");
|
||||||
|
String ticket = document.getDocumentElement().getAttribute("ticket");
|
||||||
|
return HttpSyncUtil.exec(HttpAsyncUtil.Type.通过好友申请, new JsonObject().put("v3", encryptusername).put("v4", ticket).put("permission", "0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonObject 确认收款(PrivateChatMsg msg) {
|
||||||
|
try {
|
||||||
|
String content = msg.getContent();
|
||||||
|
Document document = XmlUtil.parseXml(content);
|
||||||
|
Node paysubtype = document.getElementsByTagName("paysubtype").item(0);
|
||||||
|
if ("1".equals(paysubtype.getTextContent().trim())) {
|
||||||
|
// 手机发出去的
|
||||||
|
String textContent = document.getElementsByTagName("receiver_username").item(0).getTextContent();
|
||||||
|
if (!InitWeChat.WXID_MAP.contains(textContent)) {
|
||||||
|
return new JsonObject().put("spick", true);
|
||||||
|
}
|
||||||
|
Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0);
|
||||||
|
Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0);
|
||||||
|
return HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, new JsonObject().put("wxid", msg.getFromUser())
|
||||||
|
.put("transcationId", transcationid.getTextContent())
|
||||||
|
.put("transferId", transferid.getTextContent()));
|
||||||
|
|
||||||
|
}
|
||||||
|
// 如果是确认接受收款,则跳过
|
||||||
|
return new JsonObject();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static JsonObject 发送文本(String wxid, String msg) {
|
||||||
|
return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送文本, JsonObject.mapFrom(new SendMsg().setMsg(msg).setWxid(wxid)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonObject 发送文本(String msg) {
|
||||||
|
return 发送文本(ArrHandle.getPriMsg().getFromUser(), msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonObject 发送at文本(String chatRoomId, String wxids, String msg) {
|
||||||
|
return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送at文本, JsonObject.mapFrom(new SendMsg().setMsg(msg).setWxids(wxids).setChatRoomId(chatRoomId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonObject 发送at文本(String wxids, String msg) {
|
||||||
|
return 发送at文本(ArrHandle.getPriMsg().getFromGroup(), wxids, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonObject 发送图片(String wxid, String msg) {
|
||||||
|
return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送图片, JsonObject.mapFrom(new SendMsg().setImagePath(msg).setWxid(wxid)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonObject 发送图片(String msg) {
|
||||||
|
return 发送图片(ArrHandle.getPriMsg().getFromUser(), msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonObject 发送文件(String wxid, String msg) {
|
||||||
|
return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送文件, JsonObject.mapFrom(new SendMsg().setFilePath(msg).setWxid(wxid)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonObject 发送文件(String msg) {
|
||||||
|
return 发送文件(ArrHandle.getPriMsg().getFromUser(), msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonObject 添加好友(AddFriends p) {
|
||||||
|
return HttpSyncUtil.exec(HttpAsyncUtil.Type.添加好友, p.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String 获取当前登陆微信id() {
|
||||||
|
JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject());
|
||||||
|
return exec.getJsonObject("data").getString("wxid");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ContactList 联系人列表(){
|
||||||
|
JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.联系人列表, new JsonObject());
|
||||||
|
return exec.mapTo(ContactList.class);
|
||||||
|
}
|
||||||
|
public static JsonObject 开启hook(OpenHook hook){
|
||||||
|
JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.开启hook,hook.toJson());
|
||||||
|
return exec;
|
||||||
|
}
|
||||||
|
public static JsonObject 关闭hook(){
|
||||||
|
JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.关闭hook,new JsonObject());
|
||||||
|
return exec;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GroupMembers 获取群成员(GetGroupMembers p){
|
||||||
|
return HttpSyncUtil.exec(HttpAsyncUtil.Type.获取群成员, p.toJson()).mapTo(GroupMembers.class);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static JsonObject 确认收款(ConfirmThePayment payment){
|
||||||
|
return HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, payment.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public static com.example.wxhk.infe.SendMsg of(HttpAsyncUtil.Type type) {
|
||||||
|
switch (type) {
|
||||||
|
|
||||||
|
case 检查微信登陆 -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
case 获取登录信息 -> {
|
||||||
|
}
|
||||||
|
case 发送文本 -> {
|
||||||
|
return new SendText();
|
||||||
|
}
|
||||||
|
case 发送at文本 -> {
|
||||||
|
return new SendAtText();
|
||||||
|
}
|
||||||
|
case 发送图片 -> {
|
||||||
|
return new SendImg();
|
||||||
|
}
|
||||||
|
case 发送文件 -> {
|
||||||
|
return new SendFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return new SendText();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.example.wxhk.util;
|
||||||
|
|
||||||
|
import com.example.wxhk.tcp.vertx.InitWeChat;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import org.dromara.hutool.http.client.ClientConfig;
|
||||||
|
import org.dromara.hutool.http.client.Request;
|
||||||
|
import org.dromara.hutool.http.client.engine.ClientEngine;
|
||||||
|
import org.dromara.hutool.http.client.engine.ClientEngineFactory;
|
||||||
|
import org.dromara.hutool.http.meta.Method;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http同步请求
|
||||||
|
*
|
||||||
|
* @author wt
|
||||||
|
* @date 2023/05/25
|
||||||
|
*/
|
||||||
|
public class HttpSyncUtil {
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
static final ClientEngine engine;
|
||||||
|
|
||||||
|
static {
|
||||||
|
ClientConfig clientConfig = ClientConfig.of()
|
||||||
|
.setTimeout(30 * 1000);
|
||||||
|
engine = ClientEngineFactory.createEngine(clientConfig);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonObject exec(HttpAsyncUtil.Type type, JsonObject obj) {
|
||||||
|
String post = engine.send(Request.of("http://localhost:" + InitWeChat.wxPort + "/api/?type=" + type.getType()).method(Method.POST).body(obj.encode())).bodyStr();
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("type:{},{}", type.getType(), post);
|
||||||
|
}
|
||||||
|
return new JsonObject(post);
|
||||||
|
}
|
||||||
|
}
|
4
java_client/src/main/resources/application.properties
Normal file
4
java_client/src/main/resources/application.properties
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
wx.path=D:\\Program Files (x86)\\Tencent\\WeChat\\[3.9.2.23]\\WeChat.exe
|
||||||
|
wx.port=19088
|
||||||
|
spring.profiles.active=local
|
||||||
|
vertx.port=8080
|
BIN
java_client/src/main/resources/exec/c.exe
Normal file
BIN
java_client/src/main/resources/exec/c.exe
Normal file
Binary file not shown.
BIN
java_client/src/main/resources/exec/wxhelper.dll
Normal file
BIN
java_client/src/main/resources/exec/wxhelper.dll
Normal file
Binary file not shown.
171
java_client/src/main/resources/logback-spring.xml
Normal file
171
java_client/src/main/resources/logback-spring.xml
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<!--日志格式应用spring boot默认的格式,也可以自己更改-->
|
||||||
|
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||||
|
|
||||||
|
<!--定义日志存放的位置,默认存放在项目启动的相对路径的目录-->
|
||||||
|
<springProperty scope="context" name="LOG_PATH" source="log.path" defaultValue="log"/>
|
||||||
|
<property name="withLineNumber_debug"
|
||||||
|
value="%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) [%t] %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
|
||||||
|
|
||||||
|
<property name="file_pattern"
|
||||||
|
value="%d{MM-dd HH:mm:ss.SSS} %-5level [${PID:- } %thread] %logger{50}#%method,%line : %msg%n"/>
|
||||||
|
|
||||||
|
<!-- ****************************************************************************************** -->
|
||||||
|
<!-- ****************************** 本地开发只在控制台打印日志 ************************************ -->
|
||||||
|
<!-- ****************************************************************************************** -->
|
||||||
|
<springProfile name="local">
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>${withLineNumber_debug}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 日志记录器,日期滚动记录 -->
|
||||||
|
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
|
||||||
|
<!-- 正在记录的日志文件的路径及文件名 -->
|
||||||
|
<file>log_error.log</file>
|
||||||
|
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||||
|
<maxFileSize>50MB</maxFileSize>
|
||||||
|
<maxHistory>100</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
|
||||||
|
<!-- 追加方式记录日志 -->
|
||||||
|
<append>true</append>
|
||||||
|
|
||||||
|
<!-- 日志文件的格式 -->
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>${file_pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
|
||||||
|
<!-- 此日志文件只记录error级别的 -->
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>error</level>
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
|
||||||
|
<!--默认所有的包以info-->
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<!--各个服务的包在本地执行的时候,打开debug模式-->
|
||||||
|
<logger name="com.example.wxhk" level="debug" additivity="false">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
<appender-ref ref="FILE_ERROR"/>
|
||||||
|
</logger>
|
||||||
|
<logger name="org.springframework" level="info" additivity="false">
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
</logger>
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
<!-- ********************************************************************************************** -->
|
||||||
|
<!-- **** 放到服务器上不管在什么环境都只在文件记录日志,控制台(catalina.out)打印logback捕获不到的日志 **** -->
|
||||||
|
<!-- ********************************************************************************************** -->
|
||||||
|
<springProfile name="!local">
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 日志记录器,日期滚动记录 -->
|
||||||
|
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
|
||||||
|
<!-- 正在记录的日志文件的路径及文件名 -->
|
||||||
|
<file>${LOG_PATH}/log_error.log</file>
|
||||||
|
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||||
|
<maxFileSize>50MB</maxFileSize>
|
||||||
|
<maxHistory>100</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
|
||||||
|
<!-- 追加方式记录日志 -->
|
||||||
|
<append>true</append>
|
||||||
|
|
||||||
|
<!-- 日志文件的格式 -->
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>${file_pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
|
||||||
|
<!-- 此日志文件只记录error级别的 -->
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>error</level>
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 日志记录器,日期滚动记录 -->
|
||||||
|
<appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<!-- 正在记录的日志文件的路径及文件名 -->
|
||||||
|
<file>${LOG_PATH}/log_total.log</file>
|
||||||
|
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/total/%d{yyyy-MM}/log_total-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||||
|
<maxFileSize>50MB</maxFileSize>
|
||||||
|
<maxHistory>100</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<!-- 追加方式记录日志 -->
|
||||||
|
<append>true</append>
|
||||||
|
<!-- 日志文件的格式 -->
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>${file_pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
<!-- 业务错误 -->
|
||||||
|
<appender name="business_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<!-- 正在记录的日志文件的路径及文件名 -->
|
||||||
|
<file>${LOG_PATH}/log_business.log</file>
|
||||||
|
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/business/%d{yyyy-MM}/log_business-%d{yyyy-MM-dd}.%i.log.gz
|
||||||
|
</fileNamePattern>
|
||||||
|
<maxFileSize>50MB</maxFileSize>
|
||||||
|
<maxHistory>100</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<!-- 追加方式记录日志 -->
|
||||||
|
<append>true</append>
|
||||||
|
<!-- 日志文件的格式 -->
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>${file_pattern}</pattern>
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="com.example.wxhk" level="info" additivity="false">
|
||||||
|
<appender-ref ref="business_log"/>
|
||||||
|
<appender-ref ref="FILE_ERROR"/>
|
||||||
|
</logger>
|
||||||
|
<logger name="p6spy" level="info" additivity="false">
|
||||||
|
<appender-ref ref="business_log"/>
|
||||||
|
</logger>
|
||||||
|
<logger name="org.springframework" level="warn"/>
|
||||||
|
|
||||||
|
<!--记录到文件时,记录两类一类是error日志,一个是所有日志-->
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="FILE_ERROR"/>
|
||||||
|
<appender-ref ref="FILE_ALL"/>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
|
|
||||||
|
|
10
java_client/src/main/resources/spy.properties
Normal file
10
java_client/src/main/resources/spy.properties
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
|
||||||
|
# 使用Slf4J记录sql
|
||||||
|
appender=com.p6spy.engine.spy.appender.Slf4JLogger
|
||||||
|
# 是否开启慢SQL记录
|
||||||
|
outagedetection=true
|
||||||
|
# 慢SQL记录标准,单位秒
|
||||||
|
outagedetectioninterval=2
|
||||||
|
#日期格式
|
||||||
|
dateformat=HH:mm:ss
|
||||||
|
customLogMessageFormat=%(executionTime)|%(category)|connection%(connectionId)|%(sqlSingleLine)
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.example.wxhk;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class WxhkApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.example.wxhk.tcp;
|
||||||
|
|
||||||
|
import com.example.wxhk.util.HttpAsyncUtil;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.client.HttpResponse;
|
||||||
|
import org.dromara.hutool.core.lang.Console;
|
||||||
|
import org.dromara.hutool.core.thread.ThreadUtil;
|
||||||
|
import org.dromara.hutool.log.Log;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class HttpAsyncUtilTest {
|
||||||
|
|
||||||
|
protected static final Log log = Log.get();
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void exec() {
|
||||||
|
Future<HttpResponse<Buffer>> exec = HttpAsyncUtil.exec(HttpAsyncUtil.Type.联系人列表, new JsonObject());
|
||||||
|
exec.onSuccess(event -> {
|
||||||
|
Console.log(event.bodyAsJsonObject());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void exec1() {
|
||||||
|
|
||||||
|
for(int i=0;i<10000;i++){
|
||||||
|
int finalI = i;
|
||||||
|
HttpAsyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject(), event -> {
|
||||||
|
if (event.succeeded()) {
|
||||||
|
log.info("i:{},{}", finalI,event.result().bodyAsJsonObject());
|
||||||
|
}else{
|
||||||
|
event.cause().printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
log.info("发出请求:{}",i);
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadUtil.sync(this);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void exec2() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
92
java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java
Normal file
92
java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,45 @@
|
|||||||
|
package com.example.wxhk.util;
|
||||||
|
|
||||||
|
import com.example.wxhk.model.request.GetGroupMembers;
|
||||||
|
import com.example.wxhk.model.response.ContactList;
|
||||||
|
import com.example.wxhk.model.response.GroupMembers;
|
||||||
|
import org.dromara.hutool.core.lang.Console;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class HttpSendUtilTest {
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void 获取当前登陆微信id() {
|
||||||
|
String s = HttpSendUtil.获取当前登陆微信id();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void 联系人列表() {
|
||||||
|
ContactList contactList = HttpSendUtil.联系人列表();
|
||||||
|
|
||||||
|
List<ContactList.DataBean> data = contactList.getData();
|
||||||
|
for (ContactList.DataBean datum : data) {
|
||||||
|
Console.log(datum.getWxid(),datum.getUserName());
|
||||||
|
}
|
||||||
|
Console.log(contactList);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void 开启hook() {
|
||||||
|
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void 关闭ook() {
|
||||||
|
HttpSendUtil.关闭hook();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void 获取群成员() {
|
||||||
|
GroupMembers 获取群成员 = HttpSendUtil.获取群成员(new GetGroupMembers().setChatRoomId("24964676359@chatroom"));
|
||||||
|
Console.log(获取群成员);
|
||||||
|
}
|
||||||
|
}
|
51
python/decrypt.py
Normal file
51
python/decrypt.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import ctypes
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
|
||||||
|
# pip install pycryptodome
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt(password, input_file, out_file):
|
||||||
|
password = bytes.fromhex(password.replace(' ', ''))
|
||||||
|
with open(input_file, 'rb') as (f):
|
||||||
|
blist = f.read()
|
||||||
|
print(len(blist))
|
||||||
|
salt = blist[:16]
|
||||||
|
key = hashlib.pbkdf2_hmac('sha1', password, salt, DEFAULT_ITER, KEY_SIZE)
|
||||||
|
first = blist[16:DEFAULT_PAGESIZE]
|
||||||
|
mac_salt = bytes([x ^ 58 for x in salt])
|
||||||
|
mac_key = hashlib.pbkdf2_hmac('sha1', key, mac_salt, 2, KEY_SIZE)
|
||||||
|
hash_mac = hmac.new(mac_key, digestmod='sha1')
|
||||||
|
hash_mac.update(first[:-32])
|
||||||
|
hash_mac.update(bytes(ctypes.c_int(1)))
|
||||||
|
if hash_mac.digest() == first[-32:-12]:
|
||||||
|
print('decrypt success')
|
||||||
|
else:
|
||||||
|
print('password error')
|
||||||
|
return
|
||||||
|
blist = [blist[i:i + DEFAULT_PAGESIZE] for i in range(DEFAULT_PAGESIZE, len(blist), DEFAULT_PAGESIZE)]
|
||||||
|
with open(out_file, 'wb') as (f):
|
||||||
|
f.write(SQLITE_FILE_HEADER)
|
||||||
|
t = AES.new(key, AES.MODE_CBC, first[-48:-32])
|
||||||
|
f.write(t.decrypt(first[:-48]))
|
||||||
|
f.write(first[-48:])
|
||||||
|
for i in blist:
|
||||||
|
t = AES.new(key, AES.MODE_CBC, i[-48:-32])
|
||||||
|
f.write(t.decrypt(i[:-48]))
|
||||||
|
f.write(i[-48:])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
password = '565735E30E474DA09250CB5AA047E3940FFA1C6F767C4263B13ABB512933DA49'
|
||||||
|
input_file = 'C:/var/Applet.db'
|
||||||
|
out_file = 'c:/var/out/Applet.db'
|
||||||
|
decrypt(password, input_file, out_file)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
SQLITE_FILE_HEADER = bytes('SQLite format 3', encoding='ASCII') + bytes(1)
|
||||||
|
KEY_SIZE = 32
|
||||||
|
DEFAULT_PAGESIZE = 4096
|
||||||
|
DEFAULT_ITER = 64000
|
||||||
|
main()
|
20
source/CMakeLists.txt
Normal file
20
source/CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.0.0)
|
||||||
|
project(ConsoleApplication VERSION 1.0.0)
|
||||||
|
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D '_UNICODE' /D 'UNICODE'")
|
||||||
|
|
||||||
|
file(GLOB INJECT_CPP_FILES ${PROJECT_SOURCE_DIR}/*.cc ${PROJECT_SOURCE_DIR}/*.cpp)
|
||||||
|
|
||||||
|
add_executable (ConsoleApplication ${INJECT_CPP_FILES})
|
||||||
|
|
||||||
|
SET_TARGET_PROPERTIES(ConsoleApplication PROPERTIES LINKER_LANGUAGE C
|
||||||
|
ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin
|
||||||
|
OUTPUT_NAME "ConsoleApplication"
|
||||||
|
PREFIX "")
|
||||||
|
|
@ -570,6 +570,38 @@ HMODULE GetDLLHandle(wchar_t* wDllName, DWORD dPid)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOL EnableDebugPrivilege()
|
||||||
|
{
|
||||||
|
HANDLE TokenHandle = NULL;
|
||||||
|
TOKEN_PRIVILEGES TokenPrivilege;
|
||||||
|
|
||||||
|
LUID uID;
|
||||||
|
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle)) {
|
||||||
|
if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &uID)) {
|
||||||
|
TokenPrivilege.PrivilegeCount = 1;
|
||||||
|
TokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||||
|
TokenPrivilege.Privileges[0].Luid = uID;
|
||||||
|
if (AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivilege, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
|
||||||
|
CloseHandle(TokenHandle);
|
||||||
|
TokenHandle = INVALID_HANDLE_VALUE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
CloseHandle(TokenHandle);
|
||||||
|
TokenHandle = INVALID_HANDLE_VALUE;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static unsigned char GetProcAddressAsmCode[] = {
|
static unsigned char GetProcAddressAsmCode[] = {
|
||||||
0x55, // push ebp;
|
0x55, // push ebp;
|
||||||
@ -614,44 +646,12 @@ LPVOID FillAsmCode(HANDLE handle) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL RemoteLibraryFunction(HANDLE hProcess, LPCSTR lpModuleName, LPCSTR lpProcName, LPVOID lpParameters, SIZE_T dwParamSize, PVOID* ppReturn)
|
|
||||||
{
|
|
||||||
LPVOID lpRemoteParams = NULL;
|
|
||||||
|
|
||||||
LPVOID lpFunctionAddress = GetProcAddress(GetModuleHandleA(lpModuleName), lpProcName);
|
|
||||||
if (!lpFunctionAddress) lpFunctionAddress = GetProcAddress(LoadLibraryA(lpModuleName), lpProcName);
|
|
||||||
if (!lpFunctionAddress) goto ErrorHandler;
|
|
||||||
|
|
||||||
if (lpParameters)
|
|
||||||
{
|
|
||||||
lpRemoteParams = VirtualAllocEx(hProcess, NULL, dwParamSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
|
||||||
if (!lpRemoteParams) goto ErrorHandler;
|
|
||||||
|
|
||||||
SIZE_T dwBytesWritten = 0;
|
|
||||||
BOOL result = WriteProcessMemory(hProcess, lpRemoteParams, lpParameters, dwParamSize, &dwBytesWritten);
|
|
||||||
if (!result || dwBytesWritten < 1) goto ErrorHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpFunctionAddress, lpRemoteParams, NULL, NULL);
|
|
||||||
if (!hThread) goto ErrorHandler;
|
|
||||||
|
|
||||||
DWORD dwOut = 0;
|
|
||||||
while (GetExitCodeThread(hThread, &dwOut)) {
|
|
||||||
if (dwOut != STILL_ACTIVE) {
|
|
||||||
*ppReturn = (PVOID)dwOut;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
|
|
||||||
ErrorHandler:
|
|
||||||
if (lpRemoteParams) VirtualFreeEx(hProcess, lpRemoteParams, dwParamSize, MEM_RELEASE);
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
int InjectDllAndStartHttp(wchar_t* szPName, wchar_t* szDllPath, DWORD port)
|
int InjectDllAndStartHttp(wchar_t* szPName, wchar_t* szDllPath, DWORD port)
|
||||||
{
|
{
|
||||||
|
if(!EnableDebugPrivilege()){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
int result = 0;
|
int result = 0;
|
||||||
HANDLE hRemoteThread = NULL;
|
HANDLE hRemoteThread = NULL;
|
||||||
LPTHREAD_START_ROUTINE lpSysLibAddr = NULL;
|
LPTHREAD_START_ROUTINE lpSysLibAddr = NULL;
|
||||||
@ -660,9 +660,9 @@ int InjectDllAndStartHttp(wchar_t* szPName, wchar_t* szDllPath, DWORD port)
|
|||||||
HANDLE hProcess;
|
HANDLE hProcess;
|
||||||
unsigned int dwPid;
|
unsigned int dwPid;
|
||||||
size_t ulDllLength;
|
size_t ulDllLength;
|
||||||
wchar_t* dllName = L"wxhelper.dll";
|
wchar_t* dllName = (wchar_t*)L"wxhelper.dll";
|
||||||
size_t dllNameLen = wcslen(dllName) * 2 + 2;
|
size_t dllNameLen = wcslen(dllName) * 2 + 2;
|
||||||
char* funcName = "http_start";
|
char* funcName = (char* )"http_start";
|
||||||
size_t funcNameLen = strlen(funcName) + 1;
|
size_t funcNameLen = strlen(funcName) + 1;
|
||||||
|
|
||||||
HANDLE hStartHttp = NULL;
|
HANDLE hStartHttp = NULL;
|
||||||
@ -771,8 +771,133 @@ error:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int InjectDllAndStartHttpByPid(unsigned int pid, wchar_t* szDllPath, DWORD port)
|
||||||
|
{
|
||||||
|
if(!EnableDebugPrivilege()){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int result = 0;
|
||||||
|
HANDLE hRemoteThread = NULL;
|
||||||
|
LPTHREAD_START_ROUTINE lpSysLibAddr = NULL;
|
||||||
|
HINSTANCE__* hKernelModule = NULL;
|
||||||
|
LPVOID lpRemoteDllBase = NULL;
|
||||||
|
HANDLE hProcess;
|
||||||
|
size_t ulDllLength;
|
||||||
|
wchar_t* dllName = (wchar_t*)L"wxhelper.dll";
|
||||||
|
size_t dllNameLen = wcslen(dllName) * 2 + 2;
|
||||||
|
char* funcName = (char* )"http_start";
|
||||||
|
size_t funcNameLen = strlen(funcName) + 1;
|
||||||
|
|
||||||
|
HANDLE hStartHttp = NULL;
|
||||||
|
LPVOID portAddr = NULL;
|
||||||
|
HANDLE getProcThread = NULL;
|
||||||
|
|
||||||
|
LPVOID paramsAddr = NULL;
|
||||||
|
LPVOID param1Addr = NULL;
|
||||||
|
LPVOID param2Addr = NULL;
|
||||||
|
LPVOID GetProcFuncAddr = NULL;
|
||||||
|
|
||||||
|
DWORD params[2] = { 0 };
|
||||||
|
|
||||||
|
ulDllLength = (wcslen(szDllPath) + 1) * sizeof(wchar_t);
|
||||||
|
hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
|
||||||
|
if (!hProcess) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
lpRemoteDllBase = VirtualAllocEx(hProcess, NULL, ulDllLength, MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
if (lpRemoteDllBase)
|
||||||
|
{
|
||||||
|
if (WriteProcessMemory(hProcess, lpRemoteDllBase, szDllPath, ulDllLength, NULL)
|
||||||
|
&& (hKernelModule = GetModuleHandleW(L"kernel32.dll")) != 0
|
||||||
|
&& (lpSysLibAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernelModule, "LoadLibraryW")) != 0
|
||||||
|
&& (hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, lpSysLibAddr, lpRemoteDllBase, 0, NULL)) != 0)
|
||||||
|
{
|
||||||
|
WaitForSingleObject(hRemoteThread, INFINITE);
|
||||||
|
GetProcFuncAddr = FillAsmCode(hProcess);
|
||||||
|
param1Addr = VirtualAllocEx(hProcess, NULL, dllNameLen, MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
if (param1Addr) {
|
||||||
|
SIZE_T dwWriteSize;
|
||||||
|
BOOL bRet = WriteProcessMemory(hProcess, (LPVOID)param1Addr, dllName, dllNameLen, &dwWriteSize);
|
||||||
|
if (!bRet) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
param2Addr = VirtualAllocEx(hProcess, NULL, funcNameLen, MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
if (param2Addr) {
|
||||||
|
SIZE_T dwWriteSize;
|
||||||
|
BOOL bRet = WriteProcessMemory(hProcess, (LPVOID)param2Addr, funcName, funcNameLen, &dwWriteSize);
|
||||||
|
if (!bRet) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params[0] = (DWORD)param1Addr;
|
||||||
|
params[1] = (DWORD)param2Addr;
|
||||||
|
|
||||||
|
paramsAddr = VirtualAllocEx(hProcess, NULL, sizeof(params), MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
if (paramsAddr) {
|
||||||
|
SIZE_T dwWriteSize;
|
||||||
|
BOOL bRet = WriteProcessMemory(hProcess, (LPVOID)paramsAddr, ¶ms[0], sizeof(params), &dwWriteSize);
|
||||||
|
if (!bRet) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD dwRet = 0;
|
||||||
|
getProcThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcFuncAddr, paramsAddr, 0, NULL);
|
||||||
|
|
||||||
|
if (getProcThread)
|
||||||
|
{
|
||||||
|
WaitForSingleObject(getProcThread, INFINITE);
|
||||||
|
GetExitCodeThread(getProcThread, &dwRet);
|
||||||
|
if (dwRet) {
|
||||||
|
hStartHttp = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)dwRet, (LPVOID)port, 0, NULL);
|
||||||
|
WaitForSingleObject(hStartHttp, INFINITE);
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error:
|
||||||
|
if (hRemoteThread) {
|
||||||
|
CloseHandle(hRemoteThread);
|
||||||
|
}
|
||||||
|
if (getProcThread) {
|
||||||
|
CloseHandle(getProcThread);
|
||||||
|
}
|
||||||
|
if (hStartHttp) {
|
||||||
|
CloseHandle(hStartHttp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lpRemoteDllBase) {
|
||||||
|
VirtualFreeEx(hProcess, lpRemoteDllBase, ulDllLength, MEM_DECOMMIT | MEM_RELEASE);
|
||||||
|
}
|
||||||
|
if (param1Addr) {
|
||||||
|
VirtualFreeEx(hProcess, param1Addr, dllNameLen, MEM_DECOMMIT | MEM_RELEASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param2Addr) {
|
||||||
|
VirtualFreeEx(hProcess, param1Addr, funcNameLen, MEM_DECOMMIT | MEM_RELEASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paramsAddr) {
|
||||||
|
VirtualFreeEx(hProcess, param1Addr, sizeof(params), MEM_DECOMMIT | MEM_RELEASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetProcFuncAddr) {
|
||||||
|
VirtualFreeEx(hProcess, GetProcFuncAddr, sizeof(GetProcAddressAsmCode), MEM_DECOMMIT | MEM_RELEASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
int InjectDll(wchar_t* szPName, wchar_t* szDllPath)
|
int InjectDll(wchar_t* szPName, wchar_t* szDllPath)
|
||||||
{
|
{
|
||||||
|
if(!EnableDebugPrivilege()){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
int result = 0;
|
int result = 0;
|
||||||
HANDLE hRemoteThread;
|
HANDLE hRemoteThread;
|
||||||
LPTHREAD_START_ROUTINE lpSysLibAddr;
|
LPTHREAD_START_ROUTINE lpSysLibAddr;
|
||||||
@ -802,6 +927,61 @@ int InjectDll(wchar_t* szPName, wchar_t* szDllPath)
|
|||||||
CloseHandle(hRemoteThread);
|
CloseHandle(hRemoteThread);
|
||||||
CloseHandle(hProcess);
|
CloseHandle(hProcess);
|
||||||
OutputDebugStringA("[DBG] dll inject success");
|
OutputDebugStringA("[DBG] dll inject success");
|
||||||
|
printf("dll inject success");
|
||||||
|
printf("dll path : %s ", szDllPath);
|
||||||
|
printf("dll path : %d ", dwPid);
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
VirtualFreeEx(hProcess, lpRemoteDllBase, ulDllLength, MEM_DECOMMIT | MEM_RELEASE);
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int InjectDllByPid(unsigned int pid, wchar_t* szDllPath)
|
||||||
|
{
|
||||||
|
if(!EnableDebugPrivilege()){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int result = 0;
|
||||||
|
HANDLE hRemoteThread;
|
||||||
|
LPTHREAD_START_ROUTINE lpSysLibAddr;
|
||||||
|
HINSTANCE__* hKernelModule;
|
||||||
|
LPVOID lpRemoteDllBase;
|
||||||
|
HANDLE hProcess;
|
||||||
|
size_t ulDllLength;
|
||||||
|
|
||||||
|
ulDllLength = (wcslen(szDllPath) + 1) * sizeof(wchar_t);
|
||||||
|
hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
|
||||||
|
if (!hProcess) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lpRemoteDllBase = VirtualAllocEx(hProcess, NULL, ulDllLength, MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
if (lpRemoteDllBase)
|
||||||
|
{
|
||||||
|
if (WriteProcessMemory(hProcess, lpRemoteDllBase, szDllPath, ulDllLength, NULL)
|
||||||
|
&& (hKernelModule = GetModuleHandleW(L"kernel32.dll")) != 0
|
||||||
|
&& (lpSysLibAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernelModule, "LoadLibraryW")) != 0
|
||||||
|
&& (hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, lpSysLibAddr, lpRemoteDllBase, 0, NULL)) != 0)
|
||||||
|
{
|
||||||
|
WaitForSingleObject(hRemoteThread, INFINITE);
|
||||||
|
VirtualFreeEx(hProcess, lpRemoteDllBase, ulDllLength, MEM_DECOMMIT | MEM_RELEASE);
|
||||||
|
CloseHandle(hRemoteThread);
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
OutputDebugStringA("[DBG] dll inject success");
|
||||||
|
printf("dll inject success");
|
||||||
|
printf("dll path : %s ", szDllPath);
|
||||||
|
printf("pid : %d ", pid);
|
||||||
result = 1;
|
result = 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -872,8 +1052,9 @@ int main(int argc, char** argv)
|
|||||||
int port = 0;
|
int port = 0;
|
||||||
|
|
||||||
ULONG pid = 0;
|
ULONG pid = 0;
|
||||||
|
unsigned int injectPid =0;
|
||||||
|
|
||||||
while ((param = getopt(argc, argv, "i:p:u:d:m:P:h")) != -1)
|
while ((param = getopt(argc, argv, "i:p:u:d:m:P:I:h")) != -1)
|
||||||
{
|
{
|
||||||
switch (param)
|
switch (param)
|
||||||
{
|
{
|
||||||
@ -910,6 +1091,9 @@ int main(int argc, char** argv)
|
|||||||
case 'P':
|
case 'P':
|
||||||
port = std::atoi(optarg);
|
port = std::atoi(optarg);
|
||||||
break;
|
break;
|
||||||
|
case 'I':
|
||||||
|
injectPid = std::atoi(optarg);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
abort();
|
abort();
|
||||||
break;
|
break;
|
||||||
@ -917,8 +1101,26 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pid) {
|
if (pid) {
|
||||||
FindHandles(pid, "_WeChat_App_Instance_Identity_Mutex_Name", TRUE, TRUE);
|
FindHandles(pid, (LPSTR)"_WeChat_App_Instance_Identity_Mutex_Name", TRUE, TRUE);
|
||||||
}
|
}
|
||||||
|
if (injectPid != 0 && cDllPath[0] != 0)
|
||||||
|
{
|
||||||
|
if(cDllPath[0] != '\0')
|
||||||
|
{
|
||||||
|
if (port == 0) {
|
||||||
|
std::wstring wsPath = Utf8ToUnicode(cDllPath);
|
||||||
|
int ret = InjectDllByPid(injectPid, (wchar_t*)wsPath.c_str());
|
||||||
|
printf(" 注入结果:%i \n", ret);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::wstring wsPath = Utf8ToUnicode(cDllPath);
|
||||||
|
int ret = InjectDllAndStartHttpByPid(injectPid, (wchar_t*)wsPath.c_str(), port);
|
||||||
|
printf(" 注入结果:%i \n", ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (cInjectprogram[0] != 0 && cDllPath[0] != 0)
|
if (cInjectprogram[0] != 0 && cDllPath[0] != 0)
|
||||||
{
|
{
|
BIN
tool/injector/injector.dll
Normal file
BIN
tool/injector/injector.dll
Normal file
Binary file not shown.
@ -1 +1 @@
|
|||||||
## 可以使用3.9.0.28 分支下的注入工具,或者自己编译一下 source目录下的注入程序。
|
## 可以使用对应分支下的注入工具,或者自己编译一下 source目录下的注入程序。
|
||||||
|
8
weChatHook-java/.idea/.gitignore
vendored
Normal file
8
weChatHook-java/.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
13
weChatHook-java/.idea/compiler.xml
Normal file
13
weChatHook-java/.idea/compiler.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<annotationProcessing>
|
||||||
|
<profile name="Maven default annotation processors profile" enabled="true">
|
||||||
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
|
<outputRelativeToContentRoot value="true" />
|
||||||
|
<module name="weChatHook-java" />
|
||||||
|
</profile>
|
||||||
|
</annotationProcessing>
|
||||||
|
</component>
|
||||||
|
</project>
|
36
weChatHook-java/.idea/inspectionProfiles/Project_Default.xml
Normal file
36
weChatHook-java/.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="TOP_LEVEL_CLASS_OPTIONS">
|
||||||
|
<value>
|
||||||
|
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||||
|
<option name="REQUIRED_TAGS" value="" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="INNER_CLASS_OPTIONS">
|
||||||
|
<value>
|
||||||
|
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||||
|
<option name="REQUIRED_TAGS" value="" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="METHOD_OPTIONS">
|
||||||
|
<value>
|
||||||
|
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||||
|
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="FIELD_OPTIONS">
|
||||||
|
<value>
|
||||||
|
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
|
||||||
|
<option name="REQUIRED_TAGS" value="" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="IGNORE_DEPRECATED" value="false" />
|
||||||
|
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
|
||||||
|
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
|
||||||
|
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
|
||||||
|
<option name="myAdditionalJavadocTags" value="date" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
20
weChatHook-java/.idea/jarRepositories.xml
Normal file
20
weChatHook-java/.idea/jarRepositories.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Central Repository" />
|
||||||
|
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
14
weChatHook-java/.idea/misc.xml
Normal file
14
weChatHook-java/.idea/misc.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
29
weChatHook-java/README.md
Normal file
29
weChatHook-java/README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#maven打包
|
||||||
|
```aidl
|
||||||
|
进入weChatHook-java项目根目录执行如下,命令进行打包
|
||||||
|
|
||||||
|
mvn package
|
||||||
|
|
||||||
|
打包完成后在target目录下找到
|
||||||
|
weChatHook-java-1.0-jar-with-dependencies.jar
|
||||||
|
文件就可以直接启动了
|
||||||
|
```
|
||||||
|
#启动命令
|
||||||
|
####命令参数说明
|
||||||
|
###port:监听的端口 默认端口19077
|
||||||
|
###hookApi:消息转发的接口 为空不转发
|
||||||
|
```aidl
|
||||||
|
java -jar .\weChatHook-java-1.0-jar-with-dependencies.jar --port=9999 --hookApi=http://localhost:29099/api/demo/msg
|
||||||
|
```
|
||||||
|
#java接收hook消息示例
|
||||||
|
```aidl
|
||||||
|
@RequestMapping("/api/demo")
|
||||||
|
public class DemoController {
|
||||||
|
@PostMapping("/msg")
|
||||||
|
public void getMsg(String msg){
|
||||||
|
JSONObject jsonObject = JSON.parseObject(msg);
|
||||||
|
jsonObject.forEach((k,v)->{
|
||||||
|
System.out.println(k+":"+v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
77
weChatHook-java/pom.xml
Normal file
77
weChatHook-java/pom.xml
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>org.example</groupId>
|
||||||
|
<artifactId>weChatHook-java</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<!-- jsoup 依赖-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
<version>1.14.3</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- fastjson 阿里依赖-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>1.2.83</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- lombok 依赖-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.24</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- netty 依赖-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-all</artifactId>
|
||||||
|
<version>4.1.51.Final</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.13.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<addClasspath>true</addClasspath>
|
||||||
|
<mainClass>com.example.service.WeChatHookNettyServer</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>make-assembly</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,619 @@
|
|||||||
|
package com.example.client;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.jsoup.Connection;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @PACKAGE_NAME: com.example.client
|
||||||
|
* @NAME: WeChatHookClient
|
||||||
|
* @AUTHOR: wxs
|
||||||
|
* @DATE: 2023/5/31 15:08
|
||||||
|
* @PROJECT_NAME: WeChatHook-java
|
||||||
|
**/
|
||||||
|
public class WeChatHookClient {
|
||||||
|
|
||||||
|
private static final String apiPath = "http://127.0.0.1:19088/api/";
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(check_login());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否登录
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject check_login() {
|
||||||
|
String url = apiPath + "?type=0";
|
||||||
|
JSONObject response = post(url, null);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录用户信息
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject user_info() {
|
||||||
|
String url = apiPath + "?type=8";
|
||||||
|
JSONObject response = post(url, null);
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送文本
|
||||||
|
*
|
||||||
|
* @param wxid
|
||||||
|
* @param msg
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject send_text(String wxid, String msg) {
|
||||||
|
String url = apiPath + "?type=2";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("wxid", wxid);
|
||||||
|
map.put("msg", msg);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送@消息
|
||||||
|
*
|
||||||
|
* @param chatRoomId
|
||||||
|
* @param wxids notify@all
|
||||||
|
* @param msg
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject send_at(String chatRoomId, String wxids, String msg) {
|
||||||
|
String url = apiPath + "?type=3";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("chatRoomId", chatRoomId);
|
||||||
|
map.put("wxids", wxids);
|
||||||
|
map.put("msg", msg);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送图片
|
||||||
|
*
|
||||||
|
* @param wxid
|
||||||
|
* @param imagePath C:/Users/ww/Downloads/素材图片 (4).jpg
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject send_img(String wxid, String imagePath) {
|
||||||
|
String url = apiPath + "?type=5";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("wxid", wxid);
|
||||||
|
map.put("imagePath", imagePath);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送文件
|
||||||
|
*
|
||||||
|
* @param wxid
|
||||||
|
* @param filePath C:/test.txt
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject send_file(String wxid, String filePath) {
|
||||||
|
String url = apiPath + "?type=6";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("wxid", wxid);
|
||||||
|
map.put("filePath", filePath);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hook 消息
|
||||||
|
*
|
||||||
|
* @param ip
|
||||||
|
* @param port
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject hook_msg(String ip, String port) {
|
||||||
|
String url = apiPath + "?type=9";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("ip", ip);
|
||||||
|
map.put("port", port);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消消息hook
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject unhook_msg() {
|
||||||
|
String url = apiPath + "?type=10";
|
||||||
|
JSONObject response = post(url, null);
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hook 图片
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject hook_img(String imgDir) {
|
||||||
|
String url = apiPath + "?type=11";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("imgDir", imgDir);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消hook 图片
|
||||||
|
*
|
||||||
|
* @param imgDir C:\img
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject unhook_img(String imgDir) {
|
||||||
|
String url = apiPath + "?type=12";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("imgDir", imgDir);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hook 语音
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject hook_voice(Long msgId) {
|
||||||
|
String url = apiPath + "?type=56";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("msgId", msgId);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消hook 语音
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject unhook_voice() {
|
||||||
|
String url = apiPath + "?type=14";
|
||||||
|
JSONObject response = post(url, null);
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除好友
|
||||||
|
*
|
||||||
|
* @param wxid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject del_friend(String wxid) {
|
||||||
|
String url = apiPath + "?type=17";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("wxid", wxid);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络搜素用户
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject search_friend(String keyword) {
|
||||||
|
String url = apiPath + "?type=19";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("keyword", keyword);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加好友
|
||||||
|
*
|
||||||
|
* @param wxid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject add_friend(String wxid) {
|
||||||
|
String url = apiPath + "?type=20";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("wxid", wxid);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群成员
|
||||||
|
*
|
||||||
|
* @param chatRoomId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject fetch_chat_room_members(String chatRoomId) {
|
||||||
|
String url = apiPath + "?type=25";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("chatRoomId", chatRoomId);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群成员昵称
|
||||||
|
*
|
||||||
|
* @param chatRoomId
|
||||||
|
* @param memberId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject get_member_nickname(String chatRoomId, String memberId) {
|
||||||
|
String url = apiPath + "?type=26";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("chatRoomId", chatRoomId);
|
||||||
|
map.put("memberId", memberId);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除群成员
|
||||||
|
*
|
||||||
|
* @param chatRoomId
|
||||||
|
* @param memberIds
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject del_member(String chatRoomId, String memberIds) {
|
||||||
|
String url = apiPath + "?type=27";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("chatRoomId", chatRoomId);
|
||||||
|
map.put("memberIds", memberIds);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加群成员
|
||||||
|
*
|
||||||
|
* @param chatRoomId
|
||||||
|
* @param memberIds
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject add_member(String chatRoomId, String memberIds) {
|
||||||
|
String url = apiPath + "?type=28";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("chatRoomId", chatRoomId);
|
||||||
|
map.put("memberIds", memberIds);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改群昵称
|
||||||
|
*
|
||||||
|
* @param chatRoomId
|
||||||
|
* @param wxid
|
||||||
|
* @param nickName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject modify_room_name(String chatRoomId, String wxid, String nickName) {
|
||||||
|
String url = apiPath + "?type=31";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("chatRoomId", chatRoomId);
|
||||||
|
map.put("wxid", wxid);
|
||||||
|
map.put("nickName", nickName);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取sqlite3的操作句柄
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject get_db_handlers() {
|
||||||
|
String url = apiPath + "?type=32";
|
||||||
|
JSONObject response = post(url, null);
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询数据库
|
||||||
|
*
|
||||||
|
* @param dbHandle
|
||||||
|
* @param sql
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject query_db_by_sql(String dbHandle, String sql) {
|
||||||
|
String url = apiPath + "?type=34";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("dbHandle", dbHandle);
|
||||||
|
map.put("sql", sql);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hook 日志
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject hook_log() {
|
||||||
|
String url = apiPath + "?type=36";
|
||||||
|
JSONObject response = post(url, null);
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消hook日志
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject unhook_log() {
|
||||||
|
String url = apiPath + "?type=37";
|
||||||
|
JSONObject response = post(url, null);
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转发消息
|
||||||
|
*
|
||||||
|
* @param wxid
|
||||||
|
* @param msgid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject forward(String wxid, String msgid) {
|
||||||
|
String url = apiPath + "?type=40";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("wxid", wxid);
|
||||||
|
map.put("msgid", msgid);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject logout() {
|
||||||
|
String url = apiPath + "?type=44";
|
||||||
|
JSONObject response = post(url, null);
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认收款
|
||||||
|
*
|
||||||
|
* @param wxid
|
||||||
|
* @param transcationId
|
||||||
|
* @param transferId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject confirm_receipt(String wxid, String transcationId, String transferId) {
|
||||||
|
String url = apiPath + "?type=45";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("wxid", wxid);
|
||||||
|
map.put("transcationId", transcationId);
|
||||||
|
map.put("transferId", transferId);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 好友列表
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject contact_list() {
|
||||||
|
String url = apiPath + "?type=46";
|
||||||
|
JSONObject response = post(url, null);
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群详情
|
||||||
|
*
|
||||||
|
* @param chatRoomId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject room_detail(String chatRoomId) {
|
||||||
|
String url = apiPath + "?type=47";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("chatRoomId", chatRoomId);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ocr提取文字
|
||||||
|
*
|
||||||
|
* @param imagePath
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject ocr(String imagePath) {
|
||||||
|
String url = apiPath + "?type=49";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("imagePath", imagePath);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拍一拍
|
||||||
|
*
|
||||||
|
* @param chatRoomId
|
||||||
|
* @param wxid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject pat(String chatRoomId, String wxid) {
|
||||||
|
String url = apiPath + "?type=50";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("chatRoomId", chatRoomId);
|
||||||
|
map.put("wxid", wxid);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息置顶
|
||||||
|
*
|
||||||
|
* @param chatRoomId
|
||||||
|
* @param msgid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject top_msg(String chatRoomId, Long msgid) {
|
||||||
|
String url = apiPath + "?type=51";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("chatRoomId", chatRoomId);
|
||||||
|
map.put("msgid", msgid);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消置顶
|
||||||
|
*
|
||||||
|
* @param chatRoomId
|
||||||
|
* @param msgid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject close_top_msg(String chatRoomId, Long msgid) {
|
||||||
|
String url = apiPath + "?type=52";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("chatRoomId", chatRoomId);
|
||||||
|
map.put("msgid", msgid);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 朋友圈首页
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject sns_first() {
|
||||||
|
String url = apiPath + "?type=53";
|
||||||
|
JSONObject response = post(url, null);
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 朋友圈下一页
|
||||||
|
*
|
||||||
|
* @param snsId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject sns_next(String snsId) {
|
||||||
|
String url = apiPath + "?type=54";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("snsId", snsId);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询联系人或群名称
|
||||||
|
*
|
||||||
|
* @param wxid 微信id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject query_nickname(String wxid) {
|
||||||
|
String url = apiPath + "?type=55";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("id", wxid);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载消息附件
|
||||||
|
*
|
||||||
|
* @param msgId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject download_msg_attach(Long msgId) {
|
||||||
|
String url = apiPath + "?type=56";
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("msgId", msgId);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取群/群成员信息
|
||||||
|
*
|
||||||
|
* @param wxid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static JSONObject get_member_info(String wxid) {
|
||||||
|
String url = apiPath + "?type=57";
|
||||||
|
Map<String, String> map = new HashMap<>();
|
||||||
|
map.put("wxid", wxid);
|
||||||
|
JSONObject response = post(url, JSON.toJSONString(map));
|
||||||
|
System.out.println(response);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static JSONObject post(String url, String json) {
|
||||||
|
String body = Jsoup.connect(url)
|
||||||
|
.method(Connection.Method.POST)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.requestBody(json)
|
||||||
|
.execute().body();
|
||||||
|
return JSON.parseObject(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static JSONObject hook(String url, String json) {
|
||||||
|
String body = Jsoup.connect(url)
|
||||||
|
.data("msg",json)
|
||||||
|
.method(Connection.Method.POST)
|
||||||
|
.timeout(1000)
|
||||||
|
.execute().body();
|
||||||
|
return JSON.parseObject(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,272 @@
|
|||||||
|
package com.example.service;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.example.client.WeChatHookClient;
|
||||||
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.socket.SocketChannel;
|
||||||
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
|
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
|
||||||
|
import io.netty.handler.codec.Delimiters;
|
||||||
|
import io.netty.handler.codec.string.StringDecoder;
|
||||||
|
import io.netty.handler.codec.string.StringEncoder;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
import io.netty.util.internal.StringUtil;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @PACKAGE_NAME: com.example.service
|
||||||
|
* @NAME: WeChatHookNettyServer
|
||||||
|
* @AUTHOR: wxs
|
||||||
|
* @DATE: 2023/5/31 15:07
|
||||||
|
* @PROJECT_NAME: WeChatHook-java
|
||||||
|
**/
|
||||||
|
public class WeChatHookNettyServer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接启动main方法
|
||||||
|
*
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
Integer serverPort = 19077;
|
||||||
|
|
||||||
|
String hookApi = null;
|
||||||
|
|
||||||
|
for (String arg : args) {
|
||||||
|
System.out.println(arg);
|
||||||
|
if (arg.startsWith("--port")) {
|
||||||
|
serverPort = Integer.valueOf(arg.split("=")[1]);
|
||||||
|
}
|
||||||
|
if (arg.startsWith("--hookApi")) {
|
||||||
|
hookApi = arg.split("=")[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//1、注入
|
||||||
|
inject();
|
||||||
|
|
||||||
|
//2、开启hook
|
||||||
|
try {
|
||||||
|
JSONObject result = WeChatHookClient.hook_msg("127.0.0.1", serverPort.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("hook 失败,请检查微信是否登录");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//3、启动服务
|
||||||
|
start(serverPort, hookApi);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行注入命令
|
||||||
|
*/
|
||||||
|
public static void inject() {
|
||||||
|
//
|
||||||
|
File consoleInjectTemp = null;
|
||||||
|
File wxhelperTemp = null;
|
||||||
|
try {
|
||||||
|
consoleInjectTemp = createTempFile("ConsoleInject", ".exe");
|
||||||
|
wxhelperTemp = createTempFile("wxhelper", ".dll");
|
||||||
|
|
||||||
|
String ConsoleInject = consoleInjectTemp.getAbsolutePath();
|
||||||
|
|
||||||
|
String wxhelper = wxhelperTemp.getAbsolutePath();
|
||||||
|
|
||||||
|
//ConsoleInject.exe -i WeChat.exe -p C:\Users\DELL\Desktop\injector\wxhelper.dll
|
||||||
|
String command = ConsoleInject + " -i WeChat.exe -p " + wxhelper;
|
||||||
|
|
||||||
|
//重试3次
|
||||||
|
int retryCount = 3;
|
||||||
|
do {
|
||||||
|
retryCount--;
|
||||||
|
try {
|
||||||
|
//检查登录状态
|
||||||
|
JSONObject jsonObject = WeChatHookClient.check_login();
|
||||||
|
//如果已登录不需要注入
|
||||||
|
if (jsonObject.getInteger("code").equals(1)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println(e.getMessage() + "请确认微信已登录");
|
||||||
|
}
|
||||||
|
//执行注入命令
|
||||||
|
excuteShell(command);
|
||||||
|
|
||||||
|
} while (retryCount >= 0);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
////如果不为空删除临时文件
|
||||||
|
if (Objects.nonNull(consoleInjectTemp)) {
|
||||||
|
consoleInjectTemp.delete();
|
||||||
|
}
|
||||||
|
//如果不为空删除临时文件
|
||||||
|
if (Objects.nonNull(wxhelperTemp)) {
|
||||||
|
wxhelperTemp.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建临时文件
|
||||||
|
*
|
||||||
|
* @param fileName
|
||||||
|
* @param suffix
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static File createTempFile(String fileName, String suffix) {
|
||||||
|
InputStream inputStream = WeChatHookNettyServer.class.getResourceAsStream("/" + fileName + suffix);
|
||||||
|
|
||||||
|
FileOutputStream outputStream = null;
|
||||||
|
try {
|
||||||
|
File tempFile = File.createTempFile(fileName, suffix);
|
||||||
|
outputStream = new FileOutputStream(tempFile);
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
return tempFile;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
if (Objects.nonNull(outputStream)) {
|
||||||
|
try {
|
||||||
|
outputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动服务
|
||||||
|
*
|
||||||
|
* @param port
|
||||||
|
*/
|
||||||
|
public static void start(Integer port, String hookApi) {
|
||||||
|
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
|
||||||
|
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||||
|
try {
|
||||||
|
ServerBootstrap serverBootstrap = new ServerBootstrap();
|
||||||
|
serverBootstrap.group(bossGroup, workerGroup)
|
||||||
|
.channel(NioServerSocketChannel.class)
|
||||||
|
.localAddress(new InetSocketAddress(port))
|
||||||
|
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||||
|
@Override
|
||||||
|
protected void initChannel(SocketChannel ch) {
|
||||||
|
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024 * 100, Delimiters.lineDelimiter()));
|
||||||
|
ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
|
||||||
|
ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
|
||||||
|
ch.pipeline().addLast(new ReceiveMsgHandler(hookApi));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.option(ChannelOption.SO_BACKLOG, 128)
|
||||||
|
.childOption(ChannelOption.SO_KEEPALIVE, true);
|
||||||
|
|
||||||
|
Channel channel = serverBootstrap.bind().sync().channel();
|
||||||
|
System.out.println("服务启动成功 端口号 " + port);
|
||||||
|
channel.closeFuture().sync();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
bossGroup.shutdownGracefully();
|
||||||
|
workerGroup.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ReceiveMsgHandler extends SimpleChannelInboundHandler<String> {
|
||||||
|
|
||||||
|
private String hookApi;
|
||||||
|
|
||||||
|
public ReceiveMsgHandler(String hookApi) {
|
||||||
|
this.hookApi = hookApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
|
||||||
|
JSONObject jsonObject = JSON.parseObject(msg);
|
||||||
|
|
||||||
|
jsonObject.forEach((k, v) -> {
|
||||||
|
System.out.println(k + " = " + v);
|
||||||
|
});
|
||||||
|
|
||||||
|
String fromGroup = jsonObject.getString("fromGroup");
|
||||||
|
String fromUser = jsonObject.getString("fromUser");
|
||||||
|
String from;
|
||||||
|
if (fromGroup.equals(fromUser)) {
|
||||||
|
JSONObject fromGroupJson = WeChatHookClient.query_nickname(jsonObject.getString("fromGroup"));
|
||||||
|
String groupNname = fromGroupJson.getString("name");
|
||||||
|
from = "消息来自:" + groupNname;
|
||||||
|
jsonObject.put("fromUserName", groupNname);
|
||||||
|
} else {
|
||||||
|
JSONObject fromGroupJson = WeChatHookClient.query_nickname(jsonObject.getString("fromGroup"));
|
||||||
|
String groupNname = fromGroupJson.getString("name");
|
||||||
|
|
||||||
|
JSONObject fromUserJson = WeChatHookClient.query_nickname(jsonObject.getString("fromUser"));
|
||||||
|
String fromUserName = fromUserJson.getString("name");
|
||||||
|
jsonObject.put("fromUserName", fromUserName);
|
||||||
|
|
||||||
|
from = "消息来自:" + groupNname + "->" + fromUserName;
|
||||||
|
}
|
||||||
|
System.out.println("----------" + from + "----------");
|
||||||
|
//消息转发
|
||||||
|
if (StringUtil.isNullOrEmpty(hookApi)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//检查api接口是否是通的
|
||||||
|
//转发消息
|
||||||
|
try {
|
||||||
|
WeChatHookClient.hook(hookApi, msg);
|
||||||
|
} catch (Exception e) {
|
||||||
|
//请检查hookApi服务是否正常
|
||||||
|
System.err.println("--》消息转发失败,请检查hookApi服务是否正常");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
cause.printStackTrace();
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行shell 命令
|
||||||
|
*
|
||||||
|
* @param command
|
||||||
|
*/
|
||||||
|
public static void excuteShell(String command) {
|
||||||
|
try {
|
||||||
|
Process process = Runtime.getRuntime().exec(command);
|
||||||
|
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
System.out.println(line);
|
||||||
|
}
|
||||||
|
int exitCode = process.waitFor();
|
||||||
|
System.out.println("Exit Code: " + exitCode);
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
weChatHook-java/src/main/resources/ConsoleInject.exe
Normal file
BIN
weChatHook-java/src/main/resources/ConsoleInject.exe
Normal file
Binary file not shown.
BIN
weChatHook-java/src/main/resources/injector.dll
Normal file
BIN
weChatHook-java/src/main/resources/injector.dll
Normal file
Binary file not shown.
BIN
weChatHook-java/src/main/resources/wxhelper.dll
Normal file
BIN
weChatHook-java/src/main/resources/wxhelper.dll
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user