年前想把一個功能驅動和過濾驅動傳到wu上,但是因為過濾驅動和第三方廠商驅動的hardwareid值相同,直接傳到wu上後使用者一旦更新會有問題。一個同僚提到一個解決方案:把過濾驅動的hardwareid改成一個無關緊要的id,然後為功能驅動的inf檔案附加一個協安裝器(coinstaller),把他們一起傳到wu上。這樣,當使用者更新功能驅動時,通過coinstaller啟動過濾驅動安裝程式來加載過濾驅動。
這樣說可能還是有點空洞,讓我用前面的SampleChar驅動來解釋同僚的方法。SampleChar是一個功能驅動,這個驅動安裝包中隻有sys/inf檔案(測試驅動不包含cat檔案):SampleChar.sys/SampleChar.inf,使用者通過inf來安裝功能驅動。以下是SampleChar.inf的内容:
[Version]
Signature="$WINDOWS NT$"
Class=System
ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318}
Provider=%ProviderName%
DriverVer=12/19/2016,15.52.17.267
CatalogFile=SampleChar.cat
[DestinationDirs]
DefaultDestDir = 12
[SourceDisksNames]
1 = %DiskName%,,,""
[SourceDisksFiles]
SampleChar.sys = 1,,
[Manufacturer]
%ManufacturerName%=Standard
[Standard]
%SampleChar_Desc%=SampleChar_Device, ROOT\Sample
[SampleChar_Device.NT]
CopyFiles=Drivers_Dir
[SampleChar_Device.NT.HW]
AddReg=SampleChar_Device.NT.AddReg
[SampleChar_Device.NT.AddReg]
HKR,,DeviceCharacteristics,0x10001,0x0100
HKR,,Security,,"D:P(A;;GA;;;BA)(A;;GA;;;SY)"
[Drivers_Dir]
SampleChar.sys
[SampleChar_Device.NT.Services]
AddService=SampleChar,%SPSVCINST_ASSOCSERVICE%,SampleChar_Service_Inst
[SampleChar_Service_Inst]
DisplayName = %sampleChar.SVCDESC%
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 3 ; SERVICE_DEMAND_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %12%\SampleChar.sys
LoadOrderGroup = Extended Base
[Strings]
ManufacturerName="Eugen"
ClassName=""
DiskName="SampleChar Source Disk"
ProviderName="Eugen"
SampleChar_Desc="SampleChar"
sampleChar.SVCDESC="Sample char driver"
SPSVCINST_ASSOCSERVICE= 0x00000002
為了能在安裝SampleChar.sys過程中運作裝置協安裝器,就需要修改inf檔案的内容,為drvinst.exe(windows驅動安裝程式)指明安裝過程中用到的協安裝器檔案名及其入口點。
[Version]
Signature="$WINDOWS NT$"
Class=System
ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318}
Provider=%ProviderName%
DriverVer=2016/11/18, 1.0.0
CatalogFile=SampleChar.cat
[DestinationDirs]
DefaultDestDir = 12
CoInstaller_CopyFiles=11 ;協安裝器檔案拷貝相關内容
[SourceDisksNames]
1 = %DiskName%,,,""
[SourceDisksFiles]
SampleChar.sys = 1,,
ConInstall.dll=1,, ;協安裝器源檔案清單
[Manufacturer]
%ManufacturerName%=Standard
[Standard]
%SampleChar_Desc%=SampleChar_Device, ROOT\Sample
[SampleChar_Device.NT]
CopyFiles=SampleChar_Device.NT.Copy
[SampleChar_Device.NT.HW]
AddReg=SampleChar_Device.NT.HW.AddReg
[SampleChar_Device.NT.HW.AddReg]
HKR,,DeviceCharacteristics,0x10001,0x0100
HKR,,Security,,"D:P(A;;GA;;;BA)(A;;GA;;;SY)"
[SampleChar_Device.NT.Copy]
SampleChar.sys
[SampleChar_Device.NT.Services]
AddService=SampleChar,%SPSVCINST_ASSOCSERVICE%,SampleChar_Service_Inst
[SampleChar_Service_Inst]
DisplayName = %sampleChar.SVCDESC%
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 3 ; SERVICE_DEMAND_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %12%\SampleChar.sys
LoadOrderGroup = Extended Base
[SampleChar_Device.NT.CoInstallers] ;裝置協安裝器安裝節
AddReg=CoInstaller_AddReg
CopyFiles=CoInstaller_CopyFiles
[CoInstaller_CopyFiles]
ConInstall.dll
[CoInstaller_AddReg]
HKR,,CoInstallers32,0x00010000,"ConInstall.dll,CoInstaller" ;指明協安裝器檔案名和入口函數名
僅僅有inf檔案還不夠,還需要提供dll形式的協安裝器并導出函數名。以下是我自定義的協安裝器,其目的是在驅動安裝過程中建立notepad子程式。
DWORD CALLBACK CoInstaller(DI_FUNCTION DifCode,
HDEVINFO devInfoset,
PSP_DEVINFO_DATA devInfoData,
PCOINSTALLER_CONTEXT_DATA Context)
{
FILE* fp = NULL;
STARTUPINFO si = {sizeof(si)};
PROCESS_INFORMATION pi;
_asm int 3;
switch(DifCode)
{
case DIF_INSTALLDEVICE:
CreateProcess(NULL,
"notepad",
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi);
DbgOut("DIF_INSTALLDEVICE");
break;
default:
DbgOut("?????");
break;
}
return NO_ERROR;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpReserved)
{
_asm int 3;
return TRUE;
}
函數本身很簡單,但需要注意的地方有很多:
1.函數的接口形式。裝置協安裝器的接口形式和類安裝器的接口形式有點相像,
類安裝器的接口形式:
DWORD CALLBACK ClassInstaller(DI_FUNCTION,HDEVINFO,PSP_DEVINFO_DATA);
如果inf檔案中用到了[classinstall32]節,那麼就需要以這種形式提供類安裝器。
本文提到的裝置協安裝器的接口形式,比類安裝器多一個參數:
DWORD CALLBACK DevInstaller(DI_FUNCTION,HDEVINFO,PSP_DEVINFO_DATA,PCOINSTALLER_CONTEXT_DATA);
如果inf檔案中用到了[.CoInstallers]節,就需要提供這種形式的裝置安裝器。如果不加以區分,會導緻安裝失敗。
2.函數導出名字。一般大家都會用vs編譯生成協安裝器DLL,預設導出的函數名是c++形式的,即_func@nXYZ的形式。會和inf檔案中指定的函數名不一緻,導緻無法安裝成功。是以建議大家在dll生成後,用dependence工具檢視一下函數名是否和預期的一緻。
3.調用時機。驅動安裝時,會加載裝置安裝器,這是會調用dll的通用接口----DllMain;之後,協安裝器會響應SETUPAPI!SetupDiCallClassInstaller發出的各種DI_FUNCTION功能碼,進入協安裝器入口。就是這裡的CoInstaller函數:
DWORD CALLBACK CoInstaller(DI_FUNCTION,HDEVINFO,PSP_DEVINFO_DATA,PCOINSTALLER_CONTEXT_DATA);
這可以在協安裝器中增加int 3斷點來觀察。首次運作devcon.exe安裝SampleChar時,windbg會遇到int 3異常。
Break instruction exception - code 80000003 (first chance) ;第一次遇到int 3異常
001b:70d91a95 cc int 3
kd> .symfix C:\symbols\w7RTMx86
kd> .sympath+ C:\studio\classinstaller\objchk_win7_x86\i386
Symbol search path is: srv*;C:\studio\classinstaller\objchk_win7_x86\i386
Expanded Symbol search path is: SRV*C:\symbols\w7RTMx86*http://msdl.microsoft.com/download/symbols;c:\studio\classinstaller\objchk_win7_x86\i386
kd> .reload /user ;因為現在是核心态調試,是以要手動加載使用者态調試符号
Loading User Symbols
.......................................
kd> kb ;通過函數調用棧,可以看到drvinst首先會用LdrLoadDll加載子產品,然後調用dll的DllMain函數
ChildEBP RetAddr Args to Child
0065df58 70d91d86 70d90000 00000001 00000000 ConInstall!DllMain+0x5 [c:\studio\classinstaller\coninstall.c @ 152] ;調用DllMain函數
0065dfb8 77ccaf24 70d90000 00000001 00000000 ConInstall!__DllMainCRTStartup+0xe1 [d:\5359\minkernel\crts\crtw32\dllstuff\crtdll.c @ 573]
0065dfd8 77ccfd2e 70d91f06 70d90000 00000001 ntdll!LdrpCallInitRoutine+0x14
0065e0cc 77cd01db 00000000 77b5b4fe 77cb70da ntdll!LdrpRunInitializeRoutines+0x26f
0065e238 77ccf5f9 0065e298 0065e264 00000000 ntdll!LdrpLoadDll+0x4d1
0065e26c 7607b8a4 00211ebc 0065e2ac 0065e298 ntdll!LdrLoadDll+0x92
0065e2a4 767f57c9 00000000 00000000 00000001 KERNELBASE!LoadLibraryExW+0x15a
0065e928 7680e414 ffffffff 00228e10 7680e480 SETUPAPI!GetModuleEntryPoint+0x2ca
0065e9ac 767f479a 00216150 0065fbf0 0021d214 SETUPAPI!pSetupDiGetCoInstallerList+0x275
0065eebc 767f44f8 00000020 00216150 0065fbf0 SETUPAPI!_SetupDiCallClassInstaller+0x742
0065ef04 004cf951 00000020 00216150 0065fbf0 SETUPAPI!SetupDiCallClassInstaller+0x4e
0065f95c 004d1246 00000080 00216150 0065fbf0 DrvInst!InstallSelectedDeviceDriver+0xbfd
0065fba8 004d14ba 00000080 00216150 0065fbf0 DrvInst!InstallSpecificDriver+0x152
0065fc10 004cbf0b 00000002 00000080 006a22c0 DrvInst!pHandleDeviceInstall+0x11a
0065fc64 76751174 001af9cc 0065fcb0 77ccb3f5 DrvInst!HandleDeviceInstallEntry+0x2f
0065fc70 77ccb3f5 001af9cc 77b5aa76 00000000 kernel32!BaseThreadInitThunk+0xe
0065fcb0 77ccb3c8 004cbedc 001af9cc 00000000 ntdll!__RtlUserThreadStart+0x70
0065fcc8 00000000 004cbedc 001af9cc 00000000 ntdll!_RtlUserThreadStart+0x1b
kd> lm
start end module name
004a0000 004e1000 DrvInst (pdb symbols) C:\symbols\w7RTMx86\DrvInst.pdb\6373CA6671B7426686B254FE79AC602F1\DrvInst.pdb
70d90000 70d95000 ConInstall (private pdb symbols) c:\studio\classinstaller\objchk_win7_x86\i386\ConInstall.pdb
當調用DllMain後,CoInstall開始等待并響應安裝管理子產品的SETUPAPI!SetupDiCallClassInstaller消息,
并且由于安裝管理子產品會多次發出DI_FUNCTION消息,裝置協安裝器的安裝入口會多次被調用,是以這裡需要妥善處理全局變量:
kd> g
Break instruction exception - code 80000003 (first chance)
ConInstall!CoInstaller+0x26:
001b:70d91766 cc int 3
kd> kb
ChildEBP RetAddr Args to Child
0065e9ac 7680e2be 00000020 00216150 0065fbf0 ConInstall!CoInstaller+0x26 [c:\studio\classinstaller\coninstall.c @ 19] ;調用CoInstaller函數
0065eebc 767f44f8 00000020 00216150 0065fbf0 SETUPAPI!_SetupDiCallClassInstaller+0x95f
0065ef04 004cf951 00000020 00216150 0065fbf0 SETUPAPI!SetupDiCallClassInstaller+0x4e
0065f95c 004d1246 00000080 00216150 0065fbf0 DrvInst!InstallSelectedDeviceDriver+0xbfd
0065fba8 004d14ba 00000080 00216150 0065fbf0 DrvInst!InstallSpecificDriver+0x152
0065fc10 004cbf0b 00000002 00000080 006a22c0 DrvInst!pHandleDeviceInstall+0x11a
0065fc64 76751174 001af9cc 0065fcb0 77ccb3f5 DrvInst!HandleDeviceInstallEntry+0x2f
0065fc70 77ccb3f5 001af9cc 77b5aa76 00000000 kernel32!BaseThreadInitThunk+0xe
0065fcb0 77ccb3c8 004cbedc 001af9cc 00000000 ntdll!__RtlUserThreadStart+0x70
0065fcc8 00000000 004cbedc 001af9cc 00000000 ntdll!_RtlUserThreadStart+0x1b
4.被裝置協安裝器調用啟動的可執行程式的賬戶。我初次調試時一直沒有看到notepad程式界面,是以懷疑程式是不是錯了。但CreateProcess每次都傳回TRUE。這就很疑惑了,notepad去哪了?