Merge branch 'ttttupup:main' into main

This commit is contained in:
terry-tt 2023-07-06 13:58:20 +08:00 committed by GitHub
commit bdf19fe4e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 5316 additions and 51 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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.

View 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.

View File

@ -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

View File

@ -1,5 +1,5 @@
# wxhelper
wechat hook 。PC端微信逆向学习。支持3.8.0.413.8.1.26, 3.9.0.28, 3.9.2.23版本。
wechat hook 。PC端微信逆向学习。支持3.8.0.413.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.413.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.pyhttp 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.安装vcpkgcmakevscode
@ -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

File diff suppressed because it is too large Load Diff

33
java_client/.gitignore vendored Normal file
View 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
View 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
View 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>

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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() {
}
}

View File

@ -0,0 +1,11 @@
package com.example.wxhk.infe;
/**
* http 响应
* @author wt
* @date 2023/06/01
*/
public interface Resp extends java.io.Serializable{
}

View 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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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);
}
}

View File

@ -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);
}

View File

@ -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()));
}
}

View File

@ -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("退出线程了");
});
}
}

View File

@ -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
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View 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

Binary file not shown.

Binary file not shown.

View 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>

View 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)

View File

@ -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() {
}
}

View File

@ -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() {
}
}

File diff suppressed because one or more lines are too long

View File

@ -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
View 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
View 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 "")

View File

@ -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, &params[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

Binary file not shown.

View File

@ -1 +1 @@
## 可以使用3.9.0.28 分支下的注入工具,或者自己编译一下 source目录下的注入程序。
## 可以使用对应分支下的注入工具,或者自己编译一下 source目录下的注入程序。

8
weChatHook-java/.idea/.gitignore vendored Normal file
View 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

View 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>

View 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>

View 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>

View 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
View 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
View 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>

View File

@ -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);
}
}

View File

@ -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();
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.