diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -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. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -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. diff --git a/CMakeLists.txt b/CMakeLists.txt index d6944ab..3c264f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/README.md b/README.md index bd7e074..b171419 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # wxhelper -wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26, 3.9.0.28, 3.9.2.23版本。 +wechat hook 。PC端微信逆向学习。支持3.8.0.41,3.8.1.26, 3.9.0.28, 3.9.2.23,3.9.2.26版本。 #### 免责声明: 本仓库发布的内容,仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关! @@ -19,13 +19,13 @@ dll在注入成功时,创建了一个默认端口为19088的http服务端, ``` #### 使用说明: -支持的版本3.8.0.41,3.8.1.26,3.9.0.28。 +支持的版本3.8.0.41、3.8.1.26、3.9.0.28、3.9.2.23、3.9.2.26 。 源码和主要实现在相应的分支内。 src:主要的dll代码 tool:简单的注入工具,一个是控制台,一个是图形界面。 -python: tcpserver.py: 简单的服务器,用以接收消息内容。decrpty.py: 微信数据库解密工具。 +python: tcpserver.py: 简单的服务器,用以接收消息内容。decrpty.py: 微信数据库解密工具。 http_server.py:http server端。 source: 简单的命令行远程注入源码。 - +其他目录:热心作者提供的一些客户端。 #### 0.首先安装对应的版本的微信,分支名称即代表的是微信对应的版本。dll的前缀都会带有微信版本。 #### 1.使用注入工具注入wxhelper.dll,注入成功后,即可通过postman直接调用对应的接口。 @@ -49,7 +49,7 @@ source: 简单的命令行远程注入源码。 个人常用的方法,请参考https://github.com/ttttupup/wxhelper/wiki 使用上的问题,可查询https://github.com/ttttupup/wxhelper/discussions 数据库解密,请参考https://github.com/ttttupup/wxhelper/wiki - +个人精力有限,只维护最新版本,旧版本的bug会在新版本中修复,不维护旧版本。 #### 编译环境 @@ -63,6 +63,21 @@ cmake vcpkg #### 编译构建 先准备好编译环境。 +``` +cd wxhelper +mkdir build +cd build +cmake -DCMAKE_C_COMPILER=cl.exe \ +-DCMAKE_CXX_COMPILER=cl.exe \ +-DCMAKE_BUILD_TYPE=Debug \ +-DCMAKE_INSTALL_PREFIX=C:/other/codeSource/windows/wxhelper/out/install/x86-debug \ +-DCMAKE_TOOLCHAIN_FILE:FILEPATH=C:/vcpkg/scripts/buildsystems/vcpkg.cmake \ +-SC:/wxhelper \ +-BC:/wxhelper/build/x86-debug\ +-G Ninja + +cmake --build .. +``` 以下是在vscode中操作,vs中的操作类似。 1.安装vcpkg,cmake,vscode @@ -100,7 +115,7 @@ vcpkg } ``` -4.vscode中右键configure all projects,在Terminal中点击Run Task,如没有先配置build任务,然后运行即可 +4.执行cmake build vscode中右键configure all projects,在Terminal中点击Run Task,如没有先配置build任务,然后运行即可 5.命令行注入工具,注入命令 ``` javascript @@ -108,6 +123,7 @@ vcpkg // -u 卸载程序名 -d 卸载dll名称 // -m pid 关闭微信互斥体,多开微信 // -P port 指定http端口,需要使用 specify-port 分支的生成的dll + // -I 注入程序的pid //注入 ConsoleInject.exe -i demo.exe -p E:\testInject.dll //卸载 @@ -115,7 +131,17 @@ vcpkg //多开 ConsoleInject.exe -m 1222 // 注入并指定http端口 - ConsoleInject.exe -i demo.exe -p E:\testInject.dll -P 18888 + ConsoleInject.exe -i demo.exe -p E:\testInject.dll -P 18888 + // 注入指定pid并关闭多开限制 + ConsoleInject.exe -I 15048 -p E:\testInject.dll -m 15048 + +``` + +6.如果想改变端口,可以在微信目录下创建config.ini配置文件,修改端口即可。不创建则默认端口19088。 +``` shell +[config] +port=19099 + ``` #### 更新说明 @@ -145,7 +171,13 @@ vcpkg 2023-03-04 : 新增消息附件下载 -2023-03-21 : 新增hook语音 +2023-03-21 : 新增hook语音 + +2023-03-30 : 新增获取语音文件(推荐使用这个非hook接口) + +2023-04-08 : 3.9.2.23版本功能更新 + +2023-06-05 :3.9.2.26版本更新 #### 功能预览: 0.检查是否登录 @@ -162,7 +194,8 @@ vcpkg 14.取消hook语音 17.删除好友 19.通过手机或qq查找微信 -20.通过wxid添加好友 +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 diff --git a/doc/3.9.2.23.md b/doc/3.9.2.23.md new file mode 100644 index 0000000..ac1fe01 --- /dev/null +++ b/doc/3.9.2.23.md @@ -0,0 +1,1633 @@ + +## 3.9.2.23版本,预览功能没有的接口,不能使用,文档仅供参考。 +#### 功能预览: +0.检查是否登录 +1.获取登录微信信息 +2.发送文本 +3.发送@文本 +5.发送图片 +6.发送文件 +9.hook消息 +10.取消hook消息 +11.hook图片 +12.取消hook图片 +13.hook语音(不推荐) +14.取消hook语音 + +19.通过手机或qq查找微信 +20.通过wxid添加好友 +23.通过好友申请 +25.获取群成员 +26.获取群成员昵称 +27.删除群成员 +28.增加群成员 +31.修改群昵称 +32.获取数据库句柄 +34.查询数据库 +35.hook日志 +36.关闭hook日志 +40.转发消息 +44.退出登录 + +46.联系人列表 +47.获取群详情 +48.获取解密图片 +49.图片提取文字ocr +50.拍一拍 +51.群消息置顶消息 +52.群消息取消置顶 +53.朋友圈首页 +54.朋友圈下一页 +55.获取联系人或者群名称 +56.获取消息附件(图片,视频,文件) +57.获取消息语音文件 +### 接口文档: + + +#### 0.检查微信登录** +###### 接口功能 +> 检查微信是否登录 + +###### 接口地址 +> [/api/?type=0](/api/?type=0) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1 成功, 0失败| +|result|string|成功提示| +|data|string|响应内容| + +###### 接口示例 +入参: +``` javascript +``` +响应: +``` javascript +{ + "code": 1, + "result": "ok" +} +``` + +#### 1.获取登录用户信息** +###### 接口功能 +> 获取登录用户信息 + +###### 接口地址 +> [/api/?type=1](/api/?type=1) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1 成功, 0失败| +|result|string|成功提示| +|data|object|响应内容| +|account|string|账号| +|headImage|string|头像| +|city|string|城市| +|country|string|国家| +|currentDataPath|string|当前数据目录,登录的账号目录| +|dataSavePath|string|微信保存目录| +|mobile|string|手机| +|name|string|昵称| +|province|string|省| +|wxid|string|wxid| +|signature|string|个人签名| +|dbKey|string|数据库的SQLCipher的加密key,可以使用该key配合decrypt.py解密数据库 + +###### 接口示例 +入参: +``` javascript +``` +响应: +``` javascript +{"code":1,"data":{"account":"xx","headImage":"https://wx.qlogo.cn/mmhead/ver_1xx","city":"xx","country":"CN","currentDataPath":"C:\\xx\\wxid_xxxxx","dataSavePath":"C:\\xx","mobie":"13812345678","name":"xx","province":"xx","signature":"xx","wxid":"xx","dbKey":"aaa2222"},"result":"OK"} +``` + + + + +#### 2.发送文本消息** +###### 接口功能 +> 发送文本消息 + +###### 接口地址 +> [/api/?type=2](/api/?type=2) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 接收人wxid | +|msg|true |string|消息文本内容| + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,不为0成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 + +入参: +``` javascript +{ + "wxid": "filehelper", + "msg": "1112222" +} +``` +响应: +``` javascript +{"code":345686720,"result":"OK"} +``` + + + +#### 3.发送@文本消息** +###### 接口功能 +> 发送@文本消息 + +###### 接口地址 +> [/api/?type=3](/api/?type=3) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | +|msg|true |string|消息文本内容| +|wxids |true |string| @的用户微信id用,号分隔, @所有人 传 notify@all ,区分大小写 | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,不为0成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 + +入参: +``` javascript +{ + "chatRoomId": "123333@chatroom", + "wxids":"notify@all,wxid_122221", + "msg": "12333" +} +``` +响应: +``` javascript +{"code":345686720,"result":"OK"} +``` + + + +#### 5.发送图片消息** +###### 接口功能 +> 发送图片消息 + +###### 接口地址 +> [/api/?type=5](/api/?type=5) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 接收人wxid | +|imagePath|true |string|图片路径| + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,不为0成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "wxid": "filehelper", + "imagePath": "C:/Users/123.png" +} +``` +响应: +``` javascript +{"code":345686724,"result":"OK"} +``` + + + +#### 6.发送文件消息** +###### 接口功能 +> 发送文件 + +###### 接口地址 +> [/api/?type=6](/api/?type=6) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 接收人wxid | +|filePath|true |string|文件路径| + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "wxid": "filehelper", + "filePath": "C:/Users/123.txt" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 9.hook消息** +###### 接口功能 +> hook接收文本消息,图片消息,群消息.该接口将hook的消息通过tcp回传给本地的端口 + +###### 接口地址 +> [/api/?type=9](/api/?type=9) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|port |true |string| 本地服务端端口,用来接收消息内容 | +|ip |true |string| 服务端ip地址,用来接收消息内容,可以是任意ip,即tcp客户端连接的服务端的ip (3.8.1.26版本)| + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "port": "19099" + "ip":"127.0.0.1" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 10.取消hook消息** +###### 接口功能 +> 取消hook消息 + +###### 接口地址 +> [/api/?type=10](/api/?type=10) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + +#### 11.hook图片** +###### 接口功能 +> hook图片原始内容,不推荐该接口,可以使用图片查询接口 + +###### 接口地址 +> [/api/?type=11](/api/?type=11) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|imgDir |true |string| 图片保存的目录 | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "imgDir":"C:\\other" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 12.取消hook图片** +###### 接口功能 +> 取消hook图片 + +###### 接口地址 +> [/api/?type=12](/api/?type=12) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + +#### 13.hook语音** +###### 接口功能 +> hook语音 + +###### 接口地址 +> [/api/?type=13](/api/?type=13) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|voiceDir |true |string| 语音保存的目录 | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "voiceDir":"C:\\other" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 14.取消hook语音** +###### 接口功能 +> 取消hook语音 + +###### 接口地址 +> [/api/?type=14](/api/?type=14) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 17.删除好友** +###### 接口功能 +> 删除好友,该接口不够完善,删除后,只会在通讯录里删除,如果点击聊天记录,又会重新加回来,删除的不彻底。 + +###### 接口地址 +> [/api/?type=17](/api/?type=17) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 好友wxid | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,0成功, -1失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "wxid":"wxid_o" +} +``` +响应: +``` javascript +{"code":0,"result":"OK"} +``` + +#### 19.通过手机或qq查找微信** +###### 接口功能 +> 通过手机或qq查找微信 + +###### 接口地址 +> [/api/?type=19](/api/?type=19) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|keyword |true |string| 手机或qq | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| +|userInfo|object|用户信息| +|  bigImage|string|大头像| +|  smallImage|string|小头像| +|  city|string|城市| +|  nation|string|民族| +|  nickname|string|昵称| +|  province|string|省| +|  sex|number|性别| +|  signature|string|签名| +|  v2|string|v2| +|  v3|string|v3| + +###### 接口示例 +入参: +``` javascript +{ + "keyword":"131111111" +} +``` +响应: +``` javascript +{ + "code": 1, + "result": "OK", + "userInfo": { + "bigImage": "http://wx.qlogo.cn/mmhead/ver_1/7NIHQAyXeaAPa7Vd7p122mKxgETJwoAiaERdk1sSyOyfnLLQOfElw4G9I32QkZzh7bGfZr2lg0OIQE1Az3cUwtWaLUM79Q/0", + "city": "", + "nation": "", + "nickname": "昵称", + "province": "", + "sex": 0, + "signature": "", + "smallImage": "http://wx.qlogo.cn/mmhead/ver_1/7NIHQAyXeaAPa7Vd7p4KR3vxiasmKxgETJwoAiaER23QE6G5mLBcdBQkZzh7bGfZr2lg0OIQE1Az3cUwtWaLUM79Q/132", + "v2": "wxid_12333", + "v3": "v3_020b3826fd0301000000000098ca23832239a3dba12f95f6b60a0536a1adb6b40fc4086288f46c0b89e6c4eb70c34f118c7b4b6a6845144843b088f0077e406507f821068571289b36c4158a8ac47ec41ae47bee65e9@stranger" + } +} +``` + + + +#### 20.通过wxid添加好友** +###### 接口功能 +> 添加好友 + +###### 接口地址 +> [/api/?type=20](/api/?type=20) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 好友wxid | +|msg |true |string| 验证消息 | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "wxid":"wxid_o1112222" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + +#### 23.通过好友申请** +###### 接口功能 +> 通过好友的申请 + +###### 接口地址 +> [/api/?type=23](/api/?type=23) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|v3 |true |string| 添加好友消息内容里的encryptusername| +|v4 |true |string| 添加好友消息内容里的ticket| +|permission |true |string|好友权限,0是无限制,1是不让他看我,2是不看他,3是1+2| + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + + "v3":"v3", + "v4":"v4", + "permission":"0" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 25.获取群成员** +###### 接口功能 +> 获取群成员 + +###### 接口地址 +> [/api/?type=25](/api/?type=25) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| +|data|object|返回内容| +|admin|string|群主id| +|chatRoomId|string|群id| +|members|string|群成员id以^分隔| + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"123@chatroom" +} +``` +响应: +``` javascript +{"code":1,"data":{"admin":"wxid","chatRoomId":"123@chatroom","members":"wxid_123^Gwxid_456^Gwxid_45677"},"result":"OK"} +``` + + +#### 26.获取群成员昵称** +###### 接口功能 +> 获取群成员群内昵称 + +###### 接口地址 +> [/api/?type=26](/api/?type=26) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | +|memberId |true |string| 群成员id | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| +|nickname|string|昵称| + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"123@chatroom", + "memberId":"wxid_123" +} +``` +响应: +``` javascript +{"code":1,"nickname":"昵称","result":"OK"} +``` + + +#### 27.删除群成员** +###### 接口功能 +> 删除群成员 + +###### 接口地址 +> [/api/?type=27](/api/?type=27) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | +|memberIds |true |string| 成员id,以,分割 | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"34932563384@chatroom", + "memberIds":"wxid_oyb662qhop4422" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + + +#### 28.增加群成员** +###### 接口功能 +> 增加群成员 + +###### 接口地址 +> [/api/?type=28](/api/?type=28) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | +|memberIds |true |string| 成员id,以,分割 | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"34932563384@chatroom", + "memberIds":"wxid_oyb662qhop4422" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 31.修改自身群昵称** +###### 接口功能 +> 修改群名片 + +###### 接口地址 +> [/api/?type=31](/api/?type=31) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | +|wxid |true |string| 自己的id,只能修改自己的群名片 | +|nickName |true |string| 修改的昵称 | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"34932563384@chatroom", + "wxid":"wxid_272211111121112", + "nickName":"昵称test" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + + +#### 32.获取数据库句柄** +###### 接口功能 +> 获取sqlite3数据库句柄 + +###### 接口地址 +> [/api/?type=32](/api/?type=32) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| +|data|array|返回数据| +|databaseName|string|数据库名称| +|handle|int|数据库句柄| +|tables|array|表信息| +|name|string|表名| +|rootpage|string|rootpage| +|sql|string|sql| +|tableName|string|tableName| + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{ + "data": [ + { + "databaseName": "MicroMsg.db", + "handle": 119561688, + "tables": [ + { + "name": "Contact", + "rootpage": "2", + "sql": "CREATE TABLE Contact(UserName TEXT PRIMARY KEY ,Alias TEXT,EncryptUserName TEXT,DelFlag INTEGER DEFAULT 0,Type INTEGER DEFAULT 0,VerifyFlag INTEGER DEFAULT 0,Reserved1 INTEGER DEFAULT 0,Reserved2 INTEGER DEFAULT 0,Reserved3 TEXT,Reserved4 TEXT,Remark TEXT,NickName TEXT,LabelIDList TEXT,DomainList TEXT,ChatRoomType int,PYInitial TEXT,QuanPin TEXT,RemarkPYInitial TEXT,RemarkQuanPin TEXT,BigHeadImgUrl TEXT,SmallHeadImgUrl TEXT,HeadImgMd5 TEXT,ChatRoomNotify INTEGER DEFAULT 0,Reserved5 INTEGER DEFAULT 0,Reserved6 TEXT,Reserved7 TEXT,ExtraBuf BLOB,Reserved8 INTEGER DEFAULT 0,Reserved9 INTEGER DEFAULT 0,Reserved10 TEXT,Reserved11 TEXT)", + "tableName": "Contact" + } + ] + } + ], + "result": "OK" +} +``` + + + +#### 34.查询数据库** +###### 接口功能 +> 查询数据库 + +###### 接口地址 +> [/api/?type=34](/api/?type=34) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|dbHandle |true |int| 句柄 | +|sql |true |string| sql语句 | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| +|data|array|返回数据| + + + +###### 接口示例 +入参: +``` javascript +{ + "dbHandle": 219277920, + "sql":"select * from MSG where MsgSvrID=8985035417589024392" +} +``` +响应: +``` javascript +{"code":1,"data":[["localId","TalkerId","MsgSvrID","Type","SubType","IsSender","CreateTime","Sequence","StatusEx","FlagEx","Status","MsgServerSeq","MsgSequence","StrTalker","StrContent","DisplayContent","Reserved0","Reserved1","Reserved2","Reserved3","Reserved4","Reserved5","Reserved6","CompressContent","BytesExtra","BytesTrans"],["6346","24","8985035417589024392","1","0","0","1670897832","1670897832000","0","0","2","1","778715089","wxid_1222","112","","0","2","","","","","","","CgQIEBAAGkEIBxI9PG1zZ3NvdXJjZT4KCTxzaWduYXR1cmU+djFfSFFyeVAwZTE8L3NpZ25hdHVyZT4KPC9tc2dzb3VyY2U+ChokCAISIDU5NjI1NjUxNWE0YzU2ZDQxZDJlOWMyYmIxMjFhNmZl",""]],"result":"OK"} +``` + + + +#### 35.hook日志** +###### 接口功能 +> hook微信日志,在控制台打印日志,方便调试 + +###### 接口地址 +> [/api/?type=35](/api/?type=35) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 36.取消hook日志** +###### 接口功能 +> 取消hook日志 + +###### 接口地址 +> [/api/?type=36](/api/?type=36) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + +#### 40.转发消息** +###### 接口功能 +> 直接转发消息 + +###### 接口地址 +> [/api/?type=40](/api/?type=40) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 消息接收人wxid | +|msgid |true |number| 消息id,hook消息接口中返回的消息id | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,非0成功| +|result|string|成功提示| + + +###### 接口示例 +入参: +``` javascript +{ + "wxid": "filehelper", + "msgid":7215505498606506901 +} +``` +响应: +``` javascript +{"code":4344,"result":"OK"} +``` + + +#### 44.退出登录** +###### 接口功能 +> 退出登录微信,相当于直接退出微信,跟手动退出比,少了重新打开登录的一步,dll注入后也会随微信关闭而关闭。调用后不能再继续操作dll。 + +###### 接口地址 +> [/api/?type=44](/api/?type=44) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,非0成功| +|result|string|成功提示| + + + + +###### 接口示例 +入参: +``` javascript + +``` +响应: +``` javascript +{"code":4344,"result":"OK"} +``` + + +#### 45.确认收款** +###### 接口功能 +> 收到转账消息后,自动收款确认。type=49 即是转账消息。 + +###### 接口地址 +> [/api/?type=45](/api/?type=45) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid|string|转账人微信id,从hook的消息中获取| +|transcationId|string|从hook的消息中获取对应的字段内容。| +|transferId|string|从hook的消息中获取对应的字段内容。| + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功| +|result|string|成功提示| + + + + +###### 接口示例 +入参: +``` javascript +{ + "wxid":"wxid_agz5q76f11112", + "transcationId":"10000500012302060002831233124719620", + "transferId":"10000500012023020619112332136412" +} +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + +#### 46.联系人列表** +###### 接口功能 +> 联系人列表 + +###### 接口地址 +> [/api/?type=46](/api/?type=46) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| +|data|array|返回内容| +|customAccount|string|自定义账号| +|delFlag|int|删除标志| +|type|int|好友类型| +|userName|string|用户名称| +|verifyFlag|int|验证| +|wxid|string|wxid| + + + +###### 接口示例 +入参: +``` javascript +{ + "wxid": "filehelper", + "msgid":7215505498606506901 +} +``` +响应: +``` javascript +{"code":1,"data":[{"customAccount":"custom","delFlag":0,"type":8388611,"userName":"昵称","verifyFlag":0,"wxid":"wxid_123pcqm22"}]} +``` + + +#### 47.群详情** +###### 接口功能 +> 获取群详情 + +###### 接口地址 +> [/api/?type=47](/api/?type=47) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 群id | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| +|data|object|返回内容| +|admin|string|群主id| +|chatRoomId|int|群id| +|notice|int|通知| +|xml|string|xml| + + + +###### 接口示例 +入参: +``` javascript +{ + "wxid": "filehelper", + "msgid":7215505498606506901 +} +``` +响应: +``` javascript +{"code":1,"data":{"admin":"123","chatRoomId":"123@chatroom","notice":"1222","xml":""},"result":"OK"} +``` + + + +#### 48.获取解密图片** +###### 接口功能 +> 获取解密图片 + +###### 接口地址 +> [/api/?type=48](/api/?type=48) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|imagePath |true |string| 图片路径 | +|savePath |true |string| 保存路径 | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 0失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "imagePath":"C:\\3a610d7bc1cf5a15d12225a64b8962.dat", + "savePath":"C:\\other" +} + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + + +#### 49.提取文字** +###### 接口功能 +> 提取图片中的文字 + +###### 接口地址 +> [/api/?type=49](/api/?type=49) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|imagePath |true |string| 图片路径 | + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,0成功, -1失败,1 2 则是缓存或者正在进行中需再调用一次| +|result|string|成功提示| +|text|string|提取的相应文字| + + +#### 50.拍一拍** +###### 接口功能 +> 群里拍一拍用户 + +###### 接口地址 +> [/api/?type=50](/api/?type=50) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 微信群聊id | +|wxid |true |string| 要拍的用户wxid,如果使用用户自定义的微信号,则不会显示群内昵称 | +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"123331@chatroom", + "wxid":"wxid_123456" +} + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 51.群内消息置顶** +###### 接口功能 +> 在群聊里置顶某条消息,可以置顶文字和图片消息,其他消息未测试,部分低版本移动端置顶消息点击后会直接取消,高版本会一直置顶,其他未测试。 + +###### 接口地址 +> [/api/?type=51](/api/?type=51) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|wxid |true |string| 置顶消息的发送人wxid | +|msgid |true |string| 消息id | +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,0成功, -1失败,-2 未查到该消息| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "wxid":"wxid_oy11111p4422", + "msgid":3334956046278903121 +} + +``` +响应: +``` javascript +{"code":0,"result":"OK"} +``` + + +#### 52.取消群内消息置顶** +###### 接口功能 +> 取消置顶的消息。部分低版本移动端会不显示移除消息,但是会正常移除。 + +###### 接口地址 +> [/api/?type=52](/api/?type=52) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|chatRoomId |true |string| 微信群聊id | +|msgid |true |string| 消息id | +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,0成功, -1失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "chatRoomId":"2136311004@chatroom", + "msgid":3374951233278903120 +} + +``` +响应: +``` javascript +{"code":0,"result":"OK"} +``` + + + +#### 53.朋友圈首页消息** +###### 接口功能 +> 获取朋友圈最新消息,调用之后,会在tcpserver服务中收到朋友圈的消息。格式如下: +``` javascript +{ + 'data': [ + { + 'content': '朋友圈[玫瑰][玫瑰]', + 'createTime': 1675827480, + 'senderId': 'wxid_12333', + 'snsId': 14057859804711563695, + 'xml': '00' + }] +} + +``` + +###### 接口地址 +> [/api/?type=53](/api/?type=53) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| + + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript + + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 54.朋友圈下一页** +###### 接口功能 +> 朋友圈下一页,会在tcpserver服务中收到朋友圈的消息。格式如下: +``` javascript +{ + 'data': [ + { + 'content': '朋友圈[玫瑰][玫瑰]', + 'createTime': 1675827480, + 'senderId': 'wxid_12333', + 'snsId': 14057859804711563695, + 'xml': '00' + }] +} + +``` + +###### 接口地址 +> [/api/?type=54](/api/?type=54) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|snsId |true |string| 朋友圈的snsId | + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "snsId":"14056334227327177401" + +} + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 55.获取联系人或者群名称** +###### 接口功能 +> 根据wxid,获取联系人微信名称,传入群id获取群名称,传入群内非好友获取的是微信名称不是群内昵称。 + +###### 接口地址 +> [/api/?type=55](/api/?type=55) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|id |true |string| wxid或者群id | + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, -1失败| +|result|string|成功提示| +|name|string|名称| + + +###### 接口示例 +入参: +``` javascript +{ + "id":"wxid_123" + +} + +``` +响应: +``` javascript +{"code":1,"name":"文件助手","result":"OK"} +``` + + + +#### 56.获取消息附件** +###### 接口功能 +> 根据消息id,下载消息附件(图片,视频,文件) + +###### 接口地址 +> [/api/?type=56](/api/?type=56) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|msgId |true |string| 消息id | + + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,0成功, 非0失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "msgId": 3224560917391784099 + +} + +``` +响应: +``` javascript +{"code":0,"result":"OK"} +``` + + +#### 57.获取语音文件** +###### 接口功能 +> 根据消息id,获取该语音消息的语音文件,文件为silk3格式,可以自行转换mp3. + +###### 接口地址 +> [/api/?type=57](/api/?type=57) + +###### HTTP请求方式 +> POST JSON + +###### 请求参数 +|参数|必选|类型|说明| +|---|---|---|---| +|msgId |true |string| 消息id | +|voiceDir |true |string| 语音文件保存的目录,文件名称为 (msgid).amr | + + +###### 返回字段 +|返回字段|字段类型|说明 | +|---|---|---| +|code|int|返回状态,1成功, 非0失败| +|result|string|成功提示| + + + +###### 接口示例 +入参: +``` javascript +{ + "msgId": 3224560917391784099, + "voiceDir" : "c:\\voice" + +} + +``` +响应: +``` javascript +{"code":1,"result":"OK"} +``` + + +#### 感谢 +https://github.com/ljc545w/ComWeChatRobot + +https://github.com/NationalSecurityAgency/ghidra + +https://github.com/x64dbg/x64dbg diff --git a/java_client/.gitignore b/java_client/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/java_client/.gitignore @@ -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/ diff --git a/java_client/README.md b/java_client/README.md new file mode 100644 index 0000000..590f061 --- /dev/null +++ b/java_client/README.md @@ -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) + + + +启动项目需要去修改配置文件的微信路径 diff --git a/java_client/pom.xml b/java_client/pom.xml new file mode 100644 index 0000000..3a98e6f --- /dev/null +++ b/java_client/pom.xml @@ -0,0 +1,184 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.0 + + + com.example + wxhk + 0.0.1-SNAPSHOT + wxhk + wxhk + + 17 + + + + org.springframework.boot + spring-boot-starter-mail + + + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + + + + + + io.netty + netty-all + 4.1.92.Final + + + com.squareup.okhttp3 + okhttp + 4.11.0 + + + + io.vertx + vertx-core + 4.4.2 + + + io.vertx + vertx-web + 4.4.2 + + + io.vertx + vertx-web-client + 4.4.2 + + + io.vertx + vertx-mysql-client + 4.4.2 + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.dromara.hutool + hutool-all + 6.0.0.M3 + + + com.fasterxml.jackson.core + jackson-databind + 2.15.1 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + + + + + + + + **/com/example/wxhk/** + + + + + + + true + lib/ + false + + com.example.wxhk.WxhkApplication + + + + resources/ + + + + ${project.build.directory}/pack/ + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + copy-dependencies + package + + copy-dependencies + + + + ${project.build.directory}/pack/lib + + + + + + maven-resources-plugin + + + + copy-resources + package + + copy-resources + + + + + src/main/resources + + + + ${project.build.directory}/pack/resources + + + + + + + + diff --git a/java_client/src/main/java/com/example/wxhk/WxhkApplication.java b/java_client/src/main/java/com/example/wxhk/WxhkApplication.java new file mode 100644 index 0000000..da0ccbd --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/WxhkApplication.java @@ -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); + } + +} diff --git a/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java b/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java new file mode 100644 index 0000000..de9f7ad --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/constant/WxMsgType.java @@ -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; + } +} diff --git a/java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java b/java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java new file mode 100644 index 0000000..78b2e5d --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/controller/WxMsgController.java @@ -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() { + + } +} diff --git a/java_client/src/main/java/com/example/wxhk/infe/Resp.java b/java_client/src/main/java/com/example/wxhk/infe/Resp.java new file mode 100644 index 0000000..1c3fd04 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/infe/Resp.java @@ -0,0 +1,11 @@ +package com.example.wxhk.infe; + +/** + * http 响应 + * @author wt + * @date 2023/06/01 + */ +public interface Resp extends java.io.Serializable{ + + +} diff --git a/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java b/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java new file mode 100644 index 0000000..4050bd6 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/infe/SendMsg.java @@ -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 extends java.io.Serializable{ + + default JsonObject toJson(){ + return JsonObject.mapFrom(this); + } + +} diff --git a/java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java b/java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java new file mode 100644 index 0000000..25424b7 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/PrivateChatMsg.java @@ -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; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/dto/PayoutInformation.java b/java_client/src/main/java/com/example/wxhk/model/dto/PayoutInformation.java new file mode 100644 index 0000000..5ed4396 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/dto/PayoutInformation.java @@ -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); + } +} \ No newline at end of file diff --git a/java_client/src/main/java/com/example/wxhk/model/request/AddFriends.java b/java_client/src/main/java/com/example/wxhk/model/request/AddFriends.java new file mode 100644 index 0000000..42178aa --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/AddFriends.java @@ -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 { + String wxid; + /** + * 验证信息 + */ + String msg; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/ConfirmThePayment.java b/java_client/src/main/java/com/example/wxhk/model/request/ConfirmThePayment.java new file mode 100644 index 0000000..d17d7ff --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/ConfirmThePayment.java @@ -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 { + /** + * 转账人微信id,从hook的消息中获取 + */ + String wxid; + /** + * 从hook的消息中获取对应的字段内容 + */ + String transcationId; + /** + * 从hook的消息中获取对应的字段内容。 + */ + String transferId; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/FindWeChat.java b/java_client/src/main/java/com/example/wxhk/model/request/FindWeChat.java new file mode 100644 index 0000000..68a80a9 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/FindWeChat.java @@ -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 { + /** + * 通过 手机或qq查询信息 + */ + String keyword; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/ForwardMessages.java b/java_client/src/main/java/com/example/wxhk/model/request/ForwardMessages.java new file mode 100644 index 0000000..fed4d28 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/ForwardMessages.java @@ -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 { + /** + * 消息接收人wxid + */ + String wxid; + /** + * 消息id + */ + String msgid; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/GetGroupMembers.java b/java_client/src/main/java/com/example/wxhk/model/request/GetGroupMembers.java new file mode 100644 index 0000000..036fa9d --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/GetGroupMembers.java @@ -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 { + String chatRoomId; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/GetsTheNicknameOfAGroupMember.java b/java_client/src/main/java/com/example/wxhk/model/request/GetsTheNicknameOfAGroupMember.java new file mode 100644 index 0000000..5d68590 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/GetsTheNicknameOfAGroupMember.java @@ -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 { + /** + * 聊天室id + */ + String chatRoomId; + /** + * 成员id + */ + String memberId; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/IncreaseGroupMembership.java b/java_client/src/main/java/com/example/wxhk/model/request/IncreaseGroupMembership.java new file mode 100644 index 0000000..a80cee2 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/IncreaseGroupMembership.java @@ -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 { + /** + * 聊天室id + */ + String chatRoomId; + /** + * 成员id,以,分割 + */ + String memberIds; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/OpenHook.java b/java_client/src/main/java/com/example/wxhk/model/request/OpenHook.java new file mode 100644 index 0000000..12929d0 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/OpenHook.java @@ -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 { + String port; + String ip; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendAtText.java b/java_client/src/main/java/com/example/wxhk/model/request/SendAtText.java new file mode 100644 index 0000000..d158bc3 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendAtText.java @@ -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 { + /** + * 聊天室id,群聊用 + */ + String chatRoomId; + /** + * 群聊的时候用at多个用逗号隔开,@所有人则是notify@all + */ + String wxids; + + String msg; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendFile.java b/java_client/src/main/java/com/example/wxhk/model/request/SendFile.java new file mode 100644 index 0000000..54fcfb9 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendFile.java @@ -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 { + String wxid; + /** + * 发送文件路径 + * "filePath": "C:/Users/123.txt" + */ + String filePath; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendImg.java b/java_client/src/main/java/com/example/wxhk/model/request/SendImg.java new file mode 100644 index 0000000..cd62977 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendImg.java @@ -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 { + String wxid; + /** + * 发送图片接口 + * "imagePath": "C:/Users/123.png" + */ + String imagePath; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendMsg.java b/java_client/src/main/java/com/example/wxhk/model/request/SendMsg.java new file mode 100644 index 0000000..e86b213 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendMsg.java @@ -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多个用逗号隔开,@所有人则是notify@all + */ + String wxids; + /** + * 发送图片接口 + * "imagePath": "C:/Users/123.png" + */ + String imagePath; + /** + * 发送文件路径 + * "filePath": "C:/Users/123.txt" + */ + String filePath; + /** + * 通过 手机或qq查询信息 + */ + String keyword; + +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/SendText.java b/java_client/src/main/java/com/example/wxhk/model/request/SendText.java new file mode 100644 index 0000000..a749c3d --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/SendText.java @@ -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 { + String wxid; + String msg; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/request/ThroughFriends.java b/java_client/src/main/java/com/example/wxhk/model/request/ThroughFriends.java new file mode 100644 index 0000000..4e68332 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/request/ThroughFriends.java @@ -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 { + /** + * 添加好友消息内容里的encryptusername + */ + String v3; + /** + * 添加好友消息内容里的ticket + */ + String v4; + /** + * 好友权限,0是无限制,1是不让他看我,2是不看他,3是1+2 + */ + String permission; +} diff --git a/java_client/src/main/java/com/example/wxhk/model/response/ContactList.java b/java_client/src/main/java/com/example/wxhk/model/response/ContactList.java new file mode 100644 index 0000000..fcd1770 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/response/ContactList.java @@ -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 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; + } +} diff --git a/java_client/src/main/java/com/example/wxhk/model/response/GroupMembers.java b/java_client/src/main/java/com/example/wxhk/model/response/GroupMembers.java new file mode 100644 index 0000000..f13cf09 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/model/response/GroupMembers.java @@ -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; + } +} diff --git a/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java new file mode 100644 index 0000000..dbfc8aa --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/msg/WxMsgHandle.java @@ -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 map = new ConcurrentHashMap<>(32); + protected static final Log log = Log.get(); + /** + * 文件传输助手 + */ + public static final String FILEHELPER = "filehelper"; + /** + * 收款码缓存 因为有2段信息,一段是交易id,里面可以解析出来源方,二段解析出金额 + */ + public static ConcurrentHashMap 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> entries = collection_code_caching.entrySet(); + Iterator> iterator = entries.iterator(); + while (iterator.hasNext()) { + Map.Entry 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段 + * 会自动进行收款 + * + * @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); + } +} diff --git a/java_client/src/main/java/com/example/wxhk/server/WxSmgServer.java b/java_client/src/main/java/com/example/wxhk/server/WxSmgServer.java new file mode 100644 index 0000000..fe2bd3a --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/server/WxSmgServer.java @@ -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); +} diff --git a/java_client/src/main/java/com/example/wxhk/server/impl/WxSmgServerImpl.java b/java_client/src/main/java/com/example/wxhk/server/impl/WxSmgServerImpl.java new file mode 100644 index 0000000..3bfebd9 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/server/impl/WxSmgServerImpl.java @@ -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())); + } +} diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java new file mode 100644 index 0000000..a827f7d --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/ArrHandle.java @@ -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 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("退出线程了"); + }); + + } + +} diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java new file mode 100644 index 0000000..fb3a231 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/InitWeChat.java @@ -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 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 + } +} diff --git a/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java b/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java new file mode 100644 index 0000000..069f603 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/tcp/vertx/VertxTcp.java @@ -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 LINKED_BLOCKING_QUEUE = new LinkedBlockingQueue<>(); + /** + * 这个只保留交易相关的类型 + */ + public final static LinkedBlockingQueue LINKED_BLOCKING_QUEUE_MON = new LinkedBlockingQueue<>(); + protected static final Log log = Log.get(); + NetServer netServer; + + @Override + public void start(Promise 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 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)); + } +} diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java new file mode 100644 index 0000000..db408bf --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/util/HttpAsyncUtil.java @@ -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> 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> exec(Type type, JsonObject object, Handler>> 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; + } + } +} diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java new file mode 100644 index 0000000..281db83 --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/util/HttpSendUtil.java @@ -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(); + } + +} diff --git a/java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java b/java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java new file mode 100644 index 0000000..21686ac --- /dev/null +++ b/java_client/src/main/java/com/example/wxhk/util/HttpSyncUtil.java @@ -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); + } +} diff --git a/java_client/src/main/resources/application.properties b/java_client/src/main/resources/application.properties new file mode 100644 index 0000000..199c37a --- /dev/null +++ b/java_client/src/main/resources/application.properties @@ -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 \ No newline at end of file diff --git a/java_client/src/main/resources/exec/c.exe b/java_client/src/main/resources/exec/c.exe new file mode 100644 index 0000000..68787d2 Binary files /dev/null and b/java_client/src/main/resources/exec/c.exe differ diff --git a/java_client/src/main/resources/exec/wxhelper.dll b/java_client/src/main/resources/exec/wxhelper.dll new file mode 100644 index 0000000..168a32b Binary files /dev/null and b/java_client/src/main/resources/exec/wxhelper.dll differ diff --git a/java_client/src/main/resources/logback-spring.xml b/java_client/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..5027603 --- /dev/null +++ b/java_client/src/main/resources/logback-spring.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + ${withLineNumber_debug} + utf-8 + + + + + + + + log_error.log + + + ${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz + 50MB + 100 + + + + true + + + + ${file_pattern} + utf-8 + + + + + error + ACCEPT + DENY + + + + + + + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + utf-8 + + + + + + + + ${LOG_PATH}/log_error.log + + + ${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz + 50MB + 100 + + + + true + + + + ${file_pattern} + utf-8 + + + + + error + ACCEPT + DENY + + + + + + + + ${LOG_PATH}/log_total.log + + + ${LOG_PATH}/total/%d{yyyy-MM}/log_total-%d{yyyy-MM-dd}.%i.log.gz + 50MB + 100 + + + true + + + ${file_pattern} + utf-8 + + + + + + ${LOG_PATH}/log_business.log + + + ${LOG_PATH}/business/%d{yyyy-MM}/log_business-%d{yyyy-MM-dd}.%i.log.gz + + 50MB + 100 + + + true + + + ${file_pattern} + utf-8 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java_client/src/main/resources/spy.properties b/java_client/src/main/resources/spy.properties new file mode 100644 index 0000000..d64f7b1 --- /dev/null +++ b/java_client/src/main/resources/spy.properties @@ -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) \ No newline at end of file diff --git a/java_client/src/test/java/com/example/wxhk/WxhkApplicationTests.java b/java_client/src/test/java/com/example/wxhk/WxhkApplicationTests.java new file mode 100644 index 0000000..c7a768f --- /dev/null +++ b/java_client/src/test/java/com/example/wxhk/WxhkApplicationTests.java @@ -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() { + } + +} diff --git a/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java b/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java new file mode 100644 index 0000000..23d1ba4 --- /dev/null +++ b/java_client/src/test/java/com/example/wxhk/tcp/HttpAsyncUtilTest.java @@ -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> 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() { + + } +} \ No newline at end of file diff --git a/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java b/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java new file mode 100644 index 0000000..78587d1 --- /dev/null +++ b/java_client/src/test/java/com/example/wxhk/tcp/XmlTest.java @@ -0,0 +1,92 @@ +package com.example.wxhk.tcp; + +import com.example.wxhk.model.PrivateChatMsg; +import com.example.wxhk.msg.WxMsgHandle; +import io.vertx.core.json.JsonObject; +import org.dromara.hutool.core.lang.Console; +import org.dromara.hutool.core.util.XmlUtil; +import org.dromara.hutool.http.HttpUtil; +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class XmlTest { + @Test + void t(){ + String con = "{\"content\":\"\",\"fromGroup\":\"fmessage\",\"fromUser\":\"fmessage\",\"isSendMsg\":0,\"msgId\":4949224622157906392,\"pid\":1860,\"sign\":\"ab8277a517ed906cf31a64843d4c61d5\",\"signature\":\"\\n\\tv1_ZLVKn3eO\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-05-25 10:50:39\",\"timestamp\":1684983039,\"type\":37}"; + + + JsonObject entries = new JsonObject(con); + String content = entries.getString("content"); + Document document = XmlUtil.parseXml(content); + String encryptusername = document.getDocumentElement().getAttribute("encryptusername"); + + String ticket = document.getDocumentElement().getAttribute("ticket"); + System.out.println(ticket); + + String post = HttpUtil.post("http://127.0.0.1:19088/api/?type=23", new JsonObject().put("v3", encryptusername).put("v4", ticket) + .put("type","8") + .put("permission","5") + .encode()); + System.out.println(post); + } + + + + @Test + void 接受转账(){ + String smg = "{\"content\":\"\\n\\n<![CDATA[微信转账]]>\\n\\n\\n2000\\n\\n\\n\\n\\n\\n\\n\\n1\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"fromGroup\":\"wxid_gf1fogt5a0pq22\",\"fromUser\":\"wxid_gf1fogt5a0pq22\",\"isSendMsg\":0,\"msgId\":3157044781598783480,\"path\":\"wxid_4yr8erik0uho22\\\\FileStorage\\\\Cache\\\\2023-05\\\\324af3cccfe197a57506357bff7c85f7\",\"pid\":14268,\"sign\":\"e588b9632126b915e17c5a960e4ef288\",\"signature\":\"\\n\\tv1_LzLnadY1\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-05-29 10:00:15\",\"timestamp\":1685325615,\"type\":49}"; + JsonObject entries = new JsonObject(smg); + String content = entries.getString("content"); + Document document = XmlUtil.parseXml(content); + Element documentElement = document.getDocumentElement(); + Node transcationid = documentElement.getElementsByTagName("transcationid").item(0); + Node transferid = documentElement.getElementsByTagName("transferid").item(0); + Console.log(transcationid); + + } + + @Test + void 扫码支付(){ + String smg = "{\"content\":\"\\n \\n 9\\n \\n \\n\\t\\t\\n\\t\\t\\n \\n\\t\\t\\n\\t\\t\\n \\n\\t\\t\\n 1685325848\\n 1\\n 1\\n \\n\\n\\n\\n 0\\n 1\\n10001071012023052901214894726608\\n \\n \\n\",\"fromGroup\":\"wxid_4yr8erik0uho22\",\"fromUser\":\"wxid_4yr8erik0uho22\",\"isSendByPhone\":1,\"isSendMsg\":1,\"msgId\":3869459965780413850,\"pid\":14268,\"sign\":\"2285732a2e5b17729e7eeb8e305add15\",\"signature\":\"\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-05-29 10:04:08\",\"timestamp\":1685325848,\"type\":10002}"; + JsonObject entries = new JsonObject(smg); + String content = entries.getString("content"); + Document document = XmlUtil.parseXml(content); + Element documentElement = document.getDocumentElement(); + String localName = documentElement.getLocalName(); + String type = documentElement.getAttribute("type"); + NodeList outtradeno = documentElement.getElementsByTagName("outtradeno"); + if (outtradeno.getLength()>0) { + Console.log(outtradeno.item(0).getTextContent()); + } + + } + @Test + void 扫码支付2duan(){ + String smg = "{\"content\":\" \\t<![CDATA[微信支付收款0.01元(朋友到店)]]> \\t \\t \\t5 \\t1 0 \\t \\t0 \\t \\t \\t \\t\\t0 \\t\\t \\t\\t \\t\\t \\t\\t \\t\\t \\t \\t \\t \\t \\t \\t\\t \\t\\t\\t \\t\\t\\t \\t\\t\\t\\t \\t\\t\\t\\t0 \\t\\t\\t\\t0 \\t\\t\\t\\t \\t\\t\\t \\t\\t\\t\\t \\t4 \\t<![CDATA[微信支付收款0.01元(朋友到店)]]> \\t \\t \\t \\t1685325848 \\t \\t \\t \\t0 \\t \\t \\t \\t \\t \\t\\n\\n\\n\\n\\n\\t 0 0 0 \\t \\t \\t1 \\t \\t \\t967 \\t0 0 0 \\t0 \\t \\t \\t\\t0\\t \\t 0 0 0 0 \\t \\t \\t\\t \\t\\t \\t\\t\\t \\t\\t\\t \\t\\t \\t\\t<![CDATA[收款到账通知]]>\\n\\n1685325848\\n\\n\\n1\\n1\\n\\n\\n0\\n\\n\\n1\\n1\\n1\\n0\\n\\n0\\n\\n\\n\\n0\\n\\n\\n\\n\\n \\t\\t1\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n1\\n\\n\\n\\n\\n1\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n1\\n967\\n0\\n\\n0\\n0\\n\\n0\\n\\n\\n\\n0\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n1\\n967\\n0\\n\\n0\\n0\\n\\n0\\n\\n\\n\\n0\\n\\n\\n1\\n\\n0\\n \\t 0 \\t \\t \\t \\t 01\",\"fromGroup\":\"gh_f0a92aa7146c\",\"fromUser\":\"gh_f0a92aa7146c\",\"isSendMsg\":0,\"msgId\":4286533159027281478,\"pid\":14268,\"sign\":\"65bd099003179e79b95fbbab461d1f6c\",\"signature\":\"\\n\\t3\\n\\t\\n\\t\\t2\\n\\t\\t\\n\\t\\n\\tv1_xf1t7z4t\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-05-29 10:04:09\",\"timestamp\":1685325849,\"type\":49}"; + + WxMsgHandle.解析扫码支付第二段(new JsonObject(smg).mapTo(PrivateChatMsg.class)); + } + + + @Test + void 解析名片(){ + String con = "{\"content\":\"\\n\\n\",\"fromGroup\":\"filehelper\",\"fromUser\":\"filehelper\",\"isSendByPhone\":1,\"isSendMsg\":1,\"msgId\":3235211232446491438,\"pid\":21868,\"sign\":\"bfb1db52fe99dc947586af50e6964c37\",\"signature\":\"\\n\\tv1_aebFg5gw\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-06-01 16:48:39\",\"timestamp\":1685609319,\"type\":42}"; + PrivateChatMsg privateChatMsg = new JsonObject(con).mapTo(PrivateChatMsg.class); + Document document = XmlUtil.parseXml(privateChatMsg.getContent()); + Element documentElement = document.getDocumentElement(); + String username = documentElement.getAttribute("username"); + String alias = documentElement.getAttribute("alias"); + Console.log(alias,username); + } + + @Test + void 公众号(){ + String con = "{\"content\":\"请问您指的是账单的什么问题呢?请回复数字选择:\\n1.如何导出微信账单记录\\n2.如何查看已删除账单\\n3.怎么删除交易记录\",\"fromGroup\":\"gh_3dfda90e39d6\",\"fromUser\":\"gh_3dfda90e39d6\",\"isSendMsg\":0,\"msgId\":9025889923001869810,\"pid\":9920,\"sign\":\"0a66d3dab6b64ca646f512cd278d5f3d\",\"signature\":\"\\n\\t3\\n\\t\\n\\t\\t3\\n\\t\\t\\n\\t\\n\\t1\\n\\t1\\n\\t13\\n\\tv1_iJNyfNLb\\n\\t\\n\\t\\t<![CDATA[]]>\\n\\t\\n\\n\",\"time\":\"2023-06-05 11:32:17\",\"timestamp\":1685935937,\"type\":1}"; + PrivateChatMsg privateChatMsg = new JsonObject(con).mapTo(PrivateChatMsg.class); + Document document = XmlUtil.parseXml(privateChatMsg.getSignature()); + Element documentElement = document.getDocumentElement(); + } +} diff --git a/java_client/src/test/java/com/example/wxhk/util/HttpSendUtilTest.java b/java_client/src/test/java/com/example/wxhk/util/HttpSendUtilTest.java new file mode 100644 index 0000000..442acae --- /dev/null +++ b/java_client/src/test/java/com/example/wxhk/util/HttpSendUtilTest.java @@ -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 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(获取群成员); + } +} \ No newline at end of file diff --git a/python/decrypt.py b/python/decrypt.py new file mode 100644 index 0000000..eae7a97 --- /dev/null +++ b/python/decrypt.py @@ -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() \ No newline at end of file diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt new file mode 100644 index 0000000..9e36448 --- /dev/null +++ b/source/CMakeLists.txt @@ -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 "") + \ No newline at end of file diff --git a/source/ConsoleApplication.cpp b/source/ConsoleApplication.cc similarity index 85% rename from source/ConsoleApplication.cpp rename to source/ConsoleApplication.cc index d06f638..c86dc6d 100644 --- a/source/ConsoleApplication.cpp +++ b/source/ConsoleApplication.cc @@ -570,6 +570,38 @@ HMODULE GetDLLHandle(wchar_t* wDllName, DWORD dPid) return result; } +BOOL EnableDebugPrivilege() +{ + HANDLE TokenHandle = NULL; + TOKEN_PRIVILEGES TokenPrivilege; + + LUID uID; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &TokenHandle)) { + if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &uID)) { + TokenPrivilege.PrivilegeCount = 1; + TokenPrivilege.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + TokenPrivilege.Privileges[0].Luid = uID; + if (AdjustTokenPrivileges(TokenHandle, FALSE, &TokenPrivilege, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) { + CloseHandle(TokenHandle); + TokenHandle = INVALID_HANDLE_VALUE; + return TRUE; + } + else + goto fail; + + } + else + goto fail; + } + else + goto fail; + +fail: + CloseHandle(TokenHandle); + TokenHandle = INVALID_HANDLE_VALUE; + return FALSE; +} + static unsigned char GetProcAddressAsmCode[] = { 0x55, // push ebp; @@ -614,44 +646,12 @@ LPVOID FillAsmCode(HANDLE handle) { } -BOOL RemoteLibraryFunction(HANDLE hProcess, LPCSTR lpModuleName, LPCSTR lpProcName, LPVOID lpParameters, SIZE_T dwParamSize, PVOID* ppReturn) -{ - LPVOID lpRemoteParams = NULL; - - LPVOID lpFunctionAddress = GetProcAddress(GetModuleHandleA(lpModuleName), lpProcName); - if (!lpFunctionAddress) lpFunctionAddress = GetProcAddress(LoadLibraryA(lpModuleName), lpProcName); - if (!lpFunctionAddress) goto ErrorHandler; - - if (lpParameters) - { - lpRemoteParams = VirtualAllocEx(hProcess, NULL, dwParamSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); - if (!lpRemoteParams) goto ErrorHandler; - - SIZE_T dwBytesWritten = 0; - BOOL result = WriteProcessMemory(hProcess, lpRemoteParams, lpParameters, dwParamSize, &dwBytesWritten); - if (!result || dwBytesWritten < 1) goto ErrorHandler; - } - - HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpFunctionAddress, lpRemoteParams, NULL, NULL); - if (!hThread) goto ErrorHandler; - - DWORD dwOut = 0; - while (GetExitCodeThread(hThread, &dwOut)) { - if (dwOut != STILL_ACTIVE) { - *ppReturn = (PVOID)dwOut; - break; - } - } - - return TRUE; - -ErrorHandler: - if (lpRemoteParams) VirtualFreeEx(hProcess, lpRemoteParams, dwParamSize, MEM_RELEASE); - return FALSE; -} int InjectDllAndStartHttp(wchar_t* szPName, wchar_t* szDllPath, DWORD port) { + if(!EnableDebugPrivilege()){ + return 0; + } int result = 0; HANDLE hRemoteThread = NULL; LPTHREAD_START_ROUTINE lpSysLibAddr = NULL; @@ -660,9 +660,9 @@ int InjectDllAndStartHttp(wchar_t* szPName, wchar_t* szDllPath, DWORD port) HANDLE hProcess; unsigned int dwPid; size_t ulDllLength; - wchar_t* dllName = L"wxhelper.dll"; + wchar_t* dllName = (wchar_t*)L"wxhelper.dll"; size_t dllNameLen = wcslen(dllName) * 2 + 2; - char* funcName = "http_start"; + char* funcName = (char* )"http_start"; size_t funcNameLen = strlen(funcName) + 1; HANDLE hStartHttp = NULL; @@ -771,8 +771,133 @@ error: return result; } +int InjectDllAndStartHttpByPid(unsigned int pid, wchar_t* szDllPath, DWORD port) +{ + if(!EnableDebugPrivilege()){ + return 0; + } + int result = 0; + HANDLE hRemoteThread = NULL; + LPTHREAD_START_ROUTINE lpSysLibAddr = NULL; + HINSTANCE__* hKernelModule = NULL; + LPVOID lpRemoteDllBase = NULL; + HANDLE hProcess; + size_t ulDllLength; + wchar_t* dllName = (wchar_t*)L"wxhelper.dll"; + size_t dllNameLen = wcslen(dllName) * 2 + 2; + char* funcName = (char* )"http_start"; + size_t funcNameLen = strlen(funcName) + 1; + + HANDLE hStartHttp = NULL; + LPVOID portAddr = NULL; + HANDLE getProcThread = NULL; + + LPVOID paramsAddr = NULL; + LPVOID param1Addr = NULL; + LPVOID param2Addr = NULL; + LPVOID GetProcFuncAddr = NULL; + + DWORD params[2] = { 0 }; + + ulDllLength = (wcslen(szDllPath) + 1) * sizeof(wchar_t); + hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pid); + if (!hProcess) { + goto error; + } + + lpRemoteDllBase = VirtualAllocEx(hProcess, NULL, ulDllLength, MEM_COMMIT, PAGE_READWRITE); + if (lpRemoteDllBase) + { + if (WriteProcessMemory(hProcess, lpRemoteDllBase, szDllPath, ulDllLength, NULL) + && (hKernelModule = GetModuleHandleW(L"kernel32.dll")) != 0 + && (lpSysLibAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernelModule, "LoadLibraryW")) != 0 + && (hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, lpSysLibAddr, lpRemoteDllBase, 0, NULL)) != 0) + { + WaitForSingleObject(hRemoteThread, INFINITE); + GetProcFuncAddr = FillAsmCode(hProcess); + param1Addr = VirtualAllocEx(hProcess, NULL, dllNameLen, MEM_COMMIT, PAGE_READWRITE); + if (param1Addr) { + SIZE_T dwWriteSize; + BOOL bRet = WriteProcessMemory(hProcess, (LPVOID)param1Addr, dllName, dllNameLen, &dwWriteSize); + if (!bRet) { + goto error; + } + } + param2Addr = VirtualAllocEx(hProcess, NULL, funcNameLen, MEM_COMMIT, PAGE_READWRITE); + if (param2Addr) { + SIZE_T dwWriteSize; + BOOL bRet = WriteProcessMemory(hProcess, (LPVOID)param2Addr, funcName, funcNameLen, &dwWriteSize); + if (!bRet) { + goto error; + } + } + + params[0] = (DWORD)param1Addr; + params[1] = (DWORD)param2Addr; + + paramsAddr = VirtualAllocEx(hProcess, NULL, sizeof(params), MEM_COMMIT, PAGE_READWRITE); + if (paramsAddr) { + SIZE_T dwWriteSize; + BOOL bRet = WriteProcessMemory(hProcess, (LPVOID)paramsAddr, ¶ms[0], sizeof(params), &dwWriteSize); + if (!bRet) { + goto error; + } + } + + DWORD dwRet = 0; + getProcThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcFuncAddr, paramsAddr, 0, NULL); + + if (getProcThread) + { + WaitForSingleObject(getProcThread, INFINITE); + GetExitCodeThread(getProcThread, &dwRet); + if (dwRet) { + hStartHttp = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)dwRet, (LPVOID)port, 0, NULL); + WaitForSingleObject(hStartHttp, INFINITE); + result = 1; + } + } + } + } +error: + if (hRemoteThread) { + CloseHandle(hRemoteThread); + } + if (getProcThread) { + CloseHandle(getProcThread); + } + if (hStartHttp) { + CloseHandle(hStartHttp); + } + + if (lpRemoteDllBase) { + VirtualFreeEx(hProcess, lpRemoteDllBase, ulDllLength, MEM_DECOMMIT | MEM_RELEASE); + } + if (param1Addr) { + VirtualFreeEx(hProcess, param1Addr, dllNameLen, MEM_DECOMMIT | MEM_RELEASE); + } + + if (param2Addr) { + VirtualFreeEx(hProcess, param1Addr, funcNameLen, MEM_DECOMMIT | MEM_RELEASE); + } + + if (paramsAddr) { + VirtualFreeEx(hProcess, param1Addr, sizeof(params), MEM_DECOMMIT | MEM_RELEASE); + } + + if (GetProcFuncAddr) { + VirtualFreeEx(hProcess, GetProcFuncAddr, sizeof(GetProcAddressAsmCode), MEM_DECOMMIT | MEM_RELEASE); + } + + CloseHandle(hProcess); + return result; +} + int InjectDll(wchar_t* szPName, wchar_t* szDllPath) { + if(!EnableDebugPrivilege()){ + return 0; + } int result = 0; HANDLE hRemoteThread; LPTHREAD_START_ROUTINE lpSysLibAddr; @@ -802,6 +927,61 @@ int InjectDll(wchar_t* szPName, wchar_t* szDllPath) CloseHandle(hRemoteThread); CloseHandle(hProcess); OutputDebugStringA("[DBG] dll inject success"); + printf("dll inject success"); + printf("dll path : %s ", szDllPath); + printf("dll path : %d ", dwPid); + result = 1; + } + else + { + VirtualFreeEx(hProcess, lpRemoteDllBase, ulDllLength, MEM_DECOMMIT | MEM_RELEASE); + CloseHandle(hProcess); + result = 0; + } + } + else + { + CloseHandle(hProcess); + result = 0; + } + return result; +} + +int InjectDllByPid(unsigned int pid, wchar_t* szDllPath) +{ + if(!EnableDebugPrivilege()){ + return 0; + } + int result = 0; + HANDLE hRemoteThread; + LPTHREAD_START_ROUTINE lpSysLibAddr; + HINSTANCE__* hKernelModule; + LPVOID lpRemoteDllBase; + HANDLE hProcess; + size_t ulDllLength; + + ulDllLength = (wcslen(szDllPath) + 1) * sizeof(wchar_t); + hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pid); + if (!hProcess) { + return 0; + } + + lpRemoteDllBase = VirtualAllocEx(hProcess, NULL, ulDllLength, MEM_COMMIT, PAGE_READWRITE); + if (lpRemoteDllBase) + { + if (WriteProcessMemory(hProcess, lpRemoteDllBase, szDllPath, ulDllLength, NULL) + && (hKernelModule = GetModuleHandleW(L"kernel32.dll")) != 0 + && (lpSysLibAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hKernelModule, "LoadLibraryW")) != 0 + && (hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, lpSysLibAddr, lpRemoteDllBase, 0, NULL)) != 0) + { + WaitForSingleObject(hRemoteThread, INFINITE); + VirtualFreeEx(hProcess, lpRemoteDllBase, ulDllLength, MEM_DECOMMIT | MEM_RELEASE); + CloseHandle(hRemoteThread); + CloseHandle(hProcess); + OutputDebugStringA("[DBG] dll inject success"); + printf("dll inject success"); + printf("dll path : %s ", szDllPath); + printf("pid : %d ", pid); result = 1; } else @@ -872,8 +1052,9 @@ int main(int argc, char** argv) int port = 0; ULONG pid = 0; + unsigned int injectPid =0; - while ((param = getopt(argc, argv, "i:p:u:d:m:P:h")) != -1) + while ((param = getopt(argc, argv, "i:p:u:d:m:P:I:h")) != -1) { switch (param) { @@ -910,6 +1091,9 @@ int main(int argc, char** argv) case 'P': port = std::atoi(optarg); break; + case 'I': + injectPid = std::atoi(optarg); + break; default: abort(); break; @@ -917,8 +1101,26 @@ int main(int argc, char** argv) } if (pid) { - FindHandles(pid, "_WeChat_App_Instance_Identity_Mutex_Name", TRUE, TRUE); + FindHandles(pid, (LPSTR)"_WeChat_App_Instance_Identity_Mutex_Name", TRUE, TRUE); } + if (injectPid != 0 && cDllPath[0] != 0) + { + if(cDllPath[0] != '\0') + { + if (port == 0) { + std::wstring wsPath = Utf8ToUnicode(cDllPath); + int ret = InjectDllByPid(injectPid, (wchar_t*)wsPath.c_str()); + printf(" 注入结果:%i \n", ret); + } + else + { + std::wstring wsPath = Utf8ToUnicode(cDllPath); + int ret = InjectDllAndStartHttpByPid(injectPid, (wchar_t*)wsPath.c_str(), port); + printf(" 注入结果:%i \n", ret); + } + } + } + if (cInjectprogram[0] != 0 && cDllPath[0] != 0) { diff --git a/tool/injector/injector.dll b/tool/injector/injector.dll new file mode 100644 index 0000000..eb61d1c Binary files /dev/null and b/tool/injector/injector.dll differ diff --git a/tool/injector/readme.md b/tool/injector/readme.md index e505953..d2fe096 100644 --- a/tool/injector/readme.md +++ b/tool/injector/readme.md @@ -1 +1 @@ -## 可以使用3.9.0.28 分支下的注入工具,或者自己编译一下 source目录下的注入程序。 \ No newline at end of file +## 可以使用对应分支下的注入工具,或者自己编译一下 source目录下的注入程序。 diff --git a/weChatHook-java/.idea/.gitignore b/weChatHook-java/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/weChatHook-java/.idea/.gitignore @@ -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 diff --git a/weChatHook-java/.idea/compiler.xml b/weChatHook-java/.idea/compiler.xml new file mode 100644 index 0000000..0264bce --- /dev/null +++ b/weChatHook-java/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/weChatHook-java/.idea/inspectionProfiles/Project_Default.xml b/weChatHook-java/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..6560a98 --- /dev/null +++ b/weChatHook-java/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,36 @@ + + + + \ No newline at end of file diff --git a/weChatHook-java/.idea/jarRepositories.xml b/weChatHook-java/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/weChatHook-java/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/weChatHook-java/.idea/misc.xml b/weChatHook-java/.idea/misc.xml new file mode 100644 index 0000000..132404b --- /dev/null +++ b/weChatHook-java/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/weChatHook-java/README.md b/weChatHook-java/README.md new file mode 100644 index 0000000..be80b0f --- /dev/null +++ b/weChatHook-java/README.md @@ -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); + }); +} +``` \ No newline at end of file diff --git a/weChatHook-java/pom.xml b/weChatHook-java/pom.xml new file mode 100644 index 0000000..5fa9c97 --- /dev/null +++ b/weChatHook-java/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + org.example + weChatHook-java + 1.0 + + + 8 + 8 + + + + + org.jsoup + jsoup + 1.14.3 + + + + com.alibaba + fastjson + 1.2.83 + + + + org.projectlombok + lombok + 1.18.24 + + + + + io.netty + netty-all + 4.1.51.Final + + + junit + junit + 4.13.2 + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + true + com.example.service.WeChatHookNettyServer + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + + \ No newline at end of file diff --git a/weChatHook-java/src/main/java/com/example/client/WeChatHookClient.java b/weChatHook-java/src/main/java/com/example/client/WeChatHookClient.java new file mode 100644 index 0000000..df214cb --- /dev/null +++ b/weChatHook-java/src/main/java/com/example/client/WeChatHookClient.java @@ -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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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); + } + +} diff --git a/weChatHook-java/src/main/java/com/example/service/WeChatHookNettyServer.java b/weChatHook-java/src/main/java/com/example/service/WeChatHookNettyServer.java new file mode 100644 index 0000000..600ed01 --- /dev/null +++ b/weChatHook-java/src/main/java/com/example/service/WeChatHookNettyServer.java @@ -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() { + @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 { + + 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(); + } + } +} diff --git a/weChatHook-java/src/main/resources/ConsoleInject.exe b/weChatHook-java/src/main/resources/ConsoleInject.exe new file mode 100644 index 0000000..ed0fe3f Binary files /dev/null and b/weChatHook-java/src/main/resources/ConsoleInject.exe differ diff --git a/weChatHook-java/src/main/resources/injector.dll b/weChatHook-java/src/main/resources/injector.dll new file mode 100644 index 0000000..eb61d1c Binary files /dev/null and b/weChatHook-java/src/main/resources/injector.dll differ diff --git a/weChatHook-java/src/main/resources/wxhelper.dll b/weChatHook-java/src/main/resources/wxhelper.dll new file mode 100644 index 0000000..49439ae Binary files /dev/null and b/weChatHook-java/src/main/resources/wxhelper.dll differ