mirror of
https://github.com/yincongcyincong/wechat_chatter.git
synced 2026-07-15 10:26:52 +08:00
add image
This commit is contained in:
+3
-1
@@ -8,4 +8,6 @@ data
|
||||
go_al/*.go
|
||||
onebot/onebot
|
||||
*.tar.gz
|
||||
release
|
||||
release
|
||||
onebot/audio
|
||||
onebot/image
|
||||
@@ -1,5 +1,6 @@
|
||||
触发 STNManager__MMStartTask (ida搜 MMStartTask)
|
||||
图片hook: startUploadMedia (ida搜), OnUploadCompleted (ce查)
|
||||
startUploadMedia 上传图片,OnUploadCompleted 上传图片完成后的回调
|
||||
加密后的图片在_OnRecvFileData
|
||||
|
||||
WeChatExt中会有导致idapro出问题的代码
|
||||
@@ -82,65 +82,6 @@ function generateAESKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
function getCleanString(uint8Array) {
|
||||
var out = "";
|
||||
var i = 0;
|
||||
var len = uint8Array.length;
|
||||
|
||||
while (i < len) {
|
||||
var c = uint8Array[i++];
|
||||
|
||||
// 1. 处理单字节 (ASCII: 0xxxxxxx)
|
||||
if (c < 0x80) {
|
||||
// 只保留可见字符 (Space 32 到 ~ 126)
|
||||
if (c >= 32 && c <= 126) {
|
||||
out += String.fromCharCode(c);
|
||||
}
|
||||
}
|
||||
// 2. 处理双字节 (110xxxxx 10xxxxxx)
|
||||
else if ((c & 0xE0) === 0xC0 && i < len) {
|
||||
var c2 = uint8Array[i++];
|
||||
if ((c2 & 0xC0) === 0x80) {
|
||||
// 这种通常是特殊拉丁字母等,按需保留
|
||||
var charCode = ((c & 0x1F) << 6) | (c2 & 0x3F);
|
||||
out += String.fromCharCode(charCode);
|
||||
}
|
||||
}
|
||||
// 3. 处理三字节 (1110xxxx 10xxxxxx 10xxxxxx) -> 绝大多数汉字在此
|
||||
else if ((c & 0xF0) === 0xE0 && i + 1 < len) {
|
||||
var c2 = uint8Array[i++];
|
||||
var c3 = uint8Array[i++];
|
||||
if ((c2 & 0xC0) === 0x80 && (c3 & 0xC0) === 0x80) {
|
||||
var charCode = ((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
|
||||
if (
|
||||
(charCode >= 0x4E00 && charCode <= 0x9FA5) || // 基本汉字
|
||||
(charCode >= 0x3000 && charCode <= 0x303F) || // 常用中文标点 (。,、)
|
||||
(charCode >= 0xFF00 && charCode <= 0xFFEF) || // 全角符号/标点 (!:?)
|
||||
(charCode >= 0x2000 && charCode <= 0x206F) || // 常用标点扩展 (含 \u2005)
|
||||
(charCode >= 0x3400 && charCode <= 0x4DBF) // 扩展 A 区汉字
|
||||
) {
|
||||
out += String.fromCharCode(charCode);
|
||||
}
|
||||
}
|
||||
} else if ((c & 0xF8) === 0xF0 && i + 2 < len) {
|
||||
var c2 = uint8Array[i++];
|
||||
var c3 = uint8Array[i++];
|
||||
var c4 = uint8Array[i++];
|
||||
if ((c2 & 0xC0) === 0x80 && (c3 & 0xC0) === 0x80 && (c4 & 0xC0) === 0x80) {
|
||||
// 计算 Unicode 码点
|
||||
var codePoint = ((c & 0x07) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F);
|
||||
|
||||
// Emoji 范围通常在 U+1F000 到 U+1F9FF 之间
|
||||
if (codePoint >= 0x1F000 && codePoint <= 0x1FADF) {
|
||||
// 使用 fromCodePoint 处理 4 字节字符
|
||||
out += String.fromCodePoint(codePoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function generateBytes(n) {
|
||||
// 生成随机字符串
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
+32
-13
@@ -4,13 +4,11 @@ if (!baseAddr) {
|
||||
}
|
||||
|
||||
var buf2RespAddr = baseAddr.add(0x37173B0)
|
||||
var downloadImagAddr = baseAddr.add(0x49FB92C)
|
||||
|
||||
// -------------------------接收消息分区-------------------------
|
||||
function setReceiver() {
|
||||
|
||||
// 3. 开始拦截
|
||||
Interceptor.attach(buf2RespAddr,
|
||||
{
|
||||
Interceptor.attach(buf2RespAddr, {
|
||||
onEnter: function (args) {
|
||||
const currentPtr = this.context.x1;
|
||||
let start = 0x1e;
|
||||
@@ -40,7 +38,7 @@ function setReceiver() {
|
||||
const userContent = fields[5]
|
||||
const msgId = protobufVarintToNumberString(fields[6])
|
||||
|
||||
if (sender === "" || receiver === "" || content === "") {
|
||||
if (sender === "" || receiver === "" || content === "" || msgId === "") {
|
||||
console.log("字段缺失,无法解析 sender:" + sender + " receiver:" + receiver + hexdump(currentPtr, {
|
||||
length: x2,
|
||||
header: true,
|
||||
@@ -60,6 +58,7 @@ function setReceiver() {
|
||||
msgType = "group"
|
||||
groupId = sender
|
||||
|
||||
let splitIndex = content.indexOf(':')
|
||||
const sendUserStart = content.indexOf('wxid_')
|
||||
senderUser = content.substring(sendUserStart, splitIndex).trim();
|
||||
|
||||
@@ -77,13 +76,13 @@ function setReceiver() {
|
||||
// 处理用户的名称
|
||||
splitIndex = userContent?.indexOf(':')
|
||||
if (splitIndex === -1) {
|
||||
splitIndex = userContent?.indexOf('在群聊中@了你')
|
||||
splitIndex = userContent?.indexOf('在群聊中@了你') !== -1 ? userContent?.indexOf('在群聊中@了你') : userContent?.indexOf('在群聊中发了一段语')
|
||||
senderNickname = userContent?.substring(0, splitIndex).trim();
|
||||
} else {
|
||||
senderNickname = userContent?.substring(0, splitIndex).trim();
|
||||
}
|
||||
if (!senderNickname) {
|
||||
senderNickname = sender
|
||||
senderNickname = senderUser
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -91,7 +90,7 @@ function setReceiver() {
|
||||
const splitIndex = userContent?.indexOf(':')
|
||||
senderNickname = userContent?.substring(0, splitIndex).trim();
|
||||
if (!senderNickname) {
|
||||
senderNickname = sender
|
||||
senderNickname = senderUser
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,10 +108,31 @@ function setReceiver() {
|
||||
sender: {user_id: senderUser, nickname: senderNickname},
|
||||
msgsource: xml,
|
||||
raw_message: content,
|
||||
show_content:userContent
|
||||
show_content: userContent
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
Interceptor.attach(downloadImagAddr, { // 建议使用函数起始地址或你计算出的偏移地址
|
||||
onEnter: function (args) {
|
||||
var dataPtr = this.context.x1;
|
||||
var dataLen = this.context.x2.toInt32();
|
||||
var fileId = this.context.sp.add(0x30).readPointer().readUtf8String();
|
||||
var cdnUrl = this.context.x19.add(0x2F8).readPointer().readUtf8String();
|
||||
|
||||
if (dataLen > 0) {
|
||||
var buffer = dataPtr.readByteArray(dataLen);
|
||||
var uint8Array = new Uint8Array(buffer);
|
||||
|
||||
send({
|
||||
type: "download",
|
||||
media: Array.from(uint8Array),
|
||||
file_id: fileId,
|
||||
cdn_url: cdnUrl,
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -139,11 +159,11 @@ function getMessages(content, sender, mediaContent) {
|
||||
if (content.startsWith("<?xml version=\"1.0\"?><msg><img")) {
|
||||
messages.push({type: "image", data: {text: content}});
|
||||
} else if (content.startsWith("<msg><voicemsg")) {
|
||||
const audioStart = mediaContent.indexOf(35);
|
||||
const audioStart = mediaContent.indexOf(2);
|
||||
if (audioStart !== -1) {
|
||||
mediaContent = mediaContent.subarray(audioStart);
|
||||
}
|
||||
messages.push({type: "record", data: {text: content, media: mediaContent}});
|
||||
messages.push({type: "record", data: {text: content, media: Array.from(mediaContent)}});
|
||||
} else {
|
||||
messages.push({type: "text", data: {text: content}});
|
||||
}
|
||||
@@ -211,7 +231,7 @@ function getProtobufRawBytes(pBuffer, scanSize) {
|
||||
|
||||
for (; i < uint8Array.length; i++) {
|
||||
if (uint8Array[i] === 0x60 && i + 10 <= uint8Array.length) {
|
||||
finalResults.push(uint8Array.slice(i+1, i+10))
|
||||
finalResults.push(uint8Array.slice(i + 1, i + 10))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +327,6 @@ function protobufVarintToNumberString(uint8Array) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -----------------------测试函数-------------------------
|
||||
|
||||
function testGetProtobufRawBytes() {
|
||||
|
||||
+4
-9
@@ -71,7 +71,7 @@ func sendHandler(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func SendHttpReq(msg map[string]interface{}) {
|
||||
func SendHttpReq(jsonData []byte) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("panic: %v, %v\n", r, string(debug.Stack()))
|
||||
@@ -79,17 +79,12 @@ func SendHttpReq(msg map[string]interface{}) {
|
||||
}()
|
||||
|
||||
time.Sleep(time.Duration(config.SendInterval) * time.Millisecond)
|
||||
// 这里处理你的 X1 数据
|
||||
jsonData, err := json.Marshal(msg["payload"])
|
||||
jsonReq, err := HandleMsg(jsonData)
|
||||
if err != nil {
|
||||
log.Printf("JSON 序列化失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
m := new(WechatMessage)
|
||||
jsonReq, err := HandleMsg(jsonData, m)
|
||||
if err != nil {
|
||||
log.Printf("JSON 序列化失败: %v\n", err)
|
||||
if jsonReq == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -102,7 +97,7 @@ func SendHttpReq(msg map[string]interface{}) {
|
||||
|
||||
// 5. 设置 Header (OneBot 接口通常要求 application/json)
|
||||
h := hmac.New(sha1.New, []byte(config.OnebotToken))
|
||||
h.Write(jsonData)
|
||||
h.Write(jsonReq)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Signature", "sha1="+hex.EncodeToString(h.Sum(nil)))
|
||||
|
||||
|
||||
+29
-80
@@ -9,88 +9,14 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"text/template"
|
||||
|
||||
"github.com/frida/frida-go/frida"
|
||||
)
|
||||
|
||||
// 全局变量,保持 Frida 脚本对象
|
||||
var (
|
||||
fridaScript *frida.Script
|
||||
session *frida.Session
|
||||
taskId = int64(0x20000000)
|
||||
myWechatId = ""
|
||||
|
||||
msgChan = make(chan *SendMsg, 10)
|
||||
finishChan = make(chan struct{})
|
||||
|
||||
config = &Config{}
|
||||
|
||||
userID2NicknameMap sync.Map
|
||||
)
|
||||
|
||||
type WechatMessage struct {
|
||||
GroupId string `json:"group_id"`
|
||||
SelfID string `json:"self_id"`
|
||||
UserID string `json:"user_id"`
|
||||
Sender *Sender `json:"sender"`
|
||||
Time int64 `json:"time"`
|
||||
PostType string `json:"post_type"`
|
||||
MessageId string `json:"message_id"`
|
||||
Message []*Message `json:"message"`
|
||||
}
|
||||
|
||||
type Sender struct {
|
||||
UserID string `json:"user_id"`
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
|
||||
type SendMsg struct {
|
||||
UserId string
|
||||
GroupID string
|
||||
Content string
|
||||
Type string
|
||||
AtUser string
|
||||
}
|
||||
|
||||
// SendRequest 请求结构体
|
||||
type SendRequest struct {
|
||||
Message []*Message `json:"message"`
|
||||
UserID string `json:"user_id"`
|
||||
GroupID string `json:"group_id"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Type string `json:"type"`
|
||||
Data *SendRequestData `json:"data"`
|
||||
}
|
||||
|
||||
type SendRequestData struct {
|
||||
Id string `json:"id"`
|
||||
Text string `json:"text"`
|
||||
File string `json:"file"`
|
||||
URL string `json:"url"`
|
||||
QQ string `json:"qq"`
|
||||
Media []byte `json:"media"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
FridaType string `json:"frida_type"`
|
||||
SendURL string `json:"send_url"`
|
||||
ReceiveHost string `json:"receive_host"`
|
||||
FridaGadgetAddr string `json:"frida_gadget_addr"`
|
||||
WechatPid int `json:"wechat_pid"`
|
||||
OnebotToken string `json:"onebot_token"`
|
||||
ImagePath string `json:"image_path"`
|
||||
ConnType string `json:"conn_type"`
|
||||
SendInterval int `json:"send_interval"`
|
||||
|
||||
WechatConf string `json:"wechat_conf"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
initFlag()
|
||||
if config.FridaType == "gadget" {
|
||||
@@ -115,7 +41,7 @@ func main() {
|
||||
}()
|
||||
|
||||
// 3. 启动服务
|
||||
fmt.Printf("HTTP 服务启动在 %s", config.ReceiveHost)
|
||||
fmt.Printf("HTTP 服务启动在 %s\n", config.ReceiveHost)
|
||||
if err := http.ListenAndServe(config.ReceiveHost, nil); err != nil {
|
||||
log.Printf("服务启动失败: %v\n", err)
|
||||
}
|
||||
@@ -145,7 +71,14 @@ func initFlag() {
|
||||
fmt.Println("ImagePath", config.ImagePath)
|
||||
fmt.Println("WechatConf", config.WechatConf)
|
||||
|
||||
EnsureDir("./audio")
|
||||
err := EnsureDir("./audio")
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 无法创建音频目录: %v\n", err)
|
||||
}
|
||||
err = EnsureDir("./image")
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 无法创建图片目录: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func initFridaGadget() {
|
||||
@@ -220,8 +153,18 @@ func loadJs() {
|
||||
|
||||
// 打印 JS 里的 console.log
|
||||
script.On("message", func(rawMsg string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("panic: %v, %v\n", r, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
var msg map[string]interface{}
|
||||
json.Unmarshal([]byte(rawMsg), &msg)
|
||||
err = json.Unmarshal([]byte(rawMsg), &msg)
|
||||
if err != nil {
|
||||
log.Printf("JSON解析失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
msgType := msg["type"].(string)
|
||||
|
||||
@@ -229,13 +172,14 @@ func loadJs() {
|
||||
case "send":
|
||||
if p, ok := msg["payload"]; ok {
|
||||
if pMap, ok := p.(map[string]interface{}); ok {
|
||||
payloadJson, _ := json.Marshal(pMap)
|
||||
if t, ok := pMap["type"]; ok {
|
||||
switch t.(string) {
|
||||
case "send":
|
||||
if config.ConnType == "http" {
|
||||
go SendHttpReq(msg)
|
||||
go SendHttpReq(payloadJson)
|
||||
} else {
|
||||
go SendWebSocketMsg(msg)
|
||||
go SendWebSocketMsg(payloadJson)
|
||||
}
|
||||
case "finish":
|
||||
finishChan <- struct{}{}
|
||||
@@ -257,6 +201,11 @@ func loadJs() {
|
||||
}
|
||||
}
|
||||
msgChan <- m
|
||||
case "download":
|
||||
err = Download(payloadJson)
|
||||
if err != nil {
|
||||
log.Printf("下载失败: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func Download(rawMsg []byte) error {
|
||||
downloadReq := new(DownloadRequest)
|
||||
err := json.Unmarshal(rawMsg, downloadReq)
|
||||
if err != nil {
|
||||
log.Printf("JSON解析失败: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("下载文件: %s, %d, %s\n", downloadReq.FileID, len(downloadReq.Media), downloadReq.CDNURL[:10])
|
||||
if downloadReqInter, ok := userID2FileMsgMap.Load(downloadReq.CDNURL); ok {
|
||||
downloadReq = downloadReqInter.(*DownloadRequest)
|
||||
downloadReq.Media = append(downloadReq.Media, downloadReq.Media...)
|
||||
} else {
|
||||
userID2FileMsgMap.Store(downloadReq.CDNURL, downloadReq)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"sync"
|
||||
|
||||
"github.com/frida/frida-go/frida"
|
||||
)
|
||||
|
||||
// 全局变量,保持 Frida 脚本对象
|
||||
var (
|
||||
fridaScript *frida.Script
|
||||
session *frida.Session
|
||||
taskId = int64(0x20000000)
|
||||
myWechatId = ""
|
||||
|
||||
msgChan = make(chan *SendMsg, 10)
|
||||
finishChan = make(chan struct{})
|
||||
|
||||
config = &Config{}
|
||||
|
||||
userID2NicknameMap sync.Map
|
||||
userID2FileMsgMap sync.Map
|
||||
)
|
||||
|
||||
type WechatMessage struct {
|
||||
GroupId string `json:"group_id"`
|
||||
SelfID string `json:"self_id"`
|
||||
UserID string `json:"user_id"`
|
||||
Sender *Sender `json:"sender"`
|
||||
Time int64 `json:"time"`
|
||||
PostType string `json:"post_type"`
|
||||
MessageId string `json:"message_id"`
|
||||
Message []*Message `json:"message"`
|
||||
MsgResource string `json:"msgsource"`
|
||||
RawMessage string `json:"raw_message"`
|
||||
ShowContent string `json:"show_content"`
|
||||
}
|
||||
|
||||
type Sender struct {
|
||||
UserID string `json:"user_id"`
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
|
||||
type SendMsg struct {
|
||||
UserId string
|
||||
GroupID string
|
||||
Content string
|
||||
Type string
|
||||
AtUser string
|
||||
}
|
||||
|
||||
// SendRequest 请求结构体
|
||||
type SendRequest struct {
|
||||
Message []*Message `json:"message"`
|
||||
UserID string `json:"user_id"`
|
||||
GroupID string `json:"group_id"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Type string `json:"type"`
|
||||
Data *SendRequestData `json:"data"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
FridaType string `json:"frida_type"`
|
||||
SendURL string `json:"send_url"`
|
||||
ReceiveHost string `json:"receive_host"`
|
||||
FridaGadgetAddr string `json:"frida_gadget_addr"`
|
||||
WechatPid int `json:"wechat_pid"`
|
||||
OnebotToken string `json:"onebot_token"`
|
||||
ImagePath string `json:"image_path"`
|
||||
ConnType string `json:"conn_type"`
|
||||
SendInterval int `json:"send_interval"`
|
||||
|
||||
WechatConf string `json:"wechat_conf"`
|
||||
}
|
||||
|
||||
// VoiceMsg 对应外层的 <msg> 标签
|
||||
type VoiceMsg struct {
|
||||
XMLName xml.Name `xml:"msg"`
|
||||
VoiceMsg *VoiceMsgInfo `xml:"voicemsg"`
|
||||
}
|
||||
|
||||
// VoiceMsgInfo 对应内部的 <voicemsg> 标签及其属性
|
||||
type VoiceMsgInfo struct {
|
||||
EndFlag int `xml:"endflag,attr"`
|
||||
CancelFlag int `xml:"cancelflag,attr"`
|
||||
ForwardFlag int `xml:"forwardflag,attr"`
|
||||
VoiceFormat int `xml:"voiceformat,attr"`
|
||||
VoiceLength int `xml:"voicelength,attr"`
|
||||
Length int `xml:"length,attr"`
|
||||
BufID int `xml:"bufid,attr"`
|
||||
AESKey string `xml:"aeskey,attr"`
|
||||
VoiceURL string `xml:"voiceurl,attr"`
|
||||
VoiceMD5 string `xml:"voicemd5,attr"`
|
||||
ClientMsgID string `xml:"clientmsgid,attr"`
|
||||
FromUserName string `xml:"fromusername,attr"`
|
||||
}
|
||||
|
||||
// FileMsg 对应 <msg> 标签
|
||||
type FileMsg struct {
|
||||
XMLName xml.Name `xml:"msg"`
|
||||
Image Image `xml:"img"`
|
||||
}
|
||||
|
||||
// Image 对应 <img> 标签及其属性和子节点
|
||||
type Image struct {
|
||||
// 属性(Attributes)
|
||||
AesKey string `xml:"aeskey,attr"`
|
||||
EncryVer int `xml:"encryver,attr"`
|
||||
ThumbAesKey string `xml:"cdnthumbaeskey,attr"`
|
||||
ThumbURL string `xml:"cdnthumburl,attr"`
|
||||
Length int `xml:"length,attr"`
|
||||
Md5 string `xml:"md5,attr"`
|
||||
HDHeight int `xml:"cdnhdheight,attr"`
|
||||
HDWidth int `xml:"cdnhdwidth,attr"`
|
||||
MidImgURL string `xml:"cdnmidimgurl,attr"`
|
||||
|
||||
// 子节点
|
||||
SecHashInfo string `xml:"secHashInfoBase64"`
|
||||
Live Live `xml:"live"`
|
||||
}
|
||||
|
||||
// Live 对应 <live> 标签
|
||||
type Live struct {
|
||||
Duration int `xml:"duration"`
|
||||
Size int `xml:"size"`
|
||||
FileID string `xml:"fileid"`
|
||||
}
|
||||
|
||||
type DownloadRequest struct {
|
||||
FileID string `json:"file_id"`
|
||||
Media []byte `json:"media"`
|
||||
CDNURL string `json:"cdn_url"`
|
||||
}
|
||||
|
||||
type ScriptMessage struct {
|
||||
Type string `json:"type"`
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
+28
-8
@@ -284,6 +284,7 @@ var uploadGetCallbackWrapperAddr = baseAddr.add({{.uploadGetCallbackWrapperAddr}
|
||||
var uploadGetCallbackWrapperFuncAddr = baseAddr.add({{.uploadGetCallbackWrapperFuncAddr}});
|
||||
var uploadOnCompleteAddr = baseAddr.add({{.uploadOnCompleteAddr}});
|
||||
var uploadOnCompleteFuncAddr = baseAddr.add({{.uploadOnCompleteFuncAddr}});
|
||||
var downloadImagAddr = baseAddr.add({{.downloadImagAddr}});
|
||||
|
||||
var uploadImageX1;
|
||||
var imgCgiAddr = ptr(0);
|
||||
@@ -1105,8 +1106,6 @@ rpc.exports = {
|
||||
|
||||
// -------------------------接收消息分区-------------------------
|
||||
function setReceiver() {
|
||||
|
||||
// 3. 开始拦截
|
||||
Interceptor.attach(buf2RespAddr, {
|
||||
onEnter: function (args) {
|
||||
const currentPtr = this.context.x1;
|
||||
@@ -1137,7 +1136,7 @@ function setReceiver() {
|
||||
const userContent = fields[5]
|
||||
const msgId = protobufVarintToNumberString(fields[6])
|
||||
|
||||
if (sender === "" || receiver === "" || content === "") {
|
||||
if (sender === "" || receiver === "" || content === "" || msgId === "") {
|
||||
console.log("字段缺失,无法解析 sender:" + sender + " receiver:" + receiver + hexdump(currentPtr, {
|
||||
length: x2,
|
||||
header: true,
|
||||
@@ -1157,6 +1156,7 @@ function setReceiver() {
|
||||
msgType = "group"
|
||||
groupId = sender
|
||||
|
||||
let splitIndex = content.indexOf(':')
|
||||
const sendUserStart = content.indexOf('wxid_')
|
||||
senderUser = content.substring(sendUserStart, splitIndex).trim();
|
||||
|
||||
@@ -1174,13 +1174,13 @@ function setReceiver() {
|
||||
// 处理用户的名称
|
||||
splitIndex = userContent?.indexOf(':')
|
||||
if (splitIndex === -1) {
|
||||
splitIndex = userContent?.indexOf('在群聊中@了你')
|
||||
splitIndex = userContent?.indexOf('在群聊中@了你') !== -1 ? userContent?.indexOf('在群聊中@了你') : userContent?.indexOf('在群聊中发了一段语')
|
||||
senderNickname = userContent?.substring(0, splitIndex).trim();
|
||||
} else {
|
||||
senderNickname = userContent?.substring(0, splitIndex).trim();
|
||||
}
|
||||
if (!senderNickname) {
|
||||
senderNickname = sender
|
||||
senderNickname = senderUser
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -1188,7 +1188,7 @@ function setReceiver() {
|
||||
const splitIndex = userContent?.indexOf(':')
|
||||
senderNickname = userContent?.substring(0, splitIndex).trim();
|
||||
if (!senderNickname) {
|
||||
senderNickname = sender
|
||||
senderNickname = senderUser
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1206,11 +1206,31 @@ function setReceiver() {
|
||||
sender: {user_id: senderUser, nickname: senderNickname},
|
||||
msgsource: xml,
|
||||
raw_message: content,
|
||||
// media: mediaContent,
|
||||
show_content:userContent
|
||||
show_content: userContent
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
Interceptor.attach(downloadImagAddr, { // 建议使用函数起始地址或你计算出的偏移地址
|
||||
onEnter: function (args) {
|
||||
var dataPtr = this.context.x1;
|
||||
var dataLen = this.context.x2.toInt32();
|
||||
var fileId = this.context.sp.add(0x30).readPointer().readUtf8String();
|
||||
var cdnUrl = this.context.x19.add(0x2F8).readPointer().readUtf8String();
|
||||
|
||||
if (dataLen > 0) {
|
||||
var buffer = dataPtr.readByteArray(dataLen);
|
||||
var uint8Array = new Uint8Array(buffer);
|
||||
|
||||
send({
|
||||
type: "download",
|
||||
media: Array.from(uint8Array),
|
||||
file_id: fileId,
|
||||
cdn_url: cdnUrl,
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
+67
-2
@@ -2,10 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
log "log"
|
||||
@@ -155,7 +157,8 @@ func SilkToMp3(silkBytes []byte) ([]byte, error) {
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
func HandleMsg(jsonData []byte, m *WechatMessage) ([]byte, error) {
|
||||
func HandleMsg(jsonData []byte) ([]byte, error) {
|
||||
m := new(WechatMessage)
|
||||
err := json.Unmarshal(jsonData, m)
|
||||
if err != nil {
|
||||
log.Printf("解析消息失败: %v\n", err)
|
||||
@@ -173,9 +176,71 @@ func HandleMsg(jsonData []byte, m *WechatMessage) ([]byte, error) {
|
||||
log.Printf("保存音频失败: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
msg.Data.URL = path
|
||||
msg.Data.URL = "file://" + path
|
||||
msg.Data.Media = nil
|
||||
} else if msg.Type == "image" {
|
||||
var fileMsg FileMsg
|
||||
err := xml.Unmarshal([]byte(msg.Data.Text), &fileMsg)
|
||||
if err != nil {
|
||||
log.Printf("XML解析失败: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果有图片等待图片下载完成再处理
|
||||
if len(msg.Data.Media) == 0 {
|
||||
if downloadMsgInter, ok := userID2FileMsgMap.Load(fileMsg.Image.ThumbURL); ok {
|
||||
downloadReq := downloadMsgInter.(*DownloadRequest)
|
||||
aesKey, _ := hex.DecodeString(fileMsg.Image.ThumbAesKey)
|
||||
path, err := GetImagePath(downloadReq.Media, aesKey)
|
||||
if err != nil {
|
||||
log.Printf("获取图片路径失败: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
msg.Data.URL = "file://" + path
|
||||
userID2FileMsgMap.Delete(fileMsg.Image.ThumbURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
func GetImagePath(data []byte, key []byte) (string, error) {
|
||||
block, _ := aes.NewCipher(key)
|
||||
decrypted := make([]byte, len(data))
|
||||
bs := block.BlockSize()
|
||||
for i := 0; i < len(data); i += bs {
|
||||
block.Decrypt(decrypted[i:i+bs], data[i:i+bs])
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(decrypted, []byte{0xFF, 0xD8, 0xFF}) {
|
||||
return SaveImageToFile("jpg", decrypted)
|
||||
} else if bytes.HasPrefix(decrypted, []byte{0x89, 0x50, 0x4E, 0x47}) {
|
||||
return SaveImageToFile("png", decrypted)
|
||||
} else if bytes.HasPrefix(decrypted, []byte("GIF89a")) || bytes.HasPrefix(decrypted, []byte("GIF87a")) {
|
||||
return SaveImageToFile("gif", decrypted)
|
||||
} else if bytes.HasPrefix(decrypted, []byte{0x42, 0x4D}) {
|
||||
return SaveImageToFile("bmp", decrypted)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("无法解析的图像数据")
|
||||
}
|
||||
|
||||
func SaveImageToFile(ext string, data []byte) (string, error) {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
randomNumber := r.Intn(1000)
|
||||
timestamp := time.Now().Unix()
|
||||
fileName := fmt.Sprintf("%d_%d.%s", randomNumber, timestamp, ext)
|
||||
targetPath := filepath.Dir(exePath) + "/image/" + fileName
|
||||
err = os.WriteFile(targetPath, data, 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return targetPath, nil
|
||||
}
|
||||
|
||||
+4
-8
@@ -81,7 +81,7 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
func SendWebSocketMsg(msg map[string]interface{}) {
|
||||
func SendWebSocketMsg(jsonData []byte) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("panic: %v, %v\n", r, string(debug.Stack()))
|
||||
@@ -89,17 +89,13 @@ func SendWebSocketMsg(msg map[string]interface{}) {
|
||||
}()
|
||||
|
||||
time.Sleep(time.Duration(config.SendInterval) * time.Millisecond)
|
||||
// 这里处理你的 X1 数据
|
||||
jsonData, err := json.Marshal(msg["payload"])
|
||||
|
||||
jsonReq, err := HandleMsg(jsonData)
|
||||
if err != nil {
|
||||
log.Printf("JSON 序列化失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
m := new(WechatMessage)
|
||||
jsonReq, err := HandleMsg(jsonData, m)
|
||||
if err != nil {
|
||||
log.Printf("JSON 序列化失败: %v\n", err)
|
||||
if jsonReq == nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -17,5 +17,6 @@
|
||||
"uploadGetCallbackWrapperAddr": "0x49068CC",
|
||||
"uploadGetCallbackWrapperFuncAddr": "0x36AFBEC",
|
||||
"uploadOnCompleteAddr": "0x4906EC8",
|
||||
"uploadOnCompleteFuncAddr": "0x36B0DD0"
|
||||
"uploadOnCompleteFuncAddr": "0x36B0DD0",
|
||||
"downloadImagAddr": "0x49FB92C"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user