在阅读本文前,先确定几个概念。
FileName(文件名):即文件在硬盘上的名字,这个名字随着复制、改名,会变化。
InternalName(文件原始名):在附加了.rc编译时,编译器会将.rc中的名字链接进去,这个名字,不会随着文件的复制改名而变化(用PE工具修改后再改CheckSum除外), 在Windows中,右击文件名->属性->版本->内部名称 就是此字段
FileVersion(版本) 在附加了.rc编译时,编译器会将.rc中的版本链接进去,在Windows中,右击文件名->属性->版本->文件版本 就是此字段
nt内核:即ntoskrnl.exe/ ntkrnlmp.exe/ ntkrnlpa.exe/ ntkrpamp.exe
win32k内核:即win32k.sys

我们在做windows内核相关工作的时候,经常需要确定当前是哪个内核。

就XP SP3来说,nt内核就有以下几种历史版本
5.1.2600.5512 (xpsp.080413-2111)
5.1.2600.5657 (xpsp_sp3_gdr.080814-1236)
5.1.2600.5755 (xpsp_sp3_gdr.090206-1234)
5.1.2600.5781 (xpsp_sp3_gdr.090321-1317)
除了gdr版本外,还有qfe版本。

win32k内核
5.1.2600.5796 (xpsp_sp3_gdr.090417-1242)
win32k.sys不管系统是PAE还是非PAE,单CPU还是多CPU,都是win32k.sys,这点不会给我们造成困扰。但是ntkrnl就不一样了。对于上面说的nt内核的每1个历史版本,它都有4种版本,分别是
  PAE  多CPU
ntoskrnl.exe  N  N
ntkrnlmp.exe  N  Y
ntkrnlpa.exe  Y  N
ntkrpamp.exe  Y  Y
(假如你在windows\system32下没找到这4个文件的话,你可以到C:\WINDOWS\Driver Cache\i386里去找sp2.cab或sp3.cab,然后
expand sp2.cab -F:ntoskrnl.exe c:\windows\system32)

当系统从单CPU升级为多CPU时,windows会“提示找到新硬件”,用多核的nt内核文件覆盖当前单核nt内核文件,并重新启动,启动后就用上多CPU的nt内核,但当从多CPU降为单CPU时,仍然在使用多CPU的nt内核,所以,我们不能根据系统中当前有几个CPU来判断当前是用哪个内核文件。但是可以用当前是否为PAE模式来判断当前内核文件。

如果你的目的只是想Load硬盘上的内核文件到内存中,进行PE解析(例如找到原始SSDT/ShadowSSDT/IDT及其他ring0 inline hook check),那么你可以看方式1,如果你要精确识别,请看方式2
方式1:
使用PsLoadedModuleList或NtQuerySystemInformation或ZWQuerySystemInformation,找到nt内核和win32k内核,根据PLDR_DATA_TABLE_ENTRY->FullDllName这个FileName,载入硬盘上相应文件,展开,修正IAT,修正Fixup(载入/展开等就不列在这里了),ARK工具就可以进行hook识别了。
(感谢qihoocom 轩辕小聪的提醒,我才发现,原来PLDR_DATA_TABLE_ENTRY->FullDllName是准确的硬盘PE Image文件名字)

方式2:
如果你想借助.pdb来找出内核未导出函数的名字、地址等,那你就一定要精确地区分出系统中是哪个内核(InternalName)和具体版本(FileVersion)。
而PsLoadedModuleList或NtQuerySystemInformation或ZWQuerySystemInformation中得到的nt内核FileName,与该文件所对应的.pdb,并不存在直接关联(因为Copy的原因,FileName!= InternalName)
那么,怎么办呢?如何用程序实现像Windows看软件版本一样的功能呢?
经实践,发现可以对ntkernel的BaseAddr,用PE解析的方法,找到Resource->Version,取InternalName和FileVersion,见下表
CompanyName  Microsoft Corporation
InternalName  ntkrnlpa.exe
FileVersion  5.1.2600.5755 (xpsp_sp3_gdr.090206-1234)
ProductVersion  5.1.2600.5755
对于同样的5.1.2600 build 5755的windowsXP而言,存在2个完全不同的版本:gdr(General Distribution Releases)和qfe(Quick Fix Engineering),所以ProductVersion不能作为内核的区别。
InternalName和FileVersion,这2个字符串能唯一地确定内核Image版本。
代码范例(以下代码在VC++/DDK都可编译):
DWORD VA2RAW(DWORD va,PVA2RAW_REC pva2raw_rec)
{
  int i;
  for (i=0;pva2raw_rec[i].VA!=0;i++)
    if ((va>=pva2raw_rec[i].VA)&&(va<=pva2raw_rec[i].VAEnd))
      return pva2raw_rec[i].RAW+va-pva2raw_rec[i].VA;
  return 0;
}
int PE_Head_Pointer(char *pbuf,
    PIMAGE_DOS_HEADER  *pIDH,
    PIMAGE_NT_HEADERS  *pINH,
    PIMAGE_FILE_HEADER  *pIFH,
    PIMAGE_OPTIONAL_HEADER  *pIOH,
    PIMAGE_SECTION_HEADER  *pISH)
{
  int i;

  *pIDH=(PIMAGE_DOS_HEADER)pbuf;

  if  ((*pIDH)->e_magic!=IMAGE_DOS_SIGNATURE)
    return -1;
  *pINH=(PIMAGE_NT_HEADERS)((DWORD)pbuf+(*pIDH)->e_lfanew);

  if ((*pINH)->Signature!=IMAGE_NT_SIGNATURE)
    return -1;
  *pIFH=&((*pINH)->FileHeader);

  *pIOH=&((*pINH)->OptionalHeader);
  if ((*pIOH)->Magic!=IMAGE_NT_OPTIONAL_HDR32_MAGIC)
    return -1;
  #ifdef COMPILE_DEBUG1
    DbgPrint("IMAGE_DIRECTORY_ENTRIES\n");
    for (i=0;i<IMAGE_NUMBEROF_DIRECTORY_ENTRIES;i++)
    {
      switch(i)
      {
      case IMAGE_DIRECTORY_ENTRY_EXPORT:
          DbgPrint("0 Export Directory");
        break;
      case IMAGE_DIRECTORY_ENTRY_IMPORT:
          DbgPrint("1 Import Directory");
        break;
      case IMAGE_DIRECTORY_ENTRY_RESOURCE:
          DbgPrint("2 Resource Directory");
        break;
      case IMAGE_DIRECTORY_ENTRY_EXCEPTION:
          DbgPrint("3 Exception Directory");
        break;
      case IMAGE_DIRECTORY_ENTRY_SECURITY:
          DbgPrint("4 Security Directory");
        break;
      case IMAGE_DIRECTORY_ENTRY_BASERELOC:
          DbgPrint("5 Base Relocation Table");
        break;
      case IMAGE_DIRECTORY_ENTRY_DEBUG:
          DbgPrint("6 Debug Directory");
        break;
      case IMAGE_DIRECTORY_ENTRY_ARCHITECTURE:
          DbgPrint("7 Architecture Specific Data");
        break;
      case IMAGE_DIRECTORY_ENTRY_GLOBALPTR:
          DbgPrint("8 RVA of GP");
        break;
      case IMAGE_DIRECTORY_ENTRY_TLS:
          DbgPrint("9 TLS Directory");
        break;
      case IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG:
          DbgPrint("10 Load Configuration Directory");
        break;
      case IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT:
          DbgPrint("11 Bound Import Directory in headers");
        break;
      case IMAGE_DIRECTORY_ENTRY_IAT:
          DbgPrint("12 Import Address Table");
        break;
      case IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT:
          DbgPrint("13 Delay Load Import Descriptors");
        break;
      case IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:
          DbgPrint("14 COM Runtime descriptor");
        break;
      default:
          DbgPrint("%d",i);
      }
      DbgPrint("  %0.8X %0.8X\n",
        (*pIOH)->DataDirectory[i].VirtualAddress,
        (*pIOH)->DataDirectory[i].Size
        );  
    }
    DbgPrint("ImageBase=0x%0.8X\tBaseofCode=0x%0.8X\tIDAStart(CodeStart)=0x%0.8X\n",
      (*pIOH)->ImageBase,(*pIOH)->BaseOfCode,(*pIOH)->ImageBase+(*pIOH)->BaseOfCode);
  #endif
  *pISH=(PIMAGE_SECTION_HEADER)((*pIOH)+1);

  return 1;
}

int PE_Dump_Resource_Dir(char *pbuf,BYTE bRAWFile,
        DWORD  dwFirstIRD_Offset,
        PIMAGE_RESOURCE_DIRECTORY pIRD,
        BYTE  bLevel,
        DWORD  dwResourceType,
        char *szCompanyName,
        int iCompanyNameLen,
        char *szFileVersion,
        int iFileVersionLen,
        PVA2RAW_REC  pva2raw_rec)
{
  int i,j,iLen=0;
  DWORD dwAddr;
  char Buf[1024],
    szTemp1[256]={0};
  PIMAGE_RESOURCE_DIRECTORY_ENTRY  pIRDE;
  PIMAGE_RESOURCE_DATA_ENTRY    pIRDataE;
  PIMAGE_RESOURCE_DIR_STRING_U  pRDSU;

  if (bLevel>=4)  //4层及以上结构,就不再挖了
    return 1;

  pIRDE=(PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pIRD +1);
  for (i=0;i <pIRD->NumberOfIdEntries+pIRD->NumberOfNamedEntries;i++,pIRDE++)
  {
    if (pIRDE->DataIsDirectory)
    {
      #if (!defined _WINDOWS) && (!defined _CONSOLE)
        DbgPrint("DataIsDirectory pbuf+%X=%X\n",
          pIRDE->OffsetToDirectory+(DWORD)pIRD-(DWORD)pbuf,
          pIRDE->OffsetToDirectory+(DWORD)pIRD);
      #endif
      PE_Dump_Resource_Dir(pbuf,bRAWFile,dwFirstIRD_Offset,
        (PIMAGE_RESOURCE_DIRECTORY)(pIRDE->OffsetToDirectory+(DWORD)pbuf+dwFirstIRD_Offset),bLevel+1,
        dwResourceType,szCompanyName,iCompanyNameLen,szFileVersion,iFileVersionLen,
        pva2raw_rec);
    }else
    {
      pIRDataE = (PIMAGE_RESOURCE_DATA_ENTRY)
              ((DWORD)pbuf+dwFirstIRD_Offset + pIRDE->OffsetToData);
      #if (!defined _WINDOWS) && (!defined _CONSOLE)
        DbgPrint("DataRVA: OffsetToData=%05X  Size=%X  CodePage=%X\n",
            pIRDataE->OffsetToData,
            pIRDataE->Size,  pIRDataE->CodePage);
      #endif
      if (dwResourceType==RESOURCE_ID_VERSION)
      {
        if (bRAWFile)
          dwAddr=VA2RAW(pIRDataE->OffsetToData,pva2raw_rec);
        else
          dwAddr=pIRDataE->OffsetToData;
        pRDSU=(PIMAGE_RESOURCE_DIR_STRING_U)((DWORD)pbuf+dwAddr);

        #if (defined _WINDOWS) || (defined _CONSOLE)
          iLen=WideCharToMultiByte(CP_ACP, 0, pRDSU->NameString, pRDSU->Length,Buf, sizeof(Buf), 0, 0);
          iLen=iLen/2;
        #else
          //iLen=Clh_Unicode2Char((UNICODE_STRING *)pRDSU,Buf,sizeof(Buf));
          iLen=Clh_WChar2Char(pRDSU->NameString,pRDSU->Length,Buf,sizeof(Buf));
        #endif
        
        for (j=0;j<iLen-1;j++)
        {
          if (szCompanyName)
            if (strcmp("CompanyName",&Buf[j])==0)
              strcpy(szCompanyName,&Buf[j+strlen("CompanyName")+2]);
          if (szFileVersion)
          {
            if (strcmp("InternalName",&Buf[j])==0)
              strcpy(szFileVersion,&Buf[j+strlen("InternalName")+1]);
            if (strcmp("FileVersion",&Buf[j])==0)
              strcpy(szTemp1,&Buf[j+strlen("FileVersion")+2]);
          }
        }
        szFileVersion[strlen(szFileVersion)-4]='_';
        szFileVersion[strlen(szFileVersion)-3]=0;
        strcat(szFileVersion,szTemp1);
        #if (!defined _WINDOWS) && (!defined _CONSOLE)
          if (szCompanyName)
            DbgPrint("szCompanyName=%s\n",szCompanyName);
          if (szFileVersion)
            DbgPrint("szFileVersion=%s\n",szFileVersion);
        #endif
      }
    }//End of if (pIRDE->DataIsDirectory)
  }//End of for (i=0;i <pIRD->

  return 1;
}

int PE_Dump_Resource(char *pbuf,BYTE bRAWFile,
    DWORD dwFirstIRD_Offset,
    char *szCompanyName,
    int iCompanyNameLen,
    char *szFileVersion,
    int iFileVersionLen,
    PVA2RAW_REC  pva2raw_rec)
{
  DWORD dwDiff,dwAddr;
  int i,iCount,iFixupCount;
  PIMAGE_RESOURCE_DIRECTORY    pIRD;
  PIMAGE_RESOURCE_DIRECTORY_ENTRY  pIRDE;
  PIMAGE_RESOURCE_DIR_STRING_U prdsu;

  pIRD=(PIMAGE_RESOURCE_DIRECTORY)((DWORD)pbuf + dwFirstIRD_Offset);
  pIRDE=(PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pIRD +1);
  //DbgPrint("pIRD=%X  pIRDE=%X\n",pIRD,pIRDE);
  for (i=0;i <pIRD->NumberOfIdEntries+pIRD->NumberOfNamedEntries;i++,pIRDE++)
  {
    if (pIRDE->Id==RESOURCE_ID_VERSION)
    {
      //printf("RESOURCE_ID_VERSION\n");
      if (pIRDE->DataIsDirectory)
      {
        #if (!defined _WINDOWS) && (!defined _CONSOLE)
          DbgPrint("DataIsDirectory Addr=%X\n",pIRDE->OffsetToDirectory+(DWORD)pIRD);
        #endif
        PE_Dump_Resource_Dir(pbuf,bRAWFile,
          dwFirstIRD_Offset,(PIMAGE_RESOURCE_DIRECTORY)(pIRDE->OffsetToDirectory+(DWORD)pIRD),1,RESOURCE_ID_VERSION,
          szCompanyName,iCompanyNameLen,szFileVersion,iFileVersionLen,
          pva2raw_rec);
      }
    }
  }
  return 1;
}

int PE_Get_VersionString(char *pbuf,BYTE bRAWFile,
    char *szCompanyName,
    int iCompanyNameLen,
    char *szFileVersion,
    int iFileVersionLen)
{
  int i;
  DWORD dwAddr;
  PIMAGE_DOS_HEADER  pIDH;
  PIMAGE_NT_HEADERS  pINH;
  PIMAGE_FILE_HEADER  pIFH;
  PIMAGE_OPTIONAL_HEADER  pIOH;
  PIMAGE_SECTION_HEADER  pISH;
  VA2RAW_REC  PE_va2raw_rec[50];

  #ifdef COMPILE_DEBUG1
    DbgPrint("PE_Get_VersionString(%X)\n",pbuf);
  #endif

  if (PE_Head_Pointer(pbuf,&pIDH,&pINH,&pIFH,&pIOH,&pISH)<0)
    return -1;
  if (bRAWFile)
  {
    memset(&PE_va2raw_rec,0,sizeof(VA2RAW_REC));
    //1:生成反向查找表
    for (i=0;i<pIFH->NumberOfSections;i++)
    {
      PE_va2raw_rec[i].VA    =(pISH+i)->VirtualAddress;
      PE_va2raw_rec[i].VAEnd  =(pISH+i)->VirtualAddress  +(pISH+i)->Misc.VirtualSize;
      PE_va2raw_rec[i].RAW  =(pISH+i)->PointerToRawData;
      PE_va2raw_rec[i].RAWEnd  =(pISH+i)->PointerToRawData  +(pISH+i)->Misc.VirtualSize;
    }
  }
  if (pIOH->DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size>0)
  {
    if (bRAWFile)
      dwAddr=VA2RAW(pIOH->DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress,PE_va2raw_rec);
    else
      dwAddr=pIOH->DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
    if (PE_Dump_Resource(pbuf,bRAWFile,dwAddr,
      szCompanyName,iCompanyNameLen,szFileVersion,iFileVersionLen,
      PE_va2raw_rec)<0 )
    {
      #if (!defined _WINDOWS) && (!defined _CONSOLE)
        DbgPrint("PE_Dump_Resource error\n");
      #endif
    }
  }
  return 1;
}

当然,MSDN也介绍了API GetVersionInfo,但是笔者认为,既然是ARK,那么对于并不复杂的API,能自己实现的,还是自己实现为好。以下是GetVersionInfo的范例
   void GetInternalName()
   {
      // Get the file version for the notepad.
      FileVersionInfo^ myFileVersionInfo = FileVersionInfo::GetVersionInfo( "%systemroot%\\Notepad.exe" );
      
      // Print the internal name.
      textBox1->Text = String::Concat( "Internal name: ", myFileVersionInfo->InternalName );
   }