找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

630

积分

0

好友

90

主题
发表于 3 天前 | 查看: 8| 回复: 0

PE文件的证书表(Certificate Table)是用于存储数字签名信息的关键数据结构。它通常被称为安全目录(Security Directory),是实现对可执行文件进行 Authenticode 签名的基础,使 Windows 系统能够验证文件的完整性与发布者身份,确保其未被篡改且来源可信。

证书表概述

证书表,也称为安全目录,是PE文件格式中专门存放 Authenticode 数字签名的区域。这一设计使得操作系统能够验证可执行文件的完整性和来源,从而确认文件是否被修改,以及其发布者是否可信。

证书表结构

证书表主要由以下部分组成:

1. 数据目录项

证书表在数据目录表中的位置是第5个元素(索引为4):

// 数据目录中的证书表项
DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]

重要提示:与其他数据目录项不同,证书表的VirtualAddress字段存储的是文件偏移量,而非RVA。

2. WIN_CERTIFICATE结构

证书表包含一个或多个WIN_CERTIFICATE结构:

typedef struct _WIN_CERTIFICATE {
    DWORD dwLength;                      // 证书结构的总长度
    WORD  wRevision;                     // 结构版本
    WORD  wCertificateType;              // 证书类型
    BYTE  bCertificate[ANYSIZE_ARRAY];   // 证书数据
} WIN_CERTIFICATE, *LPWIN_CERTIFICATE;

各字段说明:

  1. dwLength:整个WIN_CERTIFICATE结构的大小(包括头部和证书数据),按8字节对齐。
  2. wRevision:结构版本号,通常为WIN_CERT_REVISION_2_0(0x0200)。
  3. wCertificateType:证书类型,常见值如下:
    • WIN_CERT_TYPE_X509(0x0001):标准X.509证书。
    • WIN_CERT_TYPE_PKCS_SIGNED_DATA(0x0002):PKCS#7签名数据。
    • WIN_CERT_TYPE_RESERVED_1(0x0003):保留类型。
  4. bCertificate:实际的证书或签名数据。

证书类型

Windows支持多种证书格式:

  1. X.509证书:标准的数字证书格式。
  2. PKCS#7签名数据:最常见的类型,包含完整的签名信息。
  3. PKCS#1签名数据:较少使用。

目前,绝大多数Windows可执行文件都采用PKCS#7签名数据格式。

证书表在PE文件中的位置

证书表的位置和大小信息存储在可选头的数据目录数组中,索引为IMAGE_DIRECTORY_ENTRY_SECURITY(值为4):

// 数据目录中的安全目录项
DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]

Authenticode签名过程

Authenticode签名的生成过程主要包括以下步骤:

  1. 计算文件的哈希值(需排除证书表部分)。
  2. 创建包含文件哈希、发布者证书等信息的PKCS#7签名数据。
  3. 将签名数据附加到PE文件的末尾,形成证书表。
  4. 更新数据目录中证书表项的信息。

在计算文件哈希时,以下部分会被排除:

  1. 可选头中的CheckSum字段。
  2. 数据目录表中证书表项的内容。
  3. 证书表数据区本身。

实际示例

以下是读取并解析PE文件证书表的示例代码。通过调用系统底层的 Win32 API,我们可以深入理解文件签名的验证机制。

#include <windows.h>
#include <stdio.h>
#include <wintrust.h>
#include <softpub.h>

// 证书类型定义
#define WIN_CERT_REVISION_1_0           0x0100
#define WIN_CERT_REVISION_2_0           0x0200
#define WIN_CERT_TYPE_X509              0x0001
#define WIN_CERT_TYPE_PKCS_SIGNED_DATA  0x0002
#define WIN_CERT_TYPE_RESERVED_1        0x0003

void PrintCertificateInfo(PWIN_CERTIFICATE cert) {
    printf("=== 证书信息 ===\n");
    printf("证书长度: %lu 字节\n", cert->dwLength);
    printf("版本: 0x%04X\n", cert->wRevision);
    printf("证书类型: 0x%04X ", cert->wCertificateType);

    switch(cert->wCertificateType) {
        case WIN_CERT_TYPE_X509:
            printf("(X.509 证书)\n");
            break;
        case WIN_CERT_TYPE_PKCS_SIGNED_DATA:
            printf("(PKCS#7 签名数据)\n");
            break;
        case WIN_CERT_TYPE_RESERVED_1:
            printf("(保留类型)\n");
            break;
        default:
            printf("(未知类型)\n");
            break;
    }

    printf("证书数据大小: %lu 字节\n", cert->dwLength - sizeof(WIN_CERTIFICATE));
}

void PrintCertificateTable(const char* filename) {
    HANDLE hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ,
                              NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile == INVALID_HANDLE_VALUE) {
        printf("无法打开文件: %s\n", filename);
        return;
    }

    HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if(!hMapping) {
        printf("创建文件映射失败\n");
        CloseHandle(hFile);
        return;
    }

    LPVOID lpBase = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
    if(!lpBase) {
        printf("映射文件失败\n");
        CloseHandle(hMapping);
        CloseHandle(hFile);
        return;
    }

    // 获取DOS头
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)lpBase;

    // 验证DOS签名
    if(dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
        printf("不是有效的PE文件\n");
        UnmapViewOfFile(lpBase);
        CloseHandle(hMapping);
        CloseHandle(hFile);
        return;
    }

    // 获取NT头
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)lpBase + dosHeader->e_lfanew);

    // 验证PE签名
    if(ntHeaders->Signature != IMAGE_NT_SIGNATURE) {
        printf("不是有效的PE文件\n");
        UnmapViewOfFile(lpBase);
        CloseHandle(hMapping);
        CloseHandle(hFile);
        return;
    }

    // 获取证书表信息
    PIMAGE_DATA_DIRECTORY certDir = 
        &ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];

    if(certDir->VirtualAddress == 0) {
        printf("该文件没有数字签名\n");
        UnmapViewOfFile(lpBase);
        CloseHandle(hMapping);
        CloseHandle(hFile);
        return;
    }

    // 注意:证书表使用文件偏移而不是RVA
    PWIN_CERTIFICATE cert = (PWIN_CERTIFICATE)((LPBYTE)lpBase + certDir->VirtualAddress);

    printf("=== 证书表信息 ===\n");
    printf("证书表文件偏移: 0x%08X\n", certDir->VirtualAddress);
    printf("证书表大小: %d 字节\n", certDir->Size);

    // 遍历证书表中的所有证书
    DWORD offset = 0;
    int certIndex = 0;

    while(offset < certDir->Size) {
        PWIN_CERTIFICATE currentCert = (PWIN_CERTIFICATE)((LPBYTE)cert + offset);

        if(currentCert->dwLength == 0) {
            break;
        }

        printf("\n证书 %d:\n", certIndex++);
        PrintCertificateInfo(currentCert);

        // 按8字节对齐
        offset += ((currentCert->dwLength + 7) / 8) * 8;
    }

    UnmapViewOfFile(lpBase);
    CloseHandle(hMapping);
    CloseHandle(hFile);
}

// 使用Windows API验证文件签名
BOOL VerifyFileSignature(const char* filename) {
    WINTRUST_FILE_INFO fileInfo = {0};
    fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO);
    fileInfo.pcwszFilePath = (LPCWSTR)filename;
    fileInfo.hFile = NULL;
    fileInfo.pgKnownSubject = NULL;

    WINTRUST_DATA trustData = {0};
    trustData.cbStruct = sizeof(WINTRUST_DATA);
    trustData.pPolicyCallbackData = NULL;
    trustData.pSIPClientData = NULL;
    trustData.dwUIChoice = WTD_UI_NONE;
    trustData.fdwRevocationChecks = WTD_REVOKE_NONE;
    trustData.dwUnionChoice = WTD_CHOICE_FILE;
    trustData.dwStateAction = WTD_STATEACTION_VERIFY;
    trustData.hWVTStateData = NULL;
    trustData.pwszURLReference = NULL;
    trustData.dwProvFlags = WTD_SAFER_FLAG;
    trustData.dwUIContext = 0;
    trustData.pFile = &fileInfo;

    GUID actionId = WINTRUST_ACTION_GENERIC_VERIFY_V2;
    LONG result = WinVerifyTrust(NULL, &actionId, &trustData);

    switch(result) {
        case ERROR_SUCCESS:
            printf("文件签名验证成功\n");
            return TRUE;
        case TRUST_E_NOSIGNATURE:
            printf("文件没有数字签名\n");
            return FALSE;
        case TRUST_E_EXPLICIT_DISTRUST:
            printf("文件签名被明确拒绝\n");
            return FALSE;
        case TRUST_E_SUBJECT_NOT_TRUSTED:
            printf("文件签名不被信任\n");
            return FALSE;
        default:
            printf("文件签名验证失败,错误码: 0x%08X\n", result);
            return FALSE;
    }
}

证书表的重要性

证书表在Windows安全体系中扮演着核心角色:

  1. 文件完整性验证:通过数字签名验证文件自签名后是否被篡改。
  2. 来源验证:确认文件发布者的身份,建立信任链。
  3. 恶意软件防护:帮助安全软件和系统策略识别、阻止未签名或签名无效的可疑程序。
  4. 企业安全策略:支持"仅允许运行已签名应用"等组策略的实施。
  5. 驱动程序验证:Windows内核要求加载的驱动程序必须具有有效的数字签名。

总结

证书表是PE文件格式中用于存储数字签名信息的关键结构。它通过WIN_CERTIFICATE结构存储PKCS#7签名数据,使得Windows能够验证可执行文件的完整性和来源。深入理解证书表的结构、存放位置(使用文件偏移而非RVA)以及在哈希计算中被排除的特性,对于软件安全开发、恶意软件分析及数字签名验证等工作至关重要。掌握这些底层数据结构和解析逻辑是进行系统安全研究的坚实基础。




上一篇:SpringBoot项目模板定制实战:团队规范与通用功能封装
下一篇:每周文章精选:Claude Code、Vite、React、DeepSeekChatBI
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-12 03:36 , Processed in 0.087491 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表