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)
|
||||
|
||||
|
||||
include_directories(${VCPKG_INSTALLED_DIR}/x86-windows/include)
|
||||
|
||||
include_directories(c:/soft/vcpkg/installed/x86-windows/include)
|
||||
|
||||
# add_subdirectory(3rd)
|
||||
|
||||
# add_subdirectory(source)
|
||||
|
||||
find_package(nlohmann_json CONFIG REQUIRED)
|
||||
find_package(unofficial-mongoose CONFIG REQUIRED)
|
||||
# find_package(spdlog CONFIG REQUIRED)
|
||||
# find_package(minhook CONFIG REQUIRED)
|
||||
|
||||
|
||||
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 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
|
||||
ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib
|
||||
|
58
README.md
58
README.md
@ -1,5 +1,5 @@
|
||||
# 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代码
|
||||
tool:简单的注入工具,一个是控制台,一个是图形界面。
|
||||
python: tcpserver.py: 简单的服务器,用以接收消息内容。decrpty.py: 微信数据库解密工具。
|
||||
python: tcpserver.py: 简单的服务器,用以接收消息内容。decrpty.py: 微信数据库解密工具。 http_server.py:http server端。
|
||||
source: 简单的命令行远程注入源码。
|
||||
|
||||
其他目录:热心作者提供的一些客户端。
|
||||
|
||||
#### 0.首先安装对应的版本的微信,分支名称即代表的是微信对应的版本。dll的前缀都会带有微信版本。
|
||||
#### 1.使用注入工具注入wxhelper.dll,注入成功后,即可通过postman直接调用对应的接口。
|
||||
@ -49,7 +49,7 @@ source: 简单的命令行远程注入源码。
|
||||
个人常用的方法,请参考https://github.com/ttttupup/wxhelper/wiki
|
||||
使用上的问题,可查询https://github.com/ttttupup/wxhelper/discussions
|
||||
数据库解密,请参考https://github.com/ttttupup/wxhelper/wiki
|
||||
|
||||
个人精力有限,只维护最新版本,旧版本的bug会在新版本中修复,不维护旧版本。
|
||||
|
||||
|
||||
#### 编译环境
|
||||
@ -63,6 +63,21 @@ cmake
|
||||
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中的操作类似。
|
||||
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.命令行注入工具,注入命令
|
||||
``` javascript
|
||||
@ -108,6 +123,7 @@ vcpkg
|
||||
// -u 卸载程序名 -d 卸载dll名称
|
||||
// -m pid 关闭微信互斥体,多开微信
|
||||
// -P port 指定http端口,需要使用 specify-port 分支的生成的dll
|
||||
// -I 注入程序的pid
|
||||
//注入
|
||||
ConsoleInject.exe -i demo.exe -p E:\testInject.dll
|
||||
//卸载
|
||||
@ -116,6 +132,16 @@ vcpkg
|
||||
ConsoleInject.exe -m 1222
|
||||
// 注入并指定http端口
|
||||
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-30 : 新增获取语音文件(推荐使用这个非hook接口)
|
||||
|
||||
2023-04-08 : 3.9.2.23版本功能更新
|
||||
|
||||
2023-06-05 :3.9.2.26版本更新
|
||||
|
||||
#### 功能预览:
|
||||
0.检查是否登录
|
||||
1.获取登录微信信息
|
||||
@ -163,6 +195,7 @@ vcpkg
|
||||
17.删除好友
|
||||
19.通过手机或qq查找微信
|
||||
20.通过wxid添加好友
|
||||
23.通过好友申请
|
||||
25.获取群成员
|
||||
26.获取群成员昵称
|
||||
27.删除群成员
|
||||
@ -186,9 +219,22 @@ vcpkg
|
||||
54.朋友圈下一页
|
||||
55.获取联系人或者群名称
|
||||
56.获取消息附件(图片,视频,文件)
|
||||
57.获取语音文件(silk3格式)
|
||||
58.登录二维码
|
||||
59.邀请入群
|
||||
60.获取群/群成员详情
|
||||
61.撤回消息
|
||||
62.发送公众号消息
|
||||
63.转发公众号消息
|
||||
64.发送小程序
|
||||
65.退款
|
||||
66.下载头像(勿用,没什么用)
|
||||
#### 感谢
|
||||
https://github.com/ljc545w/ComWeChatRobot
|
||||
|
||||
https://github.com/NationalSecurityAgency/ghidra
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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[] = {
|
||||
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)
|
||||
{
|
||||
if(!EnableDebugPrivilege()){
|
||||
return 0;
|
||||
}
|
||||
int result = 0;
|
||||
HANDLE hRemoteThread = NULL;
|
||||
LPTHREAD_START_ROUTINE lpSysLibAddr = NULL;
|
||||
@ -660,9 +660,9 @@ int InjectDllAndStartHttp(wchar_t* szPName, wchar_t* szDllPath, DWORD port)
|
||||
HANDLE hProcess;
|
||||
unsigned int dwPid;
|
||||
size_t ulDllLength;
|
||||
wchar_t* dllName = L"wxhelper.dll";
|
||||
wchar_t* dllName = (wchar_t*)L"wxhelper.dll";
|
||||
size_t dllNameLen = wcslen(dllName) * 2 + 2;
|
||||
char* funcName = "http_start";
|
||||
char* funcName = (char* )"http_start";
|
||||
size_t funcNameLen = strlen(funcName) + 1;
|
||||
|
||||
HANDLE hStartHttp = NULL;
|
||||
@ -771,8 +771,133 @@ error:
|
||||
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)
|
||||
{
|
||||
if(!EnableDebugPrivilege()){
|
||||
return 0;
|
||||
}
|
||||
int result = 0;
|
||||
HANDLE hRemoteThread;
|
||||
LPTHREAD_START_ROUTINE lpSysLibAddr;
|
||||
@ -802,6 +927,61 @@ int InjectDll(wchar_t* szPName, wchar_t* szDllPath)
|
||||
CloseHandle(hRemoteThread);
|
||||
CloseHandle(hProcess);
|
||||
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;
|
||||
}
|
||||
else
|
||||
@ -872,8 +1052,9 @@ int main(int argc, char** argv)
|
||||
int port = 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)
|
||||
{
|
||||
@ -910,6 +1091,9 @@ int main(int argc, char** argv)
|
||||
case 'P':
|
||||
port = std::atoi(optarg);
|
||||
break;
|
||||
case 'I':
|
||||
injectPid = std::atoi(optarg);
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
break;
|
||||
@ -917,8 +1101,26 @@ int main(int argc, char** argv)
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
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