mirror of
https://github.com/yincongcyincong/wechat_chatter.git
synced 2026-07-15 10:26:52 +08:00
finish 4.1.9.52
This commit is contained in:
+13
-6
@@ -23,7 +23,7 @@ function scanPatterns() {
|
||||
console.log(`"${name}": "${offset}", 绝对地址: ${address}`);
|
||||
break;
|
||||
case "downloadFileAddr":
|
||||
console.log(`"${name}": "${offset}", 绝对地址: ${address}`);
|
||||
console.log(`"${name}": "${offset.sub(0x08)}", 绝对地址: ${address.sub(0x08)}`);
|
||||
break;
|
||||
case "uploadImageAddr":
|
||||
console.log(`"${name}": "${offset.sub(0x30)}", 绝对地址: ${address.sub(0x30)}`);
|
||||
@@ -32,13 +32,16 @@ function scanPatterns() {
|
||||
console.log(`"${name}": "${offset.sub(0x44)}", 绝对地址: ${address.sub(0x44)}`);
|
||||
break;
|
||||
case "buf2RespAddr":
|
||||
console.log(`"${name}": "${offset.sub(0x10)}", 绝对地址: ${address.sub(0x10)}`);
|
||||
console.log(`"${name}": "${offset.sub(0x14)}", 绝对地址: ${address.sub(0x14)}`);
|
||||
break;
|
||||
case "startDownloadMedia":
|
||||
console.log(`"${name}": "${offset.sub(0x28)}", 绝对地址: ${address.sub(0x28)}`);
|
||||
break;
|
||||
case "downloadVideoAddr":
|
||||
console.log(`"${name}": "${offset.sub(0xc)}", 绝对地址: ${address.sub(0xc)}`);
|
||||
console.log(`"${name}": "${offset.sub(0x2c)}", 绝对地址: ${address.sub(0x2c)}`);
|
||||
break;
|
||||
case "downloadImageAddr":
|
||||
console.log(`"${name}": "${offset.sub(0x18)}", 绝对地址: ${address.sub(0x18)}`);
|
||||
break;
|
||||
}
|
||||
},
|
||||
@@ -64,9 +67,13 @@ const myPatterns = [
|
||||
name: "req2bufEnterAddr",
|
||||
pattern: "09 0F 46 F8 C9 01 00 B4 E8 03 18 AA 2A 21 40 B9"
|
||||
},
|
||||
{
|
||||
name: "downloadImageAddr",
|
||||
pattern: "68 52 41 F9 19 75 41 F9 1A 00 14 8B 5F 03 19 EB"
|
||||
},
|
||||
{
|
||||
name: "downloadFileAddr",
|
||||
pattern: "08 01 40 F9 A8 83 1B F8 08 50 41 F9 08 29 41 F9"
|
||||
pattern: "FF 07 00 B9 68 52 41 F9 15 71 41 F9 E0 03 14 AA"
|
||||
},
|
||||
{
|
||||
name: "uploadImageAddr",
|
||||
@@ -78,7 +85,7 @@ const myPatterns = [
|
||||
},
|
||||
{
|
||||
name: "buf2RespAddr",
|
||||
pattern: "3C 00 80 52 E0 C3 00 91"
|
||||
pattern: "E8 DF C1 39 28 E6 FF 36 E0 33 40 F9 E8 3B 40 F9"
|
||||
},
|
||||
{
|
||||
name: "startDownloadMedia",
|
||||
@@ -86,7 +93,7 @@ const myPatterns = [
|
||||
},
|
||||
{
|
||||
name: "downloadVideoAddr",
|
||||
pattern: "F5 03 00 AA 76 43 09 91 E0 03 16 AA"
|
||||
pattern: "E0 03 01 91 01 00 80 52 A5 5B 80 52 06 00 80 52"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
+3
-2
@@ -18,7 +18,7 @@ var triggerX1Payload;
|
||||
var insertMsgAddr;
|
||||
|
||||
// 回复消息回调函数地址 (基于用户提供: 0x24BDE40)
|
||||
var replyCallbackFuncAddr = baseAddr.add(0x24BDE40);
|
||||
var replyCallbackFuncAddr = baseAddr.add(0x260C444);
|
||||
var replyProtobufAddr = replyCallbackFuncAddr.add(0x50);
|
||||
var patchReplyProtobufFunc1 = replyCallbackFuncAddr.add(0x10);
|
||||
var patchReplyProtobufFunc1Byte;
|
||||
@@ -26,13 +26,14 @@ var patchReplyProtobufFunc2 = replyCallbackFuncAddr.add(0x30);
|
||||
var patchReplyProtobufFunc2Byte;
|
||||
var replyProtobufDeleteAddr = replyCallbackFuncAddr.add(0x6c);
|
||||
var replyProtobufDeleteAddrByte;
|
||||
var replyMessageCallbackFunc1 = baseAddr.add(0x8C29A08); // 复用图片的callback函数
|
||||
|
||||
// Reply消息相关地址 - 需要导出为全局变量供file.js使用
|
||||
var sendReplyMessageAddr = ptr(0);
|
||||
var replyMessageAddr = ptr(0);
|
||||
var replyCgiAddr = ptr(0);
|
||||
var replyProtobufBufAddr = ptr(0); // 用于存储protobuf数据的缓冲区
|
||||
var replyMessageCallbackFunc1 = baseAddr.add(0x89170D0); // 复用图片的callback函数
|
||||
|
||||
|
||||
// Reply消息内容相关
|
||||
var replyContent = ""; // 回复的文本内容
|
||||
|
||||
@@ -55,6 +55,52 @@ func sendHandler(w http.ResponseWriter, r *http.Request) {
|
||||
Content: v.Data.File,
|
||||
Type: v.Type,
|
||||
}
|
||||
} else if v.Type == "reply" {
|
||||
if v.Data.ReplyMessage == nil {
|
||||
Error("reply_message为空")
|
||||
continue
|
||||
}
|
||||
rm := v.Data.ReplyMessage
|
||||
|
||||
// 提取被回复消息的内容
|
||||
referContent := ""
|
||||
referMsgType := 1 // 默认text
|
||||
if len(rm.Message) > 0 {
|
||||
switch rm.Message[0].Type {
|
||||
case "text":
|
||||
referContent = rm.Message[0].Data.Text
|
||||
referMsgType = 1
|
||||
case "image":
|
||||
referMsgType = 3
|
||||
case "video":
|
||||
referMsgType = 43
|
||||
case "file":
|
||||
referMsgType = 49
|
||||
}
|
||||
}
|
||||
|
||||
// 提取发送者昵称
|
||||
displayName := ""
|
||||
if rm.Sender != nil {
|
||||
displayName = rm.Sender.Nickname
|
||||
}
|
||||
|
||||
// msgsource需要JSON unescape(双重编码: \\u003c → \u003c → <)
|
||||
msgsource := jsonUnescapeString(rm.MsgResource)
|
||||
|
||||
msgChan <- &SendMsg{
|
||||
UserId: req.UserID,
|
||||
GroupID: req.GroupID,
|
||||
Content: v.Data.Text,
|
||||
Type: "reply",
|
||||
ReferMsgId: rm.MessageId,
|
||||
ReferMsgSender: rm.UserID,
|
||||
ReferMsgType: referMsgType,
|
||||
ReferCreateTime: rm.Time,
|
||||
ReferMsgsource: msgsource,
|
||||
ReferDisplayName: displayName,
|
||||
ReferContent: referContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,3 +169,16 @@ func SendHttpReq(jsonData []byte) {
|
||||
|
||||
Info("返回内容", "status", resp.StatusCode, "body", string(body))
|
||||
}
|
||||
|
||||
// jsonUnescapeString 对双重JSON编码的字符串做unescape
|
||||
// 例如: \\u003c → \u003c (第一次json.Unmarshal) → < (本函数)
|
||||
func jsonUnescapeString(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
var result string
|
||||
if err := json.Unmarshal([]byte(`"`+s+`"`), &result); err != nil {
|
||||
return s
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
+15
-6
@@ -63,6 +63,14 @@ type SendMsg struct {
|
||||
VideoId string
|
||||
Duration int32
|
||||
VideoSize int32
|
||||
|
||||
ReferMsgId string
|
||||
ReferMsgSender string
|
||||
ReferMsgType int
|
||||
ReferCreateTime int64
|
||||
ReferMsgsource string
|
||||
ReferDisplayName string
|
||||
ReferContent string
|
||||
}
|
||||
|
||||
type VideoInfo struct {
|
||||
@@ -83,12 +91,13 @@ type Message struct {
|
||||
}
|
||||
|
||||
type SendRequestData struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
File string `json:"file,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
QQ string `json:"qq,omitempty"`
|
||||
Media []byte `json:"media,omitempty"`
|
||||
Id string `json:"id,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
File string `json:"file,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
QQ string `json:"qq,omitempty"`
|
||||
Media []byte `json:"media,omitempty"`
|
||||
ReplyMessage *WechatMessage `json:"reply_message,omitempty"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v3.5.1
|
||||
// source: proto/wxproto/reply_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结构
|
||||
// CG路径: /cgi-bin/micromsg-bin/sendappmsg
|
||||
// 消息类型: appmsg type=57
|
||||
type WxSendReplyMsg struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Header *ReplyMsgHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"`
|
||||
Body *ReplyMsgBody `protobuf:"bytes,2,opt,name=body" json:"body,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *WxSendReplyMsg) Reset() {
|
||||
*x = WxSendReplyMsg{}
|
||||
mi := &file_proto_wxproto_reply_msg_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *WxSendReplyMsg) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*WxSendReplyMsg) ProtoMessage() {}
|
||||
|
||||
func (x *WxSendReplyMsg) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_wxproto_reply_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 WxSendReplyMsg.ProtoReflect.Descriptor instead.
|
||||
func (*WxSendReplyMsg) Descriptor() ([]byte, []int) {
|
||||
return file_proto_wxproto_reply_msg_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *WxSendReplyMsg) GetHeader() *ReplyMsgHeader {
|
||||
if x != nil {
|
||||
return x.Header
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WxSendReplyMsg) GetBody() *ReplyMsgBody {
|
||||
if x != nil {
|
||||
return x.Body
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Header: 固定64字节
|
||||
type ReplyMsgHeader struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Flag []byte `protobuf:"bytes,1,opt,name=flag" json:"flag,omitempty"` // 固定 [0x00]
|
||||
Timestamp *uint32 `protobuf:"varint,2,opt,name=timestamp" json:"timestamp,omitempty"` // 请求时间戳(秒)
|
||||
ClientProof []byte `protobuf:"bytes,3,opt,name=client_proof,json=clientProof" json:"client_proof,omitempty"` // 16字节随机数据/md5
|
||||
DeviceId *uint64 `protobuf:"varint,4,opt,name=device_id,json=deviceId" json:"device_id,omitempty"` // 设备标识(大整数)
|
||||
Platform *string `protobuf:"bytes,5,opt,name=platform" json:"platform,omitempty"` // 平台信息 "UnifiedPCMac 26 arm64"
|
||||
Version *uint32 `protobuf:"varint,6,opt,name=version" json:"version,omitempty"` // 版本/标记 = 163
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ReplyMsgHeader) Reset() {
|
||||
*x = ReplyMsgHeader{}
|
||||
mi := &file_proto_wxproto_reply_msg_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ReplyMsgHeader) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ReplyMsgHeader) ProtoMessage() {}
|
||||
|
||||
func (x *ReplyMsgHeader) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_wxproto_reply_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 ReplyMsgHeader.ProtoReflect.Descriptor instead.
|
||||
func (*ReplyMsgHeader) Descriptor() ([]byte, []int) {
|
||||
return file_proto_wxproto_reply_msg_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ReplyMsgHeader) GetFlag() []byte {
|
||||
if x != nil {
|
||||
return x.Flag
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ReplyMsgHeader) GetTimestamp() uint32 {
|
||||
if x != nil && x.Timestamp != nil {
|
||||
return *x.Timestamp
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ReplyMsgHeader) GetClientProof() []byte {
|
||||
if x != nil {
|
||||
return x.ClientProof
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ReplyMsgHeader) GetDeviceId() uint64 {
|
||||
if x != nil && x.DeviceId != nil {
|
||||
return *x.DeviceId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ReplyMsgHeader) GetPlatform() string {
|
||||
if x != nil && x.Platform != nil {
|
||||
return *x.Platform
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ReplyMsgHeader) GetVersion() uint32 {
|
||||
if x != nil && x.Version != nil {
|
||||
return *x.Version
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Body: 消息体
|
||||
type ReplyMsgBody struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Sender *string `protobuf:"bytes,1,opt,name=sender" json:"sender,omitempty"` // 接收方wxid (field1是target)
|
||||
Unknown2 []byte `protobuf:"bytes,2,opt,name=unknown2" json:"unknown2,omitempty"` // 空 (必须显式存在)
|
||||
Unknown3 *int32 `protobuf:"varint,3,opt,name=unknown3" json:"unknown3,omitempty"` // 0 (必须显式存在)
|
||||
Receiver *string `protobuf:"bytes,4,opt,name=receiver" json:"receiver,omitempty"` // 发送方wxid
|
||||
MsgType *int32 `protobuf:"varint,5,opt,name=msg_type,json=msgType" json:"msg_type,omitempty"` // 消息类型 = 57
|
||||
Content []byte `protobuf:"bytes,6,opt,name=content" json:"content,omitempty"` // XML内容 (appmsg XML)
|
||||
SendTimestamp *int64 `protobuf:"varint,7,opt,name=send_timestamp,json=sendTimestamp" json:"send_timestamp,omitempty"` // 发送时间戳
|
||||
ClientMsgId *string `protobuf:"bytes,8,opt,name=client_msg_id,json=clientMsgId" json:"client_msg_id,omitempty"` // 消息ID
|
||||
Unknown9 *int32 `protobuf:"varint,9,opt,name=unknown9" json:"unknown9,omitempty"` // 0
|
||||
Flag *int32 `protobuf:"varint,10,opt,name=flag" json:"flag,omitempty"` // 1
|
||||
Unknown11 *int32 `protobuf:"varint,11,opt,name=unknown11" json:"unknown11,omitempty"` // 0
|
||||
Msgsource []byte `protobuf:"bytes,12,opt,name=msgsource" json:"msgsource,omitempty"` // msgsource XML
|
||||
Unknown13 []byte `protobuf:"bytes,13,opt,name=unknown13" json:"unknown13,omitempty"` // 空 (必须显式存在)
|
||||
Unknown14 []byte `protobuf:"bytes,14,opt,name=unknown14" json:"unknown14,omitempty"` // 空 (必须显式存在)
|
||||
Unknown15 []byte `protobuf:"bytes,15,opt,name=unknown15" json:"unknown15,omitempty"` // 空 (必须显式存在)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) Reset() {
|
||||
*x = ReplyMsgBody{}
|
||||
mi := &file_proto_wxproto_reply_msg_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ReplyMsgBody) ProtoMessage() {}
|
||||
|
||||
func (x *ReplyMsgBody) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_wxproto_reply_msg_proto_msgTypes[2]
|
||||
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 ReplyMsgBody.ProtoReflect.Descriptor instead.
|
||||
func (*ReplyMsgBody) Descriptor() ([]byte, []int) {
|
||||
return file_proto_wxproto_reply_msg_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetSender() string {
|
||||
if x != nil && x.Sender != nil {
|
||||
return *x.Sender
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetUnknown2() []byte {
|
||||
if x != nil {
|
||||
return x.Unknown2
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetUnknown3() int32 {
|
||||
if x != nil && x.Unknown3 != nil {
|
||||
return *x.Unknown3
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetReceiver() string {
|
||||
if x != nil && x.Receiver != nil {
|
||||
return *x.Receiver
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetMsgType() int32 {
|
||||
if x != nil && x.MsgType != nil {
|
||||
return *x.MsgType
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetContent() []byte {
|
||||
if x != nil {
|
||||
return x.Content
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetSendTimestamp() int64 {
|
||||
if x != nil && x.SendTimestamp != nil {
|
||||
return *x.SendTimestamp
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetClientMsgId() string {
|
||||
if x != nil && x.ClientMsgId != nil {
|
||||
return *x.ClientMsgId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetUnknown9() int32 {
|
||||
if x != nil && x.Unknown9 != nil {
|
||||
return *x.Unknown9
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetFlag() int32 {
|
||||
if x != nil && x.Flag != nil {
|
||||
return *x.Flag
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetUnknown11() int32 {
|
||||
if x != nil && x.Unknown11 != nil {
|
||||
return *x.Unknown11
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetMsgsource() []byte {
|
||||
if x != nil {
|
||||
return x.Msgsource
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetUnknown13() []byte {
|
||||
if x != nil {
|
||||
return x.Unknown13
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetUnknown14() []byte {
|
||||
if x != nil {
|
||||
return x.Unknown14
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ReplyMsgBody) GetUnknown15() []byte {
|
||||
if x != nil {
|
||||
return x.Unknown15
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_proto_wxproto_reply_msg_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proto_wxproto_reply_msg_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1dproto/wxproto/reply_msg.proto\x12\awxproto\"l\n" +
|
||||
"\x0eWxSendReplyMsg\x12/\n" +
|
||||
"\x06header\x18\x01 \x01(\v2\x17.wxproto.ReplyMsgHeaderR\x06header\x12)\n" +
|
||||
"\x04body\x18\x02 \x01(\v2\x15.wxproto.ReplyMsgBodyR\x04body\"\xb8\x01\n" +
|
||||
"\x0eReplyMsgHeader\x12\x12\n" +
|
||||
"\x04flag\x18\x01 \x01(\fR\x04flag\x12\x1c\n" +
|
||||
"\ttimestamp\x18\x02 \x01(\rR\ttimestamp\x12!\n" +
|
||||
"\fclient_proof\x18\x03 \x01(\fR\vclientProof\x12\x1b\n" +
|
||||
"\tdevice_id\x18\x04 \x01(\x04R\bdeviceId\x12\x1a\n" +
|
||||
"\bplatform\x18\x05 \x01(\tR\bplatform\x12\x18\n" +
|
||||
"\aversion\x18\x06 \x01(\rR\aversion\"\xc0\x03\n" +
|
||||
"\fReplyMsgBody\x12\x16\n" +
|
||||
"\x06sender\x18\x01 \x01(\tR\x06sender\x12\x1a\n" +
|
||||
"\bunknown2\x18\x02 \x01(\fR\bunknown2\x12\x1a\n" +
|
||||
"\bunknown3\x18\x03 \x01(\x05R\bunknown3\x12\x1a\n" +
|
||||
"\breceiver\x18\x04 \x01(\tR\breceiver\x12\x19\n" +
|
||||
"\bmsg_type\x18\x05 \x01(\x05R\amsgType\x12\x18\n" +
|
||||
"\acontent\x18\x06 \x01(\fR\acontent\x12%\n" +
|
||||
"\x0esend_timestamp\x18\a \x01(\x03R\rsendTimestamp\x12\"\n" +
|
||||
"\rclient_msg_id\x18\b \x01(\tR\vclientMsgId\x12\x1a\n" +
|
||||
"\bunknown9\x18\t \x01(\x05R\bunknown9\x12\x12\n" +
|
||||
"\x04flag\x18\n" +
|
||||
" \x01(\x05R\x04flag\x12\x1c\n" +
|
||||
"\tunknown11\x18\v \x01(\x05R\tunknown11\x12\x1c\n" +
|
||||
"\tmsgsource\x18\f \x01(\fR\tmsgsource\x12\x1c\n" +
|
||||
"\tunknown13\x18\r \x01(\fR\tunknown13\x12\x1c\n" +
|
||||
"\tunknown14\x18\x0e \x01(\fR\tunknown14\x12\x1c\n" +
|
||||
"\tunknown15\x18\x0f \x01(\fR\tunknown15B>Z<github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto"
|
||||
|
||||
var (
|
||||
file_proto_wxproto_reply_msg_proto_rawDescOnce sync.Once
|
||||
file_proto_wxproto_reply_msg_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_proto_wxproto_reply_msg_proto_rawDescGZIP() []byte {
|
||||
file_proto_wxproto_reply_msg_proto_rawDescOnce.Do(func() {
|
||||
file_proto_wxproto_reply_msg_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_wxproto_reply_msg_proto_rawDesc), len(file_proto_wxproto_reply_msg_proto_rawDesc)))
|
||||
})
|
||||
return file_proto_wxproto_reply_msg_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_proto_wxproto_reply_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_proto_wxproto_reply_msg_proto_goTypes = []any{
|
||||
(*WxSendReplyMsg)(nil), // 0: wxproto.WxSendReplyMsg
|
||||
(*ReplyMsgHeader)(nil), // 1: wxproto.ReplyMsgHeader
|
||||
(*ReplyMsgBody)(nil), // 2: wxproto.ReplyMsgBody
|
||||
}
|
||||
var file_proto_wxproto_reply_msg_proto_depIdxs = []int32{
|
||||
1, // 0: wxproto.WxSendReplyMsg.header:type_name -> wxproto.ReplyMsgHeader
|
||||
2, // 1: wxproto.WxSendReplyMsg.body:type_name -> wxproto.ReplyMsgBody
|
||||
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_reply_msg_proto_init() }
|
||||
func file_proto_wxproto_reply_msg_proto_init() {
|
||||
if File_proto_wxproto_reply_msg_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wxproto_reply_msg_proto_rawDesc), len(file_proto_wxproto_reply_msg_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_proto_wxproto_reply_msg_proto_goTypes,
|
||||
DependencyIndexes: file_proto_wxproto_reply_msg_proto_depIdxs,
|
||||
MessageInfos: file_proto_wxproto_reply_msg_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proto_wxproto_reply_msg_proto = out.File
|
||||
file_proto_wxproto_reply_msg_proto_goTypes = nil
|
||||
file_proto_wxproto_reply_msg_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package wxproto;
|
||||
|
||||
option go_package = "github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto";
|
||||
|
||||
// 发送回复(引用)消息的protobuf结构
|
||||
// CG路径: /cgi-bin/micromsg-bin/sendappmsg
|
||||
// 消息类型: appmsg type=57
|
||||
message WxSendReplyMsg {
|
||||
optional ReplyMsgHeader header = 1;
|
||||
optional ReplyMsgBody body = 2;
|
||||
}
|
||||
|
||||
// Header: 固定64字节
|
||||
message ReplyMsgHeader {
|
||||
optional bytes flag = 1; // 固定 [0x00]
|
||||
optional uint32 timestamp = 2; // 请求时间戳(秒)
|
||||
optional bytes client_proof = 3; // 16字节随机数据/md5
|
||||
optional uint64 device_id = 4; // 设备标识(大整数)
|
||||
optional string platform = 5; // 平台信息 "UnifiedPCMac 26 arm64"
|
||||
optional uint32 version = 6; // 版本/标记 = 163
|
||||
}
|
||||
|
||||
// Body: 消息体
|
||||
message ReplyMsgBody {
|
||||
optional string sender = 1; // 接收方wxid (field1是target)
|
||||
optional bytes unknown2 = 2; // 空 (必须显式存在)
|
||||
optional int32 unknown3 = 3; // 0 (必须显式存在)
|
||||
optional string receiver = 4; // 发送方wxid
|
||||
optional int32 msg_type = 5; // 消息类型 = 57
|
||||
optional bytes content = 6; // XML内容 (appmsg XML)
|
||||
optional int64 send_timestamp = 7; // 发送时间戳
|
||||
optional string client_msg_id = 8; // 消息ID
|
||||
optional int32 unknown9 = 9; // 0
|
||||
optional int32 flag = 10; // 1
|
||||
optional int32 unknown11 = 11; // 0
|
||||
optional bytes msgsource = 12; // msgsource XML
|
||||
optional bytes unknown13 = 13; // 空 (必须显式存在)
|
||||
optional bytes unknown14 = 14; // 空 (必须显式存在)
|
||||
optional bytes unknown15 = 15; // 空 (必须显式存在)
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/yincongcyincong/weixin-macos/onebot/proto/wxproto"
|
||||
)
|
||||
|
||||
// BuildReplyMsgProto 构建发送回复消息的protobuf并返回hex编码的字符串
|
||||
func BuildReplyMsgProto(sender, receiver string, replyInfo *ReplyInfo) (string, error) {
|
||||
now := time.Now().Unix()
|
||||
|
||||
// 构建appmsg XML
|
||||
appmsgXml := buildReplyAppmsgXml(replyInfo)
|
||||
|
||||
// 构建客户端消息ID
|
||||
clientMsgId := fmt.Sprintf("%s_%d_%d_xwechat_1", sender, now, rand.Intn(100))
|
||||
|
||||
// msgsource
|
||||
msgsource := "<msgsource><alnode><fr>1</fr></alnode></msgsource>"
|
||||
|
||||
// proto2 需要使用指针
|
||||
var (
|
||||
unknown2 = []byte{}
|
||||
unknown3 = int32(0)
|
||||
msgType = int32(57)
|
||||
unknown9 = int32(0)
|
||||
flag = int32(1)
|
||||
unknown11 = int32(0)
|
||||
unknown13 = []byte{}
|
||||
unknown14 = []byte{}
|
||||
unknown15 = []byte{}
|
||||
timestamp = uint32(now)
|
||||
deviceId = generateDeviceId()
|
||||
version = uint32(163)
|
||||
)
|
||||
|
||||
msg := &wxproto.WxSendReplyMsg{
|
||||
Header: &wxproto.ReplyMsgHeader{
|
||||
Flag: []byte{0x00},
|
||||
Timestamp: ×tamp,
|
||||
ClientProof: generateRandomBytes(16),
|
||||
DeviceId: &deviceId,
|
||||
Platform: proto.String("UnifiedPCMac 26 arm64"),
|
||||
Version: &version,
|
||||
},
|
||||
Body: &wxproto.ReplyMsgBody{
|
||||
Sender: &receiver,
|
||||
Unknown2: unknown2,
|
||||
Unknown3: &unknown3,
|
||||
Receiver: &sender,
|
||||
MsgType: &msgType,
|
||||
Content: []byte(appmsgXml),
|
||||
SendTimestamp: proto.Int64(now),
|
||||
ClientMsgId: &clientMsgId,
|
||||
Unknown9: &unknown9,
|
||||
Flag: &flag,
|
||||
Unknown11: &unknown11,
|
||||
Msgsource: []byte(msgsource),
|
||||
Unknown13: unknown13,
|
||||
Unknown14: unknown14,
|
||||
Unknown15: unknown15,
|
||||
},
|
||||
}
|
||||
|
||||
data, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("marshal reply proto failed: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(fmt.Printf("0x% x\n", data))
|
||||
|
||||
return hex.EncodeToString(data), nil
|
||||
}
|
||||
|
||||
// ReplyInfo 回复消息的全部信息
|
||||
type ReplyInfo struct {
|
||||
Content string // 回复的文本内容
|
||||
MsgId string // 被回复消息的svrid
|
||||
MsgSender string // 被回复消息的发送者wxid
|
||||
MsgType int // 被回复消息的类型 (1=text, 3=image, 43=video, 49=appmsg)
|
||||
CreateTime int64 // 被回复消息的时间戳(毫秒)
|
||||
Msgsource string // 被回复消息的msgsource
|
||||
DisplayName string // 被回复消息发送者的昵称
|
||||
MsgContent string // 被回复消息的内容
|
||||
}
|
||||
|
||||
// buildReplyAppmsgXml 构建回复消息的appmsg XML,字段顺序匹配微信真实protobuf
|
||||
func buildReplyAppmsgXml(info *ReplyInfo) string {
|
||||
// 时间戳:毫秒转秒
|
||||
createTime := info.CreateTime / 1000
|
||||
|
||||
xml := `<appmsg appid="" sdkver="0">`
|
||||
xml += `<title>` + escapeXmlStr(info.Content) + `</title>`
|
||||
xml += `<des></des>`
|
||||
xml += `<action></action>`
|
||||
xml += `<type>57</type>`
|
||||
xml += `<showtype>0</showtype>`
|
||||
xml += `<soundtype>0</soundtype>`
|
||||
xml += `<mediatagname></mediatagname>`
|
||||
xml += `<messageext></messageext>`
|
||||
xml += `<messageaction></messageaction>`
|
||||
xml += `<content></content>`
|
||||
xml += `<contentattr>0</contentattr>`
|
||||
xml += `<url></url>`
|
||||
xml += `<lowurl></lowurl>`
|
||||
xml += `<dataurl></dataurl>`
|
||||
xml += `<lowdataurl></lowdataurl>`
|
||||
xml += `<songalbumurl></songalbumurl>`
|
||||
xml += `<songlyric></songlyric>`
|
||||
xml += `<template_id></template_id>`
|
||||
xml += `<appattach><totallen>0</totallen><attachid></attachid><emoticonmd5></emoticonmd5><fileext></fileext><aeskey></aeskey></appattach>`
|
||||
xml += `<extinfo></extinfo>`
|
||||
xml += `<sourceusername></sourceusername>`
|
||||
xml += `<sourcedisplayname></sourcedisplayname>`
|
||||
xml += `<thumburl></thumburl>`
|
||||
xml += `<md5></md5>`
|
||||
xml += `<statextstr></statextstr>`
|
||||
|
||||
// refermsg - 字段顺序与微信一致: chatusr → type → createtime → msgsource → displayname → svrid → fromusr → content
|
||||
xml += `<refermsg>`
|
||||
xml += `<chatusr>` + escapeXmlStr(info.MsgSender) + `</chatusr>`
|
||||
xml += `<type>` + fmt.Sprintf("%d", info.MsgType) + `</type>`
|
||||
xml += `<createtime>` + fmt.Sprintf("%d", createTime) + `</createtime>`
|
||||
xml += `<msgsource>` + escapeXmlStr(info.Msgsource) + `</msgsource>`
|
||||
xml += `<displayname>` + escapeXmlStr(info.DisplayName) + `</displayname>`
|
||||
xml += `<svrid>` + escapeXmlStr(info.MsgId) + `</svrid>`
|
||||
xml += `<fromusr>` + escapeXmlStr(info.MsgSender) + `</fromusr>`
|
||||
xml += `<content>` + escapeXmlStr(info.MsgContent) + `</content>`
|
||||
xml += `</refermsg>`
|
||||
xml += `</appmsg>`
|
||||
|
||||
xml += `<fromusername>` + escapeXmlStr(info.MsgSender) + `</fromusername>`
|
||||
|
||||
return xml
|
||||
}
|
||||
|
||||
// escapeXmlStr 简单的XML转义
|
||||
func escapeXmlStr(s string) string {
|
||||
result := ""
|
||||
for _, c := range s {
|
||||
switch c {
|
||||
case '&':
|
||||
result += "&"
|
||||
case '<':
|
||||
result += "<"
|
||||
case '>':
|
||||
result += ">"
|
||||
case '"':
|
||||
result += """
|
||||
case '\'':
|
||||
result += "'"
|
||||
default:
|
||||
result += string(c)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// generateRandomBytes 生成随机字节
|
||||
func generateRandomBytes(n int) []byte {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = byte(rand.Intn(256))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// generateDeviceId 动态生成设备ID (大整数,高位置1模拟真实设备)
|
||||
func generateDeviceId() uint64 {
|
||||
return rand.Uint64() | (0xFFFFFFFF << 32)
|
||||
}
|
||||
@@ -137,6 +137,23 @@ var textProtoHexGlobal = "";
|
||||
var imgProtoHexGlobal = "";
|
||||
// 视频消息protobuf全局变量 (从Go直接传入hex编码)
|
||||
var videoProtoHexGlobal = "";
|
||||
// 回复消息protobuf全局变量 (从Go直接传入hex编码)
|
||||
var replyProtoHexGlobal = "";
|
||||
|
||||
// 回复消息全局变量
|
||||
var replyCallbackFuncAddr = baseAddr.add({{.replyCallbackFuncAddr}});
|
||||
var replyProtobufAddr = replyCallbackFuncAddr.add(0x50);
|
||||
var patchReplyProtobufFunc1 = replyCallbackFuncAddr.add(0x10);
|
||||
var patchReplyProtobufFunc1Byte;
|
||||
var patchReplyProtobufFunc2 = replyCallbackFuncAddr.add(0x30);
|
||||
var patchReplyProtobufFunc2Byte;
|
||||
var replyProtobufDeleteAddr = replyCallbackFuncAddr.add(0x6c);
|
||||
var replyProtobufDeleteAddrByte;
|
||||
var replyMessageCallbackFunc = baseAddr.add({{.replyMessageCallbackFunc}});
|
||||
var replyCgiAddr = ptr(0);
|
||||
var sendReplyMessageAddr = ptr(0);
|
||||
var replyMessageAddr = ptr(0);
|
||||
var replyProtoX1PayloadAddr = ptr(0);
|
||||
|
||||
// -------------------------全局变量分区-------------------------
|
||||
|
||||
@@ -344,6 +361,10 @@ function attachReq2buf() {
|
||||
insertMsgAddr.writePointer(sendVideoMessageAddr);
|
||||
console.log("[+] 发送视频消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendVideoMessageAddr +
|
||||
"[+] Req2Buf 写入后内存预览: " + insertMsgAddr);
|
||||
} else if (sendMsgType === "reply") {
|
||||
insertMsgAddr.writePointer(sendReplyMessageAddr);
|
||||
console.log("[+] 发送回复消息成功! Req2Buf 已将 X24+0x60 指向新地址: " + sendReplyMessageAddr +
|
||||
"[+] Req2Buf 写入后内存预览: " + insertMsgAddr);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -853,6 +874,148 @@ function attachGetCallbackFromWrapper() {
|
||||
|
||||
setImmediate(attachGetCallbackFromWrapper);
|
||||
|
||||
// -------------------------发送回复消息分区-------------------------
|
||||
function setupSendReplyMessageDynamic() {
|
||||
replyCgiAddr = Memory.alloc(128);
|
||||
sendReplyMessageAddr = Memory.alloc(256);
|
||||
replyMessageAddr = Memory.alloc(256);
|
||||
replyProtoX1PayloadAddr = Memory.alloc(4096);
|
||||
|
||||
patchString(replyCgiAddr, "/cgi-bin/micromsg-bin/sendappmsg");
|
||||
|
||||
sendReplyMessageAddr.add(0x00).writeU64(0);
|
||||
sendReplyMessageAddr.add(0x08).writeU64(0);
|
||||
sendReplyMessageAddr.add(0x10).writeU64(0);
|
||||
sendReplyMessageAddr.add(0x18).writeU64(1);
|
||||
sendReplyMessageAddr.add(0x20).writeU32(taskIdGlobal);
|
||||
sendReplyMessageAddr.add(0x28).writePointer(replyMessageAddr);
|
||||
|
||||
replyMessageAddr.add(0x00).writePointer(replyMessageCallbackFunc);
|
||||
replyMessageAddr.add(0x08).writeU32(taskIdGlobal);
|
||||
replyMessageAddr.add(0x0c).writeU32(0x6e);
|
||||
replyMessageAddr.add(0x10).writeU64(0x3);
|
||||
replyMessageAddr.add(0x18).writePointer(replyCgiAddr);
|
||||
replyMessageAddr.add(0x20).writeU64(0x22);
|
||||
replyMessageAddr.add(0x28).writeU64(uint64("0x8000000000000030"));
|
||||
replyMessageAddr.add(0x30).writeU64(uint64("0x0000000001010100"));
|
||||
|
||||
patchReplyProtobufFunc1Byte = patchReplyProtobufFunc1.readByteArray(4);
|
||||
patchReplyProtobufFunc2Byte = patchReplyProtobufFunc2.readByteArray(4);
|
||||
replyProtobufDeleteAddrByte = replyProtobufDeleteAddr.readByteArray(4);
|
||||
|
||||
console.log("[+] Reply message setup complete. CgiAddr: " + replyCgiAddr + " SendAddr: " + sendReplyMessageAddr);
|
||||
}
|
||||
|
||||
setImmediate(setupSendReplyMessageDynamic);
|
||||
|
||||
function patchReplyProtoBuf() {
|
||||
Interceptor.attach(replyCallbackFuncAddr, {
|
||||
onEnter: function (args) {
|
||||
var firstValue = this.context.sp.add(0x10).readU32();
|
||||
if (firstValue === taskIdGlobal) {
|
||||
if (patchReplyProtobufFunc1.readU32() !== 3573751839) {
|
||||
Memory.patchCode(patchReplyProtobufFunc1, 4, code => {
|
||||
const cw = new Arm64Writer(code, {pc: patchReplyProtobufFunc1});
|
||||
cw.putNop();
|
||||
cw.flush();
|
||||
});
|
||||
Memory.patchCode(patchReplyProtobufFunc2, 4, code => {
|
||||
const cw = new Arm64Writer(code, {pc: patchReplyProtobufFunc2});
|
||||
cw.putNop();
|
||||
cw.flush();
|
||||
});
|
||||
Memory.patchCode(replyProtobufDeleteAddr, 4, code => {
|
||||
const cw = new Arm64Writer(code, {pc: replyProtobufDeleteAddr});
|
||||
cw.putNop();
|
||||
cw.flush();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (patchReplyProtobufFunc1.readU32() === 3573751839) {
|
||||
Memory.patchCode(patchReplyProtobufFunc1, 4, code => {
|
||||
const cw = new Arm64Writer(code, {pc: patchReplyProtobufFunc1});
|
||||
cw.putBytes(new Uint8Array(patchReplyProtobufFunc1Byte));
|
||||
cw.flush();
|
||||
});
|
||||
Memory.patchCode(patchReplyProtobufFunc2, 4, code => {
|
||||
const cw = new Arm64Writer(code, {pc: patchReplyProtobufFunc2});
|
||||
cw.putBytes(new Uint8Array(patchReplyProtobufFunc2Byte));
|
||||
cw.flush();
|
||||
});
|
||||
Memory.patchCode(replyProtobufDeleteAddr, 4, code => {
|
||||
const cw = new Arm64Writer(code, {pc: replyProtobufDeleteAddr});
|
||||
cw.putBytes(new Uint8Array(replyProtobufDeleteAddrByte));
|
||||
cw.flush();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setImmediate(patchReplyProtoBuf);
|
||||
|
||||
function attachReplyProto() {
|
||||
Interceptor.attach(replyProtobufAddr, {
|
||||
onEnter: function (args) {
|
||||
var currTaskId = this.context.sp.add(0x30).readU32();
|
||||
if (currTaskId !== taskIdGlobal) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!replyProtoHexGlobal || replyProtoHexGlobal.length === 0) {
|
||||
console.error("[!] replyProtoHexGlobal 为空");
|
||||
return;
|
||||
}
|
||||
|
||||
const finalPayload = hexToByteArray(replyProtoHexGlobal);
|
||||
replyProtoX1PayloadAddr.writeByteArray(finalPayload);
|
||||
this.context.x1 = replyProtoX1PayloadAddr;
|
||||
this.context.x2 = ptr(finalPayload.length);
|
||||
console.log("[+] Reply protobuf注入完成, length=" + finalPayload.length);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setImmediate(attachReplyProto);
|
||||
|
||||
function triggerSendReplyMessage(taskId, sender, receiver, protoHex, payloadHex) {
|
||||
if (!taskId || !receiver || !sender) {
|
||||
console.error("[!] reply: taskId or receiver or sender is empty!");
|
||||
return "fail";
|
||||
}
|
||||
|
||||
if (!triggerX0 || !triggerX1Payload) {
|
||||
console.error("[!] triggerX0 或 triggerX1Payload 尚未初始化,请等待 hook 捕获");
|
||||
return "fail";
|
||||
}
|
||||
|
||||
replyProtoHexGlobal = protoHex;
|
||||
taskIdGlobal = taskId;
|
||||
|
||||
replyMessageAddr.add(0x08).writeU32(taskIdGlobal);
|
||||
sendReplyMessageAddr.add(0x20).writeU32(taskIdGlobal);
|
||||
|
||||
const payloadData = hexToByteArray(payloadHex);
|
||||
triggerX1Payload.writeByteArray(payloadData);
|
||||
triggerX1Payload.add(0x18).writePointer(replyCgiAddr);
|
||||
triggerX1Payload.add(0xb8).writePointer(triggerX1Payload.add(0xc0));
|
||||
triggerX1Payload.add(0x190).writePointer(triggerX1Payload.add(0x198));
|
||||
sendMsgType = "reply"
|
||||
|
||||
const MMStartTask = new NativeFunction(sendFuncAddr, 'int64', ['pointer', 'pointer']);
|
||||
|
||||
try {
|
||||
MMStartTask(triggerX0, triggerX1Payload);
|
||||
return "1";
|
||||
} catch (e) {
|
||||
console.error("[!] Error trigger reply MMStartTask: " + e);
|
||||
return "fail";
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------发送回复消息分区-------------------------
|
||||
|
||||
rpc.exports = {
|
||||
triggerSendImgMessage: triggerSendImgMessage,
|
||||
triggerUploadImg: triggerUploadImg,
|
||||
@@ -860,6 +1023,7 @@ rpc.exports = {
|
||||
triggerDownload: triggerDownload,
|
||||
triggerUploadVideo: triggerUploadVideo,
|
||||
triggerSendVideoMessage: triggerSendVideoMessage,
|
||||
triggerSendReplyMessage: triggerSendReplyMessage,
|
||||
};
|
||||
|
||||
// -------------------------发送图片消息分区-------------------------
|
||||
|
||||
@@ -124,6 +124,11 @@ func BuildSendPayload(taskId int64, msgType string) string {
|
||||
payloadData[16] = 0x10
|
||||
payloadData[28] = 0x21
|
||||
payloadData[92] = 0x6E
|
||||
case "reply":
|
||||
payloadData[0] = 0x6E
|
||||
payloadData[16] = 0x10
|
||||
payloadData[28] = 0x22
|
||||
payloadData[92] = 0x6E
|
||||
}
|
||||
|
||||
// 构建完整buffer: taskId(4字节) + payloadData(412字节) = 0x1A0字节
|
||||
|
||||
+31
-2
@@ -142,6 +142,29 @@ func SendWechatMsg(m *SendMsg) {
|
||||
case "download":
|
||||
result := fridaScript.ExportsCall("triggerDownload", targetId, m.FIleCdnUrl, m.AesKey, m.FilePath, m.FileType)
|
||||
Info("📩 下载任务执行结果", "result", result, "task_id", currTaskId, "wechat_id", myWechatId, "target_id", targetId)
|
||||
case "reply":
|
||||
replyInfo := &ReplyInfo{
|
||||
Content: m.Content,
|
||||
MsgId: m.ReferMsgId,
|
||||
MsgSender: m.ReferMsgSender,
|
||||
MsgType: m.ReferMsgType,
|
||||
CreateTime: m.ReferCreateTime,
|
||||
Msgsource: m.ReferMsgsource,
|
||||
DisplayName: m.ReferDisplayName,
|
||||
MsgContent: m.ReferContent,
|
||||
}
|
||||
protoHex, err := BuildReplyMsgProto(myWechatId, targetId, replyInfo)
|
||||
if err != nil {
|
||||
Error("构建回复protobuf失败", "err", err)
|
||||
return
|
||||
}
|
||||
payloadHex := BuildSendPayload(currTaskId, "reply")
|
||||
result := fridaScript.ExportsCall("triggerSendReplyMessage", currTaskId, myWechatId, targetId, protoHex, payloadHex)
|
||||
Info("📩 发送回复任务执行结果", "result", result, "task_id", currTaskId, "wechat_id", myWechatId, "target_id", targetId)
|
||||
if result != "1" {
|
||||
Error("发送回复失败", "task_id", currTaskId, "target_id", targetId, "result", result)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -225,8 +248,14 @@ func HandleMsg(jsonData []byte) ([]byte, error) {
|
||||
Error("XML解析失败", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := DownloadFile(fileMsg.Emoji.ThumbUrl)
|
||||
|
||||
// 优先thumburl,为空则用externurl
|
||||
emojiUrl := fileMsg.Emoji.ThumbUrl
|
||||
if emojiUrl == "" {
|
||||
emojiUrl = fileMsg.Emoji.ExternUrl
|
||||
}
|
||||
|
||||
data, err := DownloadFile(emojiUrl)
|
||||
if err != nil {
|
||||
Error("下载表情失败", "err", err)
|
||||
return nil, err
|
||||
|
||||
@@ -18,5 +18,7 @@
|
||||
"downloadFileAddr": "0x4E0E264",
|
||||
"downloadVideoAddr": "0x4E28044",
|
||||
"videoCallbackFuncAddr": "0x26DD8C8",
|
||||
"videoMessageCallbackFunc": "0x8C2F0F8"
|
||||
"videoMessageCallbackFunc": "0x8C2F0F8",
|
||||
"replyCallbackFuncAddr": "0x260C444",
|
||||
"replyMessageCallbackFunc": "0x8C29A08"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user