首页
社区
课程
招聘
[分享]experiment : VerifyFileSignatureBuildIn
发表于: 2013-8-6 21:37 5542

[分享]experiment : VerifyFileSignatureBuildIn

2013-8-6 21:37
5542
在开源工程中找到一段校验PE文件签名的代码, 因为以前的工程没有用到过PE文件签名校验, 拿来做个实验.

这段代码比MS给的签名验证的Demo好,对MS程序和第三方程序做了区分.

发帖之前,在论坛内搜索了一次。
发现 http://bbs.pediy.com/showthread.php?t=65492 已经有pediyer给出了代码.
和我整理之前的那段签名校验是一样的~~, 大家都是Copy来Copy去~

那段代码有处理不细腻的地方, e.g. 签名值空间的分配, 成功后的返回值判断.

还是发一下整理后的这个签名校验接口, 对刚接触PE签名校验的同学,应该有帮助~

实验中做了些注释, 对几种签名校验(MS签名, 非MS签名, 无签名)的情况, 做了记录.

/// @file       srcVerifyFileSignature.cpp
/// @brief      测试 校验文件内建签名

#include "stdafx.h"
#include <windows.h>
#include <Wintrust.h>
#include <Mscat.h>
#include <SoftPub.h>
#include <tchar.h>
#include <Strsafe.h>

#pragma comment(lib, "Wintrust.lib")

/// 被测试校验签名的PE文件全路径

/// 有签名的系统程序, 校验签名通过
#define G_FILE_TOBE_VERIFY_SIGN L"C:\\Windows\\System32\\notepad.exe"

/// 没有加签名的第三方程序, 校验签名失败
// #define G_FILE_TOBE_VERIFY_SIGN  L"d:\\LsTemp\\srcVerifyFileSignature_X64.exe"

/// 第三方程序带签名, 从360目录里找了个软件管家的PE文件
// #define G_FILE_TOBE_VERIFY_SIGN  L"d:\\LsTemp\\SoftManager.exe"

/// @fn     VerifyFileSignatureBuildIn
/// @brief  调用MS接口, 校验文件内建签名是否正确
/// @param  wchar_t * pFilePathName, 被校验的文件全路径名称
/// @return BOOL
/// @retval TRUE
///         校验成功
/// @retval 其他
///         校验失败
BOOL VerifyFileSignatureBuildIn(wchar_t * pFilePathName);

int _tmain(int argc, _TCHAR* argv[])
{
    BOOL    bRc =   TRUE;

    bRc = VerifyFileSignatureBuildIn(G_FILE_TOBE_VERIFY_SIGN);
    _tprintf(L"%s : VerifyFileSignatureBuildIn(%s)\r\n",
        bRc ? L"TRUE" : L"FALSE",
        G_FILE_TOBE_VERIFY_SIGN);

    /// run result
    /**
    x86和x64程序也可以调用此接口校验目标程序

    已经测试了X86版PE输出校验X64版目标程序的情况
    TRUE : VerifyFileSignatureBuildIn(C:\Windows\System32\notepad.exe)

    /// 带签名的非MS程序
    TRUE : VerifyFileSignatureBuildIn(d:\LsTemp\SoftManager.exe)

    /// 对于没有签名的程序, 校验的结果是FALSE
    FALSE : VerifyFileSignatureBuildIn(d:\LsTemp\
        srcVerifyFileSignature_X64.exe)

    /// @todo 没有测试有签名的程序被篡改的情况
    */

    _tprintf(L"END, press any key to quit\r\n");
    getwchar();
    return 0;
}

BOOL VerifyFileSignatureBuildIn(wchar_t * pFilePathName)
{
    BOOL        bRc         =   FALSE;
    HANDLE      hFile       =   INVALID_HANDLE_VALUE;
    HCATADMIN   hCatAdmin   =   NULL;
    GUID        GuidAction  =   WINTRUST_ACTION_GENERIC_VERIFY_V2;
    long        lRc         =   NULL;

    WINTRUST_DATA           wd          =   {0};
    WINTRUST_FILE_INFO      wfi         =   {0};
    WINTRUST_CATALOG_INFO   wci         =   {0};
    CATALOG_INFO            ci          =   {0};
    HCATINFO                hCatInfo    =   NULL;

    BYTE *      pbHash          =   NULL;   ///< 容纳Hash值
    DWORD       dwLenHash       =   0;
    DWORD       dwIndex         =   0;
    DWORD       dwPosCur        =   0;
    wchar_t *   pcwszMemberTag  =   NULL;   ///< wfi.pcwszMemberTag

    if (!CryptCATAdminAcquireContext(&hCatAdmin, NULL, 0))
        return FALSE;

    hFile = CreateFileW(
                    pFilePathName,
                    GENERIC_READ,
                    FILE_SHARE_READ,
                    NULL,
                    OPEN_EXISTING,
                    0,
                    NULL );
    if (INVALID_HANDLE_VALUE == hFile)
    {
        bRc = FALSE;
        goto END_VerifyFileSignatureBuildIn;
    }

    /// 得到返回的hash值缓冲区大小
    bRc = CryptCATAdminCalcHashFromFileHandle(hFile, &dwLenHash, pbHash, 0);
    if (!bRc)
    {
        bRc = FALSE;
        goto END_VerifyFileSignatureBuildIn;
    }

    /// 构造字节型的HASH值缓冲区
    pbHash = new BYTE[dwLenHash + 1];
    if (NULL == pbHash)
    {
        bRc = FALSE;
        goto END_VerifyFileSignatureBuildIn;
    }

    ::ZeroMemory(pbHash, dwLenHash + 1);

    /// 计算文件HASH值
    bRc = CryptCATAdminCalcHashFromFileHandle(hFile, &dwLenHash, pbHash, 0);
    if (!bRc)
    {
        bRc = FALSE;
        goto END_VerifyFileSignatureBuildIn;
    }

    /// 从HASH值中枚举目录
    /// @note 这个目录(Catalog)的含义?
    /// 经测试, MS签名的程序都可以按照目录找到
    /// 那目录的含义就是带MS签名的HASH本地库
    /// MS在本地已经存储了该系统文件的HASH值
    hCatInfo = CryptCATAdminEnumCatalogFromHash(
                    hCatAdmin,
                    pbHash,
                    dwLenHash,
                    0,
                    NULL);

    /// vs2010 WinVerifyTrust 例子中, 给出的签名校验方式
    /// 是按照文件方式 WTD_CHOICE_FILE 方式来校验签名的
    /// 如果将被校验的文件, 分为MS签名和非MS签名, 可能会提高效率

    if (NULL == hCatInfo)
    {
        /// 按照文件来校验HASH

        /// 未加签名的程序会进这里
        /// 第三方程序(非MS出品)加签名, 也会进这里

        wfi.cbStruct       = sizeof(WINTRUST_FILE_INFO);
        wfi.pcwszFilePath  = pFilePathName;
        wfi.hFile          = NULL;
        wfi.pgKnownSubject = NULL;

        wd.cbStruct            = sizeof(WINTRUST_DATA);
        wd.dwUnionChoice       = WTD_CHOICE_FILE;
        wd.pFile               = &wfi;
        wd.dwUIChoice          = WTD_UI_NONE;
        wd.fdwRevocationChecks = WTD_REVOKE_NONE;
        wd.dwStateAction       = WTD_STATEACTION_IGNORE;
        wd.dwProvFlags         = WTD_SAFER_FLAG;
        wd.hWVTStateData       = NULL;
        wd.pwszURLReference    = NULL;
    }
    else
    {
        /// 按照目录来校验签名

        /// 已经签名未篡改的PE文件会到这里
        /// 在文件中能找到计算出的HASH值
        CryptCATCatalogInfoFromContext(hCatInfo, &ci, 0);
        wci.cbStruct             = sizeof(WINTRUST_CATALOG_INFO);
        wci.pcwszCatalogFilePath = ci.wszCatalogFile;
        wci.pcwszMemberFilePath  = pFilePathName;

        /// 一个字节的HASH值 => 2个wchar_t的HASH字符串
        /// e.g. 0x11 => L"11"
        /// 缓冲区尾部留2个L'\0'的位置, 留一个L'\0'会报错的
        pcwszMemberTag = new wchar_t[(dwLenHash + 1) * 2];
        if (NULL == pcwszMemberTag)
        {
            bRc = FALSE;
            goto END_VerifyFileSignatureBuildIn;
        }

        ::ZeroMemory(pcwszMemberTag, sizeof(wchar_t) * (dwLenHash + 1) * 2);
        for (dwIndex = 0; 
            dwIndex < dwLenHash; 
            dwIndex++, dwPosCur += sizeof(wchar_t))
        {
            swprintf_s( &pcwszMemberTag[dwIndex * 2],
                        sizeof(wchar_t) * 2, 
                        L"%02X", 
                        pbHash[dwIndex]);
        }

        wci.pcwszMemberTag       = pcwszMemberTag;

        wd.cbStruct            = sizeof(WINTRUST_DATA);
        wd.dwUnionChoice       = WTD_CHOICE_CATALOG;
        wd.pCatalog            = &wci;
        wd.dwUIChoice          = WTD_UI_NONE;
        wd.fdwRevocationChecks = WTD_STATEACTION_VERIFY;
        wd.dwProvFlags         = 0;
        wd.hWVTStateData       = NULL;
        wd.pwszURLReference    = NULL;
    }

    /**
    Note  The return value is a LONG, not an HRESULT as previously 
    documented. Do not use HRESULT macros such as SUCCEEDED to determine 
    whether the function succeeded. Instead, check the return value for 
    equality to zero.

    TRUST_E_SUBJECT_NOT_TRUSTED The subject failed the specified verification 
    action. Most trust providers return a more detailed error code that 
    describes the reason for the failure.

    TRUST_E_PROVIDER_UNKNOWN The trust provider is not recognized on this 
    system.

    TRUST_E_ACTION_UNKNOWN The trust provider does not support the specified 
    action.

    TRUST_E_SUBJECT_FORM_UNKNOWN The trust provider does not support the form 
    specified for the subject.
    */
    lRc = WinVerifyTrust(NULL, &GuidAction, &wd);
    bRc = (0 == lRc);

END_VerifyFileSignatureBuildIn:

    if (NULL != hCatInfo)
        CryptCATAdminReleaseCatalogContext(hCatAdmin, hCatInfo, 0);

    if (NULL != hCatAdmin)
        CryptCATAdminReleaseContext(hCatAdmin, 0);

    if ((NULL != hFile)
        && (INVALID_HANDLE_VALUE != hFile))
    {
        CloseHandle(hFile);
        hFile = INVALID_HANDLE_VALUE;
    }

    if (NULL != pbHash)
    {
        delete []pbHash;
        pbHash = NULL;
    }

    if (NULL != pcwszMemberTag)
    {
        delete []pcwszMemberTag;
        pcwszMemberTag = NULL;
    }

    return bRc;
}


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (5)
雪    币: 10
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
兩個問題-
1. MS也曾經出過embedded sign的file, 第三方也可以有catalog sign, 沒有判斷issuer其實很難說是不是第三方
2. 用WTD_REVOKE_NONE,以目前的狀況來說,會有可能把virus當成signed file
2013-8-6 23:22
0
雪    币: 112
活跃值: (57)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
3
kalllin 好:
  我才接触PE签名验证, 谢谢您的指点.

  * WTD_REVOKE_NONE 去掉我试试, 看能不能验证360的签名程序.
  * issuer 判断该怎么搞? 哪个结构中的成员? 能您告诉我么?

  您能给个URL, 我先学习下好么?

  谢谢~
2013-8-7 00:00
0
雪    币: 112
活跃值: (57)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
4
wd.fdwRevocationChecks = WTD_REVOKE_NONE;

/**
DWORD           fdwRevocationChecks;        // required: certificate revocation check options
#                       define      WTD_REVOKE_NONE         0x00000000
#                       define      WTD_REVOKE_WHOLECHAIN   0x00000001
*/
现在也填不了别的, 只能填 WTD_REVOKE_NONE 吧?
2013-8-7 00:15
0
雪    币: 10
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
我只是根據以前的經驗給點建議,如果不是用在產品上倒也不用那麼在意,
不過是05年左右搞的,算算也快8年了吧,只剩下些印象深刻的,
如果錯了還請多包涵 ^^"

WTD_REVOKE_WHOLECHAIN會比較保險,
但是要小心在某些特殊的網路環境下,會有不短的timeout發生,
畢竟whole certificate chain的檢查是有可能在某些條件下連上網路的,
至於條件不同版本OS不太一樣,有些甚至有bug不會連...
另外就是dwProvFlags上有不少跟revocation相關的需要你自己找找看哪個比較適合你用

至於issuer,有點複雜...sample code我不知道丟哪了,
這裡先寫我記得的,明天去公司翻翻看還有沒有留下來

1. catalog sign要先透過CryptCATCatalogInfoFromContext拿到CATALOG_INFO,
    我們需要用裡面的wszCatalogFile

2. 呼叫CryptQueryObject(使用CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED)
    去拿HCRYPTMSG,catalog signed file就用(1)拿到的path,embedded signed file就直接用原本的file path

3. 再呼叫CryptMsgGetParam(使用CMSG_SIGNER_INFO_PARAM)可以拿到CMSG_SIGNER_INFO,
    我們需要用到裡面的Issuer跟SerialNumber

4. 用CERT_INFO並填入在(3)拿到的Issuer與SerialNumber,
    呼叫CertFindCertificateInStore(使用CERT_FIND_SUBJECT_CERT)去找到相對應的certificate context,
    通常類型會選X509與PKCS_7

5. 最後,呼叫CertGetNameString(使用CERT_NAME_SIMPLE_DISPLAY_TYPE)就可以拿到了

ps. catalog sign在Vista以後廣泛地應用在很多非PE檔案上
2013-8-8 02:10
0
雪    币: 112
活跃值: (57)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
6
谢谢~

原来Issuer 的操作,有点像使用CSP操作x509证书.
我顺着您给的线索,去找找资料。

如果您能找到以前写过的Demo, 那就太好了~,期待拜读您的Demo~~

感谢您回复的这么仔细, 我收获很大~
2013-8-8 07:32
0
游客
登录 | 注册 方可回帖
返回
//