前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何验证可执行文件是可靠的 | Windows 应急响应

如何验证可执行文件是可靠的 | Windows 应急响应

作者头像
意大利的猫
发布2024-03-04 14:56:57
1310
发布2024-03-04 14:56:57
举报
文章被收录于专栏:漫流砂漫流砂

0x01 简介

相信部分朋友已经看过我们的 《Windows 应急响应手册》了,我们这边也得到部分朋友的正向反馈,包括工具、方法等。

Windows 版的应急响应手册中常规安全检查部分第一版就包含了 30 多个检查项目,按照我们的风格,每个检查项基本都给出了 Windows 默认的情况(书中以 Windows Server 2016 为例),对于存在大量检查项的(例如大量的 dll 文件需要检查),基本也都给出了 Powershell 脚本。

如果大家详细看了这些 Powershell 脚本或者看我们的描述可以发现,其实就是找到检查项,进而找到可执行文件(exedll 等),之后验证签名是否通过,这个做法仔细想是存在问题的:恶意程序也可以拥有有效的签名,所以只检查是否验证通过是不可靠的

0x02 目的说明

单纯验证每个可执行文件是否为恶意,这不是我们的工作,这是主机/终端管理程序、杀毒软件、沙箱软件的工作,对某一个可执行文件如何进行分析也不是这篇文章的目的,这是部分恶意软件/代码分析师的工作

这篇文章的目的是在应急响应过程中,面对中等数量的可执行文件等待验证,如何快速将可疑文件挑选出来

我选择的方法是先验证签名,签名通过后再验证发布者或者叫签名者 (Publisher) 名称是否为微软官方,如果两者有一个不满足,则认为是可疑文件

如果大家有更好的方法,欢迎根据应急手册上的联系方式联系我们讨论

0x03 弃用方案

为什么不采用杀毒软件对要检查的文件进行查杀?

首先是在应急响应手册的常规检查阶段包含了全盘查杀,其次并不是所有应急场景都包含杀毒程序并且杀毒程序的病毒库能够及时 联网/不联网 更新

为什么不采用沙箱进行检查?

这是出于保密要求,很多场景下客户内部没有沙箱,如果将可执行文件等直接放在互联网上的沙箱进行检查,可能会导致文件泄漏

为什么不用 Hash 验证?

Windows 升级可能会涉及部分程序,导致 Hash 变化

为什么不收集起来,人工分析?

有些时候量比较大,对于应急响应场景不现实,而且导出文件也可能造成文件泄漏

先验签名再验证发布者的方法会导致误杀吧?

是的,可以说除了微软的都会被列为可疑文件,这是我们在应急响应场景下优于杀毒软件的地方,我们检查的地方都是可以被用来做权限维持等操作的地方,第三方软件不会很多,我们的目的也是找出它们,之后进行针对性的分析

先验证签名再验证发布者的方法检验通过的程序是不是完全问题?

并不是完全是

首先得保证验证本身是没问题的,这里就涉及两个部分:1. 系统存储的证书没问题,2. 验证过程没有被篡改;

其次私钥可能会被盗嘛,或者因为漏洞绕过验证;

需要注意的是还有一种情况:对于嵌入式签名,签名本身存储位置是可以被放置 payload 的,但是默认不会执行,想要执行得有额外的加载器,文章后面部分会详细说这个事,这里拿出来说是担心部分用户在非常规安全检查阶段验证某个程序的时候被这种隐藏 payload 的方法给欺骗了

0x04 Windows 签名简介

Windows 签名一般有两种形式,一种是针对文件的;一种是针对文件集合(.cat文件)的,我们接下来简单介绍一下这两种形式

1. 针对文件集合签名

微软官方叫针对目录文件进行签名,如果看过之前关于 Linux 更新源那期文章的朋友应该还记得,Linux 是将某个文件夹中文件的 hash 等信息集中到一个文件中,之后对该文件进行签名的,而不是对某个应用程序单独签名,Windows 中针对文件集合(某个目录内的所有文件)的签名也是一个道理

这个过程都是大同小异,无非就是收集 hash 等信息,之后使用私钥配合各种算法进行签名,之后操作系统通过分发的公钥验证签名

2. 针对文件签名

针对文件的签名就聚焦到单个文件了,也就是上面提到的方案的验证对象

简单来说就是将可执行文件进行 Hash 运算(可采用不同的 Hash 算法)后,生成一个值,之后通过私钥对该值进行签名,最后将签名相关内容存储到可执行文件中的一个段(或者叫一个部分吧)

这里大家可以思考一下,是不是存在一些逻辑悖论:

我要计算一个文件的 Hash 之后再将计算的结果处理后塞到文件里,那文件的 Hash 是不是就变了,这样的话,操作系统在校验的时候是不是就得忽略签名相关的这个段(其实还有部分要去掉的),采用和签名是一样的算法进行 Hash 计算,之后比对值是否一致 这样一来,签名这个部分本身的校验就被忽略了,所以之前就有安全研究人员提出在这部分添加 payload ,但值得注意的是,这部分不属于可执行部分,只能用来存储 payload ,需要额外的加载器才能运行,除非文件本身可实现加载器的功能

因此,经过 先验证签名,后验证发布者 的方式验证通过的程序不一定与官方程序一模一样,不一定是完全无害的,这里费这么多口舌是为了提醒大家这个问题,有点黑加白的意思,而不是白加黑

0x05 针对文件签名详述

大家可能会觉得奇怪,你刚刚简述了,为什么又来个详述,这是因为看网络上的文章以及各种GPT 时有个问题我觉得不太对,困扰我2天了


http://p7.qhimg.com/t01fc41fe42a2a5e702.png

来自 https://www.anquanke.com/post/id/84470

img

来自 https://www.wosign.com/column/ssl_20220222.htm

img

来自 https://cloud.tencent.com/developer/article/2185414


还有来自官方的系列文章

https://learn.microsoft.com/zh-cn/windows-hardware/drivers/install/digital-signatures

这些内容都是在描述 Windows 签名验证过程,按照他们的描述,以及网络上的文章描述大致如下

  1. 开发者生成密钥对以及证书请求文件
  2. 向证书颁发机构(以下简称CA)
  3. CA 核验开发者身份没问题后,用 CA 自己的私钥签名一张证书,颁发给开发者,证书内容是开发者的公钥以及开发者部分信息
  4. 开发者对二进制程序进行 Hash 运算,之后通过开发者的私钥进行加密也就是数字签名并嵌入到二进制文件中
  5. 将二进制分发给用户,用户的操作系统获取文件后,解密签名,采用签名同样的方法计算 Hash
  6. 对比一致则验证通过

这是最开始看到的,这里产生的疑问是,操作系统是怎么得到开发者的公钥的?没有描述单独下载的过程嘛,证书好像没用上啊?之后带着疑问开始找资料,这时候看的资料就包括上面的几个了

这时候就搞清楚了,原来数字证书和数字签名都是被嵌入到二进制文件中的,这就很好理解为什么操作系统能够获取到开发者的公钥了

  1. 开发者生成密钥对以及证书请求文件
  2. 向证书颁发机构(以下简称CA)
  3. CA 核验开发者身份没问题后,用 CA 自己的私钥签名一张证书,颁发给开发者,证书内容是开发者的公钥以及开发者部分信息
  4. 开发者对二进制程序进行 Hash 运算,之后通过开发者的私钥进行加密也就是数字签名并嵌入到二进制文件中
  5. 同时,将CA签名过的数字证书也嵌入到二进制文件中
  6. 将二进制分发给用户,用户的操作系统获取文件后,验证证书签名,获取开发者公钥,解密数字签名,采用签名同样的方法计算 Hash
  7. 对比一致则验证通过

这个时候流程就和上面的图片里一致了,文章介绍本来到这里就应该结束了,但是我有一个疑问:

操作系统是如何验证开发者证书的签名的呢?要知道开发者证书的签名是 CA (证书颁发机构) 私钥签名的

这时候可能很多朋友说了,Windows 操作系统或者浏览器等都会内置部分证书的,搞过 Burpsuite 的都见过,这部分证书是根证书,不就可以用来验证开发者证书的签名

这应该是在几年前我参加面试的时候,有个面试官就问过我这个问题:离线的操作系统是如何验证签名的,他给出的答案就是系统内置根证书。没想到多年以后,这个问题也成了回旋镖

当然了,熟悉我们的小伙伴肯定明白,如果这样就解决了问题,我肯定不会出来写,我们先看一下 Windows 内置的证书

Windows + r 输入mmc

可以看到,操作系统确实是内置了根证书颁发机构的证书,我细数了一下,这里共有 20 个,第三方根证书颁发机构都包含在这里了,全世界这么多人要申请证书,如果仅仅由这 20 个受信任根证书的颁发机构私钥签名,颁发,吊销,那可能都得忙到冒火星,这还不是最主要的,最关键的受信任的根证书的私钥这么频繁地使用,如果其中一张根证书颁发机构的私钥泄漏了,那影响是非同小可的

所以详细看应用程序的签名,可以看到并不是直接由上面系统内置的根证书签的

颁发者是 Microsoft Code Signing PCA 2011 , 这并不在 Windows 操作系统内置受信任的根证书存储中

证书是证书,存储是存储,这个也说一下,简单来说上面那些像文件夹一样的都是存储,里面存的是证书,这里说是希望大家看别人文章的时候不会混淆

那问题来了,难道说某个根证书的公钥能解密这个根证书下面的所有的CA私钥签名的证书吗?公私钥不是一对一的吗???难道调兵遣将的虎符不止一个?

所以这不得不引起疑问对吧,这个时候又开始查询资料,把官方资料都看了好几遍了,没提这事,官方的知识平台确实比较次,看了很多文章后,有些人提出了一个概念 —— 证书链

可以看到证书是有一个链的, Microsoft Corporation 证书是由 Microsoft Code Signing PCA 2011 签名的,Microsoft Code Signing PCA 2011 是由 Microsoft Root Certificate Authority 2011 签名的

证书链是如何验证的呢?

Microsoft Corporation 证书中会记录是哪个 CA 将证书签名后颁布给开发者的

之后操作系统会发起网络请求,寻找包含 Microsoft Code Signing PCA 2011 公钥的证书,当然这个证书是被上一级 CA 的私钥签名的,直到最终到达根证书颁发机构

抓包并测试验证某个程序的签名

确实会发起相关请求,再次测试验证某个程序的签名就不会再次发起请求了

应该是证书公钥被缓存了,准确地说, Microsoft Code Signing PCA 2011 这张证书叫做中间证书,这样就可以层层校验,最终验证开发者的证书中的公钥是否可以使用


本来一切又该结束了,可恶的是咱们又是搞安全的,比较严谨,如果验证开发者证书的时候没有网络怎么办呢?

这是个好问题,带着这个问题我们测试一下

还是能够验证成功,是不是软件的问题,用 Powershell 测试一下

Powershell 也验证成功,说明软件没问题

难道是刚才的缓存问题?重启系统,再次验证

重启后还是验证成功了,会不会是 Wireshark 的中间证书也被系统内置了,在根证书或中间证书那里

它这个还多一层,我们去查查吧

第二层就没查到,这个时候大家得明白,按理说没有网络,这个证书链最多也就找到上一层,甚至上一层的详细情况都看不到,只能看到个名字

难道说刚才的缓存还在吗?

为了验证这个问题,我们在无网络的情况下(直接不分配网卡)新建一个 Windows Server 2016 系统,通过虚拟机工具映射文件夹的方式将二进制文件传递进去

重新安装系统的情况下,完全离线都能够还原证书链,并且验证成功

这个时候又开始查资料了,在官方的资料里发现了一个叫交叉证书的东西,看了几遍,也没有完全理解,但好在似乎在 21 年就已经停止了,于是我们在无网络的情况下(不分配网卡)安装了一个 Windows Server 2022 ,这样总能验证了吧

得,不是交叉证书的事

这个时候有开始找各种资料,并且各种 GPT 问,GPT们给出的答案中比较可信的还是证书缓存,但是如何查看缓存,GPT 没有给出

在系统安装以及启动过程中会涉及到很多签名校验,如果不能获取到证书缓存,就很难确定是不是真的因为证书缓存,关键似乎也没有其他可能了

这怎么办呢?千山鸟飞绝,万径人踪灭呀,我想到一个办法,你能缓存 Windows 自己的那些程序中间签名不奇怪,缓存 Wireshark 的也算是可以接受,你不至于在系统安装和启动过程中把所有软件的中间证书都缓存了吧

于是我们从腾讯管家平台下载了大量的软件进行测试,我就不信你能把这些软件的中间证书都缓存了

结果有签名的程序大部分能够签名通过,欣喜若狂发现几个没通过的,结果放到可联网的 Windows Server 2016 中也是一样不通过,测试了一定数量以后,实在是测试不动了,关键是没有找到任何一个案例

现在还有一种办法,我们自己伪造一整套证书,包括根证书、中间证书、开发者证书,将根证书和中间证书都放到操作系统中,这样就可以模拟正常的签名验证过程,之后再将中间证书删除掉,再次验证,看看能不能通过

如果还是能够通过验证,找出完整的证书链,说明是由缓存导致的验证成功,你听我这个词,“导致的”, 但这也有一个问题,如果没有验证成功,也可能是因为操作系统在删除证书的时候直接把缓存也删除掉了

最关键的是,这个过程也挺复杂的,于是放弃这个方案


到这里本来又应该结束了,因为通过案例证明,其实我们的验证方案是有效且可靠的,但是吧,不搞清楚我会一直想着这个事,而且把没有完全验证过的方法写入到应急响应手册里也不太妥当

于是我打算研究研究 Windows 二进制文件结果,之后详细看看签名和证书部分到底是什么情况,能不能找到一些关键字,之后找到 Windows 的签名验证的程序,大不了再搞一下逆向,看看能不能发现蛛丝马迹

关于 Windows 二进制格式,官方还是有详细资料的

https://learn.microsoft.com/zh-cn/windows/win32/debug/pe-format#the-attribute-certificate-table-image-only

这里面找到 属性证书表(Attribute Certificate Table)

这部分引用了一个 docx 文件 —— Windows 验证码可移植可执行签名格式

https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/Authenticode_PE.docx

建议大家用 word 而不是 wps 打开,不然里面的图可能会有些问题

这就直接交给有道翻译吧,感谢有道翻译的文档翻译功能,一点一点儿看的时候发现了一个问题

哎呀!!!原来微软官方文档里都写清楚了,属性证书表里不只是开发者证书,还有所有中间证书

GPT 都白学习了呀,没学透啊!不过总算是解释清楚了

正好一个学弟专门写过相关的论文,我的这个结论其实就是他论文中的一部分,感谢学弟

要是早知道就好了,但探究一圈过来以后,对这个过程印象就比较深了,当然本篇文章介绍的也不是全部细节,如果大家感兴趣可以去实践一下

0x06 Powershell 实现验证方案

代码语言:javascript
复制
$file = 'c:\windows\system32\cmd.exe'

$microsoftCNS = @('Microsoft Corporation', 'Microsoft Windows')

# 获取可执行文件的数字签名
$signature = Get-AuthenticodeSignature -FilePath $file

if ($signature.Status -eq 'Valid') {
    Write-Output "文件的数字签名有效。"

    # 获取签名的发布者信息
    $publisher = $signature.SignerCertificate.Subject

    # $publisher = "CN=Microsoft Windows, CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"

    # 解析发布者信息以提取 CN 字段的值
    $cnValues = @(($publisher -split ', ' | Where-Object { $_ -like 'CN=*' }).Substring(3))

    # 验证 CN 字段数量
    if ($cnValues.Count -eq 1) {
        $cnValue = $cnValues[0]
        Write-Output "CN 字段的值: $cnValue"

        # 判断 CN 字段是否为微软官方
        if ($microsoftCNS -contains $cnValue) {
            Write-Output "CN 字段值为微软官方。"
        } else {
            Write-Output "CN 字段值不是微软官方。"
        }
    } elseif ($cnValues.Count -gt 1) {
        Write-Output "错误:找到多个 CN 字段。"
    } else {
        Write-Output "错误:未找到 CN 字段。"
    }
} else {
    Write-Output "文件的数字签名无效。"
}

这是一个 demo ,经过测试发现微软的官方签名发布者(CN) 还不止一个,所以这里使用了数组,方便大家后续添加新的微软官方签名或者大家认可的非微软签名

尝试测试非微软官方的程序

这两个微软官方的签名发布者是通过 system32 文件夹以及 SysinternalsSuite 对比提取出来的,如何才能获取操作系统默认的所有已签名的应用程序的签名发布者信息呢?

我的电脑只有一个 C 盘,通过 Powershell 帮我们来完成

代码语言:javascript
复制
$rootPath = "C:\"

# 用于存储已发现的 CN 值和对应的文件地址
$cnFileMap = @{}

# 处理文件,获取 CN 值并记录对应的文件地址
function Process-File($file) {
    try {
        # 获取可执行文件的数字签名
        $signature = Get-AuthenticodeSignature -FilePath $file.FullName -ErrorAction Stop

        if ($signature -ne $null -and $signature.Status -eq 'Valid') {
            # 获取签名的发布者信息
            $publisher = $signature.SignerCertificate.Subject

            # 解析发布者信息以提取 CN 字段的值
            $cnValue = ($publisher -split ', ' | Where-Object { $_ -like 'CN=*' }).Substring(3)

            if ($cnValue) {
                if (-not $cnFileMap.ContainsKey($cnValue)) {
                    # 添加新的 CN 值到映射表
                    $cnFileMap[$cnValue] = $file.FullName
                }
            }
        }
    } catch {
        Write-Host "无法访问文件: $($file.FullName)"
    }
}

# 递归遍历目录并处理每个文件
Get-ChildItem -Path $rootPath -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
    $item = $_

    if ($item.PSIsContainer) {
        # 如果是目录,则继续递归遍历其中的文件
        Get-ChildItem -Path $item.FullName -File -ErrorAction SilentlyContinue | ForEach-Object {
            Process-File $_
        }
    } else {
        # 如果是文件,则直接处理
        Process-File $item
    }
}

# 打印所有的 CN 值
$cnValues = $cnFileMap.Keys
Write-Host "所有的 CN 值:"
Write-Host "-----------------------------------------------"
foreach ($cn in $cnValues) {
    Write-Host $cn
}
Write-Host "-----------------------------------------------"
Write-Host ""

# 打印每个 CN 值对应的文件地址
foreach ($cn in $cnValues) {
    Write-Host "CN 值: $cn"
    Write-Host "文件地址:"
    Write-Host "- $($cnFileMap[$cn])"
    Write-Host ""
}

这个脚本会将所有可以访问的文件验证成功的签名提取出来,之后进行保存,每一个新签名都会记录第一个发现的使用该签名的文件地址

代码语言:javascript
复制
所有的 CN 值:
-----------------------------------------------
Microsoft Windows Hardware Compatibility Publisher
Microsoft Corporation
Microsoft Windows
Microsoft Update
-----------------------------------------------

CN 值: Microsoft Windows Hardware Compatibility Publisher
文件地址:
- C:\Windows\System32\CatRoot\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}\windows-legacy-whql.cat

CN 值: Microsoft Corporation
文件地址:
- C:\Windows\System32\MpSigStub.exe

CN 值: Microsoft Windows
文件地址:
- C:\Windows\bfsvc.exe

CN 值: Microsoft Update
文件地址:
- C:\Windows\SoftwareDistribution\SIH\eng\SIHEng.dll

其中 Microsoft Windows Hardware Compatibility Publisher 对应的文件 C:\Windows\System32\CatRoot\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}\windows-legacy-whql.cat 就是我们上面提到的针对文件集合(目录文件)签名

最终的 demo 为

代码语言:javascript
复制
$file = 'c:\windows\system32\cmd.exe'

$microsoftCNS = @('Microsoft Corporation', 'Microsoft Windows', 'Microsoft Windows Hardware Compatibility Publisher', 'Microsoft Update')

# 获取可执行文件的数字签名
$signature = Get-AuthenticodeSignature -FilePath $file

if ($signature.Status -eq 'Valid') {
    Write-Output "文件的数字签名有效。"

    # 获取签名的发布者信息
    $publisher = $signature.SignerCertificate.Subject

    # $publisher = "CN=Microsoft Windows, CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"

    # 解析发布者信息以提取 CN 字段的值
    $cnValues = @(($publisher -split ', ' | Where-Object { $_ -like 'CN=*' }).Substring(3))

    # 验证 CN 字段数量
    if ($cnValues.Count -eq 1) {
        $cnValue = $cnValues[0]
        Write-Output "CN 字段的值: $cnValue"

        # 判断 CN 字段是否为微软官方
        if ($microsoftCNS -contains $cnValue) {
            Write-Output "CN 字段值为微软官方。"
        } else {
            Write-Output "CN 字段值不是微软官方。"
        }
    } elseif ($cnValues.Count -gt 1) {
        Write-Output "错误:找到多个 CN 字段。"
    } else {
        Write-Output "错误:未找到 CN 字段。"
    }
} else {
    Write-Output "文件的数字签名无效。"
}

0x07 COM劫持检查实战

应急响应手册v1.0 给出的版本,大家主要看 Verify-FileSignature 函数就好,这也是当时想到的,把验证步骤单独做一个函数,方便后期调整

代码语言:javascript
复制
# 定义函数来进行签名校验
function Verify-FileSignature {
    param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-Path $_ -PathType Leaf})]
        [string]$FilePath
    )
    
    if (Test-Path -Path $FilePath -PathType Leaf) {
        $signature = Get-AuthenticodeSignature -FilePath $FilePath
        if ($signature.Status -eq 'Valid') {
            return "Valid"
        } else {
            return "Invalid"
        }
    } else {
        return "File Not Found"
    }
}

# 定义函数来检查注册表地址
function Check-RegistryPaths {
    param (
        [Parameter(Mandatory=$true)]
        [string[]]$RegistryPaths
    )

    $invalidSignatures = @()

    foreach ($registryPath in $RegistryPaths) {
        if (Test-Path -Path $registryPath) {
            $subkeys = Get-ChildItem -Path $registryPath
            foreach ($subkey in $subkeys) {
                $inprocServer32Path = Join-Path -Path $subkey.PSPath -ChildPath "InprocServer32"
                if (Test-Path -Path $inprocServer32Path) {
                    $defaultPropertyValue = (Get-ItemProperty -Path $inprocServer32Path -Name "(default)" -ErrorAction SilentlyContinue)."(default)"
                    if ($defaultPropertyValue) {
                        # 此处加 Trim('"') 是为了防止类似于 Defender 这种“有个性”的软件胡乱设置注册表
                        $binaryFilePath = $defaultPropertyValue.Trim().Trim('"')
                        $binaryFilePath = [Environment]::ExpandEnvironmentVariables($binaryFilePath)
                        if (Test-Path $binaryFilePath) {
                            $result = Verify-FileSignature -FilePath $binaryFilePath
                            if ($result -eq "Invalid") {
                                $invalidSignatures += @{
                                    RegistryPath = $subkey
                                    BinaryFilePath = $binaryFilePath
                                } 
                                Write-Host "Signature is invalid for file: $binaryFilePath" -ForegroundColor Red
                            } elseif ($result -eq "Valid") {
                                Write-Host "Signature is valid for file: $binaryFilePath " -ForegroundColor Green
                            }
                        } else {
                            $dllFileName = Split-Path -Leaf $binaryFilePath
                            $found = $false
                            $searchPaths = @(
                                (Join-Path -Path $env:SystemRoot -ChildPath $dllFileName),
                                (Join-Path -Path $env:SystemRoot -ChildPath "System32\$dllFileName")
                            )
                            foreach ($path in $searchPaths) {
                                if (Test-Path $path) {
                                    $found = $true
                                    $result = Verify-FileSignature -FilePath $path
                                    if ($result -eq "Invalid") {
                                        $invalidSignatures += @{
                                            RegistryPath = $subkey
                                            BinaryFilePath = $path
                                        }
                                        Write-Host "Signature is invalid for file: $path" -ForegroundColor Red
                                    } elseif ($result -eq "Valid") {
                                        Write-Host "Signature is valid for file: $path " -ForegroundColor Green
                                    }
                                    break
                                }
                            }
                            if (-not $found) {
                                Write-Host "Could not find file '$dllFileName' in default search paths. Skipping signature verification."  -ForegroundColor Yellow
                            }
                        }
                    } else {
                        Write-Host "Binary file path is empty for subkey $($subkey.PSChildName)."  -ForegroundColor Yellow
                    }
                }
            }
        }
    }

    # 打印不通过的签名验证信息
    if ($invalidSignatures.Count -gt 0) {
        Write-Output ""
        Write-Output ""
        Write-Output "--------------------------------------------------------"
        Write-Host "Invalid signatures:" -ForegroundColor Red
        foreach ($invalidSignature in $invalidSignatures) {
            $registryPath = $invalidSignature.RegistryPath
            $binaryFilePath = $invalidSignature.BinaryFilePath
            Write-Host "Registry path: $registryPath" -ForegroundColor Yellow
            Write-Host "Binary file path: $binaryFilePath" -ForegroundColor Yellow
            Write-Output ""
        }
        Write-Output "--------------------------------------------------------"
    }
}

# 要检查的注册表地址数组
$registryPaths = @(
    "Registry::HKEY_CURRENT_USER\Software\Classes\CLSID",
    "Registry::HKEY_CLASSES_ROOT\CLSID",
    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellCompatibility\Objects\"
)

# 调用函数进行检查
Write-Host "Starting signature verification..."

Check-RegistryPaths -RegistryPaths $registryPaths

Write-Host "Signature verification completed."

增加验证步骤后的版本,大家还是主要看 Verify-FileSignature 函数

代码语言:javascript
复制
$microsoftCNS = @('Microsoft Corporation', 'Microsoft Windows', 'Microsoft Windows Hardware Compatibility Publisher', 'Microsoft Update')

# 定义函数来进行签名校验
function Verify-FileSignature {
    param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-Path $_ -PathType Leaf})]
        [string]$FilePath
    )
    
    if (Test-Path -Path $FilePath -PathType Leaf) {
        $signature = Get-AuthenticodeSignature -FilePath $FilePath
        
        if ($signature.Status -eq 'Valid') {
            $publisher = $signature.SignerCertificate.Subject
            
            # 解析发布者信息以提取 CN 字段的值
            $cnValues = @(($publisher -split ', ' | Where-Object { $_ -like 'CN=*' }).Substring(3))

            if ($cnValues.Count -eq 1) {
                $cnValue = $cnValues[0]
                # Write-Output "CN 字段的值: $cnValue"
        
                # 判断 CN 字段是否为微软官方
                if ($microsoftCNS -contains $cnValue) {
                    # Write-Output "CN 字段值为微软官方。"
                    return "Valid"
                } 
            } 
        } 

        return "Invalid"

    } 
    
    return "File Not Found"

}

# 定义函数来检查注册表地址
function Check-RegistryPaths {
    param (
        [Parameter(Mandatory=$true)]
        [string[]]$RegistryPaths
    )

    $invalidSignatures = @()

    foreach ($registryPath in $RegistryPaths) {
        if (Test-Path -Path $registryPath) {
            $subkeys = Get-ChildItem -Path $registryPath
            foreach ($subkey in $subkeys) {
                $inprocServer32Path = Join-Path -Path $subkey.PSPath -ChildPath "InprocServer32"
                if (Test-Path -Path $inprocServer32Path) {
                    $defaultPropertyValue = (Get-ItemProperty -Path $inprocServer32Path -Name "(default)" -ErrorAction SilentlyContinue)."(default)"
                    if ($defaultPropertyValue) {
                        # 此处加 Trim('"') 是为了防止类似于 Defender 这种“有个性”的软件胡乱设置注册表
                        $binaryFilePath = $defaultPropertyValue.Trim().Trim('"')
                        $binaryFilePath = [Environment]::ExpandEnvironmentVariables($binaryFilePath)
                        if (Test-Path $binaryFilePath) {
                            $result = Verify-FileSignature -FilePath $binaryFilePath
                            if ($result -eq "Invalid") {
                                $invalidSignatures += @{
                                    RegistryPath = $subkey
                                    BinaryFilePath = $binaryFilePath
                                } 
                                Write-Host "Signature is invalid for file: $binaryFilePath" -ForegroundColor Red
                            } elseif ($result -eq "Valid") {
                                Write-Host "Signature is valid for file: $binaryFilePath " -ForegroundColor Green
                            }
                        } else {
                            $dllFileName = Split-Path -Leaf $binaryFilePath
                            $found = $false
                            $searchPaths = @(
                                (Join-Path -Path $env:SystemRoot -ChildPath $dllFileName),
                                (Join-Path -Path $env:SystemRoot -ChildPath "System32\$dllFileName")
                            )
                            foreach ($path in $searchPaths) {
                                if (Test-Path $path) {
                                    $found = $true
                                    $result = Verify-FileSignature -FilePath $path
                                    if ($result -eq "Invalid") {
                                        $invalidSignatures += @{
                                            RegistryPath = $subkey
                                            BinaryFilePath = $path
                                        }
                                        Write-Host "Signature is invalid for file: $path" -ForegroundColor Red
                                    } elseif ($result -eq "Valid") {
                                        Write-Host "Signature is valid for file: $path " -ForegroundColor Green
                                    }
                                    break
                                }
                            }
                            if (-not $found) {
                                Write-Host "Could not find file '$dllFileName' in default search paths. Skipping signature verification."  -ForegroundColor Yellow
                            }
                        }
                    } else {
                        Write-Host "Binary file path is empty for subkey $($subkey.PSChildName)."  -ForegroundColor Yellow
                    }
                }
            }
        }
    }

    # 打印不通过的签名验证信息
    if ($invalidSignatures.Count -gt 0) {
        Write-Output ""
        Write-Output ""
        Write-Output "--------------------------------------------------------"
        Write-Host "Invalid signatures:" -ForegroundColor Red
        foreach ($invalidSignature in $invalidSignatures) {
            $registryPath = $invalidSignature.RegistryPath
            $binaryFilePath = $invalidSignature.BinaryFilePath
            Write-Host "Registry path: $registryPath" -ForegroundColor Yellow
            Write-Host "Binary file path: $binaryFilePath" -ForegroundColor Yellow
            Write-Output ""
        }
        Write-Output "--------------------------------------------------------"
    }
}

# 要检查的注册表地址数组
$registryPaths = @(
    "Registry::HKEY_CURRENT_USER\Software\Classes\CLSID",
    "Registry::HKEY_CLASSES_ROOT\CLSID",
    "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellCompatibility\Objects\"
)

# 调用函数进行检查
Write-Host "Starting signature verification..."

Check-RegistryPaths -RegistryPaths $registryPaths

Write-Host "Signature verification completed."

发现的四个程序其中三个是 PD 虚拟机的组件,一个是 Python 组件,这是因为我的虚拟机中安装了PD虚拟机工具和Python

【 Windows Server 2016 】默认情况

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-03-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 NOP Team 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x01 简介
  • 0x02 目的说明
  • 0x03 弃用方案
  • 0x04 Windows 签名简介
    • 1. 针对文件集合签名
      • 2. 针对文件签名
      • 0x05 针对文件签名详述
      • 0x06 Powershell 实现验证方案
      • 0x07 COM劫持检查实战
      相关产品与服务
      验证码
      腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档