Summary
The Windows Cortex XDR agent uses CLIPS rules for behavioral detections. These rules are shipped encrypted in content updates. We decrypted these rules and discovered numerous hardcoded exceptions including global whitelists that can be abused to bypass those rules. For example, if a process’s command-line arguments contain :\Windows\ccmcache, it is excluded from about half of all behavioral detections. This allows an attacker, for example, to dump LSASS using simple, well-known techniques without detection.
We initially looked at the Cortex Windows agent versions 8.7 and 8.8 with content version 1790-16658. Palo Alto removed the global whitelists in Agent Version 9.1 with content version 2160 at the end of February 2026.
Plaintext rules and decryption scripts are available on GitHub.
Introduction
EDR/XDR agents such as Palo Alto Cortex XDR use different methods for detection, such as static signature-based detections, local file analysis, or machine-learning-based detections. Among the most important of these are the Behavioral Indicators of Compromise (BIOCs) rules. During a recent red team engagement, we noticed that Cortex XDR agent 8.8 detected several techniques in our payload that were previously undetected. The Cortex portal provides limited details on what exactly triggered the alert. With some exceptions, such as Elastic or HarfangLab, most vendors maintain a closed detection rule set. Therefore, we tried to access the actual predefined rules, which are regularly updated through content updates.
Finding and Decrypting the Rules
The content updates include numerous files:
ls content-1790-16658
ace-sandbox-plugin-dev-win-x86_64.zip network_inspect_policies_encrypted-64.zip
ace-sandbox-plugin-no-ocr-prod-win-x86_64.zip network_inspect_policies_encrypted_7_5-64.zip
ace-sandbox-plugin-prod-win-x86_64.zip network_inspect_policies_encrypted_pre_2025_ev-64.zip
agent_scripts_win_arm64.zip network_inspect_policies_slowroll-64.zip
agent_scripts_win_x64.zip network_inspect_policies_threat_hunting.zip
agent_scripts_win_x86.zip network_inspect_pre_2025_ev-64.zip
cert.zip network_inspect_slowroll-64.zip
collector-64-legacy.zip nmap-win32.zip
collector-64.zip payload-32-one-dir-openssl3.zip
content_processed.zip payload-32-one-dir.zip
content_raw.zip payload-32.zip
cwp-compliance.zip payload-64-one-dir.zip
cyber.zip payload-64.zip
dse_config.zip payload-win-arm64-one-dir.zip
dse_ng.zip payload-win-x86_64-one-dir-openssl3.zip
dse.zip payload-win-x86_64-one-dir.zip
edrdotnet_slowroll.zip periodic_scripts.zip
edrdotnet.zip pki-fedramp.zip
env.zip pki-gov-fedramp.zip
infra.zip pki.zip
ivanti-content-win.zip recognizer_plugin_32.zip
ivanti-deltas-win.zip recognizer_plugin_64.zip
local_analysis_plugin_32.zip recognizer_plugin_arm64.zip
local_analysis_plugin_64.zip threat_hunting_ng.zip
local_analysis_plugin_arm64.zip threat_hunting.zip
manifest.json tsf_collector.zip
ml_plugin_32.zip va-scanner-winx64.zip
ml_plugin_64.zip va-scanner-winx86.zip
ml_plugin_8_4_32.zip winpmem-32.zip
ml_plugin_8_4_64.zip winpmem-64.zip
ml_plugin_arm64.zip xdrhealth-32.zip
model_doc_ole_7_2.zip xdrhealth-64.zip
model_doc_openXml_7_2.zip xdrhealth-ARM64.zip
model_js_8_8.zip yara_plugin_32.zip
model_macro_7_1.zip yara_plugin_64.zip
model_pe_7_1.zip yara_plugin_8_4_32.zip
model_powershell_7_3.zip yara_plugin_8_4_64.zip
model_powershell_8_4.zip yara_plugin_arm64.zip
model_vbs_8_6.zip yara_signature_rules_encrypted_mitre.zip
network_inspect-64.zip yara_signature_rules_encrypted.zip
network_inspect_7_5-64.zipThese files are unzipped into C:\ProgramData\Cyvera\LocalSystem\Download\content\. As already pointed out by this blog post from SafeBreach, the metadata of behavioral rules is stored in the file dse_rules_config.lua. The content of this file could already be used to avoid Mimikatz detections by abusing whitelists, as described in the referenced blog post. However, the actual detection logic and other exclusions are not included in this file.
The content looks like this:
["bioc.ntsystemdebugcontrol_get_eprocess"] = {
versions = {"8_8_0","8_8_0_999"},
signatureConfiguration = {
default = {
settings = {
action = "silent",
friendlyName = "ntsystemdebugcontrol_get_eprocess",
technique_id = {"T1055"},
tactic_id = {"TA0004","TA0005"},
mth_action = "report",
mth_severity = 1,
severity = 4,
external_description = "Lsass Dump Attempt",
module = 2
}
}
}
},It’s not hard to guess where the rules are stored, however, they are encrypted:
PS C:\ProgramData\Cyvera\LocalSystem\Download\content> ls dse*
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 6/18/2025 12:16 AM 195276 dse.lua
-a---- 6/18/2025 12:16 AM 23703 dse_common.lua
-a---- 6/18/2025 12:16 AM 3228 dse_config_schema.json
-a---- 6/18/2025 12:16 AM 133964 dse_internals.json
-a---- 6/18/2025 12:16 AM 2998 dse_modules.json
-a---- 6/18/2025 12:16 AM 1308480 dse_rules_8_4_0_windows_encrypt.clp
-a---- 6/18/2025 12:16 AM 1308752 dse_rules_8_5_0_windows_encrypt.clp
-a---- 6/18/2025 12:16 AM 1311456 dse_rules_8_6_0_windows_encrypt.clp
-a---- 6/18/2025 12:16 AM 1313808 dse_rules_8_7_0_windows_encrypt.clp
-a---- 6/18/2025 12:16 AM 1319536 dse_rules_8_8_0_windows_encrypt.clp
-a---- 6/18/2025 12:16 AM 1319920 dse_rules_8_9_0_windows_encrypt.clp
-a---- 6/18/2025 12:16 AM 15273694 dse_rules_config.luaGetting Plaintext Rules
To identify where and how these files are decrypted, I used ProcMon to get the stack trace for the read operation on the dse_rules_8_7_0_windows_encrypt.clp file:

Fortunately, cysvc.dll includes debug strings, which were used to rename functions in IDA Pro with a small script.

There is a function called dse::ClipsEngineFilter::DecryptClpData() that sounded promising. Using WinDBG as a Kernel-Debugger to avoid EDR’s self-protection mechanisms, I set a breakpoint at this function. I then analyzed the decompiled code to infer the purpose of the function’s four input parameters. We found that LLMs are quite effective at helping to interpret decompiled code and suggest the purpose of the parameters.
As shown in the following, the first parameter is the encrypted content:
kd> dps rcx L1
000001ff`12ff4f80 000001ff`27c90020
kd> db 000001ff`27c90020
000001ff`27c90020 6f c4 62 6e dd 4e d9 99-16 32 5a e2 0a e4 b5 07 o.bn.N...2Z.....
000001ff`27c90030 cf 78 be c5 ec 7d 8f c6-29 5a f4 69 a9 fc 79 cb .x...}..)Z.i..y.
000001ff`27c90040 29 42 7c 9d d4 93 09 ff-d3 00 90 e5 f1 7d f7 ba )B|..........}..
000001ff`27c90050 b1 e7 07 f4 a8 2d c5 00-62 95 f5 cc 0e ff 48 5e .....-..b.....H^
000001ff`27c90060 bd d3 e4 73 65 12 96 31-aa 0c 27 d0 70 8b b6 84 ...se..1..'.p...
000001ff`27c90070 7c bf e2 3e 80 7d d8 2a-a0 c3 29 5e c4 3e b8 d8 |..>.}.*..)^.>..
000001ff`27c90080 9f 62 01 58 a0 3f 26 38-97 b1 f8 e2 15 c2 7f d2 .b.X.?&8........$ hexdump -C dse_rules_8_7_0_windows_encrypt.clp
00000000 6f c4 62 6e dd 4e d9 99 16 32 5a e2 0a e4 b5 07 |o.bn.N...2Z.....|
00000010 cf 78 be c5 ec 7d 8f c6 29 5a f4 69 a9 fc 79 cb |.x...}..)Z.i..y.|
00000020 29 42 7c 9d d4 93 09 ff d3 00 90 e5 f1 7d f7 ba |)B|..........}..|
00000030 b1 e7 07 f4 a8 2d c5 00 62 95 f5 cc 0e ff 48 5e |.....-..b.....H^|
00000040 bd d3 e4 73 65 12 96 31 aa 0c 27 d0 70 8b b6 84 |...se..1..'.p...|
00000050 7c bf e2 3e 80 7d d8 2a a0 c3 29 5e c4 3e b8 d8 ||..>.}.*..)^.>..|
00000060 9f 62 01 58 a0 3f 26 38 97 b1 f8 e2 15 c2 7f d2 |.b.X.?&8........|The second parameter includes a JSON document with part of the decryption key while the third parameter is not related to the encryption.
The fourth parameter contains the decrypted content upon the function’s completion:
kd> dps r9 L1
00000080`a47fd880 00000000`00000000
kd> pt
cysvc!CySvcServiceHandler+0x89a724:
0033:00007ffc`4f50e3b4 c3 ret
kd> dps 00000080`a47fd880
00000080`a47fd880 000001ff`18800020
kd> db 000001ff`18800020
000001ff`18800020 28 64 65 66 74 65 6d 70-6c 61 74 65 20 69 6e 74 (deftemplate int
000001ff`18800030 65 72 6e 61 6c 2e 64 65-62 75 67 5f 62 75 69 6c ernal.debug_buil
000001ff`18800040 64 5f 74 69 6d 65 73 74-61 6d 70 20 28 73 6c 6f d_timestamp (slo
000001ff`18800050 74 20 63 69 64 29 20 28-73 6c 6f 74 20 70 72 69 t cid) (slot pri
000001ff`18800060 6f 29 20 28 73 6c 6f 74-20 74 69 6d 65 73 74 61 o) (slot timesta
000001ff`18800070 6d 70 29 20 28 73 6c 6f-74 20 62 75 69 6c 64 5f mp) (slot build_
000001ff`18800080 74 69 6d 65 73 74 61 6d-70 29 29 0a 28 64 65 66 timestamp)).(def
000001ff`18800090 74 65 6d 70 6c 61 74 65-20 64 69 72 65 63 74 6f template directo
kd> .writemem C:\tmp\dse_decrypt_dump.txt 000001ff18800020 000001ff18800020+00852647The plaintext CLIPS rules were then dumped to a file using .writemem. The decrypted rules are available on GitHub.
Getting Decryption Keys
Identifying the algorithm, key, and IV was relatively straightforward with assistance from an LLM.
A hardcoded string is present in the binary, but only a portion of it is used as the key (maybe this is supposed to be a obfuscation technique): 
Another part is in a lua file which is stored on disk in plaintext: 
These two strings are combined to the following key material:
Algorithm: aes-256-cbc
Key: gstHaxzjfuS1u=EsVU#QbD#d@MPhj!58
IV: HXZnRN5f*Gg$akQDUsing CyberChef, it can be confirmed, that the key material is correct: 
The keys seem to be the same across tenants and versions.
Abusing Exceptions and Whitelists
Many of the rules have simple exceptions that can be abused to bypass them. The following is an example of how to use a well-known method to dump LSASS using ProcDump from SysInternals.
Usually, this activity is detected and prevented: 
Now we can check the rule which was triggered:
(defrule bioc.sync.procdump.1
(read_virtual_memory
(cid ?cid)
(instance_id ?instance_id)
(user_generic_value1 ?user_generic_value1_4
&: (and
(integerp ?user_generic_value1_4)
(eq (bit_and ?user_generic_value1_4 ?*Is_lsass_process*) ?*Is_lsass_process*)
(eq (bit_and ?user_generic_value1_4 ?*is_werfault_process*) 0)))
(bytes_copied 4)
(base_address ?base_address_4)
(buffer "40000000")
(timestamp ?timestamp_4))
(read_virtual_memory
(cid ?cid)
(instance_id ?instance_id)
(user_generic_value1 ?user_generic_value1_3
&: (and
(integerp ?user_generic_value1_3)
(eq (bit_and ?user_generic_value1_3 ?*Is_lsass_process*) ?*Is_lsass_process*)
(eq (bit_and ?user_generic_value1_3 ?*is_werfault_process*) 0)))
(bytes_copied 4)
(base_address ?base_address_3
&: (eq (bit_and ?base_address_4 -4294967296) (bit_and ?base_address_3 -4294967296)))
(buffer "68000000")
(timestamp ?timestamp_3
&: (and
(>= ?timestamp_4 ?timestamp_3)
(is_in_time_frame 20000000 ?timestamp_4 ?timestamp_3))))
(read_virtual_memory
(cid ?cid)
(instance_id ?instance_id)
(user_generic_value1 ?user_generic_value1_2
&: (and
(integerp ?user_generic_value1_2)
(eq (bit_and ?user_generic_value1_2 ?*Is_lsass_process*) ?*Is_lsass_process*)
(eq (bit_and ?user_generic_value1_2 ?*is_werfault_process*) 0)))
(bytes_copied 8)
(base_address ?base_address_2
&: (eq (bit_and ?base_address_3 -4294967296) (bit_and ?base_address_2 -4294967296)))
(buffer ?buffer_2
&: (eq ?base_address_2 (parse_hex (hex_reverse_byte_order ?buffer_2))))
(timestamp ?timestamp_2
&: (and
(>= ?timestamp_3 ?timestamp_2)
(is_in_time_frame 20000000 ?timestamp_3 ?timestamp_2))))
(read_virtual_memory
(cid ?cid)
(instance_id ?instance_id)
(user_generic_value1 ?user_generic_value1_1
&: (and
(integerp ?user_generic_value1_1)
(eq (bit_and ?user_generic_value1_1 ?*Is_lsass_process*) ?*Is_lsass_process*)
(eq (bit_and ?user_generic_value1_1 ?*is_werfault_process*) 0)))
(bytes_copied 8)
(base_address ?base_address_1
&: (eq (bit_and ?base_address_2 -4294967296) (bit_and ?base_address_1 -4294967296)))
(timestamp ?timestamp_1
&: (and
(>= ?timestamp_2 ?timestamp_1)
(is_in_time_frame 20000000 ?timestamp_2 ?timestamp_1))))
(or
(process_start
(cid ?cid)
(instance_id ?instance_id)
(file_info_description ?file_info_description)
(command_line ?command_line)
(file_info_original_name ?file_info_original_name)
(process_image_path ?process_image_path
&: (and
(not
(or
(and
(contains_one_of ?process_image_path "windows\\system32" "windows\\syswow64")
(starts_with (file_name ?process_image_path) "werfault"))
(contains_one_of (lowcase ?file_info_description) "windows problem reporting" "windows fault reporting")))
(not
(and
(or
(eq ?file_info_original_name "rdrleakdiag.exe")
(eq (file_name ?process_image_path) "rdrleakdiag.exe"))
(not
(contains_one_of ?command_line "-fullmemdmp" "/fullmemdmp" "-memdmp" "/memdmp")))))))
(not (process_start (cid ?cid) (instance_id ?instance_id))))
(not (bioc.sync.procdump.1 (cid ?cid) (instance_id ?instance_id)))
(internal.debug_build_timestamp)
(not (internal.global_whitelist (cid ?cid)))
=>
(assert (bioc.sync.procdump.1 (cid ?cid) (instance_id ?instance_id))))There are several exceptions for werfault such as:
(contains_one_of (lowcase ?file_info_description) "windows problem reporting" "windows fault reporting")))
After adding this file info description to procdump64.exe (renamed to test.exe), this rule no longer triggers:

However, another rule was triggered because several rules exist to prevent dumping LSASS. But the rule above contains another interesting exception that allows us to reach the goal faster than bypassing several rules individually:
(not (internal.global_whitelist (cid ?cid)))
The global_whitelist is defined as:
(defrule internal.global_whitelist
?the_f <- (process_start (cid ?cid) (command_line ?command_line &: (contains_one_of ?command_line ":\\windows\\ccmcache\\")))
(not (internal.global_whitelist (cid ?cid)))
=>
(assert (internal.global_whitelist (cid ?cid) (prio ?*retention_priority_higher*) (dbg_fact_id (fact-index ?the_f)))))The bypass is remarkably simple: 
An implant started with this command line could do almost anything on the host without triggering the Cortex detection and prevention rules.
Disclosure Timeline
| Date | Event |
|---|---|
| 2025‑07‑28 | We reported the abuse of the global whitelists to Palo Alto Networks. |
| 2025‑08‑11 | The Director of Offensive Security at Palo Alto Networks contacted us and asked for a delay of the 90-day disclosure policy, as well as more detailed information about the decryption of the rules. |
| 2025‑08‑20 | The Director of Offensive Security at Palo Alto Networks requested a meeting to discuss the timeline |
| 2025‑09‑01 | Meeting with Palo Alto. They want to publish a fix in February 2026. We granted the extended time to protect Cortex customers from an abuse of global whitelists. |
| 2026‑01‑09 | Palo Alto is on track to publish a fix by the end of February. |
| 2026‑02‑24 | We asked for a final date. |
| 2026‑02‑24 | Palo Alto responded that a fix is already published in Cortex XDR Agent version 9.1 and Content version 2160. |
Analysing the Fix
The old encryption keys no longer work to decrypt the rules. However, the string !7H1@j@%J8VqP9c7SgstHaxzjfuS1u=EsVU#QMKHgogc*X8*sXoho2*t&paX5NJ@ in cysvc.dll from which a part of the key was derived is still the same. The second part and the “derivation” function changed slightly.
The actual issue, however, was the global whitelists in the decrypted rules. They fully removed the global whitelists. Spawning an implant that bypasses all kinds of rules at the same time is no longer possible. Bypassing individual rules, however, is of course still a lot easier when knowing the rules. Most of them have exceptions which can be abused.
Conclusion
There will always be a cat-and-mouse game between offensive and defensive security. A common approach in malware development is to create new techniques to bypass EDR agents, for example, by using new methods for shellcode injection. This post describes another method for evading detections when targeting specific EDRs. About half of the predefined behavioral detection rules in Cortex XDR could be bypassed by simply adding the command line argument :\Windows\ccmcache to the process that executes malicious actions. These rules are not directly accessible in plaintext, which can be compared to a “security by obscurity” approach. This raises the question of whether vendors’ detection rules should be hidden or open. For example, Elastic and HarfangLab use open rules, but this creates a trade-off, as less-skilled attackers can also inspect them. Defenders should know their tools and not blindly trust a black-box detection and prevention solution.