Windows PPL Service will not run - Code Integrity error with a Windows System32 DLL - Stack Overflow

admin2025-04-22  0

I have n Early Launch Antimalware (ELAM) capable certificate which I am using to sign a driver and my usermode application - which works just fine, however when I add some additional code to launch a child process as PPL, in EventViewer I can see:

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.

The target machine is a Windows 11 Virtual Machine that I'm using for driver development - I have statically linked vcruntime140.dll into my service as that was causing some errors.

What's the deal with fcon.dll? Windows has loaded ntdll.dll, kernel32.dll etc into the service just fine - given fcon.dll is a legitimate windows DLL coming from C:\Windows\System32 why cannot it be used in the PPL service?

How can I resolve this? Seeing as I can't statically link fcon into the binary, is this a problem with Windows? The only possible thing I can think of is making a local copy of the DLL and signing it with my PPL and loading the image into the service when it starts; but that wont account for differences between Windows versions etc where the DLL may be different. I cant be the first to have this error, but I have found surprisingly little on Google.

If it helps, here is my service binary (Rust). I do not think it is an issue with the child process, as when I invalidate the path, it still produces the error in Eventviewer

/// 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();
    }
}

I have n Early Launch Antimalware (ELAM) capable certificate which I am using to sign a driver and my usermode application - which works just fine, however when I add some additional code to launch a child process as PPL, in EventViewer I can see:

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.

The target machine is a Windows 11 Virtual Machine that I'm using for driver development - I have statically linked vcruntime140.dll into my service as that was causing some errors.

What's the deal with fcon.dll? Windows has loaded ntdll.dll, kernel32.dll etc into the service just fine - given fcon.dll is a legitimate windows DLL coming from C:\Windows\System32 why cannot it be used in the PPL service?

How can I resolve this? Seeing as I can't statically link fcon into the binary, is this a problem with Windows? The only possible thing I can think of is making a local copy of the DLL and signing it with my PPL and loading the image into the service when it starts; but that wont account for differences between Windows versions etc where the DLL may be different. I cant be the first to have this error, but I have found surprisingly little on Google.

If it helps, here is my service binary (Rust). I do not think it is an issue with the child process, as when I invalidate the path, it still produces the error in Eventviewer

/// 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();
    }
}
Share Improve this question asked Jan 31 at 7:49 letters_and_numbersletters_and_numbers 131 silver badge4 bronze badges 2
  • 1 fcon.dll seems to not include pagehashes in its signature; examine with signtool verify /v /a /ph c:\windows\system32\fcon.dll. Nothing you can do about this, but perhaps you can figure out why this DLL is being loaded in the first place? Seems like a strange one. – Luke Commented Jan 31 at 10:52
  • @Luke You are right on here. Removing the panic macro, that dll never gets loaded. Not sure why panic requires the dll - but I'll make do without, thanks! – letters_and_numbers Commented Jan 31 at 19:45
Add a comment  | 

1 Answer 1

Reset to default 0

As per Luke's response in the comments, fcon.dll does not include pagehashes in its signature.

On investigation, this DLL is required by the panic!() macro, so removing this prevents the error.

转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1745275896a293867.html

最新回复(0)