rainit2006 / My_Windows

0 stars 0 forks source link

PropCollect/ PacMViewer/ #9

Open rainit2006 opened 7 years ago

rainit2006 commented 7 years ago

取得文件的version信息,并进一步取得major version信息。

 FileVersionInfo info = FileVersionInfo.GetVersionInfo(file.FullName);
 Version version = Version.Parse(info.FileVersion);
Console.Out.WriteLine(version.Major);

判定文件的copyright里是否包含DW字样

 FileVersionInfo info = FileVersionInfo.GetVersionInfo(file.FullName);
            if (-1 == info.LegalCopyright.IndexOf("DW", StringComparison.OrdinalIgnoreCase))
            {
                Console.Out.WriteLine("Not a valid file.");
                return;
            }
rainit2006 commented 7 years ago
rainit2006 commented 7 years ago

IDictionary インターフェイス Represents a nongeneric collection of key/value pairs.

 IDictionary<string, IDictionary<string, long>> fileMap = new Dictionary<string, IDictionary<string, long>>();

IDictionary<string, long> folder = new Dictionary<string, long>();
                        for (int iFileCounter = 0; iFileCounter < iFileCount; ++iFileCounter)
                        {
                            int iFileNameLength = reader.ReadInt32() * sizeof(char);
                            long lFileSize = reader.ReadInt32();
                            byte[] bufferFileName = new byte[iFileNameLength];
                            reader.Read(bufferFileName, 0, iFileNameLength);
                            folder.Add(Encoding.Unicode.GetString(bufferFileName), lFileSize); //把文件名称和size信息放入folder里
                        }
 fileMap.Add(Encoding.Unicode.GetString(bufferFolderName), folder); //再把目录名称和folder变量放入fileMap变量里。
rainit2006 commented 7 years ago

读取Pak-ma的Binary文件信息 需要根据Pak-ma内部结构来读取相应的信息。

            IDictionary<string, IDictionary<string, long>> fileMap = new Dictionary<string, IDictionary<string, long>>();

            try
            {
                using (BinaryReader reader = new BinaryReader(File.OpenRead(file.FullName)))
                {
                    reader.BaseStream.Seek(32, SeekOrigin.Begin);
                    //这是因为Pak-ma文件的0x0020(即32)记录了Preload Data开始的address信息。
                    long lOffset = reader.ReadInt64();
                    if (lOffset <= 0)
                    {
                        Console.Out.WriteLine("Not a valid PackMan package file.");
                        return;
                    }

                    reader.BaseStream.Seek(lOffset, SeekOrigin.Begin);
                    int iFolderCount = reader.ReadInt32();
                    for (int iFolderCounter = 0; iFolderCounter < iFolderCount; ++iFolderCounter)
                    {
                        int iFolderNameLength = reader.ReadInt32() * sizeof(char);
                        int iFileCount = reader.ReadInt32();
                        byte[] bufferFolderName = new byte[iFolderNameLength];
                        reader.Read(bufferFolderName, 0, iFolderNameLength);

                        IDictionary<string, long> folder = new Dictionary<string, long>();
                        for (int iFileCounter = 0; iFileCounter < iFileCount; ++iFileCounter)
                        {
                            int iFileNameLength = reader.ReadInt32() * sizeof(char);
                            long lFileSize = reader.ReadInt32();
                            byte[] bufferFileName = new byte[iFileNameLength];
                            reader.Read(bufferFileName, 0, iFileNameLength);
                            folder.Add(Encoding.Unicode.GetString(bufferFileName), lFileSize);
                        }

                        fileMap.Add(Encoding.Unicode.GetString(bufferFolderName), folder);
                    }

image

rainit2006 commented 7 years ago

//关于ToString参考 https://dobon.net/vb/dotnet/string/inttostring.html

writer.WriterLine(string.Join(",", new string[]{ "data1", "data2", "data3", ....
}));

writer.Close(); wirter.Dispose();



- 从字符串解析成version对象
Version.tryParse ("2.1.0.0330",  version);
这样version对象里包含了メジャー バージョン番号、マイナー バージョン番号、ビルド番号、およびリビジョン番号情报。
rainit2006 commented 7 years ago

从IMAGE_FILE_HEADER構造体里取“TimeDateStamp”

private DateTime GetBuildTime()
    {
        string filePath = System.Reflection.Assembly.GetCallingAssembly().Location;
        const int c_PeHeaderOffset = 60;
        const int c_LinkerTimestampOffset = 8;
        byte[] b = new byte[2048];
        System.IO.Stream s = null;

        try
        {
            s = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
            s.Read(b, 0, 2048);
        }
        finally
        {
            if (s != null)
            {
                s.Close();
            }
        }

        int i = System.BitConverter.ToInt32(b, c_PeHeaderOffset);
        int secondsSince1970 = System.BitConverter.ToInt32(b, i + c_LinkerTimestampOffset);
        DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0);
        dt = dt.AddSeconds(secondsSince1970);
        dt = dt.AddHours(TimeZone.CurrentTimeZone.GetUtcOffset(dt).Hours);
        return dt;
    }
rainit2006 commented 7 years ago

弹出文件选择对话框 Key: Microsoft.Win32.OpenFileDialog

关于RelayCommand类的实现可参照: http://sourcechord.hatenablog.com/entry/2014/01/13/200039

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
        RelayCommand _browseFileCommand;
        public ICommand BrowseFileCommand
        {
            get
            {
                if (this._browseFileCommand == null)
                {
                    this._browseFileCommand = new RelayCommand(
                        (o) =>
                        {
                            var dialog = new Microsoft.Win32.OpenFileDialog();
                            dialog.ShowReadOnly = true;
                            dialog.Filter = "Executable files (.exe)|*.exe|All files (*.*)|*.*";
                            dialog.FilterIndex = 0;
                            var result = dialog.ShowDialog();
                            if (result == true)
                            {
                                this.FilePath = dialog.FileName;
                            }
                        },
                        (o) =>
                        {
                            return !this.IsProcessing;
                        });
                    this.PropertyChanged += (s, e) =>
                    {
                        if (e.PropertyName == nameof(IsProcessing))
                        {
                            this._browseFileCommand.RaiseCanExecuteChanged();
                        }
                    };
                }
                return this._browseFileCommand;
            }
        }
rainit2006 commented 7 years ago

关于DebuggerBrowsable(DebuggerBrowsableState.Never) http://qiita.com/tomochan154/items/b7a7f99a60fa2d98cc1b 让某些不关注属性可以在debug时不在watch widnow里可见。

rainit2006 commented 7 years ago

关于Binary文件的构造 https://codezine.jp/article/detail/403 image

 あくまで機械語なので、アセンブラではありません。メモ帳やバイナリエディタで開いても意味不明な文字の羅列しか表示されませんので、アセンブリソースを閲覧されたい方は、逆アセンブルを行う必要があります。

イメージベース + RVA = アドレス

IMAGE_DOS_HEADER構造体の中にある情報は、現在主流であるWindowsであまり扱われません。そのため、ほとんどが意味を持ちません。しかし、次に挙げる情報は重要なので、チェックしておきましょう。

e_magicメンバ …… 必ず0x5A4Dを保持しています
e_lfanewメンバ …… PEヘッダ(或NEヘッダ)が格納されているファイル位置を保持します
`NT ヘッダーの位置を得るのには、ImageNtHeader という関数を使うこともできます。`

-- MS-DOS用スタブ 万が一、MS-DOS上でプログラムを動作させてしまった場合に、MS-DOSのプログラムを実行するためのネイティブコードを保存できるスペースです。MS-DOS用スタブは、IMAGE_DOS_HEADERの直後に配置されます。 PEヘッダはWindowsに対応しているデータなので、MS-DOSでは読み込むことができません。MS-DOSで解釈可能なのは、IMAGE_DOS_HEADER構造体と、それに続くネイティブコード、つまり、このMS-DOS用スタブになります。

 このスタブデータは、一般的には「This program cannot be run in DOS mode.」という文字列を表示して終了するプログラムが保持されていますが、MS-DOS用にカスタマイズされたプログラムをこのスペースに格納しておいても問題はありません。

-- NULL空間 一般的には、0x0100バイト目までNULL空間が存在し、0x0100バイト目からPEヘッダが配置されます。

-- PEヘッダ

rainit2006 commented 7 years ago

特殊目录

Environment.SpecialFolder.ProgramFiles
Environment.SpecialFolder.ProgramFilesX86
Environment.SpecialFolder.ProgramFiles
Environment.SpecialFolder.ProgramFilesX86

验证签名是否有效

关键: WintrustData WinVerifyTrust函数

 GUID GenericActionId = WINTRUST_ACTION_GENERIC_VERIFY_V2;
// WinVerifyTrust verifies signatures as specified by the GUID 
    // and Wintrust_Data.
    lStatus = WinVerifyTrust(
        NULL,
        &GenericActionId,
        &WinTrustData);
// Now attempt to verify all secondary signatures that were found 
    for(DWORD x = 1; x <= WintrustData.pSignatureSettings->cSecondarySigs; x++) 
    { 
        wprintf(L"Verify secondary signature at index %d... ", x); 

        // Need to clear the previous state data from the last call to WinVerifyTrust 
        WintrustData.dwStateAction = WTD_STATEACTION_CLOSE; 
        Error = WinVerifyTrust(NULL, &GenericActionId, &WintrustData); 
        if (Error != ERROR_SUCCESS)  
        { 
            //No need to call WinVerifyTrust again 
            WintrustCalled = false; 
            PrintError(Error);             
            goto Cleanup; 
        } 
rainit2006 commented 7 years ago

DWORD = 4 byte;
WORD = 2 byte;

PE ファイルについて (1) - IMAGE_DOS_HEADER http://tech.blog.aerie.jp/entry/2015/12/21/183741


PE ファイルについて (2) - IMAGE_FILE_HEADER http://tech.blog.aerie.jp/entry/2015/12/23/000000

IMAGE_NT_HEADERS structure https://msdn.microsoft.com/en-us/library/windows/desktop/ms680336(v=vs.85).aspx

typedef struct _IMAGE_NT_HEADERS {
  DWORD                 Signature;
  IMAGE_FILE_HEADER     FileHeader;
  IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

■Signature:
A 4-byte signature identifying the file as a PE image. The bytes are "PE\0\0". ■FileHeader里包含下面信息: Machine: この PE ファイルがターゲットとする CPU のアーキテクチャーを表します。 x86(32bit)の場合は 0x014c、x64(64bit)の場合は 0x8664 です。 それ以外にも IA64 や ARM から MIPS、ALPHA、PowerPC、SH5 など、様々な値が定義されています。 VC++ では、リンカーオプション /MACHINE で指定します。

NumberOfSections:このファイルに含まれるセクションの数を表します。

...
reader.BaseStream.Seek(e_lfanew + 6, SeekOrigin.Begin);
var numberOfSections = reader.ReadUInt16();
...

TimeDateStamp:ファイルの作成時刻 NumberOfSymbols: COFF シンボルテーブルのシンボルの数です。PE ファイルの場合は 0 のようです。 SizeOfOptionalHeader: オプショナル ヘッダーのサイズです。IMAGE_OPTIONAL_HEADER 構造体のサイズになります。

..
reader.BaseStream.Seek(e_lfanew + 8, SeekOrigin.Begin);
var builtTime = reader.ReadUInt32();

reader.BaseStream.Seek(e_lfanew + 16, SeekOrigin.Begin);
var numberOfSymbols = reader.ReadUInt32();

reader.BaseStream.Seek(e_lfanew + 20, SeekOrigin.Begin);
var sizeOfOptionalHeader = reader.ReadUInt16();
..

■IMAGE_OPTIONAL_HEADER:见下一章

PE ファイルについて (3) - IMAGE_OPTIONAL_HEADER http://tech.blog.aerie.jp/entry/2015/12/24/013344

■Magic: このファイルが 32bit 用か 64bit 用かを表します。 IMAGE_NT_OPTIONAL_HDR32_MAGIC (0x10b) : 32bit 用 IMAGE_NT_OPTIONAL_HDR64_MAGIC (0x20b) : 64bit 用

// IMAGE_NT_HEADERS:IMAGE_OPTIONAL_HEADER32/64
const UInt16 IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b;
const UInt16 IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b;
//const UInt16 IMAGE_ROM_OPTIONAL_HDR_MAGIC = 0x107;
var ioh_offset = e_lfanew + 24;

reader.BaseStream.Seek(ioh_offset, SeekOrigin.Begin);
var ioh_magic = reader.ReadUInt16();
 if (ioh_magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC
                        && ioh_magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) throw new FileFormatException();
 bool isX64 = ioh_magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC;

■SizeOfImage:この PE ファイルをロードしたときにメモリ上に占めるサイズです。SectionAlignment の値の倍数に丸められます。ファイルサイズとは異なります。 ■SizeOfHeaders:この PE ファイル中の全てのヘッダーサイズの合計です。FileAlignment の値の倍数に丸められます。


var ioh_offset = e_lfanew + 24;

reader.BaseStream.Seek(ioh_offset + 56, SeekOrigin.Begin);
var ioh_sizeOfImage = reader.ReadUInt32();
var ioh_sizeOfHeaders = reader.ReadUInt32();

■DataDirectory:データ ディレクトリです。 IMAGE_DATA_DIRECTORY 構造体の配列になっており、要素数は NumberOfRvaAndSizes で示されます。

typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

// IMAGE_NT_HEADERS:IMAGE_OPTIONAL_HEADER:IMAGE_DATA_DIRECTORY
var ioh_idd_offset = ioh_offset + (isX64 ? 112 : 96);
reader.BaseStream.Seek(ioh_idd_offset + 8 * 4, SeekOrigin.Begin);
var ioh_idd_cert_offset = reader.ReadUInt32(); //get virtualAddress
var ioh_idd_cert_size = reader.ReadUInt32();   // get size.

PE ファイルについて (4) - IMAGE_SECTION_HEADER https://msdn.microsoft.com/en-us/library/windows/desktop/ms680305.aspx

セクション データ: セクション データはヘッダーの後の PE ファイルのデータ本体で、セクションとは、その本体領域を用途ごとにいくつかに区切ったものです。 1 つのセクションにつき、1 つのセクション ヘッダーがあります。これが IMAGE_SECTION_HEADER 構造体です。 この構造体は、IMAGE_NT_HEADERS 構造体には含まれていません。ので、厳密には NT ヘッダーの一部ではないです。

image image セクションがいくつあるかは、第 2 回でやりましたが、IMAGE_FILE_HEADER::NumberOfSections に書かれています。


PE ファイルについて (5) - IMAGE_DATA_DIRECTORY http://tech.blog.aerie.jp/entry/2015/12/27/144045

rainit2006 commented 7 years ago

MSI数据读取

MsiDatabaseIsTablePersistentの名前から予想がつくかもしれませんが、 MSIの関数の名前は第1引数に指定するハンドルの種類と一致するようになっています。 つまり、データベースのハンドルを指定する場合は、MsiDatabaseから始まり、 ビューやレコードの場合はそれぞれ、MsiView、MsiRecordとなります。 MSIの関数は非常に種類が多いので、この法則を覚えておくと関数の用途が分かりやすくなります。
判断MSI文件里是否包含“IS“文字
    static bool IsExistISInnerTbl(string filePath)
    {
        bool isExist = false;
        string tableName = string.Empty;

        IntPtr hMsiHandle = IntPtr.Zero;
        uint uiStatus = MsiOpenDatabase(filePath, MSIDBOPEN_READONLY, out hMsiHandle);
        if (uiStatus == 0)
        {
            try
            {
                IntPtr hView = IntPtr.Zero;
                string szQuery = "SELECT * FROM _Tables";
                uiStatus = MsiDatabaseOpenView(hMsiHandle, szQuery, out hView);

                if (uiStatus == 0)
                {
                    try
                    {
                        IntPtr hRecord = IntPtr.Zero;
                        uiStatus = MsiViewExecute(hView, hRecord);

                        if (uiStatus == 0)
                        {
                            try
                            {
                                while (MsiViewFetch(hView, out hRecord) == 0)
                                {
                                    IntPtr fieldVlu = IntPtr.Zero;
                                    uint dwStringLen = 0;
                                    var result1 = MsiRecordGetString(hRecord, 1, fieldVlu, ref dwStringLen);
                                    if (dwStringLen > 0)
                                    {
                                        fieldVlu = Marshal.AllocHGlobal(((int)++dwStringLen) * sizeof(char));
                                        var result2 = MsiRecordGetString(hRecord, 1, fieldVlu, ref dwStringLen);

                                        if (fieldVlu != IntPtr.Zero)
                                        {
                                            tableName = Marshal.PtrToStringUni(fieldVlu, (int)dwStringLen);
                                            Marshal.FreeHGlobal(fieldVlu);
                                        }
                                    }

                                    if (tableName.Length >= 2 && tableName.Substring(0, 2).Equals("IS"))
                                    {
                                        isExist = true;
                                        break;
                                    }
                                }
                            }
                            finally
                            {
                                if (hRecord != IntPtr.Zero)
                                {
                                    MsiCloseHandle(hRecord);
                                }
                            }
                        }
                    }
                    finally
                    {
                        if (hView != IntPtr.Zero)
                        {
                            MsiCloseHandle(hView);
                        }
                    }
                }
            }
            finally
            {
                if (hMsiHandle != IntPtr.Zero)
                {
                    MsiCloseHandle(hMsiHandle);
                }
            }
        }

        return isExist;
    }

- MsiGetSummaryInformation
サマリー情報とは、製品会社などの簡略な製品情報をまとめたもので、 これを利用することで、たとえばPropertyテーブルのManufacturerという列の フィールド値を取得するといったような手順を踏む必要がなくなります。 サマリー情報へのアクセスは、MsiGetSummaryInformationの呼び出しから始まります。
サマリー情報に含まれる個々のデータは、プロパティという単位で区切られています。 プロパティはIDによって識別され、プロパティ毎にデータの型も異なります。 これらの情報は、マイクロソフトのリファレンスに掲載されている プロパティセットとよばれる表から参照することができます。
![image](https://user-images.githubusercontent.com/12871721/31120425-502bdca6-a86f-11e7-906e-daaec353d3db.png)

从MSI文件里取得程序名称(id=18)
    static string GetProgramName(string strFile)
    {
        string programName = string.Empty;

        IntPtr hSummaryInfo;
        uint uiStatus = MsiGetSummaryInformation(IntPtr.Zero, strFile, 0, out hSummaryInfo);
        if (uiStatus == 0)
        {
            try
            {
                uint dataType = 0;
                int pvalue = 0;
                long time = 0;
                uint dwBufferLength = 0;

                var result1 = MsiSummaryInfoGetProperty(hSummaryInfo, 18, out dataType, out pvalue, out time, IntPtr.Zero, ref dwBufferLength);
                if (dwBufferLength > 0)
                {
                    IntPtr pProgramName = Marshal.AllocHGlobal(((int)++dwBufferLength) * sizeof(char));
                    var result2 = MsiSummaryInfoGetProperty(hSummaryInfo, 18, out dataType, out pvalue, out time, pProgramName, ref dwBufferLength);
                    if (pProgramName != IntPtr.Zero)
                    {
                        programName = Marshal.PtrToStringUni(pProgramName, (int)dwBufferLength);
                        Marshal.FreeHGlobal(pProgramName);
                    }
                }
            }
            finally
            {
                if (hSummaryInfo != IntPtr.Zero)
                {
                    MsiCloseHandle(hSummaryInfo);
                }
            }
        }
        return programName;
    }
rainit2006 commented 7 years ago

签名处理

//pgActionID [in] : A pointer to a GUID structure that identifies an action and the trust provider that supports that action. 比如值指定“WINTRUST_ACTION_GENERIC_VERIFY_V2” 表示“Verify a file or object using the Authenticode policy provider.”

//pWVTData [in] : A pointer that, when cast as a WINTRUST_DATA structure, contains information that the trust provider needs to process the specified action identifier.

<C++的处理>

WINTRUST_FILE_INFO结构体

WINTRUST_DATA结构体

typedef struct _WINTRUST_DATA { DWORD cbStruct; LPVOID pPolicyCallbackData; LPVOID pSIPClientData; DWORD dwUIChoice; DWORD fdwRevocationChecks; DWORD dwUnionChoice; union { struct WINTRUST_FILEINFO pFile; struct WINTRUST_CATALOGINFO pCatalog; struct WINTRUST_BLOBINFO pBlob; struct WINTRUST_SGNRINFO pSgnr; struct WINTRUST_CERTINFO pCert; }; DWORD dwStateAction; HANDLE hWVTStateData; WCHAR pwszURLReference; DWORD dwProvFlags; DWORD dwUIContext; WINTRUST_SIGNATURE_SETTINGS pSignatureSettings; } WINTRUST_DATA, PWINTRUST_DATA;


WINTRUST_SIGNATURE_SETTINGS结构体

CMSG_SIGNER_INFO structure
The CMSG_SIGNER_INFO structure contains the content of the PKCS #7 defined SignerInfo in signed messages. In decoding a received message, CryptMsgGetParam is called for each signer to get a CMSG_SIGNER_INFO structure.

typedef struct _CMSG_SIGNER_INFO { DWORD dwVersion; CERT_NAME_BLOB Issuer; CRYPT_INTEGER_BLOB SerialNumber; CRYPT_ALGORITHM_IDENTIFIER HashAlgorithm; CRYPT_ALGORITHM_IDENTIFIER HashEncryptionAlgorithm; CRYPT_DATA_BLOB EncryptedHash; CRYPT_ATTRIBUTES AuthAttrs; CRYPT_ATTRIBUTES UnauthAttrs; } CMSG_SIGNER_INFO, *PCMSG_SIGNER_INFO;


-----------------------------------------

- 判断是否存在签名

bool CSignCheck::IsCheckSigned(LPCTSTR lpFileName) { bool bSignedModule = false;

if (lpFileName && lstrlen(lpFileName))
{
    USES_CONVERSION;

    WINTRUST_FILE_INFO stFileInfo = {0};
    stFileInfo.cbStruct = sizeof(stFileInfo);
    stFileInfo.pcwszFilePath = lpFileName;

    WINTRUST_DATA stTrustData = {0};
    stTrustData.cbStruct = sizeof(stTrustData);
    stTrustData.dwUIChoice = WTD_UI_NONE;
    stTrustData.fdwRevocationChecks = WTD_REVOKE_NONE;
    stTrustData.dwUnionChoice = WTD_CHOICE_FILE;
    stTrustData.pFile = &stFileInfo;
    stTrustData.dwProvFlags = WTD_REVOCATION_CHECK_NONE;

    GUID stGuidAction = WINTRUST_ACTION_GENERIC_VERIFY_V2;
    LONG lResult = ::WinVerifyTrust((HWND)INVALID_HANDLE_VALUE, &stGuidAction, &stTrustData);

    bSignedModule = lResult == 0;
}

return bSignedModule;

}


- 取得数字签名的数量
关键点: WSS_GET_SECONDARY_SIG_COUNT 
//Set this value to return the number of secondary signatures found in the cSecondarySigs member. 

签名总数 = WSS_GET_SECONDARY_SIG_COUNT  + 1。

- 取得签名日期
CMSG_SIGNER_INFO. AuthAttrs
(1)取得CMSG_SIGNER_INFO. AuthAttrs里的pszObjId,
(2)如果pszObjId是szOID_RFC3161_counterSign 或szOID_RSA_counterSign,则分别通过不同方法取得timestamp。

RFC3161是一种時刻認証プロトコル(Time-Stamp Protocol)。
RSA是一种加密算法

public static readonly string szOID_RFC3161_counterSign = "1.3.6.1.4.1.311.3.3.1"; public static readonly string szOID_RSA_signingTime = "1.2.840.113549.1.9.5"; public static readonly string szOID_RSA_counterSign = "1.2.840.113549.1.9.6";

(3)调用Crypt32.dll的CryptMsg方法来获得CMsgSignerInfo情报
CryptMsgOpenToDecode(ENCODING_TYPE, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
CryptMsgGetParam(hMsg, Crypt32.MsgParamType.CMSG_SIGNER_INFO_PARAM, 0, IntPtr.Zero, ref dwSize))
CryptMsgGetParam(hMsg, Crypt32.MsgParamType.CMSG_SIGNER_INFO_PARAM, 0, pDataBuffer, ref dwSize))
CMSG_SIGNER_INFO counterSignerInfo = new Crypt32.CMsgSignerInfo(pDataBuffer);

(4)获得CMSG_SIGNER_INFO的AuthAttrs里的pzObjID 等于szOID_RSA_signingTime的AuthAttrs对象,
 调用 

CryptDecodeObject(ENCODING_TYPE, szOID_RSA_signingTime, attrBlob.pbData, attrBlob.cbData, 0, pDataBuffer, ref dwSize);

                if (fResult)
                    {
                        ft = 
             (System.Runtime.InteropServices.ComTypes.FILETIME)Marshal.PtrToStructure(pDataBuffer, typeof(System.Runtime.InteropServices.ComTypes.FILETIME));   //ft就是timestamp。
                        fReturn = true;
                    }

-----------------------
可以参考的网页:
http://eternalwindows.jp/crypto/filesign/filesign00.html