This commit is contained in:
yincong
2026-05-08 12:00:05 +08:00
parent c5217ecbde
commit 50e7dd960e
6 changed files with 329 additions and 52 deletions
+12 -12
View File
@@ -20,21 +20,21 @@ func sendHandler(w http.ResponseWriter, r *http.Request) {
Error("仅支持 POST")
return
}
req := new(SendRequest)
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
http.Error(w, "无效的 JSON", http.StatusBadRequest)
Error("无效的 JSON")
return
}
// 参数校验
if len(req.Message) == 0 || (req.UserID == "" && req.GroupID == "") {
http.Error(w, "参数缺失", http.StatusBadRequest)
Error("参数缺失")
return
}
sendContent := ""
atUserID := ""
for _, v := range req.Message {
@@ -47,7 +47,7 @@ func sendHandler(w http.ResponseWriter, r *http.Request) {
atUserID += v.Data.QQ + ","
}
}
} else if v.Type == "image" || v.Type == "video" {
msgChan <- &SendMsg{
UserId: req.UserID,
@@ -57,7 +57,7 @@ func sendHandler(w http.ResponseWriter, r *http.Request) {
}
}
}
if sendContent != "" {
msgChan <- &SendMsg{
UserId: req.UserID,
@@ -67,7 +67,7 @@ func sendHandler(w http.ResponseWriter, r *http.Request) {
AtUser: strings.TrimRight(atUserID, ","),
}
}
json.NewEncoder(w).Encode(map[string]any{
"status": "ok",
})
@@ -79,7 +79,7 @@ func SendHttpReq(jsonData []byte) {
Error("http panic", "err", r, "stack", string(debug.Stack()))
}
}()
time.Sleep(time.Duration(config.SendInterval) * time.Millisecond)
jsonReq, err := HandleMsg(jsonData)
if err != nil {
@@ -89,20 +89,20 @@ func SendHttpReq(jsonData []byte) {
if jsonReq == nil {
return
}
Info("发送数据", "msg", string(jsonReq))
req, err := http.NewRequest("POST", config.SendURL, bytes.NewBuffer(jsonReq))
if err != nil {
Error("创建请求失败", "err", err)
return
}
// 5. 设置 Header (OneBot 接口通常要求 application/json)
h := hmac.New(sha1.New, []byte(config.OnebotToken))
h.Write(jsonReq)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Signature", "sha1="+hex.EncodeToString(h.Sum(nil)))
client := &http.Client{
Timeout: 10 * time.Second,
}
@@ -113,13 +113,13 @@ func SendHttpReq(jsonData []byte) {
return
}
defer resp.Body.Close()
// 7. 读取返回结果
body, err := io.ReadAll(resp.Body)
if err != nil {
Error("读取响应失败", "err", err)
return
}
Info("返回内容", "status", resp.StatusCode, "body", string(body))
}
+228
View File
@@ -0,0 +1,228 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v3.5.1
// source: proto/wxproto/text_msg.proto
package wxproto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// 发送文本消息的protobuf结构
type WxSendTextMsg struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type int32 `protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"`
Body *WxSendTextBody `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WxSendTextMsg) Reset() {
*x = WxSendTextMsg{}
mi := &file_proto_wxproto_text_msg_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WxSendTextMsg) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WxSendTextMsg) ProtoMessage() {}
func (x *WxSendTextMsg) ProtoReflect() protoreflect.Message {
mi := &file_proto_wxproto_text_msg_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use WxSendTextMsg.ProtoReflect.Descriptor instead.
func (*WxSendTextMsg) Descriptor() ([]byte, []int) {
return file_proto_wxproto_text_msg_proto_rawDescGZIP(), []int{0}
}
func (x *WxSendTextMsg) GetType() int32 {
if x != nil {
return x.Type
}
return 0
}
func (x *WxSendTextMsg) GetBody() *WxSendTextBody {
if x != nil {
return x.Body
}
return nil
}
type WxSendTextBody struct {
state protoimpl.MessageState `protogen:"open.v1"`
Receiver *WxString `protobuf:"bytes,1,opt,name=receiver,proto3" json:"receiver,omitempty"`
Content []byte `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
Flag int32 `protobuf:"varint,3,opt,name=flag,proto3" json:"flag,omitempty"`
Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
MsgId int64 `protobuf:"varint,5,opt,name=msg_id,json=msgId,proto3" json:"msg_id,omitempty"`
Xml []byte `protobuf:"bytes,6,opt,name=xml,proto3" json:"xml,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WxSendTextBody) Reset() {
*x = WxSendTextBody{}
mi := &file_proto_wxproto_text_msg_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *WxSendTextBody) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*WxSendTextBody) ProtoMessage() {}
func (x *WxSendTextBody) ProtoReflect() protoreflect.Message {
mi := &file_proto_wxproto_text_msg_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use WxSendTextBody.ProtoReflect.Descriptor instead.
func (*WxSendTextBody) Descriptor() ([]byte, []int) {
return file_proto_wxproto_text_msg_proto_rawDescGZIP(), []int{1}
}
func (x *WxSendTextBody) GetReceiver() *WxString {
if x != nil {
return x.Receiver
}
return nil
}
func (x *WxSendTextBody) GetContent() []byte {
if x != nil {
return x.Content
}
return nil
}
func (x *WxSendTextBody) GetFlag() int32 {
if x != nil {
return x.Flag
}
return 0
}
func (x *WxSendTextBody) GetTimestamp() int64 {
if x != nil {
return x.Timestamp
}
return 0
}
func (x *WxSendTextBody) GetMsgId() int64 {
if x != nil {
return x.MsgId
}
return 0
}
func (x *WxSendTextBody) GetXml() []byte {
if x != nil {
return x.Xml
}
return nil
}
var File_proto_wxproto_text_msg_proto protoreflect.FileDescriptor
const file_proto_wxproto_text_msg_proto_rawDesc = "" +
"\n" +
"\x1cproto/wxproto/text_msg.proto\x12\awxproto\x1a\x1aproto/wxproto/wx_msg.proto\"P\n" +
"\rWxSendTextMsg\x12\x12\n" +
"\x04type\x18\x01 \x01(\x05R\x04type\x12+\n" +
"\x04body\x18\x02 \x01(\v2\x17.wxproto.WxSendTextBodyR\x04body\"\xb4\x01\n" +
"\x0eWxSendTextBody\x12-\n" +
"\breceiver\x18\x01 \x01(\v2\x11.wxproto.WxStringR\breceiver\x12\x18\n" +
"\acontent\x18\x02 \x01(\fR\acontent\x12\x12\n" +
"\x04flag\x18\x03 \x01(\x05R\x04flag\x12\x1c\n" +
"\ttimestamp\x18\x04 \x01(\x03R\ttimestamp\x12\x15\n" +
"\x06msg_id\x18\x05 \x01(\x03R\x05msgId\x12\x10\n" +
"\x03xml\x18\x06 \x01(\fR\x03xmlB>Z<github.com/yincongcyincong/weixin-macos/onebot/proto/wxprotob\x06proto3"
var (
file_proto_wxproto_text_msg_proto_rawDescOnce sync.Once
file_proto_wxproto_text_msg_proto_rawDescData []byte
)
func file_proto_wxproto_text_msg_proto_rawDescGZIP() []byte {
file_proto_wxproto_text_msg_proto_rawDescOnce.Do(func() {
file_proto_wxproto_text_msg_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_wxproto_text_msg_proto_rawDesc), len(file_proto_wxproto_text_msg_proto_rawDesc)))
})
return file_proto_wxproto_text_msg_proto_rawDescData
}
var file_proto_wxproto_text_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proto_wxproto_text_msg_proto_goTypes = []any{
(*WxSendTextMsg)(nil), // 0: wxproto.WxSendTextMsg
(*WxSendTextBody)(nil), // 1: wxproto.WxSendTextBody
(*WxString)(nil), // 2: wxproto.WxString
}
var file_proto_wxproto_text_msg_proto_depIdxs = []int32{
1, // 0: wxproto.WxSendTextMsg.body:type_name -> wxproto.WxSendTextBody
2, // 1: wxproto.WxSendTextBody.receiver:type_name -> wxproto.WxString
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_proto_wxproto_text_msg_proto_init() }
func file_proto_wxproto_text_msg_proto_init() {
if File_proto_wxproto_text_msg_proto != nil {
return
}
file_proto_wxproto_wx_msg_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wxproto_text_msg_proto_rawDesc), len(file_proto_wxproto_text_msg_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proto_wxproto_text_msg_proto_goTypes,
DependencyIndexes: file_proto_wxproto_text_msg_proto_depIdxs,
MessageInfos: file_proto_wxproto_text_msg_proto_msgTypes,
}.Build()
File_proto_wxproto_text_msg_proto = out.File
file_proto_wxproto_text_msg_proto_goTypes = nil
file_proto_wxproto_text_msg_proto_depIdxs = nil
}
+22
View File
@@ -0,0 +1,22 @@
syntax = "proto3";
package wxproto;
option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto";
import "proto/wxproto/wx_msg.proto";
// 发送文本消息的protobuf结构
message WxSendTextMsg {
int32 type = 1;
WxSendTextBody body = 2;
}
message WxSendTextBody {
WxString receiver = 1;
bytes content = 2;
int32 flag = 3;
int64 timestamp = 4;
int64 msg_id = 5;
bytes xml = 6;
}
+18 -39
View File
@@ -6,6 +6,14 @@ if (!baseAddr) {
console.log("[+] WeChat base address: " + baseAddr);
// -------------------------基础函数分区-------------------------
function hexToByteArray(hexStr) {
var bytes = [];
for (var i = 0; i < hexStr.length; i += 2) {
bytes.push(parseInt(hexStr.substr(i, 2), 16));
}
return bytes;
}
function toVarint(n) {
let res = [];
while (n >= 128) {
@@ -206,6 +214,8 @@ var videoCdnKeyGlobal = "";
var videoAesKeyGlobal = "";
var videoMd5KeyGlobal = "";
var videoIdGlobal = "";
// 文本消息protobuf全局变量 (从Go直接传入hex编码)
var textProtoHexGlobal = "";
const fileCp = generateBytes(16)
@@ -303,7 +313,7 @@ function patchTextProtoBuf() {
setImmediate(patchTextProtoBuf);
function triggerSendTextMessage(taskId, receiver, content, atUser) {
function triggerSendTextMessage(taskId, receiver, content, atUser, protoHex) {
// console.log("[+] Manual Trigger Started...");
if (!taskId || !receiver || !content) {
console.error("[!] taskId or Receiver or Content is empty!");
@@ -321,7 +331,8 @@ function triggerSendTextMessage(taskId, receiver, content, atUser) {
taskIdGlobal = taskId;
receiverGlobal = receiver;
contentGlobal = content;
atUserGlobal = atUser
atUserGlobal = atUser;
textProtoHexGlobal = protoHex;
console.log("triggerSendTextMessage: receiver=" + receiverGlobal);
textMessageAddr.add(0x08).writeU32(taskIdGlobal);
@@ -434,48 +445,16 @@ function attachSendTextProto() {
}
console.log(`[+] 注入 Protobuf: receiver=${receiverGlobal}`);
const type = [0x08, 0x01, 0x12]
const receiverHeader = [0x0A, receiverGlobal.length + 2, 0x0A, receiverGlobal.length];
const receiverProto = stringToHexArray(receiverGlobal);
const contentProto = stringToHexArray(contentGlobal);
const contentHeader = [0x12, ...toVarint(contentProto.length)];
const tsHeader = [0x18, 0x01, 0x20];
const tsBytes = getVarintTimestampBytes();
const msgIdHeader = [0x28]
const msgId = generateRandom5ByteVarint()
const htmlUpperPart = [0x3C, 0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x3E]
let atUserHeader = []
if (atUserGlobal) {
atUserHeader = atUserHeader.concat([0x3C, 0x61, 0x74, 0x75, 0x73, 0x65, 0x72, 0x6c, 0x69, 0x73, 0x74, 0x3e]).concat(stringToHexArray(atUserGlobal)).concat([0x3C, 0x2F, 0x61, 0x74, 0x75, 0x73, 0x65, 0x72, 0x6C, 0x69, 0x73, 0x74, 0x3E])
// 使用Go传入的protobuf数据
if (!textProtoHexGlobal || textProtoHexGlobal.length === 0) {
console.error("[!] textProtoHexGlobal 为空");
return;
}
const htmlLowerPart = [0x3C, 0x61, 0x6C, 0x6E, 0x6F,
0x64, 0x65, 0x3E, 0x3C, 0x66, 0x72, 0x3E, 0x31,
0x3C, 0x2F, 0x66, 0x72, 0x3E, 0x3C, 0x2F, 0x61,
0x6C, 0x6E, 0x6F, 0x64, 0x65, 0x3E, 0x3C, 0x2F,
0x6D, 0x73, 0x67, 0x73, 0x6F, 0x75, 0x72,
0x63, 0x65, 0x3E, 0x00]
const htmlHeader = [0x32, htmlUpperPart.length + atUserHeader.length + htmlLowerPart.length]
const valueLen = toVarint(receiverHeader.length + receiverProto.length + contentHeader.length +
contentProto.length + tsHeader.length + tsBytes.length + msgIdHeader.length + msgId.length + htmlHeader.length +
htmlUpperPart.length + atUserHeader.length + htmlLowerPart.length)
// 合并数组
const finalPayload = type.concat(valueLen).concat(receiverHeader).concat(receiverProto).concat(contentHeader).concat(contentProto).concat(tsHeader).concat(tsBytes).concat(msgIdHeader).concat(msgId).concat(htmlHeader).concat(htmlUpperPart).concat(atUserHeader).concat(htmlLowerPart);
const finalPayload = hexToByteArray(textProtoHexGlobal);
textProtoX1PayloadAddr.writeByteArray(finalPayload);
this.context.x1 = textProtoX1PayloadAddr;
this.context.x2 = ptr(finalPayload.length);
// console.log("[+] 文本寄存器修改完成: X1=" + this.context.x1 + ", X2=" + this.context.x2, hexdump(textProtoX1PayloadAddr, {
// offset: 0,
// length: 128,
// header: true,
// ansi: true
// }));
},
});
}
+43
View File
@@ -0,0 +1,43 @@
package main
import (
"encoding/hex"
"fmt"
"math/rand"
"time"
"github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto"
"google.golang.org/protobuf/proto"
)
// BuildTextMsgProto 构建发送文本消息的protobuf并返回hex编码的字符串
func BuildTextMsgProto(receiver, content, atUser string) (string, error) {
// 构建 msgsource XML
xmlStr := "<msgsource>"
if atUser != "" {
xmlStr += "<atuserlist>" + atUser + "</atuserlist>"
}
xmlStr += "<alnode><fr>1</fr></alnode></msgsource>\x00"
// 生成随机消息ID
msgId := rand.Int63n(1<<34) | (1 << 34)
msg := &wxproto.WxSendTextMsg{
Type: 1,
Body: &wxproto.WxSendTextBody{
Receiver: &wxproto.WxString{Value: receiver},
Content: []byte(content),
Flag: 1,
Timestamp: time.Now().Unix(),
MsgId: msgId,
Xml: []byte(xmlStr),
},
}
data, err := proto.Marshal(msg)
if err != nil {
return "", fmt.Errorf("marshal text proto failed: %w", err)
}
return hex.EncodeToString(data), nil
}
+6 -1
View File
@@ -53,7 +53,12 @@ func SendWechatMsg(m *SendMsg) {
switch m.Type {
case "text":
result := fridaScript.ExportsCall("triggerSendTextMessage", currTaskId, targetId, m.Content, m.AtUser)
protoHex, err := BuildTextMsgProto(targetId, m.Content, m.AtUser)
if err != nil {
Error("构建文本protobuf失败", "err", err)
return
}
result := fridaScript.ExportsCall("triggerSendTextMessage", currTaskId, targetId, m.Content, m.AtUser, protoHex)
Info("📩 发送文本任务执行结果", "result", result, "task_id", currTaskId, "target_id", targetId, "at_user", m.AtUser)
if result != "1" {
Error("发送文本失败", "task_id", currTaskId, "target_id", targetId, "result", result)