在阅读本文前,先确定几个概念。
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 );
}
- 标 题:精确区分windows内核Image文件
- 作 者:chenlihui
- 时 间:2009-09-18 12:26
- 链 接:http://bbs.pediy.com/showthread.php?t=98017