0%

PE文件中的数字签名信息(1)

简介

PE文件数字签名使用的是 Authenticode 数字签名格式,它用来验证二进制程序的来源和完整性。
基于公开密匙加密标准 PKCS #7 来签名数据,并使用 X.509 证书来关联二进制程序与其发布者。

关于在PE文件中的 Authenticode 格式签名,微软给出了详细的解释,下载链接如下:
http://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/Authenticode_PE.docx

签名的位置

在PE文件数据目录表中,存储着签名信息的偏移地址和大小,如下图所示

签名位置

灰色背景的部分,不参与签名的hash计算。粗体的部分,就是签名的相关内容。

签名的格式

提取出来的签名信息,如下结构体所示,相关信息都在 Wintrust.h 头文件中定义

1
2
3
4
5
6
typedef struct _WIN_CERTIFICATE {
DWORD dwLength; // 签名证书二进制数据的长度
WORD wRevision; // 签名证书的版本号
WORD wCertificateType; // 签名证书的类型
BYTE bCertificate[ANYSIZE_ARRAY]; // 签名证书二进制数据
} WIN_CERTIFICATE, *LPWIN_CERTIFICATE;

wRevision 签名证书版本号,内容如下所示

Value Name Notes
0x0100 WIN_CERT_REVISION_1_0 Version 1 is the legacy version of WIN_CERTIFICATE.
It is supported only for verifying legacy Authenticode signatures.
0x0200 WIN_CERT_REVISION_2_0 Version 2 is the current version of WIN_CERTIFICATE.

wCertificateType 签名证书类型,这里值为 0x0002 表示 Authenticode signatures 类型

Value Name Notes
0x0001 WIN_CERT_TYPE_X509 bCertificate contains an X.509 Certificate
0x0002 WIN_CERT_TYPE_PKCS_SIGNED_DATA bCertificate contains a PKCS SignedData structure
0x0003 WIN_CERT_TYPE_RESERVED_1 Reserved
0x0004 WIN_CERT_TYPE_TS_STACK_SIGNED Terminal Server Protocol Stack Certificate signing

bCertificate[ANYSIZE_ARRAY] 签名证书的二进制数据,使用的是 ASN.1 语法 BER 编码格式。

签名的验证

在应用层可以使用 Wintrust.lib 库中的 WinVerifyTrust 函数来验证签名的信息

1
2
3
4
5
LONG WINAPI WinVerifyTrust(
_In_ HWND hWnd,
_In_ GUID *pgActionID,
_In_ LPVOID pWVTData
);

经过测试,在使用 WinVerifyTrust 验证签名时,偶尔会出现响应特别慢的情况。假如我们在驱动中
做进程签名拦截,借助于应用层验证签名时,就会造成系统卡顿现象。或者使用 openssl 库来验证签名,
可以避免卡顿。

另一种方案就是直接在驱动中验证,自行实现签名的验证代码,可以加快检测速度,但是显然这种方案工作量
特别庞大。经过研究发现 commonName 信息在签名二进制数据中是以 UTF8 格式明文存储的,所以可以采取
只验证 commonName 信息是否在黑名单中,而不验证整个证书链是否有效的简化方案。

签名的提取

在驱动中提取签名二进制数据信息,相关结构体的定义如下,我们不关心的成员就不再详细定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
typedef struct _DOS_HEADER {
USHORT e_magic;
USHORT e_res[29];
UINT32 e_lfanew;
} DOS_HEADER, *PDOS_HEADER;

typedef struct _FILE_HEADER {
UINT32 Reserved[5];
} FILE_HEADER, *PFILE_HEADER;

typedef struct _DATA_DIRECTORY {
UINT32 VAddr;
UINT32 Size;
} DATA_DIRECTORY, *PDATA_DIRECTORY;

typedef struct _OPTIONAL_HEADER32 {
USHORT Magic;
USHORT Reserved1[11];
UINT32 ImageBase; // 32
USHORT Reserved2[20];
UINT32 Reserved3[4]; // 32
USHORT Reserved4[4];
DATA_DIRECTORY DataDir[16];
} OPTIONAL_HEADER32, *POPTIONAL_HEADER32;

typedef struct _OPTIONAL_HEADER64 {
USHORT Magic;
USHORT Reserved1[11];
UINT64 ImageBase; // 64
USHORT Reserved2[20];
UINT64 Reserved3[4]; // 64
USHORT Reserved4[4];
DATA_DIRECTORY DataDir[16];
} OPTIONAL_HEADER64, *POPTIONAL_HEADER64;

typedef struct _NT_HEADERS32 {
UINT32 Signature;
FILE_HEADER FileHeader;
OPTIONAL_HEADER32 OptHeader;
} NT_HEADERS32, *PNT_HEADERS32;

typedef struct _NT_HEADERS64 {
UINT32 Signature;
FILE_HEADER FileHeader;
OPTIONAL_HEADER64 OptHeader;
} NT_HEADERS64, *PNT_HEADERS64;

typedef struct _WIN_CERT {
UINT32 Length;
USHORT Revision;
USHORT CertType;
} WIN_CERT, *PWIN_CERT;

提取签名信息的函数如下,假定参数的路径为 \\??\\C:\Windows\System32\calc.exe 这种格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
BOOLEAN CheckSignProcess(IN PWCHAR Path)
{
PWIN_CERT WinCert = NULL;
PDOS_HEADER DosHeader = NULL;
PNT_HEADERS32 NtHeader32 = NULL;
PNT_HEADERS64 NtHeader64 = NULL;
DATA_DIRECTORY DataDir = { 0 };
BOOLEAN bRet = FALSE; // 默认不在黑名单中
HANDLE hFile = NULL;
NTSTATUS Status = STATUS_SUCCESS;
IO_STATUS_BLOCK ioStatus = { 0 };
OBJECT_ATTRIBUTES objAttrib = { 0 };
FILE_POSITION_INFORMATION PosInfo = { 0 };
// 检查参数
if (Path == NULL) return FALSE;
// 初始化文件属性
InitializeObjectAttributes(&objAttrib, &Path,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
// 打开目标文件
Status = ZwOpenFile(
&hFile, GENERIC_READ | SYNCHRONIZE, &objAttrib, &ioStatus,
FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
if (!NT_SUCCESS(Status)) return FALSE;
do
{
// 申请PE头内存空间
DosHeader = (PDOS_HEADER)ExAllocatePoolWithTag(NonPagedPool, 1024, 'MEM');
if (DosHeader == NULL) break;
// 读取PE头信息到缓冲区
Status = ZwReadFile(hFile, NULL, NULL, NULL,
&ioStatus, (PVOID)DosHeader, 1024, 0, NULL);
if (!NT_SUCCESS(Status)) break;
// 检测DOS头信息
if (ioStatus.Information < sizeof(DOS_HEADER)) break;
if (DosHeader->e_magic != 0x5A4D) break; // IMAGE_DOS_SIGNATURE
NtHeader32 = (PNT_HEADERS32)((PUCHAR)DosHeader + DosHeader->e_lfanew);
// 被改造过的程序重新读NT头
if (DosHeader->e_lfanew > 0x108) // 大于默认偏移
{
// 设置文件偏移位置
PosInfo.CurrentByteOffset.QuadPart = DosHeader->e_lfanew;
Status = ZwSetInformationFile(hFile, &ioStatus, &PosInfo,
sizeof(FILE_POSITION_INFORMATION), FilePositionInformation);
if (!NT_SUCCESS(Status)) break;
// 读取NT头信息到缓冲区
Status = ZwReadFile(hFile, NULL, NULL, NULL,
&ioStatus, (PVOID)DosHeader, 1024, 0, NULL);
if (!NT_SUCCESS(Status)) break;
if (ioStatus.Information < sizeof(NT_HEADERS32)) break;
NtHeader32 = (PNT_HEADERS32)DosHeader;
}
// 检测NT头信息
if (NtHeader32->Signature != 0x4550) break; // IMAGE_NT_SIGNATURE
if (NtHeader32->OptHeader.Magic == 0x10B) // IMAGE_NT_OPTIONAL_HDR32_MAGIC
{
// IMAGE_DIRECTORY_ENTRY_SECURITY
DataDir.VAddr = NtHeader32->OptHeader.DataDir[4].VAddr;
DataDir.Size = NtHeader32->OptHeader.DataDir[4].Size;
}
else if (NtHeader32->OptHeader.Magic == 0x20B) // IMAGE_NT_OPTIONAL_HDR64_MAGIC
{
NtHeader64 = (PNT_HEADERS64)NtHeader32;
// IMAGE_DIRECTORY_ENTRY_SECURITY
DataDir.VAddr = NtHeader64->OptHeader.DataDir[4].VAddr;
DataDir.Size = NtHeader64->OptHeader.DataDir[4].Size;
}
else break;
// 申请签名缓冲区
if ((DataDir.VAddr == 0) || (DataDir.Size == 0)) break; // 无签名
WinCert = (PWIN_CERT)ExAllocatePoolWithTag(NonPagedPool, DataDir.Size, 'MEM');
if (WinCert == NULL) break;
// 设置文件偏移位置
PosInfo.CurrentByteOffset.QuadPart = DataDir.VAddr;
Status = ZwSetInformationFile(hFile, &ioStatus, &PosInfo,
sizeof(FILE_POSITION_INFORMATION), FilePositionInformation);
if (!NT_SUCCESS(Status)) break;
// 读取文件签名信息
Status = ZwReadFile(hFile, NULL, NULL, NULL,
&ioStatus, (PVOID)WinCert, DataDir.Size, 0, NULL);
if (!NT_SUCCESS(Status)) break;
if (ioStatus.Information != DataDir.Size) break;
// 检查签名信息是否在黑名单中
if (WinCert->CertType != 0x0002) break;
bRet = CheckSign((PUCHAR)(WinCert + 1), DataDir.Size - sizeof(WIN_CERT));
// 这里CheckSign函数在下篇文章中再说明
} while (0);
// 收尾处理
ZwClose(hFile);
if (WinCert != NULL) ExFreePool(WinCert);
if (DosHeader != NULL) ExFreePool(DosHeader);
return bRet;
}