有网友在微信公众号私信我,想要这篇文章里面介绍的RomHover:
https://zhuanlan.zhihu.com/p/30106517zhuanlan.zhihu.com为了授人以鱼不如授人以渔,今天详细介绍一下它的原理。首先,最新的RomHover可以在这里下载到:链接:https://pan.baidu.com/s/1XVHUtkQNDwTnyuYpweHPug 提取码:yo0a。
RomHover如何使用
要使用RomHover,需要注册它(因为它是个COM组件),方法是用管理员方式打开命令行窗口,用regsvr32来注册:
注意必须是管理员方式,否则没有权限运行regsvr32。RomHover不在当前目录,请自动补全目录。
注册好了,最好重启一下,或者杀掉Explore进程,用来重载所有的Explore扩展组件Shell Extension。好了,我们开启一个文件资源管理器,会发现所有的.efi文件图标都换了:
把鼠标悬停在这些UEFI驱动上面,过一会,会弹出一个提示窗口:
这是标准的文件管理器的infotip窗口,里面有很多有用的信息,如支持CPU的架构是啥,对齐方式,是哪种驱动,PDB文件在哪里等等。这里看到这个是X86的64位驱动,是个Boot service驱动。我们换一个看看:
这是个标准的Shell,可以看到它的类型是UEFI应用,而且只能被用于32位的x86架构。
而同样名字的另一个Shell,却是仅支持ARM 64bit。是不是很方便?再也不会用错版本了!我们甚至可以从pdb文件路径看出它当初是在哪个目录下被build的。
看,这个支持NTFS的UEFI驱动,也是ARM 64bit的,不要用错了哦。
RomHover的原理
RomHover程序,它是一个标准的COM组件,关于它的原理,可以写一本书,这里就不展开了,希望详细了解的同学,强烈推荐这本潘爱民的《com原理与应用》:
【二手旧书9成新】COM原理与应用 潘爱民 清华大学出版社【图片 价格 品牌 报价】-京东item.jd.comcom技术和com+、dcom曾经十分火热,在Windows中得到广泛应用。很多应用扩展都是通过com技术实现。但COM技术已经被淘汰,还没入坑的同学就别跟了。
这个DLL格式的COM组件,内部包含了几个注册文件,其中比较重要的是这个
HKCR
{
RomHover.RomProperty.1 = s 'RomProperty Class'
{
CLSID = s '{44109032-A5CA-11D4-8755-0080AD121757}'
}
RomHover.RomProperty = s 'RomProperty Class'
{
CLSID = s '{44109032-A5CA-11D4-8755-0080AD121757}'
CurVer = s 'RomHover.RomProperty.1'
}
NoRemove CLSID
{
ForceRemove {44109032-A5CA-11D4-8755-0080AD121757} = s 'RomProperty Class'
{
ProgID = s 'RomHover.RomProperty.1'
VersionIndependentProgID = s 'RomHover.RomProperty'
ForceRemove 'Programmable'
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Apartment'
}
'TypeLib' = s '{44109024-A5CA-11D4-8755-0080AD121757}'
}
}
NoRemove .efi = s 'UEFIDriverTitle'
{
NoRemove shellex
{
NoRemove {00021500-0000-0000-C000-000000000046} = s '{44109032-A5CA-11D4-8755-0080AD121757}'
}
}
NoRemove UEFIDriverTitle
{
DefaultIcon = s '%MODULE%,-205'
}
标黄的部分更改了.efi文件的icon,为RomHover文件的205号图标。也就是:
#define IDI_ICON1 205
微软为文件管理器制定的标准COM接口可以在MSDN上查到[1]。悬停的接口叫做IQueryInfo[2],我们的关键类CRomProperty就从它上派生:
BEGIN_COM_MAP(CRomProperty)
COM_INTERFACE_ENTRY(IRomProperty)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IPersistFile)
COM_INTERFACE_ENTRY(IQueryInfo)
END_COM_MAP()
protected:
// IRomInfoShlExt
CString m_sFilename;
// IRomProperty
public:
// IPersistFile
STDMETHOD(GetClassID)(LPCLSID) { return E_NOTIMPL; }
STDMETHOD(IsDirty)() { return E_NOTIMPL; }
STDMETHOD(Load)(LPCOLESTR, DWORD);
STDMETHOD(Save)(LPCOLESTR, BOOL) { return E_NOTIMPL; }
STDMETHOD(SaveCompleted)(LPCOLESTR) { return E_NOTIMPL; }
STDMETHOD(GetCurFile)(LPOLESTR*) { return E_NOTIMPL; }
// IQueryInfo
STDMETHOD(GetInfoFlags)(DWORD*) { return E_NOTIMPL; }
STDMETHOD(GetInfoTip)(DWORD, LPWSTR*);
其它的method都不用实现,返回E_NOTIMPL就行。GetInfoTip是主程序,把鼠标悬停在文件上,就会调用这个方法,它的代码是:
HRESULT CRomProperty::GetInfoTip ( DWORD dwFlags, LPWSTR* ppwszTip )
{
AFX_MANAGE_STATE(AfxGetStaticModuleState()); // init MFC
LPMALLOC pMalloc;
CFile file;
CString sFirstLine;
CString sTooltip;
UNREFERENCED_PARAMETER(dwFlags);
USES_CONVERSION;
EFI_IMAGE_DOS_HEADER DosHdr;
EFI_IMAGE_OPTIONAL_HEADER_UNION Hdr;
UINT16 Magic;
// Try to open the file.
if ( !file.Open ( m_sFilename , CFile::modeRead | CFile::shareDenyWrite ))
return E_FAIL;
file.Read(&DosHdr,sizeof(EFI_IMAGE_DOS_HEADER));
if (DosHdr.e_magic == EFI_IMAGE_DOS_SIGNATURE) {
//
// DOS image header is present, so read the PE header after the DOS image header.
//
file.Seek((DosHdr.e_lfanew) & 0x0ffff,CFile::begin);
file.Read(&Hdr,sizeof(EFI_IMAGE_OPTIONAL_HEADER_UNION));
} else {
//
// DOS image header is not present, so PE header is at the image base.no in parked support for now
//
return E_FAIL;
}
CString tempstr;
if (Hdr.Pe32.Signature != EFI_IMAGE_NT_SIGNATURE) {
sTooltip = _T("不能识别该驱动!n");
goto SHOWRESULT;
}
sTooltip = _T("文件名:");
sTooltip += m_sFilename;
sTooltip += _T("n");
sTooltip += _T("nPE文件头内容:n");
sTooltip += _T("t支持CPU架构:t");
switch (Hdr.Pe32.FileHeader.Machine) {
case EFI_IMAGE_MACHINE_IA32:
//
// Assume PE32 image with IA32 Machine field.
//
Magic = EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC;
tempstr = _T("IA32(32bit)");
break;
case EFI_IMAGE_MACHINE_X64:
//
// Assume PE32+ image with X64 or IPF Machine field
//
Magic = EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC;
tempstr = _T("X64(64bit)");
break;
case EFI_IMAGE_MACHINE_EBC:
//
// Assume PE32+ image with X64 or IPF Machine field
//
Magic = EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC;
tempstr = _T("EBC");
break;
case IMAGE_FILE_MACHINE_ARMT:
Magic = Hdr.Pe32.OptionalHeader.Magic;
tempstr = _T("ARM 32 mixed");
break;
case IMAGE_FILE_MACHINE_ARM64:
Magic = Hdr.Pe32.OptionalHeader.Magic;
tempstr = _T("ARM 64bit");
break;
case EFI_IMAGE_MACHINE_IA64:
//
// Assume PE32+ image with X64 or IPF Machine field
//
Magic = EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC;
tempstr = _T("IA64(IPF 64bit)");
break;
default:
//
// For unknow Machine field, use Magic in optional Header
//
Magic = Hdr.Pe32.OptionalHeader.Magic;
tempstr = _T("Unknown");
}
sTooltip += tempstr;
sTooltip += _T("n");
if (Hdr.Pe32.FileHeader.TimeDateStamp != 0) {
time_t TimeX = (time_t)Hdr.Pe32.FileHeader.TimeDateStamp;
tm* pGMT = gmtime(&TimeX);
char* pTime = asctime(pGMT);
tempstr.Format(_T("t驱动生成时间:t%s"), pTime);
}
else {
tempstr.Format(_T("t驱动生成时间:t已清除n"));
}
sTooltip += tempstr;
sTooltip += _T("tSection数目:t");
tempstr.Format(_T("%d"),Hdr.Pe32.FileHeader.NumberOfSections);
sTooltip += tempstr;
sTooltip += _T("n");
sTooltip += _T("nOPTIONAL文件头内容:n");
sTooltip += _T("Magic:ttt");
PE_COFF_LOADER_IMAGE_CONTEXT ImageContext;
if(Hdr.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC)
{
tempstr.Format(_T("%X (PE32) "),EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC);
ImageContext.ImageType = Hdr.Pe32.OptionalHeader.Subsystem;
ImageContext.ImageSize = (UINT64)Hdr.Pe32.OptionalHeader.SizeOfImage;
ImageContext.SectionAlignment = Hdr.Pe32.OptionalHeader.SectionAlignment;
ImageContext.SizeOfHeaders = Hdr.Pe32.OptionalHeader.SizeOfHeaders;
ImageContext.NumberOfRvaAndSizes = Hdr.Pe32.OptionalHeader.NumberOfRvaAndSizes;
memcpy(ImageContext.DataDirectory,Hdr.Pe32.OptionalHeader.DataDirectory,sizeof(EFI_IMAGE_DATA_DIRECTORY)* EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES);
}else
{
tempstr.Format(_T("%X (PE32+) "),EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC);
ImageContext.ImageType = Hdr.Pe32Plus.OptionalHeader.Subsystem;
ImageContext.ImageSize = (UINT64) Hdr.Pe32Plus.OptionalHeader.SizeOfImage;
ImageContext.SectionAlignment = Hdr.Pe32Plus.OptionalHeader.SectionAlignment;
ImageContext.SizeOfHeaders = Hdr.Pe32Plus.OptionalHeader.SizeOfHeaders;
ImageContext.NumberOfRvaAndSizes = Hdr.Pe32Plus.OptionalHeader.NumberOfRvaAndSizes;
memcpy(ImageContext.DataDirectory,Hdr.Pe32Plus.OptionalHeader.DataDirectory,sizeof(EFI_IMAGE_DATA_DIRECTORY)* EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES);
}
sTooltip += tempstr;
sTooltip += _T("n");
sTooltip += _T("驱动类型:tt");
switch (ImageContext.ImageType) {
case EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER:
tempstr = _T("UEFI Runtime驱动");
break;
case EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION:
tempstr = _T("UEFI Application");
break;
case EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER:
tempstr = _T("UEFI Boot Service驱动");
break;
case EFI_IMAGE_SUBSYSTEM_SAL_RUNTIME_DRIVER:
tempstr = _T("SAL Runtime Driver");
break;
}
sTooltip += tempstr;
sTooltip += _T("n");
tempstr.Format(_T("t入口函数地址:t0x%xn"), Hdr.Pe32Plus.OptionalHeader.AddressOfEntryPoint);
sTooltip += tempstr;
tempstr.Format(_T("tSection对齐:t0x%xn"), Hdr.Pe32Plus.OptionalHeader.SectionAlignment);
sTooltip += tempstr;
tempstr.Format(_T("t文件对齐:t0x%xn"), Hdr.Pe32Plus.OptionalHeader.FileAlignment);
sTooltip += tempstr;
tempstr.Format(_T("t文件大小:t%d(Byte)n"), Hdr.Pe32Plus.OptionalHeader.SizeOfImage);
sTooltip += tempstr;
sTooltip += _T("tRva目录 数目:t");
tempstr.Format(_T("%d"), ImageContext.NumberOfRvaAndSizes);
sTooltip += tempstr;
sTooltip += _T("n");
for (int i = 0; i < 10; i++) {
switch (i) {
case 0:
sTooltip += _T("ttExport Directory RVA [size]:");
break;
case 1:
sTooltip += _T("ttImport Directory RVA [size]:");
break;
case 2:
sTooltip += _T("ttResource Directory RVA [size]:");
break;
case 3:
sTooltip += _T("ttException Directory RVA [size]:");
break;
case 4:
sTooltip += _T("ttCert Directory RVA [size]:");
break;
case 5:
sTooltip += _T("ttBaseReloc Directory RVA [size]:");
break;
case 6:
sTooltip += _T("ttDebug Directory RVA [size]:");
break;
case 7:
sTooltip += _T("ttArch Directory RVA [size]:");
break;
case 8:
sTooltip += _T("ttGlobal Ptr Directory RVA [size]:");
break;
case 9:
sTooltip += _T("ttTLS Directory RVA [size]:");
break;
case 10:
sTooltip += _T("ttLoad Conf Directory RVA [size]:");
break;
case 11:
sTooltip += _T("ttBoundImport Directory RVA [size]:");
break;
case 12:
sTooltip += _T("ttAddrTable Directory RVA [size]:");
break;
case 13:
sTooltip += _T("ttDelayImport Directory RVA [size]:");
break;
case 14:
sTooltip += _T("ttCOM Desc Directory RVA [size]:");
break;
case 15:
sTooltip += _T("ttReserved Directory RVA [size]:");
break;
}
tempstr.Format(_T("%x [%x]"), ImageContext.DataDirectory[i].VirtualAddress, ImageContext.DataDirectory[i].Size);
sTooltip += tempstr;
sTooltip += _T("n");
}
if (Hdr.Pe32Plus.OptionalHeader.SectionAlignment != Hdr.Pe32Plus.OptionalHeader.FileAlignment) {
// Can't handle mixed alignment by now, skip
sTooltip += _T("Section和文件对齐不同,部分信息已忽略!n");
goto SHOWRESULT;
}
sTooltip += _T("nDebug目录n");
sTooltip += _T(" Time Type Size RVA Pointern");
sTooltip += _T(" -------- ------ -------- -------- --------n");
EFI_IMAGE_DEBUG_DIRECTORY_ENTRY DebugEntry;
file.Seek(ImageContext.DataDirectory[6].VirtualAddress, CFile::begin);
file.Read(&DebugEntry, sizeof(EFI_IMAGE_DEBUG_DIRECTORY_ENTRY));
tempstr.Format(_T(" %08x cv %08x %08x %xn"), DebugEntry.TimeDateStamp, DebugEntry.SizeOfData, DebugEntry.RVA, DebugEntry.FileOffset);
sTooltip += tempstr;
sTooltip += _T("npdb文件位置: ");
file.Seek(DebugEntry.FileOffset, CFile::begin);
UINT8 buff[800];
memset(buff, 0, 800);
file.Read(buff, DebugEntry.SizeOfData);
void* CodeViewEntryPointer = buff;
char* lpStr;
switch (*(UINT32*)CodeViewEntryPointer) {
case CODEVIEW_SIGNATURE_NB10:
lpStr = (char*)((char*)CodeViewEntryPointer + sizeof(EFI_IMAGE_DEBUG_CODEVIEW_NB10_ENTRY));
break;
case CODEVIEW_SIGNATURE_RSDS:
lpStr = (char*)((char*)CodeViewEntryPointer + sizeof(EFI_IMAGE_DEBUG_CODEVIEW_RSDS_ENTRY));
break;
case CODEVIEW_SIGNATURE_MTOC:
lpStr = (char*)((char*)CodeViewEntryPointer + sizeof(EFI_IMAGE_DEBUG_CODEVIEW_MTOC_ENTRY));
break;
default:
lpStr = "Unknow";
break;
}
tempstr.Format(_T("%sn"), lpStr);
sTooltip += tempstr;
SHOWRESULT:
// Get an IMalloc interface from the shell.
if ( FAILED( SHGetMalloc ( &pMalloc )))
return E_FAIL;
// Allocate a buffer for Explorer. Note that the must pass the string
// back as a Unicode string, so the string length is multiplied by the
// size of a Unicode character.
*ppwszTip = (LPWSTR) pMalloc->Alloc ( (10 + lstrlen(sTooltip)) * sizeof(wchar_t) );
// Release the IMalloc interface now that we're done using it.
pMalloc->Release();
if ( NULL == *ppwszTip )
{
return E_OUTOFMEMORY;
}
// Use the Unicode string copy function to put the tooltip text in the buffer.
wcscpy ( *ppwszTip, T2COLE((LPCTSTR) sTooltip) );
return S_OK;
}
代码比较丑,见谅啊。本质上它就是按照pe格式解析了uefi驱动(uefi驱动是pe格式,和我们Windows的EXE和dll一样),并把想要输出的内容放到一个unicode的字串中,传出去拉倒,逻辑十分简单。
结语
RomHover的代码我再clean up一下,就开源到github上。权当抛砖引玉。com技术个人认为设计十分优美,充分利用了 c++的STL和ATL库,可惜现在几乎没人懂了。
欢迎大家关注我的专栏和用微信扫描下方二维码加入微信公众号"UEFIBlog",在那里有最新的文章。
参考
- ^ShellEx介绍 https://docs.microsoft.com/en-us/windows/win32/shell/handlers
- ^IQueryInfo https://docs.microsoft.com/zh-cn/windows/win32/api/shlobj_core/nn-shlobj_core-iqueryinfo?redirectedfrom=MSDN