You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows PPL服务无法运行:System32下fcon.dll代码完整性错误

问题描述

我使用具备Early Launch Antimalware (ELAM)能力的证书签名驱动和用户态程序,此前运行正常。但添加启动Protected Process Light (PPL)子进程的代码后,事件查看器出现以下错误:

Code Integrity is unable to verify the image integrity of the file 
\Device\HarddiskVolume3\Windows\System32\fcon.dll because the set 
of per-page image hashes could not be found on the system.

测试环境为Windows 11驱动开发虚拟机,已将vcruntime140.dll静态链接到服务中解决了此前的错误。

疑问:

  • fcon.dll是C:\Windows\System32下的合法Windows DLL,为何无法在PPL服务中使用?Windows已正常加载ntdll.dll、kernel32.dll等核心文件。
  • 如何解决该问题?由于无法将fcon.dll静态链接到二进制文件中,这是否是Windows自身的问题?目前能想到的唯一方法是复制该DLL并用PPL证书签名后加载,但这无法适配不同Windows版本的DLL差异,相关资料极少,特寻求解决方案。

附服务的Rust代码(即使子进程路径无效仍会报错):

/// The service entrypoint for the binary
#[unsafe(no_mangle)]
pub unsafe extern "system" fn ServiceMain(_: u32, _: *mut PWSTR) {
    // register the service with SCM (service control manager)
    let h_status = match unsafe {RegisterServiceCtrlHandlerW(
        PCWSTR(svc_name().as_ptr()), 
        Some(service_handler)
    )} {
        Ok(h) => h,
        Err(e) => panic!("[!] Could not register service. {e}"),
    };

    // notify SCM that service is starting
    unsafe { update_service_status(h_status, SERVICE_START_PENDING.0) };

    // start the service main loop
    run_service(h_status);

}


/// Main service execution loop
fn run_service(h_status: SERVICE_STATUS_HANDLE) {
    unsafe {
        update_service_status(h_status, SERVICE_RUNNING.0);

        //
        // spawn child PPL
        //
        // todo restart VM and try this, and so on until you get the error
        let mut startup_info = STARTUPINFOEXW::default();
        let mut attribute_size_list: usize = 0;

        if let Err(e) = InitializeProcThreadAttributeList(
            None,
            1, // The count of attributes to be added to the list.
            None, // This parameter is reserved and must be zero.
            &mut attribute_size_list) {
                panic!("Error calling InitializeProcThreadAttributeList. {e}");
        }

        if attribute_size_list == 0 {
            panic!("Attribute size list should not be 0. Win32 error code: {}", GetLastError().0);
        }

        let mut attribute_list_mem = vec![0u8; attribute_size_list];
        startup_info.lpAttributeList = LPPROC_THREAD_ATTRIBUTE_LIST(attribute_list_mem.as_mut_ptr() as *mut _);

        if let Err(e) = InitializeProcThreadAttributeList(
            Some(startup_info.lpAttributeList),
            1, // The count of attributes to be added to the list.
            None, // This parameter is reserved and must be zero.
            &mut attribute_size_list) {
                panic!("Error calling InitializeProcThreadAttributeList after attribute list initialised. {e}");
        }

        // update protection level to be the same as the PPL service
        let mut protection_level = PROTECTION_LEVEL_SAME;
        if let Err(e) = UpdateProcThreadAttribute(
            startup_info.lpAttributeList, 
            0, 
            PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL as _,
            Some(&mut protection_level as *mut _ as *mut _),
            size_of_val(&protection_level), 
            None, 
            None,
        ) {
            panic!("[!] Could not update protection level for child process. {e}");
        }

        // start the process
        let mut process_info = PROCESS_INFORMATION::default();
        // todo update this
        let path: Vec<u16> = r"C:\Users\flux\AppData\Roaming\Svc\etw_consumer.exe"
            .encode_utf16()
            .chain(std::iter::once(0))
            .collect();

        if let Err(e) = CreateProcessW(
            PCWSTR(path.as_ptr()), 
            None, 
            None, 
            None, 
            false, 
            EXTENDED_STARTUPINFO_PRESENT | CREATE_PROTECTED_PROCESS, 
            None, 
            PCWSTR::null(), 
            &mut startup_info.StartupInfo as *mut _ as *const _,
            &mut process_info
        ) {
            panic!("[!] Could not create child process. {e}");
        }


        // Main loop
        while !SERVICE_STOP.load(Ordering::SeqCst) {
            sleep(Duration::from_secs(1));
        }

        update_service_status(h_status, SERVICE_STOPPED.0);
    }
}


fn svc_name() -> Vec<u16> {
    let mut svc_name: Vec<u16> = vec![];
    "ppl_runner".encode_utf16().for_each(|c| svc_name.push(c));
    svc_name.push(0);
    
    svc_name
}

/// Handles service control events (e.g., stop)
unsafe extern "system" fn service_handler(control: u32) {
    match control {
        SERVICE_CONTROL_STOP => {
            SERVICE_STOP.store(true, Ordering::SeqCst);
        }
        _ => {}
    }
}

/// Update the service status in the SCM
unsafe fn update_service_status(h_status: SERVICE_STATUS_HANDLE, state: u32) {
    let mut service_status = SERVICE_STATUS {
        dwServiceType: SERVICE_WIN32_OWN_PROCESS,
        dwCurrentState: SERVICE_STATUS_CURRENT_STATE(state),
        dwControlsAccepted: if state == SERVICE_RUNNING.0 { 1 } else { 0 },
        dwWin32ExitCode: ERROR_SUCCESS.0,
        dwServiceSpecificExitCode: 0,
        dwCheckPoint: 0,
        dwWaitHint: 0,
    };

    unsafe {let _ = SetServiceStatus(h_status, &mut service_status); }
}

fn main() {
    let mut service_name: Vec<u16> = "PPLRunner\0".encode_utf16().collect();
    
    let service_table = [
        SERVICE_TABLE_ENTRYW {
            lpServiceName: PWSTR(service_name.as_mut_ptr()),
            lpServiceProc: Some(ServiceMain),
        },
        SERVICE_TABLE_ENTRYW::default(),
    ];

    unsafe {
        StartServiceCtrlDispatcherW(service_table.as_ptr()).unwrap();
    }
}
解决方案

原因分析

fcon.dll属于Windows的可选系统组件,并非所有Windows版本或配置下都预装了它的分页哈希(per-page hashes)。PPL进程的代码完整性验证要求所有加载的模块必须有对应的分页哈希(或被信任证书签名),而ntdll.dll、kernel32.dll这类核心系统DLL的分页哈希默认存在于系统代码完整性数据库中,因此能正常加载。

解决方法

  • 排查加载来源:确认PPL服务/子进程是直接还是间接加载了fcon.dll。如果是第三方依赖导致的,尝试替换或移除该依赖,改用不依赖fcon.dll的实现。
  • 启用对应Windows功能:fcon.dll通常和特定Windows可选功能绑定(如语言包、管理工具组件),可通过「启用或关闭Windows功能」界面安装对应组件,系统会自动生成该DLL的分页哈希并加入代码完整性数据库。
  • 调整PPL保护级别:若不需要子进程继承当前服务的最高保护级别,可将PROTECTION_LEVEL_SAME改为PROTECTION_LEVEL_WINTCB_LIGHT或更低级别,部分低级别PPL对代码完整性的验证要求更宽松,但会降低进程保护强度。
  • 测试环境临时修复:在测试虚拟机中,使用系统自带签名工具重新签名fcon.dll,再更新代码完整性缓存:
    signtool sign /f <系统证书路径> /t http://timestamp.digicert.com C:\Windows\System32\fcon.dll
    certutil -setreg chain\ChainCacheResyncFiletime @now
    
    此方法仅适用于测试环境,生产环境修改系统DLL签名会触发安全机制。
  • 避免动态加载:检查代码中是否有显式调用LoadLibrary加载fcon.dll的逻辑,改为延迟加载或条件加载,仅在非PPL环境下加载该DLL。

内容的提问来源于stack exchange,提问作者letters_and_numbers

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 17:04:54