1use std::borrow::Cow;
11
12use windows::Win32::Foundation::HANDLE;
13use windows::Win32::System::SystemServices::{
14 PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY, PROCESS_MITIGATION_CHILD_PROCESS_POLICY,
15 PROCESS_MITIGATION_DYNAMIC_CODE_POLICY, PROCESS_MITIGATION_IMAGE_LOAD_POLICY,
16 PROCESS_MITIGATION_PAYLOAD_RESTRICTION_POLICY,
17};
18use windows::Win32::System::Threading::{
19 GetCurrentProcess, GetCurrentProcessId, GetProcessMitigationPolicy, OpenProcess,
20 PROCESS_MITIGATION_POLICY, PROCESS_QUERY_LIMITED_INFORMATION, ProcessChildProcessPolicy,
21 ProcessDynamicCodePolicy, ProcessImageLoadPolicy, ProcessPayloadRestrictionPolicy,
22 ProcessSignaturePolicy, SetProcessMitigationPolicy,
23};
24
25use crate::error::{AccessDeniedError, Error, MitigationError, MitigationOperationError, Result};
26use crate::types::ProcessId;
27use crate::utils::OwnedHandle;
28
29const BINARY_SIGNED_MICROSOFT_SIGNED_ONLY: u32 = 0x1;
30
31const DYNAMIC_CODE_PROHIBIT: u32 = 0x1;
32
33const IMAGE_LOAD_NO_REMOTE: u32 = 0b01;
34const IMAGE_LOAD_PREFER_SYSTEM32_IMAGES: u32 = 0b0100;
35
36const PAYLOAD_RESTRICTION_ENABLE_EXPORT_ADDRESS_FILTER: u32 = 0b0001;
37const PAYLOAD_RESTRICTION_ENABLE_EXPORT_ADDRESS_FILTER_PLUS: u32 = 0b000100;
38const PAYLOAD_RESTRICTION_ENABLE_IMPORT_ADDRESS_FILTER: u32 = 0b00010000;
39const PAYLOAD_RESTRICTION_ENABLE_ROP_STACK_PIVOT: u32 = 0b0001000000;
40const PAYLOAD_RESTRICTION_ENABLE_ROP_CALLER_CHECK: u32 = 0b000100000000;
41const PAYLOAD_RESTRICTION_ENABLE_ROP_SIM_EXEC: u32 = 0b010000000000;
42
43const PAYLOAD_RESTRICTION_MASK: u32 = PAYLOAD_RESTRICTION_ENABLE_EXPORT_ADDRESS_FILTER
44 | PAYLOAD_RESTRICTION_ENABLE_EXPORT_ADDRESS_FILTER_PLUS
45 | PAYLOAD_RESTRICTION_ENABLE_IMPORT_ADDRESS_FILTER
46 | PAYLOAD_RESTRICTION_ENABLE_ROP_STACK_PIVOT
47 | PAYLOAD_RESTRICTION_ENABLE_ROP_CALLER_CHECK
48 | PAYLOAD_RESTRICTION_ENABLE_ROP_SIM_EXEC;
49
50const CHILD_RESTRICTION_NO_PROCESS_CREATION: u32 = 0b001;
51const ERROR_INVALID_PARAMETER_HRESULT: i32 = -2147024809;
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub enum ProcessMitigation {
56 MicrosoftSignedOnly,
58 BlockRemoteImages,
60 PreferSystem32Images,
62 DisableDynamicCode,
64 RestrictPayload,
66 BlockChildProcessCreation,
68}
69
70impl ProcessMitigation {
71 fn policy_name(self) -> &'static str {
72 match self {
73 ProcessMitigation::MicrosoftSignedOnly => "signature",
74 ProcessMitigation::BlockRemoteImages => "image_load_no_remote",
75 ProcessMitigation::PreferSystem32Images => "image_load_prefer_system32",
76 ProcessMitigation::DisableDynamicCode => "dynamic_code",
77 ProcessMitigation::RestrictPayload => "payload_restriction",
78 ProcessMitigation::BlockChildProcessCreation => "child_process",
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct ProcessMitigationStatus {
86 pub process_id: ProcessId,
88 pub microsoft_signed_only: bool,
90 pub block_remote_images: bool,
92 pub prefer_system32_images: bool,
94 pub disable_dynamic_code: bool,
96 pub restrict_payload: bool,
98 pub block_child_process_creation: bool,
100}
101
102#[derive(Debug, Default, Clone)]
104pub struct MitigationPlan {
105 mitigations: Vec<ProcessMitigation>,
106}
107
108impl MitigationPlan {
109 pub fn new() -> Self {
111 Self {
112 mitigations: Vec::with_capacity(8),
113 }
114 }
115
116 pub fn enable(mut self, mitigation: ProcessMitigation) -> Self {
118 if !self.mitigations.contains(&mitigation) {
119 self.mitigations.push(mitigation);
120 }
121 self
122 }
123
124 pub fn apply_to_current(&self) -> Result<()> {
126 let has_signature = self
127 .mitigations
128 .contains(&ProcessMitigation::MicrosoftSignedOnly);
129 let has_block_remote = self
130 .mitigations
131 .contains(&ProcessMitigation::BlockRemoteImages);
132 let has_prefer_system32 = self
133 .mitigations
134 .contains(&ProcessMitigation::PreferSystem32Images);
135 let has_dynamic_code = self
136 .mitigations
137 .contains(&ProcessMitigation::DisableDynamicCode);
138 let has_payload = self
139 .mitigations
140 .contains(&ProcessMitigation::RestrictPayload);
141 let has_child_block = self
142 .mitigations
143 .contains(&ProcessMitigation::BlockChildProcessCreation);
144
145 if has_signature {
146 apply_single_mitigation(ProcessMitigation::MicrosoftSignedOnly)?;
147 }
148
149 if has_block_remote || has_prefer_system32 {
150 apply_image_load_mitigation(has_block_remote, has_prefer_system32)?;
151 }
152
153 if has_dynamic_code {
154 apply_single_mitigation(ProcessMitigation::DisableDynamicCode)?;
155 }
156
157 if has_payload {
158 apply_single_mitigation(ProcessMitigation::RestrictPayload)?;
159 }
160
161 if has_child_block {
162 apply_single_mitigation(ProcessMitigation::BlockChildProcessCreation)?;
163 }
164
165 Ok(())
166 }
167
168 pub fn emit_compile_time(&self) {
196 self.emit_compile_time_with_compat(false);
197 }
198
199 pub fn emit_compile_time_with_compat(&self, enable_compat: bool) {
220 for mitigation in &self.mitigations {
221 match mitigation {
222 ProcessMitigation::MicrosoftSignedOnly => {
223 println!("cargo:rustc-link-arg=/DEPENDENTLOADFLAG:0x800");
224 println!("cargo:rustc-link-arg=/INTEGRITYCHECK");
225 }
226 ProcessMitigation::DisableDynamicCode => {
227 println!("cargo:rustc-link-arg=/guard:cf");
228 }
229 ProcessMitigation::RestrictPayload => {
230 println!("cargo:rustc-link-arg=/HIGHENTROPYVA");
231 }
232 ProcessMitigation::BlockRemoteImages
234 | ProcessMitigation::PreferSystem32Images
235 | ProcessMitigation::BlockChildProcessCreation => {}
236 }
237 }
238
239 if enable_compat {
240 println!("cargo:rustc-link-arg=/CETCOMPAT");
241 }
242 }
243}
244
245pub fn query_current() -> Result<ProcessMitigationStatus> {
247 query_process(ProcessId::new(unsafe { GetCurrentProcessId() }))
248}
249
250pub fn query_process(process_id: ProcessId) -> Result<ProcessMitigationStatus> {
252 let process = open_query_process_handle(process_id)?;
253 query_from_handle(process_id, process.raw())
254}
255
256fn apply_single_mitigation(mitigation: ProcessMitigation) -> Result<()> {
257 match mitigation {
258 ProcessMitigation::MicrosoftSignedOnly => {
259 let mut policy = PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY::default();
260 policy.Anonymous.Anonymous._bitfield = BINARY_SIGNED_MICROSOFT_SIGNED_ONLY;
261 set_policy(
262 ProcessSignaturePolicy,
263 &policy as *const _ as *const _,
264 std::mem::size_of_val(&policy),
265 mitigation.policy_name(),
266 )
267 }
268 ProcessMitigation::BlockRemoteImages => apply_image_load_mitigation(true, false),
269 ProcessMitigation::PreferSystem32Images => apply_image_load_mitigation(false, true),
270 ProcessMitigation::DisableDynamicCode => {
271 let mut policy = PROCESS_MITIGATION_DYNAMIC_CODE_POLICY::default();
272 policy.Anonymous.Anonymous._bitfield = DYNAMIC_CODE_PROHIBIT;
273 set_policy(
274 ProcessDynamicCodePolicy,
275 &policy as *const _ as *const _,
276 std::mem::size_of_val(&policy),
277 mitigation.policy_name(),
278 )
279 }
280 ProcessMitigation::RestrictPayload => apply_payload_mitigation(),
281 ProcessMitigation::BlockChildProcessCreation => {
282 let mut policy = PROCESS_MITIGATION_CHILD_PROCESS_POLICY::default();
283 policy.Anonymous.Anonymous._bitfield = CHILD_RESTRICTION_NO_PROCESS_CREATION;
284 set_policy(
285 ProcessChildProcessPolicy,
286 &policy as *const _ as *const _,
287 std::mem::size_of_val(&policy),
288 mitigation.policy_name(),
289 )
290 }
291 }
292}
293
294fn apply_image_load_mitigation(
295 block_remote_images: bool,
296 prefer_system32_images: bool,
297) -> Result<()> {
298 let mut policy = PROCESS_MITIGATION_IMAGE_LOAD_POLICY::default();
299 let mut flags = 0u32;
300 if block_remote_images {
301 flags |= IMAGE_LOAD_NO_REMOTE;
302 }
303 if prefer_system32_images {
304 flags |= IMAGE_LOAD_PREFER_SYSTEM32_IMAGES;
305 }
306
307 policy.Anonymous.Anonymous._bitfield = flags;
308 set_policy(
309 ProcessImageLoadPolicy,
310 &policy as *const _ as *const _,
311 std::mem::size_of_val(&policy),
312 "image_load",
313 )
314}
315
316fn apply_payload_mitigation() -> Result<()> {
317 let candidate_masks = [
318 PAYLOAD_RESTRICTION_MASK,
319 PAYLOAD_RESTRICTION_MASK & !PAYLOAD_RESTRICTION_ENABLE_ROP_SIM_EXEC,
320 PAYLOAD_RESTRICTION_ENABLE_EXPORT_ADDRESS_FILTER
321 | PAYLOAD_RESTRICTION_ENABLE_EXPORT_ADDRESS_FILTER_PLUS
322 | PAYLOAD_RESTRICTION_ENABLE_IMPORT_ADDRESS_FILTER
323 | PAYLOAD_RESTRICTION_ENABLE_ROP_STACK_PIVOT
324 | PAYLOAD_RESTRICTION_ENABLE_ROP_CALLER_CHECK,
325 PAYLOAD_RESTRICTION_ENABLE_EXPORT_ADDRESS_FILTER
326 | PAYLOAD_RESTRICTION_ENABLE_IMPORT_ADDRESS_FILTER
327 | PAYLOAD_RESTRICTION_ENABLE_ROP_STACK_PIVOT,
328 PAYLOAD_RESTRICTION_ENABLE_EXPORT_ADDRESS_FILTER,
329 ];
330
331 let mut last_error: Option<windows::core::Error> = None;
332
333 for mask in candidate_masks {
334 let mut policy = PROCESS_MITIGATION_PAYLOAD_RESTRICTION_POLICY::default();
335 policy.Anonymous.Anonymous._bitfield = mask;
336
337 match set_policy_raw(
338 ProcessPayloadRestrictionPolicy,
339 &policy as *const _ as *const _,
340 std::mem::size_of_val(&policy),
341 ) {
342 Ok(()) => return Ok(()),
343 Err(err) => {
344 if err.code().0 == ERROR_INVALID_PARAMETER_HRESULT {
345 last_error = Some(err);
346 continue;
347 }
348 return Err(map_windows_apply_error(err, "payload_restriction"));
349 }
350 }
351 }
352
353 Err(map_windows_apply_error(
354 last_error.unwrap_or_else(windows::core::Error::from_win32),
355 "payload_restriction",
356 ))
357}
358
359fn set_policy(
360 policy: PROCESS_MITIGATION_POLICY,
361 value: *const core::ffi::c_void,
362 len: usize,
363 policy_name: &'static str,
364) -> Result<()> {
365 set_policy_raw(policy, value, len).map_err(|e| map_windows_apply_error(e, policy_name))
366}
367
368fn set_policy_raw(
369 policy: PROCESS_MITIGATION_POLICY,
370 value: *const core::ffi::c_void,
371 len: usize,
372) -> windows::core::Result<()> {
373 unsafe { SetProcessMitigationPolicy(policy, value, len) }
374}
375
376fn query_from_handle(process_id: ProcessId, handle: HANDLE) -> Result<ProcessMitigationStatus> {
377 unsafe {
378 let mut signature = PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY::default();
379 get_policy(
380 handle,
381 ProcessSignaturePolicy,
382 &mut signature as *mut _ as *mut _,
383 std::mem::size_of_val(&signature),
384 "signature",
385 process_id,
386 )?;
387
388 let mut image_load = PROCESS_MITIGATION_IMAGE_LOAD_POLICY::default();
389 get_policy(
390 handle,
391 ProcessImageLoadPolicy,
392 &mut image_load as *mut _ as *mut _,
393 std::mem::size_of_val(&image_load),
394 "image_load",
395 process_id,
396 )?;
397
398 let mut dynamic_code = PROCESS_MITIGATION_DYNAMIC_CODE_POLICY::default();
399 get_policy(
400 handle,
401 ProcessDynamicCodePolicy,
402 &mut dynamic_code as *mut _ as *mut _,
403 std::mem::size_of_val(&dynamic_code),
404 "dynamic_code",
405 process_id,
406 )?;
407
408 let mut payload = PROCESS_MITIGATION_PAYLOAD_RESTRICTION_POLICY::default();
409 get_policy(
410 handle,
411 ProcessPayloadRestrictionPolicy,
412 &mut payload as *mut _ as *mut _,
413 std::mem::size_of_val(&payload),
414 "payload_restriction",
415 process_id,
416 )?;
417
418 let mut child = PROCESS_MITIGATION_CHILD_PROCESS_POLICY::default();
419 get_policy(
420 handle,
421 ProcessChildProcessPolicy,
422 &mut child as *mut _ as *mut _,
423 std::mem::size_of_val(&child),
424 "child_process",
425 process_id,
426 )?;
427
428 let signature_flags = signature.Anonymous.Anonymous._bitfield;
429 let image_load_flags = image_load.Anonymous.Anonymous._bitfield;
430 let dynamic_flags = dynamic_code.Anonymous.Anonymous._bitfield;
431 let payload_flags = payload.Anonymous.Anonymous._bitfield;
432 let child_flags = child.Anonymous.Anonymous._bitfield;
433
434 Ok(ProcessMitigationStatus {
435 process_id,
436 microsoft_signed_only: (signature_flags & BINARY_SIGNED_MICROSOFT_SIGNED_ONLY) != 0,
437 block_remote_images: (image_load_flags & IMAGE_LOAD_NO_REMOTE) != 0,
438 prefer_system32_images: (image_load_flags & IMAGE_LOAD_PREFER_SYSTEM32_IMAGES) != 0,
439 disable_dynamic_code: (dynamic_flags & DYNAMIC_CODE_PROHIBIT) != 0,
440 restrict_payload: (payload_flags & PAYLOAD_RESTRICTION_MASK) != 0,
441 block_child_process_creation: (child_flags & CHILD_RESTRICTION_NO_PROCESS_CREATION)
442 != 0,
443 })
444 }
445}
446
447fn get_policy(
448 handle: HANDLE,
449 policy: PROCESS_MITIGATION_POLICY,
450 out_value: *mut core::ffi::c_void,
451 len: usize,
452 policy_name: &'static str,
453 process_id: ProcessId,
454) -> Result<()> {
455 unsafe { GetProcessMitigationPolicy(handle, policy, out_value, len) }
456 .map_err(|e| map_windows_query_error(e, policy_name, process_id))
457}
458
459fn map_windows_apply_error(err: windows::core::Error, policy_name: &'static str) -> Error {
460 let code = err.code().0;
461 if code == 5 {
462 return Error::AccessDenied(AccessDeniedError::with_reason(
463 "current process",
464 "set mitigation policy",
465 Cow::Owned(format!("{}: access denied", policy_name)),
466 ));
467 }
468
469 Error::Mitigation(MitigationError::ApplyFailed(
470 MitigationOperationError::new("apply", policy_name)
471 .with_reason(Cow::Owned(err.to_string()))
472 .with_code(code),
473 ))
474}
475
476fn map_windows_query_error(
477 err: windows::core::Error,
478 policy_name: &'static str,
479 process_id: ProcessId,
480) -> Error {
481 let code = err.code().0;
482 if code == 5 {
483 return Error::AccessDenied(AccessDeniedError::with_reason(
484 Cow::Owned(format!("process {}", process_id.as_u32())),
485 "query mitigation policy",
486 Cow::Owned(format!("{}: access denied", policy_name)),
487 ));
488 }
489
490 Error::Mitigation(MitigationError::QueryFailed(
491 MitigationOperationError::new("query", policy_name)
492 .with_process_id(process_id.as_u32())
493 .with_reason(Cow::Owned(err.to_string()))
494 .with_code(code),
495 ))
496}
497
498fn open_query_process_handle(process_id: ProcessId) -> Result<OwnedHandle> {
499 let current = ProcessId::new(unsafe { GetCurrentProcessId() });
500 if process_id == current {
501 return Ok(OwnedHandle::borrowed(unsafe { GetCurrentProcess() }));
502 }
503
504 let handle = unsafe {
505 OpenProcess(
506 PROCESS_QUERY_LIMITED_INFORMATION,
507 false,
508 process_id.as_u32(),
509 )
510 }
511 .map_err(|e| map_windows_query_error(e, "open_process", process_id))?;
512
513 Ok(OwnedHandle::new(handle))
514}
515
516#[cfg(test)]
517mod tests {
518 use super::{MitigationPlan, ProcessMitigation};
519
520 #[test]
521 fn mitigation_plan_enable_deduplicates_values() {
522 let plan = MitigationPlan::new()
523 .enable(ProcessMitigation::DisableDynamicCode)
524 .enable(ProcessMitigation::DisableDynamicCode)
525 .enable(ProcessMitigation::MicrosoftSignedOnly);
526
527 let debug = format!("{plan:?}");
528 let count = debug.matches("DisableDynamicCode").count();
529 assert_eq!(count, 1);
530 assert!(debug.contains("MicrosoftSignedOnly"));
531 }
532}