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