├── Chapter-11 ├── Purple_Teaming_Report_v1.0.xlsx └── README.md ├── Chapter-12 ├── nmap │ ├── Nmap-diffing.py │ └── Nmap-run.sh ├── pingcastle │ └── PingCastle-diffing.py ├── trivy │ └── Trivy-diffing.py └── vulnscan │ └── vulnscan-diffing.py ├── Chapter-13 ├── diffing.yml ├── download_install_pingcastle.yml └── pingcastle_execution.yml ├── LICENSE └── README.md /Chapter-11/Purple_Teaming_Report_v1.0.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Purple-Team-Strategies/f468cdb04826491737a37f214aaab13a8435d930/Chapter-11/Purple_Teaming_Report_v1.0.xlsx -------------------------------------------------------------------------------- /Chapter-11/README.md: -------------------------------------------------------------------------------- 1 | # Comments 2 | 3 | ## Purple Teaming Log and Report 4 | Here you will find a concrete example of the Purple Teaming Log and Report presented in Chapter 2 of the book. 5 | It is pre-filled in to emulate a malware threat (as opposed to a threat actor) named Qakbot. 6 | Therefore you will find in this document: 7 | - Teams and roles 8 | - Exercise's objectives 9 | - A threat intelligence overview 10 | - An adversary emulation plan 11 | 12 | This is of course an example that should be adapted to your needs and environment but we hope it can serve as a main document for your first purple teaming exercise. 13 | 14 | ### A word on TTPs timeline 15 | We would like to mention one particular aspect of emulating TTPs from a threat actor. 16 | While MITRE is currently working on adding the concept of campaign on its repository, it is important to keep in mind that it currently list ALL TTPs observed and attributed to a threat actor. 17 | Just as an example it means that if you try to emulate all TTPs from a threat actor named "X", you will end up emulating TTPs from this actor observed in 2019, 2021 and maybe 2022. 18 | While it is not a real issue, the community needs to work on bounding TTPs with observation time so that we can specifically (and easily) select the latest TTPs from a particular threat actor. 19 | -------------------------------------------------------------------------------- /Chapter-12/nmap/Nmap-diffing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # Nmap-diffing.py used to perform diffing operations based on Nmap reports 3 | # David ROUTIN @rewt_1 - Purple Team Strategies (Packt Editions) - 2021 4 | 5 | import re 6 | import os 7 | import shutil 8 | import pprint 9 | from pathlib import Path 10 | reports_raw = "/opt/ptx/nmap/reports_raw/" 11 | last_known = "/opt/ptx/nmap/last_known/last_known.txt" 12 | nmap_re = "Host: (?P[^\s+]+).*?Ports:\s(?P.+?)\t+" 13 | reports = sorted(Path(reports_raw).iterdir(), key=os.path.getmtime) 14 | number_of_history_to_check = 3 15 | 16 | reports_to_use = [] 17 | 18 | for n in range(-1*number_of_history_to_check,0): 19 | try: 20 | reports_to_use.append(reports[n]) 21 | except: 22 | pass 23 | 24 | if len(reports_to_use) == 0: 25 | print("No existing reports... Leaving") 26 | exit(1) 27 | 28 | reports_to_use.reverse() 29 | 30 | def parse_report(file,regex_touse=nmap_re): 31 | with open(file, "r") as f: 32 | data = f.read() 33 | 34 | try: 35 | collected = re.findall(regex_touse, data) 36 | except: 37 | pass 38 | 39 | #[('127.0.0.1', '22/open/tcp//ssh///, 631/open/tcp//ipp///, 8080/open/tcp//http-proxy///'), ('173.249.49.55', '22/open/tcp//ssh///')] 40 | results = {} 41 | 42 | for element in collected: 43 | try: 44 | host = element[0] 45 | ports = element[1].replace(" ", "").split(",") 46 | if not host in results: 47 | results[host] = ports 48 | else: 49 | for port in ports: 50 | if not port in results[host][ports]: 51 | results[host].append(port) 52 | except Exception as e: 53 | print(e) 54 | return results 55 | 56 | 57 | # Check if last_known report exists, if not create, display a message, exit 58 | latest_report = reports_to_use[0] 59 | if not os.path.exists(last_known): 60 | shutil.copyfile(latest_report, last_known) 61 | print("No original reference found. Now created, please ensure to review the report below as it is now the first reference.\n") 62 | print(str(latest_report) + "\n") 63 | exit(2) 64 | 65 | ### Loading N history reports 66 | history_reports = [] 67 | ### we remove current from the list for history control 68 | reports_to_use = reports_to_use[1:] 69 | 70 | for report in reports_to_use: 71 | history_reports.append(parse_report(report)) 72 | ### We had last_known in the report history 73 | history_reports.append(parse_report(last_known)) 74 | ### merging all reports together 75 | merged_history = {} 76 | for element in history_reports: 77 | for host in element.keys(): 78 | try: 79 | merged_history[host] 80 | except: 81 | merged_history[host] = set() 82 | ports_l = element[host] 83 | for port in ports_l: 84 | merged_history[host].add(port) 85 | 86 | anomalies = {} 87 | latest_report_parsed=parse_report(latest_report) 88 | # Now we compare latest report with last_known, we retun anomlies (things that exist in latest_report and not in last_known) 89 | # Everything that does not exist become an anomaly 90 | 91 | for host in latest_report_parsed: 92 | for port in latest_report_parsed[host]: 93 | if port not in merged_history[host]: 94 | if host not in anomalies: 95 | anomalies[host] = set() 96 | anomalies[host].add(port) 97 | 98 | if len(anomalies) > 0: 99 | pprint.pprint(anomalies) 100 | 101 | ### The new model become the last_known 102 | #shutil.copyfile(latest_report, last_known) 103 | -------------------------------------------------------------------------------- /Chapter-12/nmap/Nmap-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin 4 | reports_raw = /opt/ptx/nmap/reports_raw/ 5 | date_of_d = $(date "+%Y-%m-%d") 6 | list_of_networks = /opt/ptx/nmap/networks_list.txt 7 | 8 | ### We check if no nmap scan are still in progress 9 | ps -ef | grep -v grep | grep nmap > /dev/null 2>&1 10 | if [ $? == 0 ] 11 | then 12 | echo "Nmap is still running" 13 | exit 14 | fi 15 | 16 | ### Running Nmap 17 | nmap -iL $list_of_networks -P0 -T4 -oG ${reports_raw}/scan-${date_of_d}.txt 18 | -------------------------------------------------------------------------------- /Chapter-12/pingcastle/PingCastle-diffing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # PingCastle-diffing.py used to perform diffing operations based on PingCastle reports 3 | # David ROUTIN @rewt_1 - Purple Team Strategies (Packt Editions) - 2021 4 | 5 | import deepdiff 6 | import json 7 | import pprint 8 | import sys 9 | import re 10 | import os.path 11 | from json import dumps 12 | from xml.etree.ElementTree import fromstring 13 | from xmljson import parker, Parker 14 | 15 | if len(sys.argv) < 3: 16 | print("Usage: PingCastle-diffing.py previous_report.xml new_report.xml") 17 | exit(1) 18 | 19 | for xmlreport in sys.argv: 20 | if os.path.isfile(xmlreport) is False: 21 | print(xmlreport + " does not exist... Leaving") 22 | exit(2) 23 | 24 | old=sys.argv[1] 25 | new=sys.argv[2] 26 | 27 | ### We are excluding specific fields to avoid false positive due only to time changes, creation dates etc 28 | excludedRegex = [ 29 | r".+Time", r".+Date.+", r".+Last", r".+Creation.+", r".+Number.+" 30 | ] 31 | 32 | ### This function open a file in XML format and return a string JSON object 33 | def xmltojs(file): 34 | with open(file, "r") as f: 35 | d=f.read() 36 | j=dumps(parker.data(fromstring(d))) 37 | return j 38 | 39 | 40 | old=json.loads(xmltojs(old)) 41 | new=json.loads(xmltojs(new)) 42 | 43 | ### Cleaning results with specific pattern to avoid incoherent diffing, this can be probably improved and is not false positives proof, you have to adapt in your context if required 44 | def clean_numbers(l): 45 | templ = [] 46 | for e in l: 47 | ### temp element 48 | temp_e = {} 49 | temp_e = e 50 | if re.findall("day\(s\) ago|weak RSA key", e["Rationale"]): 51 | temp_e["Rationale"] = re.sub("\d+", "REPLACED", e["Rationale"]) 52 | if re.findall("\[\d+\]", e["Rationale"]): 53 | temp_e["Rationale"] = re.sub("\d+", "REPLACED", e["Rationale"]) 54 | templ.append(temp_e) 55 | return templ 56 | 57 | 58 | old=clean_numbers(old["RiskRules"]["HealthcheckRiskRule"]) 59 | new=clean_numbers(new["RiskRules"]["HealthcheckRiskRule"]) 60 | 61 | anomalies=deepdiff.DeepDiff(old,new,ignore_order=True,exclude_regex_paths=excludedRegex) 62 | 63 | if len(anomalies) > 0: 64 | pprint.pprint(anomalies) 65 | -------------------------------------------------------------------------------- /Chapter-12/trivy/Trivy-diffing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import json 3 | import sys 4 | import subprocess 5 | import pandas 6 | import os 7 | import shutil 8 | import pprint 9 | from deepdiff import DeepDiff 10 | 11 | current_report = "/opt/ptx/trivy/reports_raw/current_report.json" 12 | last_known = "/opt/ptx/trivy/last_known/last_output.json" 13 | severity = "HIGH,CRITICAL" 14 | 15 | def run_cmd(cmd): 16 | cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) 17 | cmd = cmd.stdout.read() 18 | return cmd 19 | 20 | docker_images_l = run_cmd("docker image ls -q").split() 21 | docker_images_l = [ image.decode("utf-8") for image in docker_images_l ] 22 | js_output= [] 23 | 24 | for image_id in docker_images_l: 25 | cmd = run_cmd("docker image inspect " + image_id) 26 | get_docker_image_info = json.loads(cmd) 27 | name = "".join(get_docker_image_info[0]["RepoTags"]) 28 | tempd = { "image_name": name, "image_id": image_id } 29 | ### perform the Trivy scan on the image 30 | try: 31 | report = run_cmd("trivy -f json -q --severity " + severity + " " + name) 32 | except Exception as e: 33 | pass 34 | print(str(e)) 35 | continue 36 | tempd["report"] = json.loads(report) 37 | ### We may encouner multiple vulnerabilities list from different subpackages 38 | all_vuln = [] 39 | for result in tempd["report"]["Results"]: 40 | all_vuln.append(result["Vulnerabilities"]) 41 | tempd["report"] = [i for l in all_vuln for i in l] 42 | js_output.append(tempd) 43 | 44 | current_report_content = js_output 45 | 46 | def analyze_report(vulnerabilities): 47 | temp = pandas.DataFrame(vulnerabilities) 48 | temp["risk_name"] = temp["Severity"] + "/" + temp["VulnerabilityID"] + "/" + temp["Title"] + "\n------\n" 49 | temp = temp.groupby("PkgName")["risk_name"].apply(list).to_json() 50 | return json.loads(temp) 51 | 52 | ### Building resume version of a report: 53 | def resume_report(image_report): 54 | temp = {} 55 | for image in image_report: 56 | image_name = image["image_name"] 57 | temp[image_name] = {} 58 | temp[image_name]["image_id"] = image["image_id"] 59 | vulnerabilities = image["report"] 60 | temp[image_name]["vuln"] = analyze_report(vulnerabilities) 61 | return temp 62 | 63 | simplified_current_content = resume_report(current_report_content) 64 | 65 | # Updating report 66 | with open(current_report, "w") as f: 67 | f.write(json.dumps(simplified_current_content)) 68 | 69 | if not os.path.exists(last_known): 70 | shutil.copyfile(current_report, last_known) 71 | print("No original reference found. Now created, please ensure to review this report as it is now the reference.\n") 72 | exit(2) 73 | 74 | with open(last_known, "r") as f: 75 | last_known_content = f.read() 76 | last_known_content=json.loads(last_known_content) 77 | 78 | ###### DEEPDIFF 79 | previous=json.loads(open(last_known, "r").read()) 80 | new=simplified_current_content 81 | anomalies=DeepDiff(previous,new,ignore_order=True,verbose_level=2) 82 | # We don't want removed_items (patched hosts since last time) 83 | anomalies.pop("iterable_item_removed", None) 84 | anomalies.pop("dictionary_item_removed", None) 85 | if len(anomalies) > 0: 86 | pprint.pprint(anomalies) 87 | 88 | # Current report become the last_known 89 | shutil.copyfile(current_report, last_known) 90 | -------------------------------------------------------------------------------- /Chapter-12/vulnscan/vulnscan-diffing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import pandas 3 | import glob 4 | import os 5 | import json 6 | import code 7 | import shutil 8 | import pprint 9 | from deepdiff import DeepDiff 10 | 11 | reports_raw = "/opt/ptx/vulnscan/reports_raw/" 12 | last_known = "/opt/ptx/vulnscan/last_known/last_output.json" 13 | list_of_reports = glob.glob(reports_raw + "/*") 14 | 15 | # We check if some reports are available in the new reports directory 16 | if len(list_of_reports) == 0: 17 | print("Leaving... No report available\n") 18 | exit(1) 19 | 20 | # Find the last report by modification date 21 | latest_report = max(list_of_reports, key=os.path.getmtime) 22 | 23 | def groupby_vuln(report_file): 24 | # We convert the original report to a grouped version by risk_name and list object for impacted hosts 25 | with open(report_file, "r") as f: 26 | report = json.loads(f.read()) 27 | df = pandas.DataFrame(report) 28 | # We want only High or Medium severity vulnerabilities 29 | df = df[ ( df["severity"]=="High" ) | ( df["severity"] == "Medium" )] 30 | # Change the "risk_name" and "impacted_host" string accordingly to your normalization 31 | data_raw = df.groupby("risk_name")["impacted_host"].apply(list).to_json() 32 | return json.loads(data_raw) 33 | 34 | if not os.path.exists(last_known): 35 | shutil.copyfile(latest_report, last_known) 36 | print("No original reference found. Now created, please ensure to review the report below as it is now the first reference.\n") 37 | print(latest_report + "\n") 38 | exit(2) 39 | 40 | ### Reading and aggregating previous output (last_known) 41 | previous = groupby_vuln(last_known) 42 | ### Reading new output from the reports directory 43 | new = groupby_vuln(latest_report) 44 | 45 | ### Diffing the two results 46 | anomalies=DeepDiff(previous,new,ignore_order=True, verbose_level=2) 47 | # We don't want removed_items (patched hosts since last time) 48 | anomalies.pop("iterable_item_removed", None) 49 | anomalies.pop("dictionary_item_removed", None) 50 | if len(anomalies) > 0: 51 | pprint.pprint(anomalies) 52 | 53 | # The new model become the last_known 54 | shutil.copyfile(latest_report, last_known) 55 | -------------------------------------------------------------------------------- /Chapter-13/diffing.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Pingcastle diffing 3 | hosts: all 4 | gather_facts: True 5 | vars: 6 | diffing_code: /data/script/PingCastle-diffing.py 7 | 8 | tasks: 9 | - name: Get report in a audit folder created J-1 10 | shell: find "${option.path}" -mmin +60 -mmin -1440 -type f -name "*.xml" 11 | register: previous 12 | 13 | - name: Get report in a audit folder newer than 20 minutes 14 | find: 15 | paths: "${option.path}" 16 | age: "-20m" 17 | register: current 18 | 19 | - name: Run the python script in charge of "diffing" 20 | command: python3 {{ diffing_code }} {{ previous }} {{ current }} 21 | register: results 22 | 23 | - debug: 24 | var: results.stdout_lines 25 | when: results.stdout_lines|length > 0 26 | 27 | - debug: 28 | msg: "Everything is ok, no difference was found between yesterday and today" 29 | when: results.stdout_lines|length == 0 30 | 31 | -------------------------------------------------------------------------------- /Chapter-13/download_install_pingcastle.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Download, Install and Configure PingCastle 3 | hosts: all 4 | gather_facts: true 5 | vars: 6 | ansible_user: ${option.winrm_user} 7 | ansible_password: ${option.winrm_password} 8 | ansible_connection: winrm 9 | ansible_winrm_server_cert_validation: true 10 | ansible_winrm_transport: basic 11 | ansible_winrm_port: 5985 12 | tmp_directory: C:\tmp 13 | pingcastle_directory: C:\apps\pingcastle 14 | 15 | tasks: 16 | - name: Get download url from www.pingcastle.com 17 | win_shell: | 18 | get_url = (Invoke-WebRequest -Uri "https://www.pingcastle.com/download").Links.Href |select-string -pattern 'zip' | Sort-Object |Select-object -first 1 19 | Write-Host $get_url 20 | register: pwsh_output 21 | 22 | - name: Filter url result 23 | debug: 24 | msg: "{{ pwsh_output.host_out | regex_replace('[\\r\\n\\t]+','') }}" 25 | register: url_download_latest 26 | 27 | - name: Remove tmp directory 28 | win_file: 29 | path: "{{ item }}" 30 | state: absent 31 | with_items: 32 | - "{{ pingcastle_directory }}" 33 | - "{{ tmp_directory }}" 34 | 35 | - name: Create directory structure 36 | win_file: 37 | path: "{{ item }}" 38 | state: directory 39 | with_items: 40 | - "{{ pingcastle_directory }}" 41 | - "{{ tmp_directory }}" 42 | 43 | - name: Download the latest version of pingcastle 44 | win_get_url: 45 | url: "{{ url_download_latest }}" 46 | dest: "{{ tmp_directory }}" 47 | 48 | - name: Get all files in a folder 49 | win_shell: Dir -Recurse {{ tmp_directory }} | Get-Childitem | select -expandproperty name 50 | register: found_files 51 | ignore_errors: true 52 | 53 | - name: Filter and store only filename 54 | set_fact: 55 | latest_file: "{{ found_files.stdout_lines|list }}" 56 | 57 | - name: decompress the latest version of pingcastle 58 | win_unzip: 59 | src: '{{ tmp_directory }}\{{ latest_file }}' 60 | dest: "{{ pingcastle_directory }}" -------------------------------------------------------------------------------- /Chapter-13/pingcastle_execution.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Pingcastle execution and report generation 3 | hosts: all 4 | gather_facts: True 5 | vars: 6 | ansible_user: ${option.winrm_user} 7 | ansible_password: ${option.winrm_password} 8 | ansible_connection: winrm 9 | ansible_winrm_server_cert_validation: true 10 | ansible_winrm_transport: basic 11 | ansible_winrm_port: 5985 12 | pingcastle_directory: C:\apps\pingcastle 13 | report_directory: C:\apps\reports 14 | pingcastle_target: lab-purple.local 15 | 16 | tasks: 17 | - name: Get date of the day 18 | win_shell: Get-Date -Format "yyyyMMdd" 19 | register: get_date 20 | 21 | - name: Get the latest xml filename 22 | win_shell: | 23 | $latestfile = Get-ChildItem -path {{ report_directory }} -Attributes !Directory *.xml | Sort-Object -Descending -Property LastWriteTime | select -First 1 24 | $latestfile.Name 25 | register: latest_xml_report 26 | 27 | - name: Run pingcastle.exe to generate the report 28 | win_shell: | 29 | cd {{pingcastle_directory }} 30 | ./PingCastle.exe --healthcheck --datefile --server {{ pingcastle_target }} 31 | 32 | - name: Obtain information about file 33 | ansible.windows.win_stat: 34 | path: '{{ pingcastle_directory }}\{{ latest_xml_report }}' 35 | register: report_info 36 | 37 | - name: Move reports in another folder 38 | win_shell: Get-Item –Path {{ pingcastle_directory }}\ad_hc_{{ pingcastle_target }}_{{ get_date }}* | Move-Item -Destination {{ report_directory }} 39 | when: report_info.size > "1024" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Purple Team Strategies 5 | 6 | Book Name 7 | 8 | This is the code repository for [Purple Team Strategies](https://www.packtpub.com/product/purple-team-strategies/9781801074292), published by Packt. 9 | 10 | **Enhancing global security posture through uniting red and blue teams with adversary emulation** 11 | 12 | ## What is this book about? 13 | With small to large companies focusing on hardening their security systems, the term "purple team" has gained a lot of traction over the last couple of years. Purple teams represent a group of individuals responsible for securing an organization’s environment using both red team and blue team testing and integration – if you’re ready to join or advance their ranks, then this book is for you. 14 | 15 | This book covers the following exciting features: 16 | * Learn and implement the generic purple teaming process 17 | * Use cloud environments for assessment and automation 18 | * Integrate cyber threat intelligence as a process 19 | * Configure traps inside the network to detect attackers 20 | * Improve red and blue team collaboration with existing and new tools 21 | * Perform assessments of your existing security controls 22 | 23 | If you feel this book is for you, get your [copy](https://www.amazon.com/Purple-Team-Strategies-Enhancing-adversary-ebook/dp/B0B12R8DFJ) today! 24 | 25 | https://www.packtpub.com/ 26 | 27 | ## Instructions and Navigations 28 | All of the code is organized into folders. For example, Chapter03. 29 | 30 | The code will look like the following: 31 | ``` 32 | alert http $HOME_NET any -> $EXTERNAL_NET $HTTP_PORTS 33 | ( 34 | msg: "Detecting HTTP URI with a malicious string as parameter" 35 | http_uri; 36 | content:"/malicious="; 37 | pcre:"/\/malicious\x3d\w+/"; 38 | ) 39 | ``` 40 | 41 | **Following is what you need for this book:** 42 | If you're a cybersecurity analyst, SOC engineer, security leader or strategist, or simply interested in learning about cyber attack and defense strategies, then this book is for you. Purple team members and chief information security officers (CISOs) looking at securing their organizations from adversaries will also benefit from this book. You’ll need some basic knowledge of Windows and Linux operating systems along with a fair understanding of networking concepts before you can jump in, while ethical hacking and penetration testing know-how will help you get the most out of this book. 43 | 44 | With the following software and hardware list you can run all code files present in the book (Chapter 1-14). 45 | 46 | ### Software and Hardware List 47 | 48 | | Chapter | Software required | OS required | 49 | | -------- | ---------------------------------------------------------------------------------------------------| -----------------------------------| 50 | | 1-14 | Python, Ansible, Powershell | Windows, Mac OS X, and Linux (Any) | 51 | 52 | 53 | We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](https://static.packt-cdn.com/downloads/9781801074292_ColorImages.pdf). 54 | 55 | ### Related products 56 | * Mastering Defensive Security[[Packt]](https://www.packtpub.com/product/mastering-defensive-security/9781800208162) [[Amazon]](https://www.amazon.com/Mastering-Defensive-Security-techniques-infrastructure/dp/1800208162) 57 | 58 | * Cryptography Algorithms [[Packt]](https://www.packtpub.com/product/cryptography-algorithms/9781789617139) [[Amazon]](https://www.amazon.com/Next-generation-Cryptography-Algorithms-Explained-implementation-ebook/dp/B093Y11H9Q) 59 | 60 | ## Get to Know the Authors 61 | **David Routin** 62 | He became interested in computer security at a young age. He started by learning about old-school attack methods and defense against them in the 1990s with Unix/Linux systems. He now has over two decades of experience and remains passionate about both sides of security (offensive and defensive). He has made multiple contributions to the security industry in different forms, from the MITRE ATT&CK framework, the SIGMA project, and vulnerability disclosures (Microsoft) to public event speaking and multiple publications, including articles in the French MISC magazine. 63 | As a security professional, he has held multiple positions, including security engineer, open source expert, CISO, and now security operations center (SOC) and Purple Team manager at e-Xpert Solutions. Over the last 10 years, he has been in charge of building and operating multiple SOCs for MSSPs and private companies in various sectors (including industry, pharma, insurance, finance, and defense). 64 | 65 | **Samuel Rossier** 66 | He is currently SOC lead within a government entity where he focuses on detection engineering, incident response, automation, and cyber threat intelligence. He is also a teaching assistant at the SANS Institute. He was previously responsible for a private bank group CIRT, and also worked as an SOC manager within an MSSP. He also spent several years within a consulting cybersecurity practice. 67 | Samuel currently holds a master's degree in information systems and several information security certifications, including GRID, GMON, eCIR, eCTHP, eCRE, eNDP, and eJPT. 68 | He is also a contributor to the MITRE D3FEND and SIGMA frameworks and likes to speak at conferences and analyze malware. He values a strong emphasis on the people dimension of cybersecurity by sharing knowledge. 69 | 70 | **Simon Thoores** 71 | He is a cybersecurity analyst who specializes in forensics and incident response. He started his career as a security analyst after obtaining an engineering diploma in information system architecture with a focus on security. He built his forensics and reverse engineering skills during large-scale incident responses, and he finally validated these skills with GCFA. Then, he moved to the threat intelligence field to better understand and emulate attackers in order to improve infrastructure security. 72 | ### Download a free PDF 73 | 74 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
75 |

https://packt.link/free-ebook/9781801074292

--------------------------------------------------------------------------------