天天看点

驱动程序安装之设备协安装器

    年前想把一个功能驱动和过滤驱动传到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去哪了?