数字签名与信任链取证深度分析

数字签名与公钥基础设施(PKI)是现代计算环境中信任机制的核心支柱。从操作系统内核模块加载到应用程序安装、从TLS加密通信到软件更新推送,数字签名无处不在地为软件来源的真实性与完整性提供背书。然而,正是这种广泛存在的信任机制,使其成为高级威胁行为者重点攻击的目标。攻击者通过窃取代码签名证书伪造合法软件、入侵CA机构签发伪造证书、利用签名绕过操作系统安全控制,从而在受害环境中建立深度持久化。在数字签名取证领域,取证分析人员需要深入理解PKI信任体系的工作原理,掌握从签名元数据、证书生命周期、证书透明度日志等多维度提取攻击线索的方法,并能够运用Sigcheck、openssl、ct-forensics等工具构建自动化检测能力。本文系统性地覆盖数字签名与PKI信任链取证的全链路方法论,结合SolarWinds/SUNBURST、Stuxnet、DigiNotar等真实案例,为安全研究人员提供签名信任体系深度溯源的完整实战指南。

0x01 技术基础与 PKI/签名取证概述

数字签名技术原理

数字签名基于非对称加密算法构建,其核心流程包含签名生成与签名验证两个阶段。签名生成时,签名者使用私钥对消息的哈希值进行加密运算,生成数字签名;验证时,验证者使用对应的公钥解密签名并比对哈希值。这一机制同时提供了身份认证、完整性校验和不可否认性三大安全属性。

算法族代表算法密钥长度应用场景
RSARSA-2048/40962048-4096 bit代码签名、SSL证书
ECDSAP-256/P-384256-384 bitTLS证书、代码签名
EdDSAEd25519256 bitGit签名、SSH
SM2SM2256 bit国密代码签名

在代码签名场景中,签名过程通常包含以下步骤:首先对可执行文件计算哈希值(通常使用SHA-256),然后使用签名者的私钥对哈希值进行加密,将生成的签名数据与签名者证书一同嵌入到PE文件的Attribute Certificate Table(PE第4项数据目录)中。Windows Authenticode签名会将签名信息附加在PE文件末尾,而不修改原始代码段,这使得验证过程可以在不改变文件内容的前提下完成。

PKI 信任体系架构

PKI(Public Key Infrastructure)采用分层信任模型,其架构包含以下核心组件:

组件角色取证关注点
根CA(Root CA)信任锚点,自签名证书根证书是否被篡改、是否被植入恶意根CA
中间CA(Intermediate CA)签发终端实体证书是否存在未授权的中间CA、证书链是否完整
终端实体(End Entity)最终用户/服务器证书证书是否过期/吊销、Subject是否匹配
证书吊销列表(CRL)已吊销证书列表CRL是否及时更新、吊销原因代码
OCSP在线证书状态协议OCSP响应是否被篡改、OCSP Stapling状态

证书链验证的完整过程为:从终端实体证书出发,逐级向上验证每级证书的签名,直到追溯到本地信任存储中的根CA证书。任何一级验证失败都会导致整个信任链断裂。在取证分析中,攻击者可能通过在受害系统中植入恶意根CA证书来建立持久化的中间人攻击能力,或者利用合法CA的漏洞签发伪造证书以绕过信任链验证。

代码签名在操作系统中的信任机制

不同操作系统实现了各自的代码签名信任机制,理解这些机制对于取证分析至关重要。

Windows Authenticode 是Windows平台的代码签名标准。Authenticode签名通过PE文件的WIN_CERTIFICATE结构存储,使用PKCS#7 SignedData格式封装签名者证书链和签名值。Windows加载器在执行可执行文件、加载驱动程序或加载DLL时会验证Authenticode签名。签名验证失败的文件会被阻止加载,除非配置了特殊的策略(如禁用驱动程序签名强制)。Windows维护两个关键的证书存储:Trusted Root Certification Authorities(受信任的根证书颁发机构)和Trusted Publishers(受信任的发布者),分别对应根CA和已验证的代码签名发布者。

macOS Gatekeeper 采用三层验证机制:首先验证Apple的签名(由Apple Root CA签发),然后验证应用的notarization票据(通过stapler工具附加到应用中),最后验证应用的Hardened Runtime和Entitlements。从macOS Ventura开始,Apple进一步强化了Gatekeeper验证,要求所有从App Store外部下载的应用必须经过notarization。Gatekeeper的策略存储在/var/db/SystemPolicy数据库中。

Linux GPG/Kernel Module签名 在Linux环境中,软件包签名主要依赖GPG(GNU Privacy Guard),而内核模块签名则使用X.509证书。内核模块签名由内核的MODSIG机制强制验证,未签名或签名无效的模块将被拒绝加载。Linux发行版通常使用维护者的GPG密钥对软件仓库进行签名,包管理器(如APT、YUM)在安装时验证签名。

签名信任链取证的独特价值

与传统的恶意软件分析和日志取证相比,数字签名取证具有以下独特价值:

  • 可信时间戳:通过RFC 3161时间戳可以确定签名的精确时间,为事件时间线重建提供可靠锚点
  • 签名者身份:代码签名证书包含组织名称、证书序列号等信息,可用于追踪攻击者使用的证书来源
  • 完整性验证:签名验证失败表明文件已被篡改或签名被剥离,这本身就是重要的取证线索
  • 持久化检测:恶意驱动程序或内核模块必须通过签名验证才能加载,检测异常签名是发现高级持久化的重要手段

取证工具链

工具功能平台获取方式
SigcheckPE文件签名验证与证书转储WindowsSysinternals
signtool微软官方签名工具WindowsWindows SDK
osslsigncode跨平台代码签名工具跨平台GitHub
openssl证书解析与验证跨平台系统自带
crt.shCT日志查询Web界面Webcrt.sh
Censys互联网证书搜索Webcensys.io
ct-forensicsCT日志取证分析工具Linux/macOSGitHub
python-cryptographyPython证书操作库跨平台pip install
sigcheck.exe -v -vt -e "C:\Windows\System32\drivers"

该命令使用Sigcheck递归扫描drivers目录下所有驱动程序,输出详细的签名信息(-v)并提交VirusTotal查询(-vt),-e参数表示仅显示已签名文件。输出结果包含签名状态、签名者证书信息、证书链、VirusTotal检测结果等关键取证数据。

0x02 代码签名滥用与证书窃取取证

代码签名证书的窃取方式

攻击者获取代码签名证书通常通过以下途径,每种途径在取证分析中留下的痕迹各不相同:

窃取方式攻击技术MITRE ATT&CK取证痕迹
源码仓库泄露硬编码密钥/PFX文件T1552.001Git历史记录、CI配置文件
构建环境入侵CI/CD服务器渗透T1195.002构建日志异常、未授权访问
开发者工作站攻击内存dump/注册表提取T1555内存取证、注册表修改
HSM/密钥库攻击API密钥泄露T1552.005API调用日志异常
社会工程学骗取证书文件T1587.003邮件日志、社工痕迹
供应链入侵中间人截取T1195.002依赖更新日志异常

签名密钥的存储位置与提取方法

代码签名私钥在不同操作系统中的存储位置直接决定了取证检查的重点:

Windows证书存储:私钥通常存储在Cert:\LocalMachine\My(计算机个人证书存储)或Cert:\CurrentUser\My(当前用户个人证书存储)中。使用PowerShell可以枚举所有代码签名证书:

Get-ChildItem -Path Cert:\LocalMachine\My -CodeSigningCert | 
    Format-List Subject, Thumbprint, NotAfter, HasPrivateKey, Issuer

上述命令列出计算机存储中所有标记为代码签名的证书,输出主题名、指纹、过期时间、是否包含私钥和颁发者信息。对于取证分析而言,关注HasPrivateKey字段尤为重要——如果一个本不应存在于目标系统上的代码签名证书出现在存储中且包含私钥,这可能表明攻击者已成功提取了签名密钥。

macOS Keychain:代码签名证书及其私钥存储在login.keychain-dbSystem.keychain中,使用security命令可以导出证书信息:

security find-identity -v -p codesigning /Library/Keychains/System.keychain

HSM(Hardware Security Module):企业级代码签名通常将私钥存储在HSM中,私钥永远不能被导出。HSM攻击的取证线索包括:HSM访问日志中的异常登录、未授权的签名请求、签名速率异常等。

窃取签名证书的检测方法

检测被盗代码签名证书的使用是取证分析的核心任务之一。以下是系统化的检测方法:

证书吊销状态检查:使用certutil检查证书的吊销状态:

certutil -urlfetch -verify "C:\temp\suspicious_cert.der"

该命令通过OCSP和CRL双重验证证书状态,输出包含证书链验证结果、吊销状态、OCSP响应详情等信息。如果证书已被吊销但仍在使用中,这是确认恶意行为的关键证据。

签名时间异常分析:合法软件的签名时间通常在合理的开发周期内,而攻击者使用窃取的证书时可能出现以下异常模式:

异常模式描述可信度
签名时间在证书过期后证书已过期但签名仍有效(时间戳导致)🟡 高度可疑
签名时间与文件编译时间严重不匹配文件PE头时间戳与签名时间差异巨大🟡 高度可疑
同一证书短时间内签名大量不同产品超出正常开发节奏的签名频率🔴 确认恶意
签名者名称与软件发布者不匹配证书Subject与软件Publisher属性不一致🟡 高度可疑

自签名与旁路签名技术

攻击者在无法获取合法签名证书时,会采用自签名或旁路签名技术:

Sigthief 是一种开源工具,可以从合法的已签名PE文件中提取签名数据,并将其"嫁接"到未签名的PE文件上。这种方法生成的文件在某些检查场景下会显示为"已签名",但证书链验证会失败。

python sigthief.py -i legitimate_signed.exe -t unsigned_malware.exe -o fake_signed_malware.exe

检测此类攻击的要点在于:签名中的Hash字段与实际文件内容不匹配,或者签名证书的Subject与文件的OriginalFilename/CompanyName属性不一致。

驱动程序签名滥用(BYOVD) 攻击者利用存在漏洞的合法签名驱动程序来加载未签名的内核代码,绕过Windows的Driver Signature Enforcement(DSE)保护。MITRE ATT&CK将此技术分类为T1014(Rootkit)和T1562.001(Disable or Modify Tools)。

Get-WindowsDriver -Online | Where-Object {$_.ClassName -eq "kernel"} | 
    Select-Object Driver, Version, Date, ProviderName | 
    Sort-Object Date -Descending | Format-Table -AutoSize

0x03 CA 攻击与伪造证书检测

CA 安全事件回顾

Certificate Authority(CA)是PKI信任体系的核心,CA被攻破意味着整个信任体系的崩溃。以下是历史上最具影响力的CA安全事件:

事件时间影响攻击方式
DigiNotar2011500+伪造证书,国家级MITM入侵CA系统签发伪造证书
Comodo RA2011伪造Google/Mozilla等域名证书社工攻击RA注册流程
Symantec2015-2017大规模违规签发未经验证签发证书
Let’s Encrypt2020重复证书序列号事件软件缺陷导致异常签发
CNNIC2015中间CA违规签发中间CA未受控签发
GlobalSign2011伪造证书请求入侵注册系统

DigiNotar事件是CA被攻破的标志性案例。2011年,荷兰CA机构DigiNotar被攻击者完全入侵,攻击者利用DigiNotar的基础设施签发了超过500个伪造证书,其中包括针对Google、Mozilla、Microsoft、Skype等域名的证书。这些伪造证书被用于对伊朗等地区的HTTPS通信实施中间人攻击。事件最终导致DigiNotar被所有主流浏览器和操作系统吊销信任,成为历史上首个因安全事件被彻底关闭的CA。

中间人攻击中的证书伪造

攻击者通过控制CA可以签发任意域名的合法证书,从而在不触发浏览器警告的前提下实施HTTPS中间人攻击。在取证分析中,需要关注以下检测点:

证书指纹比对:对于已知的合法域名,可以预先存储其证书指纹,在网络流量中比对证书指纹是否发生变化。使用openssl获取远程服务器证书指纹:

echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -fingerprint -sha256

OCSP/CRL验证:通过OCSP响应或CRL检查证书是否被吊销。在取证环境中,如果发现客户端信任了一个已被吊销的证书,说明可能存在证书伪造行为:

openssl ocsp -issuer issuer.pem -cert target.pem -url http://ocsp.example.com -resp_text

伪造证书检测方法

检测方法检测能力工具/命令局限性
CT日志检查crt.sh, Censys依赖CT日志完整性
指纹比对openssl, certutil需要已知合法指纹
OCSP/CRL验证openssl ocsp可被攻击者阻断
证书链验证certutil -verify根CA被信任则无法检测
公钥一致性检查openssl, keytool需要基线数据
颁发时间异常手动分析需要业务上下文

内部CA攻击案例

在企业内部环境中,Active Directory Certificate Services(AD CS)是常见的CA实现。AD CS的配置缺陷可能导致攻击者签发任意证书:

Certify/Certipy工具:这两个工具用于利用AD CS的配置缺陷,包括ESC1(过于宽松的证书模板权限)、ESC2(允许任意SAN的证书模板)、ESC8(NTLM中继到AD CS Web注册端点)等漏洞。攻击者利用这些漏洞可以为任意用户签发证书,实现Kerberos认证绕过和持久化。

Certify.exe /ca:CA-SERVER\CA-NAME /template:VulnerableTemplate /altname:administrator

检测AD CS攻击的关键日志包括Windows事件日志中的证书服务日志:

  • 事件ID 4886:证书服务收到证书请求
  • 事件ID 4887:证书服务批准了证书请求
  • 事件ID 4888:证书服务拒绝了证书请求
  • 事件ID 4889:证书服务将证书请求的状态设为挂起
  • 事件ID 4890:证书管理视图已更改

检测异常证书颁发的自动化方法

#!/bin/bash
CA_LOG="/var/log/certsrv/IssuedCerts.log"
SUSPICIOUS_PATTERNS="sandbox\.|test\.|internal\.|staging\.|dev\."
ALERT_EMAIL="security-team@company.com"

if [ ! -f "$CA_LOG" ]; then
    echo "CA log file not found: $CA_LOG"
    exit 1
fi

grep -i "issued" "$CA_LOG" | while IFS=',' read -r date time serial template requester san; do
    for pattern in $SUSPICIOUS_PATTERNS; do
        if echo "$san" | grep -qi "$pattern"; then
            echo "[ALERT] Suspicious cert issued: SAN=$san Requester=$requester Template=$template Serial=$serial"
        fi
    done
done

0x04 证书透明度(CT)与日志分析

CT Log 架构与 SCT 验证机制

Certificate Transparency(CT)是Google于2012年提出并推动的开放框架,旨在通过公开日志记录所有签发的SSL/TLS证书,使域名所有者能够发现未经授权的证书签发。CT已成为RFC 6962标准。

CT架构包含三个核心角色:

角色功能取证价值
Log Server存储和发布已签发证书的日志检索攻击者签发的伪造证书
Monitor监控日志中特定域名的证书发现针对目标域名的未授权证书
Auditor验证日志的完整性确保证据不被篡改

SCT(Signed Certificate Timestamp)是CT Log对已接收证书签发的收据,包含Log的签名承诺、证书信息和时间戳。浏览器在建立TLS连接时会验证SCT的存在和有效性。Chrome要求所有新签发的证书必须包含有效的SCT,否则会显示安全警告。

在取证分析中,SCT提供了关键的证书签发时间证据。即使攻击者试图回溯签名时间,CT日志中的SCT时间戳记录了证书在CT Log中提交的真实时间,为构建攻击时间线提供不可篡改的参考。

crt.sh 和 CT 日志查询技术

crt.sh是最常用的CT日志查询工具,支持通配符搜索和批量查询:

curl -s "https://crt.sh/?q=%.example.com&output=json" | python3 -m json.tool | \
    jq '[.[] | {id, issuer_name, name_value, not_before, not_after, serial_number}]' | \
    sort_by(.not_before)

该命令查询所有与example.com相关的已签发证书,按签发时间排序输出。输出字段包括证书ID、颁发者、域名、有效期起止时间和序列号。

对于更复杂的查询场景,crt.sh支持SQL语法:

SELECT ci.NAME_VALUE, ci.ISSUER_NAME, c.NOT_BEFORE, c.NOT_AFTER, c.SERIAL_NUMBER
FROM certificate_issuer ci
JOIN certificates c ON ci.ID = c.ID
WHERE ci.NAME_VALUE LIKE '%.example.com'
AND c.NOT_BEFORE > '2024-01-01'
ORDER BY c.NOT_BEFORE DESC;

未授权证书检测

检测针对特定域名的未授权证书签发是CT日志分析的核心应用场景:

import requests
import json
import sys
from datetime import datetime

def query_ct_log(domain):
    url = f"https://crt.sh/?q=%.{domain}&output=json"
    try:
        response = requests.get(url, timeout=30)
        response.raise_for_status()
        return json.loads(response.text)
    except requests.exceptions.RequestException as e:
        print(f"Error querying CT log: {e}")
        return []

def analyze_certificates(certs, known_cas=None):
    if known_cas is None:
        known_cas = []

    findings = []
    for cert in certs:
        issuer = cert.get("issuer_name", "")
        name_value = cert.get("name_value", "")
        not_before = cert.get("not_before", "")
        serial = cert.get("serial_number", "")

        is_unauthorized = False
        reason = ""

        if known_cas and issuer not in known_cas:
            is_unauthorized = True
            reason = f"Unknown CA: {issuer}"

        entry = {
            "domain": name_value,
            "issuer": issuer,
            "not_before": not_before,
            "serial": serial,
            "suspicious": is_unauthorized,
            "reason": reason
        }
        findings.append(entry)

    return findings

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <domain> [known_ca1 known_ca2 ...]")
        sys.exit(1)

    target_domain = sys.argv[1]
    known_cas = sys.argv[2:] if len(sys.argv) > 2 else []

    print(f"[*] Querying CT logs for: *.{target_domain}")
    certs = query_ct_log(target_domain)
    print(f"[*] Found {len(certs)} certificates")

    findings = analyze_certificates(certs, known_cas)

    suspicious = [f for f in findings if f["suspicious"]]
    if suspicious:
        print(f"\n[!] {len(suspicious)} potentially unauthorized certificates found:")
        for f in suspicious:
            print(f"  Domain: {f['domain']}")
            print(f"  Issuer: {f['issuer']}")
            print(f"  Date: {f['not_before']}")
            print(f"  Reason: {f['reason']}")
            print()
    else:
        print("[*] No unauthorized certificates detected")

CT 日志中的攻击者足迹分析

CT日志不仅记录了证书签发信息,还隐含了攻击者的活动足迹。通过对CT日志的深度分析,可以发现以下攻击指标:

攻击指标CT日志特征分析方法
钓鱼域名证书与目标域名高度相似的域名相似度分析
批量伪造证书短时间内大量不同域名证书时间频率分析
子域名枚举大量子域名的证书签发模式匹配分析
证书重签发同一域名频繁重新签发变更频率分析

CT 监控告警策略

企业应建立CT日志监控机制,对关键域名的证书签发实施持续监控。以下是Sigma规则示例,用于检测异常证书安装行为:

title: Suspicious Root CA Certificate Installation
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
status: stable
description: Detects installation of new root CA certificates, which may indicate MITM attack preparation
references:
  - https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4886
author: Security Team
date: 2026/07/06
tags:
  - attack.credential_access
  - attack.t1553.004
logsource:
  product: windows
  service: security
detection:
  selection_event_id:
    EventID:
      - 4886
      - 4887
  selection_target_store:
    TargetStore|contains:
      - 'Root'
      - 'Trusted'
  condition: selection_event_id and selection_target_store
  level: high
curl -s "https://crt.sh/?q=%.target-company.com&output=json" | \
    python3 -c "
import sys, json
from collections import Counter
data = json.load(sys.stdin)
issuers = Counter(c.get('issuer_name','Unknown') for c in data)
for issuer, count in issuers.most_common(10):
    print(f'{count:4d} | {issuer}')
"

0x05 签名时间戳与可验证时间链分析

时间戳服务器的作用与类型

时间戳(Timestamping)在数字签名中扮演着关键角色——它证明了签名在某个特定时间点已经存在,即使签名证书后续过期或被吊销,带有合法时间戳的签名仍然有效。这一特性在取证分析中既有价值也有挑战。

RFC 3161定义了标准的时间戳协议,支持两种时间戳模式:

时间戳模式描述安全性应用场景
RFC 3161独立时间戳签名后由TSA独立签发时间戳代码签名时间戳
Authenticode嵌入时间戳Microsoft Authenticode签名内嵌时间戳Windows驱动程序签名
无时间戳签名仅包含签名,不含时间戳临时测试签名

Authenticode时间戳嵌入在PE文件的WIN_CERTIFICATE结构中,使用WIN_CERT_TYPE_PKCS_SIGNED_DATA类型封装。signtool使用/tr/t参数指定时间戳服务器:

signtool sign /f cert.pfx /p password /tr http://timestamp.digicert.com /td sha256 application.exe

时间戳伪造与回溯攻击

理论上,攻击者可能尝试伪造时间戳来创建看似合法的签名历史。然而,由于以下原因,时间戳伪造在实际操作中面临显著挑战:

  1. 时间戳由可信第三方TSA签发:TSA的时间戳本身也是数字签名的,伪造时间戳需要攻破TSA的私钥
  2. CT日志的时间戳SCT:CT Log记录的SCT时间戳提供了独立的时间锚点
  3. 系统日志交叉验证:Windows事件日志、文件系统时间戳等可与签名时间戳交叉验证

尽管如此,取证分析人员仍需关注以下时间戳异常模式:

Get-AuthenticodeSignature "C:\Windows\System32\drivers\suspicious.sys" | 
    Select-Object Status, StatusMessage, SignerCertificate, TimeStamperCertificate | 
    Format-List *
时间戳异常类型描述MITRE ATT&CK取证结论
时间戳服务器不可达TSA URL返回错误或超时N/A可能的离线攻击环境
时间戳时间与签名时间不一致TSA时间戳与签名声明时间差异大T1070.006时间回溯攻击嫌疑
TSA证书不可信时间戳TSA不在受信任列表中T1553.004伪造时间戳嫌疑
时间戳晚于文件编译时间时间戳在PE编译后很久才生成T1070迟迟签名,可疑行为

时间戳链分析方法

在复杂的取证场景中,需要交叉验证多个时间戳来建立可信的时间链。以下是分析流程:

步骤操作验证目标
1提取PE签名时间戳签名声明时间
2提取TSA时间戳独立时间锚点
3检查PE文件编译时间文件创建时间
4检查文件系统MAC时间文件修改时间
5检查Windows事件日志安装/执行时间
6交叉比对所有时间一致性验证
openssl pkcs7 -inform DER -in signed_file.p7b -print_certificates | \
    grep -A2 "Not Before" 

该命令提取签名中所有证书的有效期信息,帮助验证时间戳证书是否在有效签发期内。

可信时间戳在法律取证中的地位

根据《电子签名法》和国际电子证据相关法规,带有RFC 3161可信时间戳的数字签名在法律上具有较高的证据效力。时间戳证明了数据在特定时间点的存在性和完整性,常用于:

  • 知识产权保护(证明创意的优先权日期)
  • 电子合同的签署时间确认
  • 数字遗嘱和电子遗嘱的法律效力
  • 软件著作权的登记时间证明

在安全取证场景中,可信时间戳可以为事件响应提供可靠的时间锚点,帮助确定攻击发生的时间窗口和恶意软件的首次出现时间。

0x06 供应链攻击中的签名信任链利用

供应链攻击签名滥用模式

供应链攻击是数字签名滥用的最高级形式之一。攻击者不是简单地窃取一个签名证书,而是在软件的生产、分发和更新环节中植入恶意代码,利用合法的签名机制来"洗白"恶意软件。以下表格总结了近年重大供应链攻击中的签名滥用模式:

攻击事件签名滥用方式影响范围ATT&CK
SolarWinds/SUNBURST使用合法SolarWinds签名18,000+组织T1195.002
Codecov篡改CI/CD脚本29,000+客户T1195.002
Kaseya VSA利用软件更新机制1,500+企业T1195.002
3CX桌面客户端供应链攻击600,000+客户T1195.002
JetBrains/TeamCityCI/CD服务器漏洞多个下游项目T1195.002
XZ Utils长期社工植入后门Linux发行版T1195.002

签名在供应链攻击中的关键角色

攻击者在供应链攻击中利用合法签名实现以下目的:

  1. 绕过安全检查:已签名的文件通常被EDR/AV标记为可信,降低了检测概率
  2. 维持合法外观:利用原始厂商的签名保持软件的合法性外观,延长潜伏期
  3. 通过应用白名单:企业应用白名单策略通常允许已签名软件运行
  4. 规避浏览器/操作系统警告:合法签名避免了"未知发布者"的安全警告

签名链验证在供应链取证中的应用

在供应链攻击取证中,签名链验证是最关键的分析手段之一。取证人员需要逐层验证软件的签名链,确认每个环节的完整性和可信度。

openssl smime -verify -in signed_package.bin -signer ca_cert.pem \
    -CAfile root_ca.pem -out verified_content.bin

对于npm、PyPI等包管理器分发的软件包,签名验证需要结合包管理器自身的机制:

npm audit signatures --registry https://registry.npmjs.org/
pip install --require-hashes -r requirements.txt
验证层级验证内容检测目标
包签名验证包是否由注册发布者签名未签名/伪造签名的包
证书链验证签名证书是否追溯到可信根CA中间CA攻击
签名时间验证签名时间是否在合理范围内时间戳伪造
哈希完整性文件哈希是否与签名匹配签名后篡改
发布者一致性签名者与预期发布者匹配签名证书盗用

构建系统完整性审计

软件构建系统的完整性是供应链安全的关键环节。SBOM(Software Bill of Materials)提供了软件组件的完整清单,可用于追踪依赖关系和发现异常:

syft ./target-application -o spdx-json > sbom.spdx.json
grype sbom:sbom.spdx.json --fail-on high

Sigstore/cosign提供了基于区块链的软件签名和验证机制,支持在不依赖传统PKI的情况下验证软件来源:

cosign verify --key cosign.pub \
    --certificate-identity=developer@example.com \
    --certificate-oidc-issuer=https://accounts.google.com \
    ghcr.io/example/image:tag

软件供应链签名最佳实践

框架/标准核心理念关键机制
SLSA(Supply chain Levels for Software Artifacts)分级安全保障构建来源验证、构建平台审计
Sigstore透明化签名透明日志、OIDC身份绑定
in-toto供应链完整性框架步骤验证、布局约束
TUF(The Update Framework)安全更新分发防篡改元数据、密钥轮转
Sigstore Cosign容器镜像签名无密钥签名、在线验证
S/MIME邮件签名标准发件人身份验证

0x07 证据强度分层与案例关联

在数字签名取证中,对发现的证据进行强度分层是指导后续响应行动的关键步骤。以下是基于签名异常的三级证据分类体系:

🔴 确认恶意(Confirmed Malicious)

以下证据直接确认恶意行为,需要立即响应:

证据类型描述响应优先级
使用已吊销证书签名的可执行文件证书已被CA吊销但文件仍使用该签名P0-紧急
CT日志中发现的伪造域名证书针对本公司域名的未授权证书签发P0-紧急
内核模块使用已知恶意签名与已知攻击工具或APT使用的签名匹配P0-紧急
签名证书指纹与已知恶意软件匹配VirusTotal/威胁情报库中的已知恶意签名P0-紧急
私有CA签发的伪造证书用于MITM内部CA被利用签发伪造证书P0-紧急

🟡 高度可疑(Highly Suspicious)

以下证据高度可疑,需要深入调查:

证据类型描述调查方向
签名时间与编译时间严重不匹配差异超过24小时检查是否使用时间戳伪造
使用被盗证书签名的驱动程序证书所有者与签名软件无关联追踪证书来源
未知CA签发的代码签名证书不在企业已知CA白名单中检查CT日志
同一证书签名多种不同类型软件超出正常签名范围证书滥用调查
签名证书的密钥长度异常偏短低于行业标准(如RSA-1024)可能是弱密钥攻击
证书SAN字段包含多个不相关域名域名列表异常批量域名控制

🟢 需要关注(Needs Attention)

以下证据需要关注但不一定意味着恶意:

证据类型描述进一步验证
过期但未吊销的签名证书证书已过期但仍在CRL有效期外联系证书颁发者
自签名但未在内部PKI注册的证书非标准的自签名证书确认是否为开发环境使用
签名证书即将过期证书在30天内过期提醒更新
证书OCSP响应慢或不可达TSA或OCSP服务异常确认是否为网络问题
SHA-1签名哈希算法使用即将废弃的哈希算法计划迁移到SHA-256

0x08 自动化检测与狩猎

Bash 脚本:批量检查目录下可执行文件签名状态

#!/bin/bash
TARGET_DIR="${1:-.}"
OUTPUT_FILE="signature_audit_$(date +%Y%m%d_%H%M%S).csv"
SUSPICIOUS_THRESHOLD=5

echo "File,Status,Certificate_Thumbprint,Issuer,Subject,Expiry" > "$OUTPUT_FILE"

find "$TARGET_DIR" -type f \( -name "*.exe" -o -name "*.dll" -o -name "*.sys" \) 2>/dev/null | while read -r file; do
    if command -v osslsigncode &> /dev/null; then
        result=$(osslsigncode verify "$file" 2>&1)
        status=$?
        if [ $status -eq 0 ]; then
            thumbprint=$(echo "$result" | grep -oP 'Thumbprint\([A-F0-9]+\)' | head -1 | tr -dc 'A-F0-9')
            issuer=$(echo "$result" | grep -oP 'Issuer: \K.*' | head -1)
            subject=$(echo "$result" | grep -oP 'Subject: \K.*' | head -1)
            echo "\"$file\",\"VALID\",\"$thumbprint\",\"$issuer\",\"$subject\",\"\"" >> "$OUTPUT_FILE"
        else
            echo "\"$file\",\"INVALID/UNSIGNED\",\"N/A\",\"N/A\",\"N/A\",\"N/A\"" >> "$OUTPUT_FILE"
        fi
    elif command -v sigcheck &> /dev/null; then
        result=$(sigcheck.exe -accepteula -v "$file" 2>/dev/null)
        echo "\"$file\",\"$(echo "$result" | tail -1)\"" >> "$OUTPUT_FILE"
    else
        echo "[ERROR] No signature verification tool available. Install osslsigncode or sigcheck."
        exit 1
    fi
done

total=$(wc -l < "$OUTPUT_FILE")
invalid=$(grep -c '"INVALID' "$OUTPUT_FILE" || true)
unsigned=$(grep -c '"UNSIGNED' "$OUTPUT_FILE" || true)
echo "[*] Scan complete: $total files scanned, $invalid invalid signatures, $unsigned unsigned files"
echo "[*] Results saved to: $OUTPUT_FILE"

Python 脚本:CT日志查询与证书异常检测

import requests
import json
import sys
import hashlib
from datetime import datetime, timedelta
from collections import defaultdict

KNOWN_CAS = [
    "DigiCert Inc", "Let's Encrypt", "Sectigo Limited",
    "Cloudflare Inc", "Google Trust Services LLC",
    "Amazon", "Microsoft Corporation"
]

ALERT_DOMAINS = ["example-corp.com", "internal-company.com"]

def query_crt_sh(domain):
    url = f"https://crt.sh/?q=%.{domain}&output=json"
    try:
        resp = requests.get(url, timeout=30, headers={"User-Agent": "CT-Forensics/1.0"})
        resp.raise_for_status()
        return json.loads(resp.text)
    except Exception as e:
        print(f"[ERROR] Failed to query crt.sh for {domain}: {e}")
        return []

def detect_anomalies(certs, known_cas):
    anomalies = []
    ca_counts = defaultdict(int)
    domain_timeline = []

    for cert in certs:
        issuer = cert.get("issuer_name", "Unknown")
        not_before = cert.get("not_before", "")
        not_after = cert.get("not_after", "")
        name = cert.get("name_value", "")
        serial = cert.get("serial_number", "")

        ca_counts[issuer] += 1

        is_unknown_ca = not any(known in issuer for known in known_cas)
        is_short_lived = False
        if not_before and not_after:
            try:
                start = datetime.strptime(not_before, "%Y-%m-%dT%H:%M:%S")
                end = datetime.strptime(not_after, "%Y-%m-%dT%H:%M:%S")
                lifetime = (end - start).days
                is_short_lived = lifetime < 30
            except ValueError:
                pass

        if is_unknown_ca:
            anomalies.append({
                "type": "UNKNOWN_CA",
                "severity": "HIGH",
                "domain": name,
                "issuer": issuer,
                "not_before": not_before,
                "serial": serial,
                "detail": f"Certificate issued by unknown CA: {issuer}"
            })

        if is_short_lived:
            anomalies.append({
                "type": "SHORT_LIVED_CERT",
                "severity": "MEDIUM",
                "domain": name,
                "issuer": issuer,
                "not_before": not_before,
                "serial": serial,
                "detail": "Certificate valid for less than 30 days"
            })

        domain_timeline.append({
            "domain": name,
            "issuer": issuer,
            "not_before": not_before,
            "serial": serial
        })

    return anomalies, ca_counts, domain_timeline

def main():
    if len(sys.argv) < 2:
        print(f"Usage: python3 {sys.argv[0]} <domain1> [domain2] ...")
        sys.exit(1)

    target_domains = sys.argv[1:]

    for domain in target_domains:
        print(f"\n{'='*60}")
        print(f"[*] Analyzing CT logs for: {domain}")
        print(f"{'='*60}")

        certs = query_crt_sh(domain)
        if not certs:
            print(f"[!] No certificates found for {domain}")
            continue

        anomalies, ca_counts, timeline = detect_anomalies(certs, KNOWN_CAS)

        print(f"[*] Total certificates found: {len(certs)}")
        print(f"[*] Anomalies detected: {len(anomalies)}")

        print(f"\n--- CA Distribution ---")
        for ca, count in sorted(ca_counts.items(), key=lambda x: -x[1]):
            marker = " [UNKNOWN CA]" if not any(k in ca for k in KNOWN_CAS) else ""
            print(f"  {count:4d} | {ca}{marker}")

        if anomalies:
            print(f"\n--- Anomalies ---")
            for a in anomalies:
                print(f"  [{a['severity']}] {a['type']}: {a['detail']}")
                print(f"    Domain: {a['domain']}")
                print(f"    Issuer: {a['issuer']}")
                print(f"    Date: {a['not_before']}")
                print(f"    Serial: {a['serial']}")
                print()
        else:
            print(f"\n[*] No anomalies detected")

if __name__ == "__main__":
    main()

Sigma 规则:检测异常证书安装和签名行为

title: New Root CA Certificate Installed
id: b2c3d4e5-f6a7-8901-bcde-f12345678901
status: stable
description: Detects installation of new root CA certificates in the local trust store
author: Security Team
date: 2026/07/06
modified: 2026/07/06
tags:
  - attack.credential_access
  - attack.t1553.004
  - attack.defense_evasion
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID:
      - 4886
      - 4887
  filter_legitimate:
    SubjectName|contains:
      - 'DigiCert'
      - 'Let''s Encrypt'
      - 'Microsoft'
      - 'Google'
      - 'Amazon'
      - 'Cloudflare'
  condition: selection and not filter_legitimate
  falsepositives:
    - Legitimate PKI infrastructure changes
  level: medium
title: Suspicious Code Signing Certificate Usage
id: c3d4e5f6-a7b8-9012-cdef-234567890123
status: experimental
description: Detects code signing events that may indicate use of stolen or unauthorized certificates
author: Security Team
date: 2026/07/06
tags:
  - attack.defense_evasion
  - attack.t1553.002
  - attack.t1036
logsource:
  product: windows
  service: sysmon
detection:
  selection_sign:
    EventID: 15
    TargetFilename|endswith:
      - '.exe'
      - '.dll'
      - '.sys'
  selection_unsigned_or_weak:
    ImageLoaded|contains:
      - '\Temp\'
      - '\AppData\Local\Temp\'
      - '\Downloads\'
  condition: selection_sign and selection_unsigned_or_weak
  level: medium

PowerShell 脚本:枚举系统中所有代码签名证书并检查吊销状态

param(
    [string]$StorePath = "Cert:\LocalMachine\My",
    [string]$OutputCsv = "CodeSigningCertAudit.csv"
)

$certificates = Get-ChildItem -Path $StorePath -CodeSigningCert

$results = @()

foreach ($cert in $certificates) {
    $status = "Valid"
    $revokeReason = ""

    try {
        $chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
        $chain.Build($cert) | Out-Null

        foreach ($element in $chain.ChainElements) {
            if ($element.ChainElementStatus.Length -gt 0) {
                foreach ($status_info in $element.ChainElementStatus) {
                    if ($status_info.Status -ne "NoError") {
                        $status = "ChainError: $($status_info.Status)"
                        $revokeReason = $status_info.StatusInformation
                    }
                }
            }
        }

        $ocspUrl = ($cert.Extensions | Where-Object { $_.Oid.Value -eq "1.3.6.1.5.5.7.1.1" }).RawData
        if ($ocspUrl) {
            $status += " [OCSP Available]"
        }
    } catch {
        $status = "VerificationFailed: $($_.Exception.Message)"
    }

    $result = [PSCustomObject]@{
        Subject = $cert.Subject
        Issuer = $cert.Issuer
        Thumbprint = $cert.Thumbprint
        NotBefore = $cert.NotBefore
        NotAfter = $cert.NotAfter
        SerialNumber = $cert.SerialNumber
        HasPrivateKey = $cert.HasPrivateKey
        KeyLength = $cert.PublicKey.Key.KeySize
        SignatureAlgorithm = $cert.SignatureAlgorithm.FriendlyName
        Status = $status
        RevokeReason = $revokeReason
        StorePath = $StorePath
    }
    $results += $result
}

$results | Export-Csv -Path $OutputCsv -NoTypeInformation -Encoding UTF8

Write-Host "`n=== Certificate Audit Summary ===" -ForegroundColor Cyan
Write-Host "Total certificates: $($results.Count)"
Write-Host "With private key: $(($results | Where-Object { $_.HasPrivateKey }).Count)"
Write-Host "Expired: $(($results | Where-Object { $_.NotAfter -lt (Get-Date) }).Count)"
Write-Host "Chain errors: $(($results | Where-Object { $_.Status -ne 'Valid' }).Count)"

$results | Where-Object { $_.Status -ne "Valid" } | ForEach-Object {
    Write-Host "[WARNING] $($_.Subject) - Status: $($_.Status)" -ForegroundColor Yellow
}

0x09 公开案例分析

案例一:SolarWinds/SUNBURST 供应链攻击

SolarWinds攻击是近年来最复杂的供应链攻击之一,由俄罗斯国家支持的APT29(Cozy Bear)组织于2020年实施。该攻击的核心在于将恶意代码植入SolarWinds Orion软件的构建流程中,并利用合法的代码签名证书来伪装恶意软件。

攻击链概述

阶段描述ATT&CK
初始入侵入侵SolarWinds构建服务器T1195.002
代码植入在Orion源码中注入SUNBURST后门T1584.002
签名伪装使用SolarWinds合法签名T1553.002
分发部署通过正常更新通道分发T1554
持久化DNS隧道C2通信T1071.004

签名滥用细节

攻击者在SolarWinds Orion的构建过程中注入了SUNBURST后门代码(Solorigate.dll),并通过SolarWinds的正常构建流程获取合法的代码签名。这意味着分发给客户的SUNBURST恶意DLL带有SolarWinds的合法Authenticode签名,使用的是由DigiCert签发的SolarWinds代码签名证书。

签名的具体技术细节如下:

  • 签名证书:SolarWinds LLC,由DigiCert签发
  • 签名算法:RSA-2048 + SHA-256
  • 时间戳:由DigiCert Time Stamp CA签发的RFC 3161时间戳
  • 签名链验证:签名链完整,追溯到DigiCert High Assurance EV Root CA

取证发现

  1. 签名验证通过:由于使用的是合法构建流程,SUNBURST DLL的签名验证完全通过
  2. VirusTotal初检通过:在攻击初期,VirusTotal上的所有杀毒引擎都未检出该恶意DLL
  3. 签名时间与编译时间一致:恶意DLL的签名时间与构建服务器的编译时间一致
  4. 哈希检测:后续分析确定了恶意DLL的特定哈希值,可作为IOC使用

关键IOC

IOC类型
恶意DLL SHA2563250c54f6b2c340d4b8e8e4a0f3c2e1d...
C2域名avsvmcloud.com
C2 IP20.140.0.1
签名证书指纹F0:12:A9:F0:33:...
恶意文件路径SolarWinds.Orion.Core.BusinessLayer.dll

经验教训

  • 合法签名不能作为软件安全性的唯一依据
  • 构建环境的安全是软件供应链安全的关键
  • 需要建立软件构建来源验证机制(SLSA Level 4)
  • CT日志监控可以帮助发现异常证书签发

案例二:Stuxnet 使用窃取的Realtek/JMicron证书签名

Stuxnet(震网病毒)是历史上首个被公开发现的工业控制系统武器化恶意软件,于2010年被Symantec研究人员发现。该恶意软件最令人瞩目的技术特征之一是使用了从Realtek和JMicron窃取的合法代码签名证书。

攻击背景

Stuxnet于2009年至2010年间被部署,目标是伊朗纳坦兹核设施的铀浓缩离心机控制系统。该恶意软件使用了至少两个窃取的合法数字签名证书来伪装其内核驱动程序。

签名滥用详情

签名证书证书持有者签发CA被签名的组件
Realtek Semiconductor Corp.瑞昱半导体VeriSignRTCore64.sys 内核驱动
JMicron Technology Corp.智微科技VeriSign恶意DLL组件

Realtek的签名证书窃取自Realtek在台湾新竹科学园区的办公室。攻击者通过入侵该开发者的系统获取了代码签名证书的私钥,然后使用该证书对Stuxnet的内核驱动程序RTCore64.sys进行签名。

技术分析

Stuxnet使用的窃取签名实现了以下攻击目的:

  1. 绕过驱动签名强制:Windows 64位系统要求内核驱动必须由可信CA签发的证书签名。通过使用Realtek的合法签名,Stuxnet的内核驱动可以绕过Driver Signature Enforcement(DSE)保护
  2. 规避安全软件检测:安全软件通常对已知厂商签名的文件降低检测敏感度
  3. 建立内核级持久化:签名驱动可以被Windows加载器信任并加载到内核空间

取证检测方法

Get-AuthenticodeSignature "C:\Windows\System32\drivers\RTCore64.sys" | 
    Select-Object Status, SignerCertificate | Format-List *

Get-ChildItem "Cert:\LocalMachine\Disallowed" | 
    Where-Object { $_.Subject -like "*Realtek*" -or $_.Subject -like "*JMicron*" } |
    Select-Object Subject, Thumbprint

经验教训

  • 物理安全(保护签名密钥不被物理窃取)是代码签名安全的基础
  • 代码签名密钥应存储在HSM中而非普通工作站
  • 证书吊销机制的有效性取决于依赖方及时更新吊销列表
  • 即使使用合法签名的文件也不应被无条件信任

案例三:DigiNotar CA 被攻破与国家级中间人攻击

DigiNotar事件是PKI信任体系被攻破的标志性案例,深刻改变了全球对CA安全性的认知。

事件时间线

时间事件
2011年7月DigiNotar CA系统被入侵(具体时间不详)
2011年7月10日攻击者签发第一个伪造证书(*.google.com)
2011年7月-8月攻击者共签发500+伪造证书
2011年8月28日荷兰政府首次报告DigiNotar伪造证书
2011年8月29日Mozilla和Chrome开始吊销DigiNotar信任
2011年9月3日Microsoft和Apple也吊销了DigiNotar信任

攻击目标与范围

攻击者签发了针对以下高价值域名的伪造证书:

域名影响
*.google.comGoogle所有服务
*.microsoft.comMicrosoft在线服务
*.mozilla.orgFirefox更新
*.torproject.orgTor项目网站
*.skype.comSkype通信
*.twitter.comTwitter服务
*.wordpress.comWordPress平台

影响范围

荷兰政府证实约有30万荷兰用户的HTTPS通信可能被截获。由于DigiNotar曾被荷兰政府用于公民身份验证系统的HTTPS证书,此次攻击直接影响了荷兰的国家安全基础设施。

取证分析要点

  1. CT日志(当时尚未强制):2011年CT框架尚未被强制执行,因此伪造证书的签发未被即时发现
  2. 证书序列号分析:攻击者签发的伪造证书序列号呈非连续模式,与正常CA的序列号分配方式不同
  3. OCSP日志:OCSP请求日志可以揭示哪些用户曾访问过伪造证书
  4. 浏览器日志:受影响用户的浏览器可能记录了对DigiNotar OCSP服务器的查询

经验教训

  • CA安全是整个PKI信任体系的基石,需要持续的安全审计
  • CT框架的强制执行对于防止未授权证书签发至关重要
  • 浏览器厂商需要建立快速吊销不信任CA的机制
  • 企业应实施Certificate Pinning等额外的证书验证机制

0x0A 参考资料

  1. RFC 6962 - Certificate Transparency https://www.rfc-editor.org/rfc/rfc6962

  2. Microsoft Authenticode Code Signing https://learn.microsoft.com/en-us/windows/win32/seccrypto/authenticode-signing

  3. Google Certificate Transparency https://certificate.transparency.dev/

  4. SLSA - Supply chain Levels for Software Artifacts https://slsa.dev/

  5. SolarWinds Incident Analysis - CrowdStrike https://www.crowdstrike.com/blog/sunspot-malware-technical-analysis/

  6. Stuxnet Analysis - Symantec https://www.symantec.com/content/en/us/enterprise/media/security_response/whitepapers/w32_stuxnet_dossier.pdf

  7. DigiNotar Root CA Security Breach - Fox-IT https://www.fox-it.com/en/news/blog/diginotar-ca-breach/

  8. Certify - AD CS Exploitation Tool https://github.com/GhostPack/Certify

  9. Sigstore Cosign Documentation https://docs.sigstore.dev/cosign/overview/

  10. MITRE ATT&CK - Supply Chain Compromise (T1195.002) https://attack.mitre.org/techniques/T1195/002/

  11. NIST SP 800-160 Vol. 2 - Cyber Resiliency Design Strategies https://csrc.nist.gov/publications/detail/sp/800-160/vol-2/final

  12. OpenSSL Certificate Verification Documentation https://www.openssl.org/docs/man1.1.1/man1/openssl-verify.html