├── CONTRIBUTING.md ├── README.md ├── api_searcher └── api_searcher.py ├── config └── api_configs.py ├── documentation ├── Query_Creation_Guide.md ├── mappings.md └── pythia_query_format.yml ├── images ├── pythia-logos │ ├── pdf │ │ ├── logo-black.pdf │ │ ├── logo-color.pdf │ │ ├── logo-no-background.pdf │ │ └── logo-white.pdf │ ├── png │ │ ├── logo-black.png │ │ ├── logo-color.png │ │ ├── logo-no-background.png │ │ └── logo-white.png │ └── svg │ │ ├── logo-black.svg │ │ ├── logo-color.svg │ │ ├── logo-no-background.svg │ │ └── logo-white.svg ├── pythia_man.png ├── pythia_query_format.png └── pythia_sample_rule.png ├── mappings ├── mappings.md ├── pythia_binaryedge.py ├── pythia_censys.py ├── pythia_fofa.py ├── pythia_hunter.py ├── pythia_shodan.py └── pythia_zoomeye.py ├── pythia.py ├── pythia_converter ├── pythia_query_to_binaryedge_query.py ├── pythia_query_to_censys_query.py ├── pythia_query_to_fofa_query.py ├── pythia_query_to_full_query.py ├── pythia_query_to_hunter_query.py ├── pythia_query_to_shodan_query.py └── pythia_query_to_zoomeye_query.py ├── queries ├── MALWARE │ ├── asyncrat_subject_issuer_cn.yml │ ├── guloader_jarm.yml │ ├── hookbot_panel_html_title.yml │ ├── kematian_stealer_html_title.yml │ ├── laplas_clipper_subject_cn.yml │ ├── meduza_stealer_html_title.yml │ ├── mirai_multiple_architectures.yml │ ├── quasar_rat_subject_common_name.yml │ ├── redline_stealer_banner_hashes_port_asn.yml │ └── stealc_stealer_banner_hashes_asn.yml ├── OPENDIR │ ├── common_file_names_opendir_body.yml │ ├── exploit_opendir_body.yml │ └── mirai_multiple_architectures.yml ├── PHISHING │ ├── gophish_phishing_jarm.yml │ ├── greatness_phaas_body_hash.yml │ └── microsoft_outlook_phishing_jarm_favicon_hash.yml ├── TA │ ├── coldriver_c2_banner_port_cert.yml │ ├── kimsuky_apt_campaign_banner_hash.yml │ └── kimsuky_apt_certificate_asn.yml └── TOOLS │ ├── cobalt_strike_tls_subject_common_name.yml │ ├── mythic_c2_favicon_hash_dec_or_title.yml │ ├── netsupport_rat_job_board_favicon_hash_html_title.yml │ ├── supershell_html_title.yml │ ├── viper_c2_title_body.yml │ └── vshell_html_title.yml ├── requirements.txt └── tests └── pythia_format_validator.py /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to Pythia 2 | 3 | First off, thank you for considering contributing to Pythia! Your help is invaluable in keeping this project up-to-date and useful for the CTI community. 4 | 5 | The following guidelines will help you understand how to contribute effectively. 6 | 7 | ## Reporting False Positives Or Proposing New Hunting Query Ideas 🔎 8 | 9 | If you find a false positive or would like to propose a new detection query idea but do not have the time to create one, please create a new issue on the [GitHub repository](https://github.com/EfstratiosLontzetidis/pythia/issues/new/). 10 | 11 | ## 🛠Submitting Pull Requests (PRs) 12 | 13 | 1. Fork the [Pythia repository](https://github.com/EfstratiosLontzetidis/pythia) and clone your fork to your local machine. 14 | 15 | 2. Create a new branch for your changes: 16 | 17 | ```bash 18 | git checkout -b your-feature-branch 19 | ``` 20 | 21 | 3. Make your changes, and test them for validation and conversion into other platform formats, and using the API (examples): 22 | 23 | ```bash 24 | python3 pythia.py -file queries/TOOLS/major_hunting_query_APT_EL1T3.yml -validate 25 | ``` 26 | 27 | ```bash 28 | python3 pythia.py -file queries/TOOLS/major_hunting_query_APT_EL1T3.yml -convert BINARYEDGE 29 | ``` 30 | 31 | ```bash 32 | python3 pythia.py -file queries/TOOLS/major_hunting_query_APT_EL1T3.yml -convert CENSYS -api 33 | ``` 34 | 35 | 4. Once the tests are successful, commit the changes to your branch: 36 | 37 | ```bash 38 | git add . 39 | git commit -m "Your commit message" 40 | ``` 41 | 42 | 5. Push your changes to your fork: 43 | 44 | ```bash 45 | git push origin your-feature-branch 46 | ``` 47 | 48 | 6. Create a new Pull Request (PR) against the upstream repository: 49 | 50 | * Go to the [Pythia repository](https://github.com/EfstratiosLontzetidis/pythia) on GitHub 51 | * Click the "New Pull Request" button 52 | * Choose your fork and your feature branch 53 | * Add a clear and descriptive title and a detailed description of your changes 54 | * Submit the Pull Request 55 | 56 | ## Collaboration 57 | If you are interested in collaboration please reach out: 58 | - [Efstratios Lontzetidis](https://www.linkedin.com/in/efstratioslontzetidis/) or [@lontze7](https://x.com/lontze7) 59 | 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pythia - Generic Query Format for Discovering Malicious Infrastructure 2 | 3 |

4 |
5 | 6 | Pythia Logo 7 | 8 |

9 |
10 | 11 | ## What is Pythia 12 | Welcome to the Pythia main query repository. The place where threat hunters and cybersecurity researchers collaborate on malware/threat actor infrastructure hunting. 13 | 14 | Pythia offers a generic standardized query format (pretty similar to, and inspired from [Sigma](https://github.com/SigmaHQ/sigma)) that is easily convertable to multiple infrastructure hunting platforms. Such platforms are designed to scan daily (at different frequencies) multiple IP ranges and collect data (snapshots of a state in time). An important tip is to validate your findings with multiple tools to verify your results and even get more up-to-date results. That's why Pythia was developed. 15 | 16 | Named after the legendary Oracle of Delphi in Greek mythology, Pythia is a versatile tool designed to translate and adapt search queries across multiple cybersecurity platforms. Just as the ancient Pythia provided insights and prophecies to seekers from diverse backgrounds, this modern tool delivers precise and actionable intelligence by converting a standard query format into the specific syntax required by various search engines. Pythia streamlines the process of querying multiple data sources, making it an indispensable asset for cybersecurity professionals seeking comprehensive network visibility and threat intelligence. 17 | 18 | Pythia aims to be for publicly-discoverable data, what [Snort](https://www.snort.org/) is for network files, [YARA](https://github.com/VirusTotal/yara) is for files and [Sigma](https://github.com/SigmaHQ/sigma) is for log files. 19 | 20 | **Don't judge, we are still in Beta! Feel free to contribute!** 21 | 22 | ## Why Pythia 23 | Today, IoCs (Indicators of Compromise) are starting to fade away as their lifespan is decreasing. Cybercriminals no longer reuse the same infrastructure, making it difficult for traditional detection methods to keep up. Instead, the new trend is IoFA (Indicators of Future Attacks), where threat actors deploy multiple infrastructure in automated ways. The job of security researchers now involves fingerprinting these assets and searching for them on platforms like Shodan, Censys, FOFA and others to detect them before they are actively used by threat actors for their malicious campaigns. 24 | 25 | Pythia supports this modern approach by providing a common format and easy conversions into multiple platform formats. This enables researchers to validate and enrich their findings efficiently, staying ahead of cyber threats by identifying and mitigating potential attacks before they occur. 26 | 27 | ## Pythia Integrations Included 28 | - [Shodan](https://www.shodan.io/) 29 | - [Censys](https://censys.com/) 30 | - [FOFA](https://en.fofa.info/) 31 | - [BinaryEdge](https://app.binaryedge.io/login) 32 | - [ZoomEye](https://www.zoomeye.hk) 33 | - [Hunter](https://hunter.how/) 34 | 35 | ## Analyzing Pythia Query Format 36 | ![img.png](images/pythia_sample_rule.png) 37 | - **title**: A short capitalised title describing the query in high-level. 38 | - **id**: A unique Pythia id. The id starts with "pythia-" and is supplemented a uuid (you can generate one from here: https://www.uuidgenerator.net/version4). 39 | - **status**: The status of the query (experimental, test, or stable). 40 | - **description**: The description of the query. Here are the details of the query such as the malware/threat actor infrastructure trying to identify, along with the specific fingerprints. 41 | - **references**: Any references as URL for further information. 42 | - **tags**: Any tags, useful for clustering the queries. 43 | - **author**: The author of the query (i.e. FirstName Lastname, @twitter_name) 44 | - **date**: The date the query was created. 45 | - **query**: The query part consists of two sections: the **parameters**, where it consists of _parts_ (part1, part2, .., partN) and the **condition**. For the parameters section, each part is basically a _field_-_value_ pairing. The _field_ must be one of them that Pythia allows (you can find them in the mappings folders). The _values_ of each field may be the ones that someone used in the original platform that run the query. Attention here to also include the operator (i.e. : or =). Lastly, there is the **condition** section where the parts unite in logic, which includes them using logical operators (again found in mappings folder). For more information on how to structure your Pythia queries read [Query_Creation_Guide](documentation/Query_Creation_Guide.md). 46 | - **falsepositives**: Any potential false positives generated by the query. 47 | - **level**: The level of confidence for successfully identifying the true positives. Values include: low, moderate and high. 48 | 49 | 50 | ## How does Pythia Conversions Works 51 | 52 | Pythia includes 1-1 mappings with each of the supported platforms. These mappings are in fact strings in a dictionary. Those strings are searched using regular expressions, inside the condition field of the Pythia query, and if any hit is identified, the convertors perform string substitutions with the value of each platform's mappings. 53 | 54 | Example conversion fields(substitutions: jarm_fingerprint, :, and, http_favicon,:): 55 | - Pythia query (the condition part): 56 | `jarm_fingerprint:"29d29d00000000021c29d29d29d29d1f4989c319e75da83988253a39553038" and http_favicon_hash:"1768726119"` 57 | 58 | - FOFA query (converted result): 59 | `jarm="29d29d00000000021c29d29d29d29d1f4989c319e75da83988253a39553038" && icon_hash="1768726119"` 60 | 61 | ## Key Features 62 | 1. Standardized format 63 | 2. Validator scripts 64 | 3. Convertor scripts to any of the supported platforms 65 | 4. Directly searching the converted queries into the platforms using APIs 66 | 5. Shared location to store infrastructure hunting queries 67 | 68 | ## Roadmap 69 | 1. Create abstract format ✅ 70 | 2. Create validator script ✅ 71 | 2. Create mappings for each platform ✅ 72 | 3. Create script that converts the queries ✅ 73 | 4. Add API integration for searching directly into the platforms✅ 74 | 5. Expand mappings 🔜 75 | 6. Store more queries 🔜 76 | 77 | ## Query Creation 78 | Please refer to [Query_Creation_Guide.md](documentation/Query_Creation_Guide.md) 79 | 80 | ## Contributing & Collaboration 81 | Please refer to [CONTRIBUTING.md](CONTRIBUTING.md). 82 | 83 | If you are interested in collaboration please reach out: 84 | - [Efstratios Lontzetidis](https://www.linkedin.com/in/efstratioslontzetidis/) or [@lontze7](https://x.com/lontze7) 85 | 86 | ## Query Usage and Conversion 87 | CLI Usage 88 | ![img.png](images/pythia_man.png) 89 | 90 | Installation: 91 | 1. Clone and Move to the Repository 92 | ```bash 93 | git clone https://github.com/EfstratiosLontzetidis/pythia.git 94 | cd pythia 95 | ``` 96 | 2. Optional: Set Up a Virtual Environment 97 | ```bash 98 | python3 -m venv venv 99 | source venv/bin/activate 100 | ``` 101 | 3. Install Dependencies 102 | ```bash 103 | pip install -r requirements.txt 104 | ``` 105 | 106 | 4. Start using Pythia! There are queries stored in the queries folder for testing or usage. 107 | 108 | Usage Examples: 109 | 110 | - Validate Pythia query: 111 | 112 | ```bash 113 | python3 pythia.py -file queries/TOOLS/mythic_c2_favicon_hash_dec_or_title.yml -validate 114 | ``` 115 | 116 | - Convert Pythia query to a specified platform's format 117 | 118 | ```bash 119 | python3 pythia.py -file queries/MALWARE/asyncrat_subject_issuer_cn.yml -convert FOFA 120 | ``` 121 | 122 | - Convert Pythia query to a specified platform's format and open the URL in the browser 123 | 124 | ```bash 125 | python3 pythia.py -file queries/MALWARE/hookbot_panel_html_title.yml -convert SHODAN -open_url 126 | ``` 127 | 128 | - Convert Pythia query to a specified platform's format and search its API for results (you must supplement the API credentials in config/api_configs.py) 129 | 130 | ```bash 131 | python3 pythia.py -file queries/MALWARE/meduza_stealer_html_title.yml -convert CENSYS -api 132 | ``` 133 | 134 | - Convert Pythia query to all platforms' format and save them in a file 135 | 136 | ```bash 137 | python3 pythia.py -file queries/MALWARE/quasar_rat_subject_common_name.yml -convert ALL -output_file quasar_results.txt 138 | ``` 139 | 140 | ## Example Use Cases: 141 | - [Discovering Meduza Stealer Infrastructure with Pythia](https://medium.com/@s.lontzetidis/8956876cc15a) 142 | 143 | ## Resources & Further Reading on Hunting Malicious Infrastructure 144 | - [Hunting Adversary Infrastructure Course - Intel-Ops](https://academy.intel-ops.io/courses/hunting-adversary-infra) 145 | - [Will Thomas Blog](https://blog.bushidotoken.net/) 146 | - [Intel-Ops Blog](https://medium.com/@Intel_Ops) 147 | - [Embee Research Blog](https://www.embeeresearch.io/tag/intel/) 148 | - [Censys Blog](https://censys.com/category/blog/) 149 | - [Pivot Atlas](https://gopivot.ing/) 150 | 151 | ## ❗ Limitations 152 | - Pythia works with manual mappings. It's important to be validated to ensure accurate and up-to-date mappings. 153 | - Not all field mappings are included as Pythia is still in Beta. 154 | - Pythia works with string substitutions, thus it may be prone to wrong parts converted. Manual intervention may be required. 155 | - Complex queries do not work well with pythia at the moment. 156 | 157 | ## Maintainers 158 | - [Efstratios Lontzetidis (@lontze7)](https://x.com/lontze7) 159 | 160 | ## Credits 161 | In this section we will include every contributor in any type of assistance. We expect Pythia to grow from the help of the great CTI community. We are very grateful for any kind of help from contributing to the project, to adding/updating Pythia queries. 162 | -------------------------------------------------------------------------------- /api_searcher/api_searcher.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import requests 3 | import ipaddress 4 | import json 5 | from requests.auth import HTTPBasicAuth 6 | from datetime import datetime 7 | from dateutil.relativedelta import relativedelta 8 | from termcolor import colored 9 | 10 | def write_output_to_file(file, data): 11 | with open(file, "a") as f: 12 | f.write(data) 13 | 14 | def is_ipv4(ip): 15 | try: 16 | ipaddress.IPv4Address(ip) 17 | return True 18 | except ipaddress.AddressValueError: 19 | return False 20 | 21 | def log_message(message, file=None, color=None): 22 | if file: 23 | write_output_to_file(file, message + "\n") 24 | else: 25 | if color: 26 | print(colored(message, color)) 27 | else: 28 | print(message) 29 | 30 | def log_api_results(api_name, results, file): 31 | if results: 32 | message = f"{api_name} API results were identified" 33 | log_message(message, file, 'green') 34 | log_message(json.dumps(results, indent=4), file) 35 | else: 36 | message = f"No {api_name} API results identified for this query" 37 | log_message(message, file, 'red') 38 | log_message("-----------------------------------------------------------------------", file) 39 | 40 | def search_fofa_api(api_key, query, file=None): 41 | fofa_url = 'https://fofa.info/api/v1/search/all' 42 | log_message("Starting to search FOFA API...", file) 43 | response = requests.get(f"{fofa_url}?&key={api_key}&qbase64={query}").json() 44 | 45 | if response.get('error') != 'false': 46 | log_message(f"Error in FOFA API results: {response.get('errmsg')}", file, 'red') 47 | else: 48 | log_api_results("FOFA", response.get('results'), file) 49 | 50 | def search_shodan_api(api_key, query, file=None): 51 | shodan_url = 'https://api.shodan.io/shodan/host/search' 52 | log_message("Starting to search SHODAN API...", file) 53 | response = requests.get(f"{shodan_url}?key={api_key}&query={query}").json() 54 | 55 | if response.get('error'): 56 | log_message(f"Error in SHODAN API results: {response.get('error')}", file, 'red') 57 | else: 58 | log_api_results("SHODAN", response.get('matches'), file) 59 | 60 | def search_censys_api(api_id, api_secret, query, file=None): 61 | censys_url = "https://search.censys.io/api/v2/hosts/search" 62 | log_message("Starting to search CENSYS API...", file) 63 | response = requests.get(f"{censys_url}?q={query}", auth=HTTPBasicAuth(api_id, api_secret)).json() 64 | 65 | if response['result']['hits']: 66 | log_api_results("CENSYS", response['result']['hits'], file) 67 | else: 68 | log_message(f"No CENSYS API results identified for this query: {query}", file, 'red') 69 | 70 | def search_hunter_api(api_key, query, file=None): 71 | end_time = datetime.now().strftime("%Y-%m-%d") 72 | start_time = (datetime.now() - relativedelta(months=6)).strftime("%Y-%m-%d") 73 | hunter_url = 'https://api.hunter.how/search' 74 | encoded_query = base64.urlsafe_b64encode(query.encode("utf-8")).decode('ascii') 75 | log_message("Starting to search HUNTER API...", file) 76 | response = requests.get( 77 | f"{hunter_url}?api-key={api_key}&query={encoded_query}&start_time={start_time}&end_time={end_time}" 78 | ).json() 79 | 80 | if response.get('code') not in [200]: 81 | log_message(f"Error in HUNTER API results for query: {query}", file, 'red') 82 | log_message(json.dumps(response), file) 83 | elif response.get('message') == "There were no results matching the query": 84 | log_message(f"No HUNTER API results identified for this query: {query}", file, 'red') 85 | else: 86 | log_api_results("HUNTER", response, file) 87 | 88 | def search_zoomeye_api(api_key, query, file=None): 89 | headers = { 90 | "API-KEY": api_key, 91 | "User-Agent": "Pythia 1.0" 92 | } 93 | zoomeye_url = 'https://api.zoomeye.hk/host/search' 94 | log_message("Starting to search ZOOMEYE API...", file) 95 | response = requests.get(f"{zoomeye_url}?query={query}", headers=headers) 96 | 97 | if response.status_code != 200: 98 | log_message(f"Error in ZOOMEYE API results for query: {query}", file, 'red') 99 | log_message(response.content.decode(), file) 100 | else: 101 | results = response.json() 102 | log_api_results("ZOOMEYE", results if results.get('available') else [], file) 103 | 104 | def search_binaryedge_api(api_key, query, file=None): 105 | headers = {"X-Key": api_key} 106 | binaryedge_url = "https://api.binaryedge.io/v2/query/search" 107 | log_message("Starting to search BINARYEDGE API...", file) 108 | response = requests.get(f"{binaryedge_url}?query={query}", headers=headers) 109 | 110 | if response.status_code != 200: 111 | log_message(f"Error in BINARYEDGE API results for query: {query}", file, 'red') 112 | log_message(response.content.decode(), file) 113 | else: 114 | results = response.json() 115 | log_api_results("BINARYEDGE", results.get('events'), file) -------------------------------------------------------------------------------- /config/api_configs.py: -------------------------------------------------------------------------------- 1 | API_KEYS={ 2 | "fofa_api_key":"changeme", 3 | "censys_api_id":"changeme", 4 | "censys_api_secret":"changeme", 5 | "hunter_api_key":"changeme", 6 | "shodan_api_key":"changeme", 7 | "zoomeye_api_key":"changeme", 8 | "binaryedge_api_key":"changeme" 9 | } 10 | -------------------------------------------------------------------------------- /documentation/Query_Creation_Guide.md: -------------------------------------------------------------------------------- 1 | ## Analyzing Pythia Query Format 2 | ![pythia_sample_rule.png](../images/pythia_query_format.png) 3 | - **title**: A short capitalised title describing the query in high-level. 4 | - **id**: A unique Pythia id. The id starts with "pythia-" and is supplemented a uuid (you can generate one from here: https://www.uuidgenerator.net/version4). 5 | - **status**: The status of the query (experimental, test, or stable). 6 | - **description**: The description of the query. Here are the details of the query such as the malware/threat actor infrastructure trying to identify, along with the specific fingerprints. 7 | - **references**: Any references as URL for further information. 8 | - **tags**: Any tags, useful for clustering the queries. 9 | - **author**: The author of the query (i.e. FirstName Lastname, @twitter_name) 10 | - **date**: The date the query was created. 11 | - **query**: The query part consists of two sections: the **parameters**, where it consists of _parts_ (part1, part2, .., partN). Each part is basically a _field_-_value_ pairing. The _field_ must be one of them that Pythia allows (you can find them in the mappings folders). The _values_ of each field may be the ones that someone used in the original platform that run the query. Attention here to also include the operator (i.e. : or =). Lastly there is the **condition** part where the parts unite in logic, where it includes them using logical operators (again found in mappings folder). For more information on how to structure your Pythia queries read [Query_Creation_Guide](documentation/Query_Creation_Guide.md). 12 | - **falsepositives**: Any potential false positives generated by the query. 13 | - **level**: The level of confidence for successfully identifying the true positives. Values include: low, moderate and high. 14 | 15 | ## How does Pythia Conversions Works 16 | 17 | Pythia includes 1-1 mappings with each of the supported platforms. These mappings are in fact strings in a dictionary. Those strings are searched using regular expressions, inside the condition field of the Pythia query, and if any hit is identified, the convertors perform string substitutions with the value of each platform's mappings. 18 | 19 | Example conversion fields(substitutions: jarm_fingerprint, :, and, http_favicon,:): 20 | - Pythia query (the condition part): 21 | `jarm_fingerprint:"29d29d00000000021c29d29d29d29d1f4989c319e75da83988253a39553038" and http_favicon_hash:"1768726119"` 22 | 23 | - FOFA query (converted result): 24 | `jarm="29d29d00000000021c29d29d29d29d1f4989c319e75da83988253a39553038" && icon_hash="1768726119"` 25 | 26 | ## Steps and Important Tips for Pythia Query Creation (parameters and condition sections) 27 | - Start by creating a query in the syntax of your platform of comfort (i.e. Censys, FOFA, etc.) 28 | - Parameters: Distinguish this script in parts of (field:value). Those will be the parts of your Pythia query (part1, part2, part3, etc.), each containing of a field and a value. 29 | - Parameters: Translate the fields to the equivalent of your platform of choice to the one that Pythia uses. You can find it from the [mappings](mappings.md) table. In example, for FOFA, the field "title" corresponds to Pythia's "http_title" field. 30 | - Parameters: For the values of the fields, make sure to include the match operators (: - fuzzy match,= - exact match,* - wildcard if applicable) since Pythia works with string substitution. 31 | - Condition: Translate the logic operators (those make the condition part of Pythia query) such as AND, OR, NOT. In example, in FOFA platform the "&&" operator corresponds to Pythia's "and". 32 | - Condition: Lastly, in the condition part combine the logic (logical operators descriped in the previous step) along with the parts (in Pythia corresponded syntax). In example (part1 or part2) and (part3 or part4). You can use parenthesis also. 33 | 34 | ## Example Creation of a Pythia Query 35 | A basic use case for Pythia Query creation can be found on this blog: [Exploring Pythia: A Generic Query Format for Discovering Malicious Infrastructure](https://medium.com/p/8956876cc15a). 36 | -------------------------------------------------------------------------------- /documentation/mappings.md: -------------------------------------------------------------------------------- 1 | | PYTHIA | CENSYS | SHODAN | FOFA | ZOOMEYE | BINARYEDGE | HUNTER | 2 | | --------------------------------- | ---------------------------------------------------------- | ---------------------- | ------------------------ | -------------------------- | ---------------------------------------------------- | ------------------ | 3 | | Operators | 4 | | and | and | space | && | + | AND | AND/&& | 5 | | or | or | nothing | \|\| | space | OR | OR/\|\| | 6 | | not | not | \-/! | != | \- | NOT | ! | 7 | | \* | \* | nothing | \*(but with \*=) premium | nothing | \* | nothing | 8 | | : | : | : | \= | : | : | \= | 9 | | \= | \= | : | \== | nothing | .keyword: | \== | 10 | | Fields | 11 | | HOST | 12 | | label | labels | tag (premium) | category | nothing | tag | nothing | 13 | | ip_address | ip | ip | ip | ip | ip | ip | 14 | | cidr | ip | ip | ip | cidr | ip | ip | 15 | | server | name | hostname | server | hostname | nothing | domain | 16 | | domain | dns.names | server | domain | site | nothing | domain | 17 | | hostname | name | hostname | host | hostname | nothing | domain | 18 | | organization | nothing | org | org | org | nothing | nothing | 19 | | port_number | services.port | port | port | port | port | ip.port | 20 | | base_protocol | services.transport_protocol | nothing | base_protocol | service | protocol | protocol.transport | 21 | | protocol | services.service_name | nothing | protocol | service | name | protocol | 22 | | port_size_gt | service_count | nothing | port_size_gt | nothing | nothing | ip.port_count> | 23 | | port_size_lt | service_count | nothing | port_size_lt | nothing | nothing | ip.port_count< | 24 | | port_size | service_count | nothing | port_size | nothing | nothing | ip.port_count== | 25 | | operating_system | operating_system.product | os | os | os | ostype | nothing | 26 | | application | services.software | product | app | app | product | product.name | 27 | | product_version | services.software.version | version | nothing | nothing | version | product.version | 28 | | product | services.software.product | product | product | product | product | product.name | 29 | | time_scanned | last_updated_at | scan | nothing | nothing | created_at | nothing | 30 | | common_platform_enumeration_cpe | services.software.cpe | cpe | nothing | nothing | cpe | nothing | 31 | | country_code | location.country_code | country | country | country | country | ip.country | 32 | | country_name | location.country | nothing | nothing | nothing | geoip.country_name | nothing | 33 | | postal_code | location.postal_code | postal | nothing | nothing | nothing | nothing | 34 | | state_name | location.province | state | nothing | nothing | nothing | ip.state | 35 | | region_name | location.continent | region | region | subdivisions | nothing | ip.state | 36 | | city_name | location.city | city | city | city | geoip.city_name | ip.city | 37 | | autonomous_system_number | autonomous_system.asn | asn | asn | asn | asn | as.number | 38 | | autonomous_system_name | autonomous_system.name | asn | asn | asn | as_name | as.name | 39 | | autonomous_system_organization | autonomous_system.organization | asn | asn | asn | nothing | as.org | 40 | | HTTP | 41 | | http_title | services.http.response.html_title | http.title | title | title | web.title | web.title | 42 | | http_header_hash | services.banner_hashes | http.headers_hash | header_hash | nothing | web.headers.header_order_md5 | nothing | 43 | | http_header | services.http.response.headers.value.headers | http.html | header | nothing | web.headers.all | header | 44 | | service_banner_hash | services.banner_hashes | http.headers_hash | header_hash | nothing | banner_sha256 | nothing | 45 | | service_banner | services.banner | http.html | banner | nothing | banner | protocol.banner | 46 | | http_body_hash | services.http.response.body_hashes | http.html_hash | body_hash | nothing | web.body.sha256 | nothing | 47 | | http_body | services.http.response.body | http.html | body | nothing | web.body.content | web.body | 48 | | http_status_code | services.http.response.status_code | http.status | status_code | nothing | web.status.code | header.status_code | 49 | | http_favicon_hash | services.http.response.favicons.md5_hash | http.favicon.hash | nothing | iconhash | web.favicon.md5 | favicon_hash | 50 | | http_favicon_hash_dec | services.http.response.favicons.shodan_hash | http.favicon.hash | icon_hash | iconhash | web.favicon.mmh3 | nothing | 51 | | SSL/TLS | 52 | | tls_certificate_issuer_org | services.tls.certificate.parsed.issuer.organization | ssl.cert.issuer.cn | cert.issuer.org | ssl.cert.issuer.cn | ssl.cert.issuer.organization_name | cert.issuer_org | 53 | | tls_certificate_subject_org | services.tls.certificate.parsed.subject.organization | ssl.cert.subject.cn | cert.subject.org | ssl.cert.subject.cn | ssl.cert.subject.organization_name | cert.subject_org | 54 | | tls_certificate_subject_cn | services.tls.certificate.parsed.subject.common_name | ssl.cert.subject.cn | cert.subject.cn | ssl.cert.subject.cn | ssl.cert.subject.common_name | cert.subject | 55 | | tls_certificate_issuer_cn | services.tls.certificate.parsed.issuer.common_name | ssl.cert.issuer.cn | cert.issuer.cn | ssl.cert.issuer.cn | ssl.cert.issuer.common_name | cert.issuer | 56 | | tls_certificate_issuer | services.tls.certificate.parsed.issuer | ssl.cert.issuer.cn | cert.issuer | ssl.cert.issuer.cn | ssl.cert.issuer | cert.issuer | 57 | | tls_certificate_subject | services.tls.certificate.parsed.subject | ssl.cert.subject.cn | cert.subject | ssl.cert.subject.cn | ssl.cert.subject | cert.subject | 58 | | tls_certificate_not_after | services.tls.certificate.parsed.validity_period.not_after | nothing | nothing | nothing | cert.not_after | nothing | 59 | | tls_certificate_not_before | services.tls.certificate.parsed.validity_period.not_before | nothing | nothing | nothing | cert.not_before | nothing | 60 | | tls_certificate_sha1 | services.tls.certificate.fingerprint_sha1 | ssl.cert.fingerprint | cert | ssl.cert.fingerprint | ssl.cert.sha1_fingerprint | cert.sha-1 | 61 | | tls_certificate_sha256 | services.tls.certificate.fingerprint_sha256 | ssl.cert.fingerprint | cert | ssl.cert.fingerprint | ssl.cert.sha256_fingerprint | cert.sha-256 | 62 | | tls_certificate_md5 | services.tls.certificate.fingerprint_md5 | ssl.cert.fingerprint | cert | ssl.cert.fingerprint | nothing | cert.sha-md5 | 63 | | tls_certificate_is_expired | services.tls.certificate.revoked:true | ssl.cert.expired:true | cert.is_expired | ssl.cert.availability:0 | nothing | cert.is_expired | 64 | | tls_certificate.is_valid | services.tls.certificate.revoked:false | ssl.cert.expired:false | cert.is_valid | ssl.cert.availability:1 | nothing | cert.is_trust | 65 | | tls_certificate.pubkey_rsa_bits | services.tls.certificates.leaf_data.public_key.rsa.length | ssl.cert.pubkey.bits | nothing | ssl.cert.pubkey.rsa.bits | cert.public_key_info.key_size | nothing | 66 | | tls_certificate_pubkey_ecdsa_bits | services.ssh.server_host_key.ecdsa_public_key.length | ssl.cert.pubkey.bits | nothing | ssl.cert.pubkey.ecdsa.bits | cert.public_key_info.key_size | nothing | 67 | | tls_certificate_pubkey_type | services.tls.certificates.leaf_data.pubkey_algorithm | ssl.cert.pubkey.type | nothing | ssl.cert.pubkey.type | cert.public_key_info.algorithm | nothing | 68 | | tls_certificate_cipher_name | services.tls.cipher_selected | ssl.cipher.name | nothing | ssl.cipher.name | ssl.ciphers | nothing | 69 | | tls_certificate_algorithm | services.tls.certificates.leaf_data.pubkey_algorithm | ssl.cert.alg | nothing | ssl.cert.alg | ssl.cert.signature_algorithm | nothing | 70 | | tls_certificate_version | services.tls.version_selected | ssl.version | tls.version | ssl.version | ssl.server_info.highest_ssl_version_supported_string | nothing | 71 | | tls_certificate | services.certificate | ssl | cert | ssl | ssl.cert | nothing | 72 | | jarm_fingerprint | services.jarm.fingerprint | ssl.jarm | jarm | jarm | jarm.jarm_hash | tls-jarm.hash | 73 | | ja3s_fingerprint | services.tls.ja3s | ssl.ja3s | tls.ja3s | ssl.cert.fingerprint | ssl.server_info.ja3_digest | nothing | 74 | | ja4s_fingerprint | services.tls.ja4s | nothing | nothing | nothing | nothing | nothing | 75 | | SSH | 76 | | ssh_hassh | services.ssh.hassh_fingerprint | ssh.hassh | nothing | nothing | ssh.hassh | nothing | 77 | | ssh_banner_sha256 | services.banner_hashes | nothing | nothing | nothing | ssh.banner_sha256 | nothing | 78 | | ssh_banner | services.banner | nothing | nothing | nothing | ssh.banner | nothing | 79 | | ssh_key_length | services.ssh.server_host_key.rsa_public_key.length | nothing | nothing | nothing | nothing | nothing | 80 | | ssh_fingerprint | services.ssh.server_host_key.fingerprint_sha256 | nothing | nothing | nothing | ssh.fingerprint | nothing | 81 | -------------------------------------------------------------------------------- /documentation/pythia_query_format.yml: -------------------------------------------------------------------------------- 1 | title: A Short Capitalised Title with Less Than 50 Characters 2 | id: pythia-[generate one here](https://www.uuidgenerator.net/version4) 3 | status: experimental|test|stable 4 | description: A description of what your query is meant to search 5 | references: 6 | - A list of all references that can help a reader or analyst understand the origin for creating the query 7 | tags: 8 | - relevant.tag1 # example tag kimsuky_apt 9 | - relevant.tag2 # example tag kematian_stealer 10 | author: Your Name, Contributor Name # example, a list of authors 11 | date: YYYY/MM/DD # Rule date 12 | query: 13 | parameters: 14 | part1: 15 | dsdsds: ':StringValue*' # :,*,= are all operators inside the query supported by pythia 16 | part2: 17 | sdsdsds: '=IntegerValue' 18 | part3: 19 | dsdsds: 'Value' 20 | condition: describe the condition of the query here # example (part1 and part2) or part3 21 | falsepositives: 22 | - describe possible false positive conditions to help the analysts in their investigation 23 | level: low|moderate|high - one of the confidence levels 24 | -------------------------------------------------------------------------------- /images/pythia-logos/pdf/logo-black.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EfstratiosLontzetidis/pythia/d8a86299678aff3adf3a2b36f8495bca43ed36e8/images/pythia-logos/pdf/logo-black.pdf -------------------------------------------------------------------------------- /images/pythia-logos/pdf/logo-color.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EfstratiosLontzetidis/pythia/d8a86299678aff3adf3a2b36f8495bca43ed36e8/images/pythia-logos/pdf/logo-color.pdf -------------------------------------------------------------------------------- /images/pythia-logos/pdf/logo-no-background.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EfstratiosLontzetidis/pythia/d8a86299678aff3adf3a2b36f8495bca43ed36e8/images/pythia-logos/pdf/logo-no-background.pdf -------------------------------------------------------------------------------- /images/pythia-logos/pdf/logo-white.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EfstratiosLontzetidis/pythia/d8a86299678aff3adf3a2b36f8495bca43ed36e8/images/pythia-logos/pdf/logo-white.pdf -------------------------------------------------------------------------------- /images/pythia-logos/png/logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EfstratiosLontzetidis/pythia/d8a86299678aff3adf3a2b36f8495bca43ed36e8/images/pythia-logos/png/logo-black.png -------------------------------------------------------------------------------- /images/pythia-logos/png/logo-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EfstratiosLontzetidis/pythia/d8a86299678aff3adf3a2b36f8495bca43ed36e8/images/pythia-logos/png/logo-color.png -------------------------------------------------------------------------------- /images/pythia-logos/png/logo-no-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EfstratiosLontzetidis/pythia/d8a86299678aff3adf3a2b36f8495bca43ed36e8/images/pythia-logos/png/logo-no-background.png -------------------------------------------------------------------------------- /images/pythia-logos/png/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EfstratiosLontzetidis/pythia/d8a86299678aff3adf3a2b36f8495bca43ed36e8/images/pythia-logos/png/logo-white.png -------------------------------------------------------------------------------- /images/pythia-logos/svg/logo-black.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/pythia-logos/svg/logo-color.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/pythia-logos/svg/logo-no-background.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/pythia-logos/svg/logo-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/pythia_man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EfstratiosLontzetidis/pythia/d8a86299678aff3adf3a2b36f8495bca43ed36e8/images/pythia_man.png -------------------------------------------------------------------------------- /images/pythia_query_format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EfstratiosLontzetidis/pythia/d8a86299678aff3adf3a2b36f8495bca43ed36e8/images/pythia_query_format.png -------------------------------------------------------------------------------- /images/pythia_sample_rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EfstratiosLontzetidis/pythia/d8a86299678aff3adf3a2b36f8495bca43ed36e8/images/pythia_sample_rule.png -------------------------------------------------------------------------------- /mappings/mappings.md: -------------------------------------------------------------------------------- 1 | | PYTHIA | CENSYS | SHODAN | FOFA | ZOOMEYE | BINARYEDGE | HUNTER | 2 | | --------------------------------- | ---------------------------------------------------------- | ---------------------- | ------------------------ | -------------------------- | ---------------------------------------------------- | ------------------ | 3 | | Operators | 4 | | and | and | space | && | + | AND | AND/&& | 5 | | or | or | nothing | \|\| | space | OR | OR/\|\| | 6 | | not | not | \-/! | != | \- | NOT | ! | 7 | | \* | \* | nothing | \*(but with \*=) premium | nothing | \* | nothing | 8 | | : | : | : | \= | : | : | \= | 9 | | \= | \= | : | \== | nothing | .keyword: | \== | 10 | | Fields | 11 | | HOST | 12 | | label | labels | tag (premium) | category | nothing | tag | nothing | 13 | | ip_address | ip | ip | ip | ip | ip | ip | 14 | | cidr | ip | ip | ip | cidr | ip | ip | 15 | | server | name | hostname | server | hostname | nothing | domain | 16 | | domain | dns.names | server | domain | site | nothing | domain | 17 | | hostname | name | hostname | host | hostname | nothing | domain | 18 | | organization | nothing | org | org | org | nothing | nothing | 19 | | port_number | services.port | port | port | port | port | ip.port | 20 | | base_protocol | services.transport_protocol | nothing | base_protocol | service | protocol | protocol.transport | 21 | | protocol | services.service_name | nothing | protocol | service | name | protocol | 22 | | port_size_gt | service_count | nothing | port_size_gt | nothing | nothing | ip.port_count> | 23 | | port_size_lt | service_count | nothing | port_size_lt | nothing | nothing | ip.port_count< | 24 | | port_size | service_count | nothing | port_size | nothing | nothing | ip.port_count== | 25 | | operating_system | operating_system.product | os | os | os | ostype | nothing | 26 | | application | services.software | product | app | app | product | product.name | 27 | | product_version | services.software.version | version | nothing | nothing | version | product.version | 28 | | product | services.software.product | product | product | product | product | product.name | 29 | | time_scanned | last_updated_at | scan | nothing | nothing | created_at | nothing | 30 | | common_platform_enumeration_cpe | services.software.cpe | cpe | nothing | nothing | cpe | nothing | 31 | | country_code | location.country_code | country | country | country | country | ip.country | 32 | | country_name | location.country | nothing | nothing | nothing | geoip.country_name | nothing | 33 | | postal_code | location.postal_code | postal | nothing | nothing | nothing | nothing | 34 | | state_name | location.province | state | nothing | nothing | nothing | ip.state | 35 | | region_name | location.continent | region | region | subdivisions | nothing | ip.state | 36 | | city_name | location.city | city | city | city | geoip.city_name | ip.city | 37 | | autonomous_system_number | autonomous_system.asn | asn | asn | asn | asn | as.number | 38 | | autonomous_system_name | autonomous_system.name | asn | asn | asn | as_name | as.name | 39 | | autonomous_system_organization | autonomous_system.organization | asn | asn | asn | nothing | as.org | 40 | | HTTP | 41 | | http_title | services.http.response.html_title | http.title | title | title | web.title | web.title | 42 | | http_header_hash | services.banner_hashes | http.headers_hash | header_hash | nothing | web.headers.header_order_md5 | nothing | 43 | | http_header | services.http.response.headers.value.headers | http.html | header | nothing | web.headers.all | header | 44 | | service_banner_hash | services.banner_hashes | http.headers_hash | header_hash | nothing | banner_sha256 | nothing | 45 | | service_banner | services.banner | http.html | banner | nothing | banner | protocol.banner | 46 | | http_body_hash | services.http.response.body_hashes | http.html_hash | body_hash | nothing | web.body.sha256 | nothing | 47 | | http_body | services.http.response.body | http.html | body | nothing | web.body.content | web.body | 48 | | http_status_code | services.http.response.status_code | http.status | status_code | nothing | web.status.code | header.status_code | 49 | | http_favicon_hash | services.http.response.favicons.md5_hash | http.favicon.hash | nothing | iconhash | web.favicon.md5 | favicon_hash | 50 | | http_favicon_hash_dec | services.http.response.favicons.shodan_hash | http.favicon.hash | icon_hash | iconhash | web.favicon.mmh3 | nothing | 51 | | SSL/TLS | 52 | | tls_certificate_issuer_org | services.tls.certificate.parsed.issuer.organization | ssl.cert.issuer.cn | cert.issuer.org | ssl.cert.issuer.cn | ssl.cert.issuer.organization_name | cert.issuer_org | 53 | | tls_certificate_subject_org | services.tls.certificate.parsed.subject.organization | ssl.cert.subject.cn | cert.subject.org | ssl.cert.subject.cn | ssl.cert.subject.organization_name | cert.subject_org | 54 | | tls_certificate_subject_cn | services.tls.certificate.parsed.subject.common_name | ssl.cert.subject.cn | cert.subject.cn | ssl.cert.subject.cn | ssl.cert.subject.common_name | cert.subject | 55 | | tls_certificate_issuer_cn | services.tls.certificate.parsed.issuer.common_name | ssl.cert.issuer.cn | cert.issuer.cn | ssl.cert.issuer.cn | ssl.cert.issuer.common_name | cert.issuer | 56 | | tls_certificate_issuer | services.tls.certificate.parsed.issuer | ssl.cert.issuer.cn | cert.issuer | ssl.cert.issuer.cn | ssl.cert.issuer | cert.issuer | 57 | | tls_certificate_subject | services.tls.certificate.parsed.subject | ssl.cert.subject.cn | cert.subject | ssl.cert.subject.cn | ssl.cert.subject | cert.subject | 58 | | tls_certificate_not_after | services.tls.certificate.parsed.validity_period.not_after | nothing | nothing | nothing | cert.not_after | nothing | 59 | | tls_certificate_not_before | services.tls.certificate.parsed.validity_period.not_before | nothing | nothing | nothing | cert.not_before | nothing | 60 | | tls_certificate_sha1 | services.tls.certificate.fingerprint_sha1 | ssl.cert.fingerprint | cert | ssl.cert.fingerprint | ssl.cert.sha1_fingerprint | cert.sha-1 | 61 | | tls_certificate_sha256 | services.tls.certificate.fingerprint_sha256 | ssl.cert.fingerprint | cert | ssl.cert.fingerprint | ssl.cert.sha256_fingerprint | cert.sha-256 | 62 | | tls_certificate_md5 | services.tls.certificate.fingerprint_md5 | ssl.cert.fingerprint | cert | ssl.cert.fingerprint | nothing | cert.sha-md5 | 63 | | tls_certificate_is_expired | services.tls.certificate.revoked:true | ssl.cert.expired:true | cert.is_expired | ssl.cert.availability:0 | nothing | cert.is_expired | 64 | | tls_certificate.is_valid | services.tls.certificate.revoked:false | ssl.cert.expired:false | cert.is_valid | ssl.cert.availability:1 | nothing | cert.is_trust | 65 | | tls_certificate.pubkey_rsa_bits | services.tls.certificates.leaf_data.public_key.rsa.length | ssl.cert.pubkey.bits | nothing | ssl.cert.pubkey.rsa.bits | cert.public_key_info.key_size | nothing | 66 | | tls_certificate_pubkey_ecdsa_bits | services.ssh.server_host_key.ecdsa_public_key.length | ssl.cert.pubkey.bits | nothing | ssl.cert.pubkey.ecdsa.bits | cert.public_key_info.key_size | nothing | 67 | | tls_certificate_pubkey_type | services.tls.certificates.leaf_data.pubkey_algorithm | ssl.cert.pubkey.type | nothing | ssl.cert.pubkey.type | cert.public_key_info.algorithm | nothing | 68 | | tls_certificate_cipher_name | services.tls.cipher_selected | ssl.cipher.name | nothing | ssl.cipher.name | ssl.ciphers | nothing | 69 | | tls_certificate_algorithm | services.tls.certificates.leaf_data.pubkey_algorithm | ssl.cert.alg | nothing | ssl.cert.alg | ssl.cert.signature_algorithm | nothing | 70 | | tls_certificate_version | services.tls.version_selected | ssl.version | tls.version | ssl.version | ssl.server_info.highest_ssl_version_supported_string | nothing | 71 | | tls_certificate | services.certificate | ssl | cert | ssl | ssl.cert | nothing | 72 | | jarm_fingerprint | services.jarm.fingerprint | ssl.jarm | jarm | jarm | jarm.jarm_hash | tls-jarm.hash | 73 | | ja3s_fingerprint | services.tls.ja3s | ssl.ja3s | tls.ja3s | ssl.cert.fingerprint | ssl.server_info.ja3_digest | nothing | 74 | | ja4s_fingerprint | services.tls.ja4s | nothing | nothing | nothing | nothing | nothing | 75 | | SSH | 76 | | ssh_hassh | services.ssh.hassh_fingerprint | ssh.hassh | nothing | nothing | ssh.hassh | nothing | 77 | | ssh_banner_sha256 | services.banner_hashes | nothing | nothing | nothing | ssh.banner_sha256 | nothing | 78 | | ssh_banner | services.banner | nothing | nothing | nothing | ssh.banner | nothing | 79 | | ssh_key_length | services.ssh.server_host_key.rsa_public_key.length | nothing | nothing | nothing | nothing | nothing | 80 | | ssh_fingerprint | services.ssh.server_host_key.fingerprint_sha256 | nothing | nothing | nothing | ssh.fingerprint | nothing | 81 | -------------------------------------------------------------------------------- /mappings/pythia_binaryedge.py: -------------------------------------------------------------------------------- 1 | pythia_binaryedge_mappings={ 2 | "and":"AND", 3 | "or":"OR", 4 | "not":"NOT", 5 | "*":"*", 6 | ":":":", 7 | "=":".keyword:", 8 | "label":"tag", 9 | "ip_address":"ip", 10 | "cidr":"ip", 11 | "server":"", 12 | "domain":"", 13 | "hostname":"", 14 | "organization":"", 15 | "port_number":"port", 16 | "base_protocol":"protocol", 17 | "protocol":"name", 18 | "port_size_gt":"", 19 | "port_size_lt":"", 20 | "port_size":"", 21 | "operating_system":"ostype", 22 | "application":"product", 23 | "product_version":"version", 24 | "product":"product", 25 | "time_scanned":"created_at", 26 | "common_platform_enumeration_cpe":"cpe", 27 | "country_code":"country", 28 | "country_name":"geoip.country_name", 29 | "postal_code":"", 30 | "state_name":"", 31 | "region_name":"", 32 | "city_name":"geoip.city_name", 33 | "autonomous_system_number":"asn", 34 | "autonomous_system_name":"as_name", 35 | "autonomous_system_organization":"", 36 | "http_title":"web.title", 37 | "http_header_hash":"web.headers.header_order_md5", 38 | "http_header":"web.headers.all", 39 | "service_banner_hash":"banner_sha256", 40 | "service_banner":"banner", 41 | "http_body_hash":"web.body.sha256", 42 | "http_body":"web.body.content", 43 | "http_status_code":"web.status.code", 44 | "http_favicon_hash_dec":"web.favicon.mmh3", 45 | "http_favicon_hash":"web.favicon.md5", 46 | "tls_certificate_issuer_org":"ssl.cert.issuer.organization_name", 47 | "tls_certificate_subject_org":"ssl.cert.subject.organization_name", 48 | "tls_certificate_subject_cn":"ssl.cert.subject.common_name", 49 | "tls_certificate_issuer_cn":"ssl.cert.issuer.common_name", 50 | "tls_certificate_issuer":"ssl.cert.issuer", 51 | "tls_certificate_subject":"ssl.cert.subject", 52 | "tls_certificate_not_after":"cert.not_after", 53 | "tls_certificate_not_before":"cert.not_before", 54 | "tls_certificate_sha1":"ssl.cert.sha1_fingerprint", 55 | "tls_certificate_sha256":"ssl.cert.sha256_fingerprint", 56 | "tls_certificate_md5":"", 57 | "tls_certificate_is_expired":"", 58 | "tls_certificate.is_valid":"", 59 | "tls_certificate.pubkey_rsa_bits":"cert.public_key_info.key_size", 60 | "tls_certificate_pubkey_ecdsa_bits":"cert.public_key_info.key_size", 61 | "tls_certificate_pubkey_type":"cert.public_key_info.algorithm", 62 | "tls_certificate_cipher_name":"ssl.ciphers", 63 | "tls_certificate_algorithm":"ssl.cert.signature_algorithm", 64 | "tls_certificate_version":"ssl.server_info.highest_ssl_version_supported_string", 65 | "tls_certificate":"ssl.cert", 66 | "jarm_fingerprint":"jarm.jarm_hash", 67 | "ja3s_fingerprint":"ssl.server_info.ja3_digest", 68 | "ja4s_fingerprint":"", 69 | "ssh_hassh":"ssh.hassh", 70 | "ssh_banner_sha256":"ssh.banner_sha256", 71 | "ssh_banner":"ssh.banner", 72 | "ssh_key_length":"", 73 | "ssh_fingerprint":"ssh.fingerprint" 74 | } 75 | -------------------------------------------------------------------------------- /mappings/pythia_censys.py: -------------------------------------------------------------------------------- 1 | pythia_censys_mappings={ 2 | "and":"and", 3 | "or":"or", 4 | "not":"not", 5 | "*":"*", 6 | ":":":", 7 | "=":"=", 8 | "label":"labels", 9 | "ip_address":"ip", 10 | "cidr":"ip", 11 | "server":"name", 12 | "domain":"name", 13 | "hostname":"dns.names", 14 | "organization":"name", 15 | "port_number":"services.port", 16 | "base_protocol":"services.transport_protocol", 17 | "protocol":"services.service_name", 18 | "port_size_gt":"", 19 | "port_size_lt":"", 20 | "port_size":"service_count", 21 | "operating_system":"operating_system.product", 22 | "application":"services.software", 23 | "product_version":"services.software.version", 24 | "product":"services.software.product", 25 | "time_scanned":"last_updated_at", 26 | "common_platform_enumeration_cpe":"services.software.cpe", 27 | "country_code":"location.country_code", 28 | "country_name":"location.country", 29 | "postal_code":"location.postal_code", 30 | "state_name":"location.province", 31 | "region_name":"location.continent", 32 | "city_name":"location.city", 33 | "autonomous_system_number":"autonomous_system.asn", 34 | "autonomous_system_name":"autonomous_system.name", 35 | "autonomous_system_organization":"autonomous_system.organization", 36 | "http_title":"services.http.response.html_title", 37 | "http_header_hash":"services.banner_hashes", 38 | "http_header":"services.http.response.headers.value.headers", 39 | "service_banner_hash":"services.banner_hashes", 40 | "service_banner":"services.banner", 41 | "http_body_hash":"services.http.response.body_hashes", 42 | "http_body":"services.http.response.body", 43 | "http_status_code":"services.http.response.status_code", 44 | "http_favicon_hash_dec":"services.http.response.favicons.shodan_hash", 45 | "http_favicon_hash":"services.http.response.favicons.md5_hash", 46 | "tls_certificate_issuer_org":"services.tls.certificate.parsed.issuer.organization", 47 | "tls_certificate_subject_org":"services.tls.certificate.parsed.subject.organization", 48 | "tls_certificate_subject_cn":"services.tls.certificate.parsed.subject.common_name", 49 | "tls_certificate_issuer_cn":"services.tls.certificate.parsed.issuer.common_name", 50 | "tls_certificate_issuer":"services.tls.certificate.parsed.issuer", 51 | "tls_certificate_subject":"services.tls.certificate.parsed.subject", 52 | "tls_certificate_not_after":"services.tls.certificate.parsed.validity_period.not_after", 53 | "tls_certificate_not_before":"services.tls.certificate.parsed.validity_period.not_before", 54 | "tls_certificate_sha1":"services.tls.certificate.fingerprint_sha1", 55 | "tls_certificate_sha256":"services.tls.certificate.fingerprint_sha256", 56 | "tls_certificate_md5":"services.tls.certificate.fingerprint_md5", 57 | "tls_certificate_is_expired":"services.tls.certificate.revoked:true", 58 | "tls_certificate.is_valid":"services.tls.certificate.revoked:false", 59 | "tls_certificate.pubkey_rsa_bits":"services.tls.certificates.leaf_data.public_key.rsa.length", 60 | "tls_certificate_pubkey_ecdsa_bits":"services.ssh.server_host_key.ecdsa_public_key.length", 61 | "tls_certificate_pubkey_type":"services.tls.certificates.leaf_data.pubkey_algorithm", 62 | "tls_certificate_cipher_name":"services.tls.cipher_selected", 63 | "tls_certificate_algorithm":"services.tls.certificates.leaf_data.pubkey_algorithm", 64 | "tls_certificate_version":"services.tls.version_selected", 65 | "tls_certificate":"services.certificate", 66 | "jarm_fingerprint":"services.jarm.fingerprint", 67 | "ja3s_fingerprint":"services.tls.ja3s", 68 | "ja4s_fingerprint":"services.tls.ja4s", 69 | "ssh_hassh":"services.ssh.hassh_fingerprint", 70 | "ssh_banner_sha256":"services.banner_hashes", 71 | "ssh_banner":"services.banner", 72 | "ssh_key_length":"services.ssh.server_host_key.rsa_public_key.length", 73 | "ssh_fingerprint":"services.ssh.server_host_key.fingerprint_sha256" 74 | } 75 | -------------------------------------------------------------------------------- /mappings/pythia_fofa.py: -------------------------------------------------------------------------------- 1 | pythia_fofa_mappings={ 2 | "and":"&&", 3 | "or":"||", 4 | "not":"!=", 5 | "*":"*", 6 | "=":"==", 7 | ":":"=", 8 | "label":"category", 9 | "ip_address":"ip", 10 | "cidr":"ip", 11 | "server":"server", 12 | "domain":"domain", 13 | "hostname":"host", 14 | "organization":"org", 15 | "port_number":"port", 16 | "base_protocol":"base_protocol", 17 | "protocol":"protocol", 18 | "port_size_gt":"port_size_gt", 19 | "port_size_lt":"port_size_lt", 20 | "port_size":"port_size", 21 | "operating_system":"os", 22 | "application":"app", 23 | "product_version":"", 24 | "product":"product", 25 | "time_scanned":"", 26 | "common_platform_enumeration_cpe":"", 27 | "country_code":"country", 28 | "country_name":"", 29 | "postal_code":"", 30 | "state_name":"", 31 | "region_name":"region", 32 | "city_name":"city", 33 | "autonomous_system_number":"asn", 34 | "autonomous_system_name":"asn", 35 | "autonomous_system_organization":"asn", 36 | "http_title":"title", 37 | "http_header_hash":"header_hash", 38 | "http_header":"header", 39 | "service_banner_hash":"header_hash", 40 | "service_banner":"banner", 41 | "http_body_hash":"body_hash", 42 | "http_body":"body", 43 | "http_status_code":"status_code", 44 | "http_favicon_hash_dec":"icon_hash", 45 | "http_favicon_hash":"", 46 | "tls_certificate_issuer_org":"cert.issuer.org", 47 | "tls_certificate_subject_org":"cert.subject.org", 48 | "tls_certificate_subject_cn":"cert.subject.cn", 49 | "tls_certificate_issuer_cn":"cert.issuer.cn", 50 | "tls_certificate_issuer":"cert.issuer", 51 | "tls_certificate_subject":"cert.subject", 52 | "tls_certificate_not_after":"", 53 | "tls_certificate_not_before":"", 54 | "tls_certificate_sha1":"cert", 55 | "tls_certificate_sha256":"cert", 56 | "tls_certificate_md5":"cert", 57 | "tls_certificate_is_expired":"cert.is_expired", 58 | "tls_certificate.is_valid":"cert.is_valid", 59 | "tls_certificate.pubkey_rsa_bits":"", 60 | "tls_certificate_pubkey_ecdsa_bits":"", 61 | "tls_certificate_pubkey_type":"", 62 | "tls_certificate_cipher_name":"", 63 | "tls_certificate_algorithm":"", 64 | "tls_certificate_version":"tls.version", 65 | "tls_certificate":"cert", 66 | "jarm_fingerprint":"jarm", 67 | "ja3s_fingerprint":"tls.ja3s", 68 | "ja4s_fingerprint":"", 69 | "ssh_hassh":"", 70 | "ssh_banner_sha256":"", 71 | "ssh_banner":"", 72 | "ssh_key_length":"", 73 | "ssh_fingerprint":"" 74 | } 75 | -------------------------------------------------------------------------------- /mappings/pythia_hunter.py: -------------------------------------------------------------------------------- 1 | pythia_hunter_mappings={ 2 | "and":"&&", 3 | "or":"||", 4 | "not":"!", 5 | "*":"", 6 | "=":"==", 7 | ":":"=", 8 | "label":"", 9 | "ip_address":"ip", 10 | "cidr":"ip", 11 | "server":"domain", 12 | "domain":"domain", 13 | "hostname":"domain", 14 | "organization":"", 15 | "port_number":"ip.port", 16 | "base_protocol":"protocol.transport", 17 | "protocol":"protocol", 18 | "port_size_gt":"ip.port_count>", 19 | "port_size_lt":"ip.port_count<", 20 | "port_size":"ip.port_count", 21 | "operating_system":"", 22 | "application":"product.name", 23 | "product_version":"product.version", 24 | "product":"product.name", 25 | "time_scanned":"", 26 | "common_platform_enumeration_cpe":"", 27 | "country_code":"ip.country", 28 | "country_name":"", 29 | "postal_code":"", 30 | "state_name":"ip.state", 31 | "region_name":"ip.state", 32 | "city_name":"ip.city", 33 | "autonomous_system_number":"as.number", 34 | "autonomous_system_name":"as.name", 35 | "autonomous_system_organization":"as.org", 36 | "http_title":"web.title", 37 | "http_header_hash":"", 38 | "http_header":"header", 39 | "service_banner_hash":"", 40 | "service_banner":"protocol.banner", 41 | "http_body_hash":"", 42 | "http_body":"web.body", 43 | "http_status_code":"header.status_code", 44 | "http_favicon_hash_dec":"", 45 | "http_favicon_hash":"favicon_hash", 46 | "tls_certificate_issuer_org":"cert.issuer_org", 47 | "tls_certificate_subject_org":"cert.subject_org", 48 | "tls_certificate_subject_cn":"cert.subject", 49 | "tls_certificate_issuer_cn":"cert.issuer", 50 | "tls_certificate_issuer":"cert.issuer", 51 | "tls_certificate_subject":"cert.subject", 52 | "tls_certificate_not_after":"", 53 | "tls_certificate_not_before":"", 54 | "tls_certificate_sha1":"cert.sha-1", 55 | "tls_certificate_sha256":"cert.sha-256", 56 | "tls_certificate_md5":"cert.sha-md5", 57 | "tls_certificate_is_expired":"cert.is_expired", 58 | "tls_certificate.is_valid":"cert.is_trust", 59 | "tls_certificate.pubkey_rsa_bits":"", 60 | "tls_certificate_pubkey_ecdsa_bits":"", 61 | "tls_certificate_pubkey_type":"", 62 | "tls_certificate_cipher_name":"", 63 | "tls_certificate_algorithm":"", 64 | "tls_certificate_version":"", 65 | "tls_certificate":"", 66 | "jarm_fingerprint":"tls-jarm.hash", 67 | "ja3s_fingerprint":"", 68 | "ja4s_fingerprint":"", 69 | "ssh_hassh":"", 70 | "ssh_banner_sha256":"", 71 | "ssh_banner":"", 72 | "ssh_key_length":"", 73 | "ssh_fingerprint":"" 74 | } 75 | -------------------------------------------------------------------------------- /mappings/pythia_shodan.py: -------------------------------------------------------------------------------- 1 | pythia_shodan_mappings={ 2 | "and":" ", 3 | "or":"", 4 | "not":"-", 5 | "*":"", 6 | ":":":", 7 | "=":":", 8 | "label":"tag", 9 | "ip_address":"ip", 10 | "cidr":"ip", 11 | "server":"hostname", 12 | "domain":"server", 13 | "hostname":"hostname", 14 | "organization":"org", 15 | "port_number":"port", 16 | "base_protocol":"", 17 | "protocol":"", 18 | "port_size_gt":"", 19 | "port_size_lt":"", 20 | "port_size":"", 21 | "operating_system":"os", 22 | "application":"product", 23 | "product_version":"version", 24 | "product":"product", 25 | "time_scanned":"scan", 26 | "common_platform_enumeration_cpe":"cpe", 27 | "country_code":"country", 28 | "country_name":"", 29 | "postal_code":"postal", 30 | "state_name":"state", 31 | "region_name":"region", 32 | "city_name":"city", 33 | "autonomous_system_number":"asn", 34 | "autonomous_system_name":"asn", 35 | "autonomous_system_organization":"asn", 36 | "http_title":"http.title", 37 | "http_header_hash":"http.headers_hash", 38 | "http_header":"http.html", 39 | "service_banner_hash":"http.headers_hash", 40 | "service_banner":"http.html", 41 | "http_body_hash":"http.html_hash", 42 | "http_body":"http.html", 43 | "http_status_code":"http.status", 44 | "http_favicon_hash_dec":"http.favicon.hash", 45 | "http_favicon_hash":"http.favicon.hash", 46 | "tls_certificate_issuer_org":"ssl.cert.issuer.cn", 47 | "tls_certificate_subject_org":"ssl.cert.subject.cn", 48 | "tls_certificate_subject_cn":"ssl.cert.subject.cn", 49 | "tls_certificate_issuer_cn":"ssl.cert.issuer.cn", 50 | "tls_certificate_issuer":"ssl.cert.issuer.cn", 51 | "tls_certificate_subject":"ssl.cert.subject.cn", 52 | "tls_certificate_not_after":"", 53 | "tls_certificate_not_before":"", 54 | "tls_certificate_sha1":"ssl.cert.fingerprint", 55 | "tls_certificate_sha256":"ssl.cert.fingerprint", 56 | "tls_certificate_md5":"ssl.cert.fingerprint", 57 | "tls_certificate_is_expired":"ssl.cert.expired:true", 58 | "tls_certificate.is_valid":"ssl.cert.expired:false", 59 | "tls_certificate.pubkey_rsa_bits":"ssl.cert.pubkey.bits", 60 | "tls_certificate_pubkey_ecdsa_bits":"ssl.cert.pubkey.bits", 61 | "tls_certificate_pubkey_type":"ssl.cert.pubkey.type", 62 | "tls_certificate_cipher_name":"ssl.cipher.name", 63 | "tls_certificate_algorithm":"ssl.cert.alg", 64 | "tls_certificate_version":"ssl.version", 65 | "tls_certificate":"ssl", 66 | "jarm_fingerprint":"ssl.jarm", 67 | "ja3s_fingerprint":"ssl.ja3s", 68 | "ja4s_fingerprint":"", 69 | "ssh_hassh":"ssh.hassh", 70 | "ssh_banner_sha256":"", 71 | "ssh_banner":"", 72 | "ssh_key_length":"", 73 | "ssh_fingerprint":"" 74 | } 75 | -------------------------------------------------------------------------------- /mappings/pythia_zoomeye.py: -------------------------------------------------------------------------------- 1 | pythia_zoomeye_mappings={ 2 | "and":"+", 3 | "or":" ", 4 | "not":"-", 5 | "*":"", 6 | ":":":", 7 | "=":":", 8 | "label":"", 9 | "ip_address":"ip", 10 | "cidr":"cidr", 11 | "server":"hostname", 12 | "domain":"site", 13 | "hostname":"hostname", 14 | "organization":"org", 15 | "port_number":"port", 16 | "base_protocol":"service", 17 | "protocol":"service", 18 | "port_size_gt":"", 19 | "port_size_lt":"", 20 | "port_size":"", 21 | "operating_system":"os", 22 | "application":"app", 23 | "product_version":"", 24 | "product":"product", 25 | "time_scanned":"", 26 | "common_platform_enumeration_cpe":"", 27 | "country_code":"country", 28 | "country_name":"", 29 | "postal_code":"", 30 | "state_name":"", 31 | "region_name":"subdivisions", 32 | "city_name":"city", 33 | "autonomous_system_number":"asn", 34 | "autonomous_system_name":"asn", 35 | "autonomous_system_organization":"asn", 36 | "http_title":"title", 37 | "http_header_hash":"header", 38 | "http_header":"", 39 | "service_banner_hash":"", 40 | "service_banner":"", 41 | "http_body_hash":"", 42 | "http_body":"", 43 | "http_status_code":"", 44 | "http_favicon_hash_dec":"iconhash", 45 | "http_favicon_hash":"iconhash", 46 | "tls_certificate_issuer_org":"ssl.cert.issuer.cn", 47 | "tls_certificate_subject_org":"ssl.cert.subject.cn", 48 | "tls_certificate_subject_cn":"ssl.cert.subject.cn", 49 | "tls_certificate_issuer_cn":"ssl.cert.issuer.cn", 50 | "tls_certificate_issuer":"ssl.cert.issuer.cn", 51 | "tls_certificate_subject":"ssl.cert.subject.cn", 52 | "tls_certificate_not_after":"", 53 | "tls_certificate_not_before":"", 54 | "tls_certificate_sha1":"ssl.cert.fingerprint", 55 | "tls_certificate_sha256":"ssl.cert.fingerprint", 56 | "tls_certificate_md5":"ssl.cert.fingerprint", 57 | "tls_certificate_is_expired":"ssl.cert.availability:0", 58 | "tls_certificate.is_valid":"ssl.cert.availability:1", 59 | "tls_certificate.pubkey_rsa_bits":"ssl.cert.pubkey.rsa.bits", 60 | "tls_certificate_pubkey_ecdsa_bits":"ssl.cert.pubkey.ecdsa.bits", 61 | "tls_certificate_pubkey_type":"ssl.cert.pubkey.type", 62 | "tls_certificate_cipher_name":"ssl.cipher.name", 63 | "tls_certificate_algorithm":"ssl.cert.alg", 64 | "tls_certificate_version":"ssl.version", 65 | "tls_certificate":"ssl", 66 | "jarm_fingerprint":"jarm", 67 | "ja3s_fingerprint":"ssl.cert.fingerprint", 68 | "ja4s_fingerprint":"", 69 | "ssh_hassh":"", 70 | "ssh_banner_sha256":"", 71 | "ssh_banner":"", 72 | "ssh_key_length":"", 73 | "ssh_fingerprint":"" 74 | } 75 | -------------------------------------------------------------------------------- /pythia.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | import argparse 3 | from argparse import RawDescriptionHelpFormatter 4 | import tests.pythia_format_validator 5 | import pythia_converter 6 | from pythia_converter import ( 7 | pythia_query_to_fofa_query, 8 | pythia_query_to_full_query, 9 | pythia_query_to_hunter_query, 10 | pythia_query_to_censys_query, 11 | pythia_query_to_shodan_query, 12 | pythia_query_to_binaryedge_query, 13 | pythia_query_to_zoomeye_query 14 | ) 15 | from api_searcher.api_searcher import * 16 | from termcolor import colored 17 | from pyfiglet import Figlet 18 | 19 | 20 | def write_output_to_file(file, data): 21 | with open(file, "a") as f: 22 | f.write(data) 23 | 24 | 25 | def load_rule_from_yaml(file_path): 26 | with open(file_path, 'r') as file: 27 | return yaml.safe_load(file) 28 | 29 | 30 | def validate_rule(data): 31 | tests.pythia_format_validator.validator(data) 32 | 33 | 34 | def convert_to_platform_query(data, platform, api, output_file, browser): 35 | if tests.pythia_format_validator.validator(data): 36 | print(colored("[!]", 'white') + " Starting Pythia conversion...") 37 | pythia_converter.pythia_query_to_full_query.pythia_query_parser(data, output_file) 38 | converters = { 39 | "FOFA": pythia_converter.pythia_query_to_fofa_query.pythia_to_fofa_query, 40 | "HUNTER": pythia_converter.pythia_query_to_hunter_query.pythia_to_hunter_query, 41 | "CENSYS": pythia_converter.pythia_query_to_censys_query.pythia_to_censys_query, 42 | "SHODAN": pythia_converter.pythia_query_to_shodan_query.pythia_to_shodan_query, 43 | "ZOOMEYE": pythia_converter.pythia_query_to_zoomeye_query.pythia_to_zoomeye_query, 44 | "BINARYEDGE": pythia_converter.pythia_query_to_binaryedge_query.pythia_to_binaryedge_query 45 | } 46 | converters[platform](data, api, output_file, browser) 47 | 48 | 49 | def main(): 50 | ascii_art = Figlet(font='big') 51 | print(colored(ascii_art.renderText('Pythia'), 'white')) 52 | print("Pythia - Generic Query Format for Discovering Malicious Infrastructure\n") 53 | 54 | parser = argparse.ArgumentParser( 55 | prog="pythia.py", 56 | description="", 57 | epilog="Made by Efstratios Lontzetidis, @lontze7", 58 | formatter_class=RawDescriptionHelpFormatter 59 | ) 60 | parser.add_argument('-file', required=True, help='Path to the Pythia query YAML file') 61 | 62 | group = parser.add_mutually_exclusive_group(required=True) 63 | group.add_argument('-validate', action='store_true', help='Validate the Pythia query format of the file') 64 | group.add_argument( 65 | '-convert', 66 | nargs='+', 67 | choices=['FOFA', 'CENSYS', 'SHODAN', 'BINARYEDGE', 'ZOOMEYE', 'HUNTER', 'ALL'], 68 | help='Convert the query to a specified platform format. Separate the platforms using space characters.' 69 | ) 70 | parser.add_argument('-open_url', action='store_true', help='Open the URLs in a browser. (requires -convert argument)') 71 | parser.add_argument('-api', action='store_true', help='Search Pythia query results to APIs') 72 | parser.add_argument('-output_file', help='Output file to store the results') 73 | 74 | args = parser.parse_args() 75 | 76 | print("Initializing Pythia...") 77 | data = load_rule_from_yaml(args.file) 78 | if args.validate: 79 | validate_rule(data) 80 | elif args.convert: 81 | platforms = args.convert if 'ALL' not in args.convert else [ 82 | 'FOFA', 'CENSYS', 'SHODAN', 'BINARYEDGE', 'ZOOMEYE', 'HUNTER'] 83 | browser = args.open_url 84 | for platform in platforms: 85 | print(colored("[!]", 'white') + f" Starting Pythia conversion to {platform} format...") 86 | if args.output_file: 87 | write_output_to_file(args.output_file, f"Pythia query to be converted in {platform} format.\n") 88 | else: 89 | print(f"Pythia query to be converted in {platform} format.") 90 | convert_to_platform_query(data, platform, args.api, args.output_file, browser) 91 | if args.output_file: 92 | write_output_to_file(args.output_file, "-----------------------------------------------------------------------\n") 93 | else: 94 | print("-----------------------------------------------------------------------") 95 | print(colored("[+]", 'green') + " Pythia conversions run successfully.") 96 | 97 | 98 | if __name__ == "__main__": 99 | main() -------------------------------------------------------------------------------- /pythia_converter/pythia_query_to_binaryedge_query.py: -------------------------------------------------------------------------------- 1 | import re 2 | import urllib.parse 3 | import webbrowser 4 | from mappings.pythia_binaryedge import pythia_binaryedge_mappings 5 | from config.api_configs import API_KEYS 6 | from termcolor import colored 7 | from api_searcher.api_searcher import search_binaryedge_api 8 | 9 | def limit_spaces(string): 10 | return re.sub(r'\s+', ' ', string) 11 | 12 | def write_output_to_file(file, data): 13 | with open(file, "a") as f: 14 | f.write(data) 15 | 16 | def log_message(message, file=None, color=None): 17 | if file: 18 | write_output_to_file(file, message + "\n") 19 | else: 20 | if color: 21 | print(colored(message, color)) 22 | else: 23 | print(message) 24 | 25 | def pythia_to_binaryedge_query(data, api=False, output_file=None, browser=False): 26 | log_message("Converting to BINARYEDGE format...", output_file) 27 | condition = data['query']['condition'] 28 | new_condition = condition 29 | 30 | for part in data['query']['parameters']: 31 | part_number = part 32 | for key in data['query']['parameters'][part].keys(): 33 | if pythia_binaryedge_mappings[key] == "": 34 | string_part_number = "" 35 | else: 36 | string_part_number = key + data['query']['parameters'][part][key] 37 | new_condition = new_condition.replace(part_number, string_part_number) 38 | 39 | binaryedge_condition = " " + new_condition 40 | for field, replacement in pythia_binaryedge_mappings.items(): 41 | operator = field 42 | if field in ["or", "and"]: 43 | operator = rf'(?<=\s){field}(?=\s)' 44 | elif field == "*": 45 | operator = re.escape(field) 46 | binaryedge_condition = re.sub(operator, replacement, binaryedge_condition) 47 | binaryedge_condition = limit_spaces(binaryedge_condition[1:]) 48 | 49 | log_message(f"BINARYEDGE Query: {binaryedge_condition}", output_file, 'green') 50 | binaryedge_query_url = f"https://app.binaryedge.io/services/query?query={urllib.parse.quote(binaryedge_condition)}" 51 | log_message(f"BINARYEDGE Query URL: {binaryedge_query_url}", output_file, 'green') 52 | 53 | if browser: 54 | webbrowser.open(binaryedge_query_url) 55 | 56 | if api: 57 | api_key = API_KEYS.get('binaryedge_api_key', '') 58 | if api_key and api_key != 'changeme': 59 | search_binaryedge_api(api_key, urllib.parse.quote(binaryedge_condition), output_file) 60 | else: 61 | log_message("Error: API_KEY not provided for BINARYEDGE API", output_file, 'red') -------------------------------------------------------------------------------- /pythia_converter/pythia_query_to_censys_query.py: -------------------------------------------------------------------------------- 1 | import re 2 | import urllib.parse 3 | import webbrowser 4 | from mappings.pythia_censys import pythia_censys_mappings 5 | from config.api_configs import API_KEYS 6 | from api_searcher.api_searcher import search_censys_api 7 | from termcolor import colored 8 | 9 | def limit_spaces(string): 10 | return re.sub(r'\s+', ' ', string) 11 | 12 | def write_output_to_file(file, data): 13 | with open(file, "a") as f: 14 | f.write(data) 15 | 16 | def log_message(message, file=None, color=None): 17 | if file: 18 | write_output_to_file(file, message + "\n") 19 | else: 20 | if color: 21 | print(colored(message, color)) 22 | else: 23 | print(message) 24 | 25 | def pythia_to_censys_query(data, api=False, output_file=None, browser=False): 26 | log_message("Converting to CENSYS format...", output_file) 27 | condition = data['query']['condition'] 28 | new_condition = condition 29 | 30 | for part in data['query']['parameters']: 31 | part_number = part 32 | for key in data['query']['parameters'][part].keys(): 33 | if pythia_censys_mappings[key] == "": 34 | string_part_number = "" 35 | else: 36 | string_part_number = key + data['query']['parameters'][part][key] 37 | new_condition = new_condition.replace(part_number, string_part_number) 38 | 39 | censys_condition = " " + new_condition 40 | for field, replacement in pythia_censys_mappings.items(): 41 | operator = field 42 | if field in ["or", "and"]: 43 | operator = rf'(?<=\s){field}(?=\s)' 44 | elif field == "*": 45 | operator = re.escape(field) 46 | censys_condition = re.sub(operator, replacement, censys_condition) 47 | censys_condition = limit_spaces(censys_condition[1:]) 48 | 49 | log_message(f"CENSYS Query: {censys_condition}", output_file, 'green') 50 | censys_query_url = f"https://search.censys.io/search?resource=hosts&sort=RELEVANCE&per_page=25&virtual_hosts=EXCLUDE&q={urllib.parse.quote(censys_condition)}" 51 | log_message(f"CENSYS Query URL: {censys_query_url}", output_file, 'green') 52 | 53 | if browser: 54 | webbrowser.open(censys_query_url) 55 | 56 | if api: 57 | api_id = API_KEYS.get('censys_api_id', '') 58 | api_secret = API_KEYS.get('censys_api_secret', '') 59 | if api_id not in ['', 'changeme'] and api_secret not in ['', 'changeme']: 60 | search_censys_api(api_id, api_secret, urllib.parse.quote(censys_condition), output_file) 61 | else: 62 | log_message("Error: API_ID and API_SECRET not provided for CENSYS API", output_file, 'red') -------------------------------------------------------------------------------- /pythia_converter/pythia_query_to_fofa_query.py: -------------------------------------------------------------------------------- 1 | import re 2 | import base64 3 | import webbrowser 4 | from termcolor import colored 5 | from mappings.pythia_fofa import pythia_fofa_mappings 6 | from config.api_configs import API_KEYS 7 | from api_searcher.api_searcher import search_fofa_api 8 | 9 | def limit_spaces(string): 10 | return re.sub(r'\s+', ' ', string) 11 | 12 | def write_output_to_file(file, data): 13 | with open(file, "a") as f: 14 | f.write(data) 15 | 16 | def log_message(message, file=None, color=None): 17 | if file: 18 | write_output_to_file(file, message + "\n") 19 | else: 20 | if color: 21 | print(colored(message, color)) 22 | else: 23 | print(message) 24 | 25 | def pythia_to_fofa_query(data, api=False, output_file=None, browser=False): 26 | log_message("Converting to FOFA format...", output_file) 27 | condition = data['query']['condition'] 28 | new_condition = condition 29 | 30 | for part in data['query']['parameters']: 31 | part_number = part 32 | for key in data['query']['parameters'][part].keys(): 33 | if pythia_fofa_mappings[key] == "": 34 | string_part_number = "" 35 | else: 36 | string_part_number = key + data['query']['parameters'][part][key] 37 | new_condition = new_condition.replace(part_number, string_part_number) 38 | 39 | fofa_condition = " " + new_condition 40 | for field, replacement in pythia_fofa_mappings.items(): 41 | operator = field 42 | if field in ["or", "and"]: 43 | operator = rf'(?<=\s){field}(?=\s)' 44 | elif field == "*": 45 | operator = re.escape(field) 46 | fofa_condition = re.sub(operator, replacement, fofa_condition) 47 | fofa_condition = limit_spaces(fofa_condition[1:]) 48 | 49 | fofa_query_url = "https://en.fofa.info/result?qbase64=" + base64.b64encode(fofa_condition.encode('utf-8')).decode('utf-8') 50 | log_message(f"FOFA Query: {fofa_condition}", output_file, 'green') 51 | log_message(f"FOFA Query URL: {fofa_query_url}", output_file, 'green') 52 | 53 | if browser: 54 | webbrowser.open(fofa_query_url) 55 | 56 | if api: 57 | api_key = API_KEYS.get('fofa_api_key', '') 58 | if api_key not in ['', 'changeme']: 59 | search_fofa_api(api_key, base64.b64encode(fofa_condition.encode('utf-8')).decode('utf-8'), output_file) 60 | else: 61 | log_message("Error: API_KEY is not provided for FOFA API", output_file, 'red') -------------------------------------------------------------------------------- /pythia_converter/pythia_query_to_full_query.py: -------------------------------------------------------------------------------- 1 | from termcolor import colored 2 | def write_output_to_file(file,data): 3 | with open(file,"a") as file: 4 | file.write(data) 5 | file.close() 6 | def pythia_query_parser(data,output_file): 7 | if output_file: 8 | write_output_to_file(output_file,"Pythia Query: "+data['title']+"\n") 9 | else: 10 | print(colored("[+]", 'green') +"Pythia Query: "+data['title']) 11 | condition=data['query']['condition'] 12 | new_condition=condition 13 | 14 | for part in data['query']['parameters']: 15 | part_number=part 16 | for key in data['query']['parameters'][part].keys(): 17 | string_part_number=key + data['query']['parameters'][part][key] 18 | new_condition=new_condition.replace(part_number,string_part_number) 19 | 20 | if output_file: 21 | write_output_to_file(output_file,"Pythia format query: " +new_condition+"\n") 22 | else: 23 | print(colored("[+]", 'green') +"Pythia format query: " +new_condition) -------------------------------------------------------------------------------- /pythia_converter/pythia_query_to_hunter_query.py: -------------------------------------------------------------------------------- 1 | import re 2 | import urllib.parse 3 | import webbrowser 4 | from termcolor import colored 5 | from mappings.pythia_hunter import pythia_hunter_mappings 6 | from config.api_configs import API_KEYS 7 | from api_searcher.api_searcher import search_hunter_api 8 | 9 | def limit_spaces(string): 10 | return re.sub(r'\s+', ' ', string) 11 | 12 | def write_output_to_file(file, data): 13 | with open(file, "a") as f: 14 | f.write(data) 15 | 16 | def log_message(message, file=None, color=None): 17 | if file: 18 | write_output_to_file(file, message + "\n") 19 | else: 20 | if color: 21 | print(colored(message, color)) 22 | else: 23 | print(message) 24 | 25 | def pythia_to_hunter_query(data, api=False, output_file=None, browser=False): 26 | log_message("Converting to HUNTER format...", output_file) 27 | condition = data['query']['condition'] 28 | new_condition = condition 29 | 30 | for part in data['query']['parameters']: 31 | part_number = part 32 | for key in data['query']['parameters'][part].keys(): 33 | if pythia_hunter_mappings[key] == "": 34 | string_part_number = "" 35 | else: 36 | string_part_number = key + data['query']['parameters'][part][key] 37 | new_condition = new_condition.replace(part_number, string_part_number) 38 | 39 | hunter_condition = " " + new_condition 40 | for field, replacement in pythia_hunter_mappings.items(): 41 | operator = field 42 | if field in ["or", "and"]: 43 | operator = rf'(?<=\s){field}(?=\s)' 44 | elif field == "*": 45 | operator = re.escape(field) 46 | hunter_condition = re.sub(operator, replacement, hunter_condition) 47 | hunter_condition = limit_spaces(hunter_condition[1:]) 48 | 49 | hunter_query_url = "https://hunter.how/list?searchValue=" + urllib.parse.quote(hunter_condition) 50 | log_message(f"HUNTER Query: {hunter_condition}", output_file, 'green') 51 | log_message(f"HUNTER Query URL: {hunter_query_url}", output_file, 'green') 52 | 53 | if browser: 54 | webbrowser.open(hunter_query_url) 55 | 56 | if api: 57 | api_key = API_KEYS.get('hunter_api_key', '') 58 | if api_key not in ['', 'changeme']: 59 | search_hunter_api(api_key, hunter_condition, output_file) 60 | else: 61 | log_message("Error: API_KEY is not provided for HUNTER API", output_file, 'red') -------------------------------------------------------------------------------- /pythia_converter/pythia_query_to_shodan_query.py: -------------------------------------------------------------------------------- 1 | import re 2 | import urllib.parse 3 | import webbrowser 4 | from termcolor import colored 5 | from mappings.pythia_shodan import pythia_shodan_mappings 6 | from config.api_configs import API_KEYS 7 | from api_searcher.api_searcher import search_shodan_api 8 | 9 | def limit_spaces(string): 10 | return re.sub(r'\s+', ' ', string) 11 | 12 | def write_output_to_file(file, data): 13 | with open(file, "a") as f: 14 | f.write(data) 15 | 16 | def log_message(message, file=None, color=None): 17 | if file: 18 | write_output_to_file(file, message + "\n") 19 | else: 20 | if color: 21 | print(colored(message, color)) 22 | else: 23 | print(message) 24 | 25 | def pythia_to_shodan_query(data, api=False, output_file=None, browser=False): 26 | log_message("Converting to SHODAN format...", output_file) 27 | condition = data['query']['condition'] 28 | new_condition = condition 29 | 30 | for part in data['query']['parameters']: 31 | part_number = part 32 | for key in data['query']['parameters'][part].keys(): 33 | if pythia_shodan_mappings[key] == "": 34 | string_part_number = "" 35 | else: 36 | string_part_number = key + data['query']['parameters'][part][key] 37 | new_condition = new_condition.replace(part_number, string_part_number) 38 | 39 | shodan_condition = " " + new_condition 40 | for field, replacement in pythia_shodan_mappings.items(): 41 | operator = field 42 | if field in ["or", "and"]: 43 | operator = rf'(?<=\s){field}(?=\s)' 44 | elif field == "*": 45 | operator = re.escape(field) 46 | shodan_condition = re.sub(operator, replacement, shodan_condition) 47 | shodan_condition = limit_spaces(shodan_condition[1:]) 48 | 49 | shodan_query_url = "https://www.shodan.io/search?query=" + urllib.parse.quote(shodan_condition) 50 | log_message(f"SHODAN Query: {shodan_condition}", output_file, 'green') 51 | log_message(f"SHODAN Query URL: {shodan_query_url}", output_file, 'green') 52 | 53 | if browser: 54 | webbrowser.open(shodan_query_url) 55 | 56 | if api: 57 | api_key = API_KEYS.get('shodan_api_key', '') 58 | if api_key not in ['', 'changeme']: 59 | search_shodan_api(api_key, urllib.parse.quote(shodan_condition), output_file) 60 | else: 61 | log_message("Error: API_KEY is not provided for SHODAN API", output_file, 'red') -------------------------------------------------------------------------------- /pythia_converter/pythia_query_to_zoomeye_query.py: -------------------------------------------------------------------------------- 1 | import re 2 | import urllib.parse 3 | import webbrowser 4 | from termcolor import colored 5 | from mappings.pythia_zoomeye import pythia_zoomeye_mappings 6 | from config.api_configs import API_KEYS 7 | from api_searcher.api_searcher import search_zoomeye_api 8 | 9 | def remove_spaces_around_plus(string): 10 | # Check if the string contains " + " and remove surrounding spaces 11 | return re.sub(r'\s+\+\s+', '+', string) 12 | 13 | def limit_spaces(string): 14 | return re.sub(r'\s+', ' ', string) 15 | 16 | def write_output_to_file(file, data): 17 | with open(file, "a") as f: 18 | f.write(data) 19 | 20 | def log_message(message, file=None, color=None): 21 | if file: 22 | write_output_to_file(file, message + "\n") 23 | else: 24 | if color: 25 | print(colored(message, color)) 26 | else: 27 | print(message) 28 | 29 | def pythia_to_zoomeye_query(data, api=False, output_file=None, browser=False): 30 | log_message("Converting to ZOOMEYE format...", output_file) 31 | condition = data['query']['condition'] 32 | new_condition = condition 33 | 34 | for part in data['query']['parameters']: 35 | part_number = part 36 | for key in data['query']['parameters'][part].keys(): 37 | string_part_number = key + data['query']['parameters'][part][key] if pythia_zoomeye_mappings[key] != "" else "" 38 | new_condition = new_condition.replace(part_number, string_part_number) 39 | 40 | zoomeye_condition = " " + new_condition 41 | for field, replacement in pythia_zoomeye_mappings.items(): 42 | operator = field 43 | if field in ["or", "and"]: 44 | operator = rf'(?<=\s){field}(?=\s)' 45 | elif field == "*": 46 | operator = re.escape(field) 47 | zoomeye_condition = re.sub(operator, replacement, zoomeye_condition) 48 | zoomeye_condition = limit_spaces(zoomeye_condition[1:]) 49 | zoomeye_condition=remove_spaces_around_plus(zoomeye_condition) 50 | 51 | query_url = "https://www.zoomeye.hk/searchResult?q=" + urllib.parse.quote(zoomeye_condition) 52 | log_message(f"ZOOMEYE Query: {zoomeye_condition}", output_file, 'green') 53 | log_message(f"ZOOMEYE Query URL: {query_url}", output_file, 'green') 54 | 55 | if browser: 56 | webbrowser.open(query_url) 57 | 58 | if api: 59 | api_key = API_KEYS.get('zoomeye_api_key', '') 60 | if api_key not in ['', 'changeme']: 61 | search_zoomeye_api(api_key, urllib.parse.quote(zoomeye_condition), output_file) 62 | else: 63 | log_message("Error: API_KEY is not provided for ZOOMEYE API", output_file, 'red') -------------------------------------------------------------------------------- /queries/MALWARE/asyncrat_subject_issuer_cn.yml: -------------------------------------------------------------------------------- 1 | title: AsyncRAT - Subject Or Issuer CN 2 | id: pythia-b423979f-164d-472a-bb61-e639a3da7db2 3 | status: test 4 | description: This query identifies AsyncRAT infrastructure using certificate subject or issuer common names 5 | references: 6 | - https://www.embeeresearch.io/threat-intel-queries-with-fofabot/ 7 | tags: 8 | - async_rat 9 | - rat 10 | author: Matthew, @embeeresearch 11 | date: 2024/01/01 12 | query: 13 | parameters: 14 | part1: 15 | tls_certificate_subject_cn: ':"AsyncRAT Server"' 16 | part2: 17 | tls_certificate_issuer_cn: ':"AsyncRAT Server"' 18 | condition: part1 or part2 19 | falsepositives: 20 | - No false positives at the moment 21 | level: moderate 22 | -------------------------------------------------------------------------------- /queries/MALWARE/guloader_jarm.yml: -------------------------------------------------------------------------------- 1 | title: Guloader - JARM 2 | id: pythia-b163979f-114d-472a-bb61-e639a3dc9db2 3 | status: test 4 | description: This query identifies Guloader infrastructure using JARM 5 | references: 6 | - https://x.com/banthisguy9349/status/1806348993737162811 7 | tags: 8 | - guloader 9 | author: Fox_threatintel, @banthisguy9349 10 | date: 2024/06/27 11 | query: 12 | parameters: 13 | part1: 14 | jarm_fingerprint: ':"00000000000000000041d41d0000001798d6156df422564fb9b667b7418e4c"' 15 | condition: part1 16 | falsepositives: 17 | - Legitimate hosts with this jarm 18 | level: low 19 | -------------------------------------------------------------------------------- /queries/MALWARE/hookbot_panel_html_title.yml: -------------------------------------------------------------------------------- 1 | title: Hookbot - HTML Title 2 | id: pythia-b423979f-163d-482a-bb61-e639a3dc9db2 3 | status: stable 4 | description: This query identifies Hookbot panels using html title 5 | references: 6 | - https://x.com/RacWatchin8872/status/1811374359576998354 7 | tags: 8 | - hookbot 9 | - android 10 | - rat 11 | author: WatchingRac, @RacWatchin8872 12 | date: 2024/07/11 13 | query: 14 | parameters: 15 | part1: 16 | http_title: ':"HOOKBOT PANEL"' 17 | condition: part1 18 | falsepositives: 19 | - No false positives at the moment 20 | level: high -------------------------------------------------------------------------------- /queries/MALWARE/kematian_stealer_html_title.yml: -------------------------------------------------------------------------------- 1 | title: Kematian Stealer - HTML Title 2 | id: pythia-b423949c-163d-482a-bb61-e639a3dc9db2 3 | status: stable 4 | description: This query identifies Kematian Stealer infrastructure using html title 5 | references: 6 | - https://x.com/thehappydinoa/status/1809269921777344966 7 | tags: 8 | - kematian 9 | - stealer 10 | - windows 11 | author: Aidan H, @thehappydinoa 12 | date: 2024/07/05 13 | query: 14 | parameters: 15 | part1: 16 | http_title: ':"Kematian Stealer"' 17 | condition: part1 18 | falsepositives: 19 | - No false positives at the moment 20 | level: high -------------------------------------------------------------------------------- /queries/MALWARE/laplas_clipper_subject_cn.yml: -------------------------------------------------------------------------------- 1 | title: Laplas Clipper - Subject CN 2 | id: pythia-b423929f-154d-472a-bb61-e639a3dc9db2 3 | status: test 4 | description: This query identifies Laplas Clipper infrastructure using certificate subject common name 5 | references: 6 | - https://www.embeeresearch.io/threat-intel-queries-with-fofabot/ 7 | tags: 8 | - laplas_clipper 9 | author: Matthew, @embeeresearch 10 | date: 2024/01/01 11 | query: 12 | parameters: 13 | part1: 14 | tls_certificate_subject_cn: ':"Laplas.app"' 15 | condition: part1 16 | falsepositives: 17 | - No false positives at the moment 18 | level: moderate 19 | -------------------------------------------------------------------------------- /queries/MALWARE/meduza_stealer_html_title.yml: -------------------------------------------------------------------------------- 1 | title: Meduza Stealer - HTML Title Favicon Decimal Hash 2 | id: pythia-b423979c-163d-482a-bb61-e639a3dc9db2 3 | status: stable 4 | description: This query identifies Meduza Stealer infrastructure using html title and decimal favicon hash 5 | references: 6 | - https://x.com/RacWatchin8872/status/1811374359576998354 7 | tags: 8 | - meduza 9 | - stealer 10 | - windows 11 | author: Efstratios Lontzetidis, @lontze7 12 | date: 2024/07/31 13 | query: 14 | parameters: 15 | part1: 16 | http_title: ':"Meduza Stealer"' 17 | part2: 18 | http_favicon_hash_dec: ':"1783687699"' 19 | condition: part1 or part2 20 | falsepositives: 21 | - No false positives at the moment 22 | level: high -------------------------------------------------------------------------------- /queries/MALWARE/mirai_multiple_architectures.yml: -------------------------------------------------------------------------------- 1 | title: Mirai Opendir Hosting Payloads in Multiple Architectures 2 | id: pythia-b423979f-174d-472a-bb61-e639a3dc9bb0 3 | status: experimental 4 | description: This query identifies open directories that are hosting Mirai malware in multiple architectures such as .arm6,arm7,mips,etc 5 | references: 6 | - https://x.com/lontze7/status/1807997032948261022 7 | tags: 8 | - mirai 9 | - gafgyt 10 | - opendir 11 | author: Efstratios Lontzetidis, @lontze7 12 | date: 2024/07/25 13 | query: 14 | parameters: 15 | part1: 16 | http_title: ':"*Index Of*"' 17 | part2: 18 | http_title: ':"*Directory Listing*"' 19 | part3: 20 | http_body: ':"*.arm*"' 21 | part4: 22 | http_body: ':"*.mips*"' 23 | part5: 24 | http_body: ':"*.sh*"' 25 | condition: (part1 or part2) and (part3 and part4 and part5) # first parenthesis part is to identify opendirs and the second is to capture this specific hosting technique from Mirai operators 26 | falsepositives: 27 | - If the file names hosted in the opendir haven't the same name (i.e. test.arm5 and hello.mips,etc) is probably false positive since Mirai operators usually follow the same naming convention for their payloads 28 | level: moderate 29 | -------------------------------------------------------------------------------- /queries/MALWARE/quasar_rat_subject_common_name.yml: -------------------------------------------------------------------------------- 1 | title: Quasar RAT - Subject CN 2 | id: pythia-b423979f-154d-481a-bb61-e639a3dc9db2 3 | status: test 4 | description: This query identifies Quasar RAT infrastructure using subject common name 5 | references: 6 | - https://www.embeeresearch.io/threat-intel-queries-with-fofabot/ 7 | tags: 8 | - quasar 9 | - rat 10 | author: Matthew, @embeeresearch 11 | date: 2024/01/01 12 | query: 13 | parameters: 14 | part1: 15 | tls_certificate_subject_cn: ':"Quasar Server CA"' 16 | condition: part1 17 | falsepositives: 18 | - No false positives at the moment 19 | level: moderate 20 | -------------------------------------------------------------------------------- /queries/MALWARE/redline_stealer_banner_hashes_port_asn.yml: -------------------------------------------------------------------------------- 1 | title: Redline Stealer - Banner Hash Port ASN 2 | id: pythia-a224679f-114d-472a-bb61-e639a3dc9db2 3 | status: experimental 4 | description: This query identifies Redline stealer infrastructure using banner hashes port and ASN 5 | references: 6 | - https://x.com/Cyberteam008/status/1797808285988671941 7 | tags: 8 | - redline 9 | - stealer 10 | - windows 11 | author: Cyber Team, @Cyberteam008 12 | date: 2024/06/04 13 | query: 14 | parameters: 15 | part1: 16 | service_banner_hash: ':"29298e562ed96657efcb840c0a4539eb78e550b87d3bf2978d2eee1df4706542"' 17 | part2: 18 | autonomous_system_name: '="ROOTLAYERNET"' 19 | part3: 20 | port_number: '="7766"' 21 | condition: part1 and part2 and part3 22 | falsepositives: 23 | - No false positives at the moment 24 | level: moderate 25 | -------------------------------------------------------------------------------- /queries/MALWARE/stealc_stealer_banner_hashes_asn.yml: -------------------------------------------------------------------------------- 1 | title: Stealc Stealer - Banner Hash and ASN 2 | id: pythia-b423979f-114d-472a-bb61-e639a3dc9db2 3 | status: test 4 | description: This query identifies Stealc stealer infrastructure using banner hashes and ASN 5 | references: 6 | - https://x.com/Cyberteam008/status/1815594345770181093 7 | tags: 8 | - stealc 9 | - stealer 10 | author: Cyber Team, @Cyberteam008 11 | date: 2024/07/23 12 | query: 13 | parameters: 14 | part1: 15 | service_banner_hash: ':"269b1b0bdb597d852850d7fd1a161f1a7dadfef8639837f3e56fac4c15fc5de5"' 16 | part2: 17 | autonomous_system_number: ':"216319"' 18 | condition: part1 and part2 19 | falsepositives: 20 | - No false positives at the moment 21 | level: moderate 22 | -------------------------------------------------------------------------------- /queries/OPENDIR/common_file_names_opendir_body.yml: -------------------------------------------------------------------------------- 1 | title: Common Hacktool Files Opendir - HTTP Body and Title 2 | id: pythia-b487939f-174d-472a-bb51-e639a3dc9bb0 3 | status: test 4 | description: This query identifies open directories that are hosting common malicious files like beacons,shells,mimikatz using the http title and body 5 | references: 6 | - https://censys.com/a-beginners-guide-to-tracking-malware-infrastructure/ 7 | tags: 8 | - hacktool 9 | - mimikatz 10 | - shell 11 | author: Censys & Matthew, @embeeresearch 12 | date: 2024/02/09 13 | query: 14 | parameters: 15 | part1: 16 | http_title: ':"*Index Of*"' 17 | part2: 18 | http_title: ':"*Directory Listing*"' 19 | part3: 20 | http_body: ':"*beacon.exe*"' 21 | part4: 22 | http_body: ':"*shell.exe*"' 23 | part5: 24 | http_body: ':"*mimikatz*"' 25 | condition: (part1 or part2) and (part3 or part4 or part5) 26 | falsepositives: 27 | - Legitimate opendirs with hacktools from white hat teams 28 | level: moderate 29 | -------------------------------------------------------------------------------- /queries/OPENDIR/exploit_opendir_body.yml: -------------------------------------------------------------------------------- 1 | title: Exploit Files Opendir - HTTP Body and Title 2 | id: pythia-b423939f-174d-472a-bb51-e639a3dc9bb0 3 | status: test 4 | description: This query identifies open directories that are hosting exploits and kits using the http title and body 5 | references: 6 | - https://x.com/banthisguy9349/status/1799502018836308197 7 | - https://x.com/banthisguy9349/status/1799488287574233188 8 | tags: 9 | - mirai 10 | - gafgyt 11 | - opendir 12 | author: Fox_threatintel, @banthisguy9349 13 | date: 2024/06/08 14 | query: 15 | parameters: 16 | part1: 17 | http_title: ':"*Index Of*"' 18 | part2: 19 | http_title: ':"*Directory Listing*"' 20 | part3: 21 | http_body: ':"*Exploit.class*"' 22 | part4: 23 | http_body: ':"*pwnkit*"' 24 | condition: (part1 or part2) and (part3 or part4) 25 | falsepositives: 26 | - Legitimate opendirs from white hat teams 27 | level: moderate 28 | -------------------------------------------------------------------------------- /queries/OPENDIR/mirai_multiple_architectures.yml: -------------------------------------------------------------------------------- 1 | title: Mirai Opendir Hosting Payloads in Multiple Architectures 2 | id: pythia-b423979f-174d-472a-bb61-e639a3dc9bb0 3 | status: experimental 4 | description: This query identifies open directories that are hosting Mirai malware in multiple architectures such as .arm6,arm7,mips,etc 5 | references: 6 | - https://x.com/lontze7/status/1807997032948261022 7 | tags: 8 | - mirai 9 | - gafgyt 10 | - opendir 11 | author: Efstratios Lontzetidis, @lontze7 12 | date: 2024/07/25 13 | query: 14 | parameters: 15 | part1: 16 | http_title: ':"*Index Of*"' 17 | part2: 18 | http_title: ':"*Directory Listing*"' 19 | part3: 20 | http_body: ':"*.arm*"' 21 | part4: 22 | http_body: ':"*.mips*"' 23 | part5: 24 | http_body: ':"*.sh*"' 25 | condition: (part1 or part2) and (part3 and part4 and part5) # first parenthesis part is to identify opendirs and the second is to capture this specific hosting technique from Mirai operators 26 | falsepositives: 27 | - If the file names hosted in the opendir haven't the same name (i.e. test.arm5 and hello.mips,etc) is probably false positive since Mirai operators usually follow the same naming convention for their payloads 28 | level: moderate 29 | -------------------------------------------------------------------------------- /queries/PHISHING/gophish_phishing_jarm.yml: -------------------------------------------------------------------------------- 1 | title: GoPhish Phishing - JARM, Favicon Hash 2 | id: pythia-b425279f-174d-472a-bb61-e639a3dc9bb4 3 | status: experimental 4 | description: This query identifies GoPhish infrastructure from clues based on jarm 5 | references: 6 | - https://x.com/MichalKoczwara/status/1601554276106792960 7 | tags: 8 | - go_phish 9 | - phishing 10 | - man_in_the_middle 11 | author: Michael Koczwara, @MichalKoczwara 12 | date: 2022/12/10 13 | query: 14 | parameters: 15 | part1: 16 | jarm_fingerprint: ':"28d28d28d00028d00041d28d28d41dd279b0cf765af27fa62e66d7c8281124"' 17 | condition: part1 18 | falsepositives: 19 | - No information on false positives 20 | level: low 21 | -------------------------------------------------------------------------------- /queries/PHISHING/greatness_phaas_body_hash.yml: -------------------------------------------------------------------------------- 1 | title: Greatness Phishing-as-a-Service - Body Hash 2 | id: pythia-c423979f-174d-472a-bb61-e639a3dc9bb4 3 | status: experimental 4 | description: This query identifies Greatness Phishing-as-a-Service pages using body hash 5 | references: 6 | - https://x.com/Moneroon/status/1807747441036271825 7 | tags: 8 | - greatness 9 | - phishing-as-a-service 10 | author: Moneroon, @Moneroon 11 | date: 2023/07/01 12 | query: 13 | parameters: 14 | part1: 15 | http_body_hash: ':"fb6f8985e3bc804f88272c9b1325d69b3f628e3a5d0f818b5ebf7d518f2ac3e6"' 16 | condition: part1 17 | falsepositives: 18 | - No false positives at the moment 19 | level: moderate 20 | -------------------------------------------------------------------------------- /queries/PHISHING/microsoft_outlook_phishing_jarm_favicon_hash.yml: -------------------------------------------------------------------------------- 1 | title: Microsoft Outlook Phishing Infrastructure - JARM, Favicon Hash 2 | id: pythia-b423979f-174d-472a-bb61-e639a3dc9bb4 3 | status: experimental 4 | description: This query identifies Microsoft Outlook phishing infrastructure from clues based on jarm and favicon hash. 5 | references: 6 | - https://x.com/FalconFeedsio/status/1680851724817563648 7 | tags: 8 | - microsoft_outlook 9 | - phishing 10 | - man_in_the_middle 11 | author: FalconFeeds, @FalconFeedsio 12 | date: 2023/07/17 13 | query: 14 | parameters: 15 | part1: 16 | jarm_fingerprint: ':"29d29d00000000021c29d29d29d29d1f4989c319e75da83988253a39553038"' 17 | part2: 18 | http_favicon_hash_dec: ':"1768726119"' 19 | condition: part1 and part2 20 | falsepositives: 21 | - Legitimate Microsoft Outlook hosting pages and IPs 22 | level: low 23 | -------------------------------------------------------------------------------- /queries/TA/coldriver_c2_banner_port_cert.yml: -------------------------------------------------------------------------------- 1 | title: COLDRIVER Infrastructure - Banner, Port and Cert 2 | id: pythia-b423979f-174d-472a-bb61-e639a3dc9bb8 3 | status: experimental 4 | description: This query identifies COLDRIVER C2 infrastructure from clues based on banner, port and certification details. 5 | references: 6 | - https://medium.com/@fofabot/practical-fofa-asset-discovery-coldriver-bdb971f2413b 7 | tags: 8 | - coldriver 9 | - star_blizard 10 | - unc4057 11 | author: FOFA, @fofabot 12 | date: 2024/03/14 13 | query: 14 | parameters: 15 | part1: 16 | service_banner: ':"\x15\x03\x03\x00\x02\x022"' 17 | part2: 18 | port_number: '="3000"' 19 | part3: 20 | tls_certificate: ':"Internet Widgits Pty Ltd"' 21 | part4: 22 | tls_certificate: ':"2023–06–23 15:59 UTC"' 23 | condition: part1 and part2 and part3 and part4 24 | falsepositives: 25 | - No false positives at the moment 26 | level: moderate 27 | -------------------------------------------------------------------------------- /queries/TA/kimsuky_apt_campaign_banner_hash.yml: -------------------------------------------------------------------------------- 1 | title: Kimsuky APT Campaign - Banner Hash 2 | id: pythia-b423178f-114d-472a-ba61-e639a3ac9db2 3 | status: test 4 | description: This query identifies Kimsuky APT infrastructure campaign using banner hash 5 | references: 6 | - https://x.com/Cyberteam008/status/1797456640305922243 7 | tags: 8 | - kimsuky 9 | - apt43 10 | - black_banshee 11 | author: Cyber Team, @Cyberteam008 12 | date: 2024/06/26 13 | query: 14 | parameters: 15 | part1: 16 | service_banner_hash: ':"14309ae76fa5485d6498b8cda9c17e4f9e0e0a58a4fe98c47656b80bc5e6bc09"' 17 | condition: part1 18 | falsepositives: 19 | - No false positives at the moment 20 | level: moderate 21 | -------------------------------------------------------------------------------- /queries/TA/kimsuky_apt_certificate_asn.yml: -------------------------------------------------------------------------------- 1 | title: Kimsuky APT - Certificate ASN Service Count 2 | id: pythia-b423178f-114d-472a-ba61-e639a3dc9db2 3 | status: test 4 | description: This query identifies Kimsuky APT infrastructure using certificate and ASN 5 | references: 6 | - https://x.com/Cyberteam008/status/1805796115196883025 7 | tags: 8 | - kimsuky 9 | - apt43 10 | - black_banshee 11 | author: Cyber Team, @Cyberteam008 12 | date: 2024/06/26 13 | query: 14 | parameters: 15 | part1: 16 | tls_certificate: ':"9de541b039cfdb96c7810df49efd958b28cc2df73e314f67c1a91469a2b19796"' 17 | part2: 18 | autonomous_system_number: ':"19318"' 19 | part3: 20 | port_size: ':"3"' 21 | condition: part1 and part2 and part3 22 | falsepositives: 23 | - No false positives at the moment 24 | level: moderate 25 | -------------------------------------------------------------------------------- /queries/TOOLS/cobalt_strike_tls_subject_common_name.yml: -------------------------------------------------------------------------------- 1 | title: Cobalt Strike geacon_pro Profile - Subject CN 2 | id: pythia-b423979f-154d-472a-bb61-e639a3dc9db2 3 | status: test 4 | description: This query identifies Cobalt Strike geacon_pro profile using certificate subject common name 5 | references: 6 | - https://x.com/MichalKoczwara/status/1641676761283850241 7 | tags: 8 | - cobalt_strike 9 | - geacon_pro 10 | author: Michael Koczwara, @MichalKoczwara 11 | date: 2023/03/31 12 | query: 13 | parameters: 14 | part1: 15 | tls_certificate_subject_cn: ':"foren.zik"' 16 | condition: part1 17 | falsepositives: 18 | - No false positives at the moment 19 | level: moderate 20 | -------------------------------------------------------------------------------- /queries/TOOLS/mythic_c2_favicon_hash_dec_or_title.yml: -------------------------------------------------------------------------------- 1 | title: Mythic C2 - Icon Hash Decimal and Title 2 | id: pythia-b423979f-174d-472a-bb61-e639a3dc9bb2 3 | status: experimental 4 | description: This query identifies Mythic C2 infrastructure using default favicon hash or html title 5 | references: 6 | - https://www.embeeresearch.io/threat-intel-queries-with-fofabot/ 7 | tags: 8 | - mythic 9 | - c2 10 | - commandandcontrol 11 | author: Matthew, @embeeresearch 12 | date: 2024/01/01 13 | query: 14 | parameters: 15 | part1: 16 | http_favicon_hash_dec: ':"-859291042"' 17 | part2: 18 | http_title: '="Mythic"' 19 | condition: part1 and part2 20 | falsepositives: 21 | - No false positives at the moment 22 | level: moderate 23 | -------------------------------------------------------------------------------- /queries/TOOLS/netsupport_rat_job_board_favicon_hash_html_title.yml: -------------------------------------------------------------------------------- 1 | title: NetSupport RAT Job Board - Favicon Hash HTML Title 2 | id: pythia-b123979f-264d-472a-bb61-e639a3dc9db2 3 | status: experimental 4 | description: This query identifies NetSupport RAT Job Boards using html title and favicon hash 5 | references: 6 | - https://x.com/banthisguy9349/status/1808872152419954828 7 | tags: 8 | - netsupport 9 | - rat 10 | - remote_admin_tool 11 | author: Fox_threatintel, @banthisguy9349 12 | date: 2024/07/04 13 | query: 14 | parameters: 15 | part1: 16 | http_favicon_hash: ':"821018649c8fdad8391c36fadcb793a5"' 17 | part2: 18 | http_title: ':"Job Board"' 19 | condition: part1 and part2 20 | falsepositives: 21 | - Legitimate Job Boards 22 | level: low 23 | -------------------------------------------------------------------------------- /queries/TOOLS/supershell_html_title.yml: -------------------------------------------------------------------------------- 1 | title: Supershell C2 - HTML Title 2 | id: pythia-b423978f-164d-472a-bb61-e639a3dc9db2 3 | status: stable 4 | description: This query identifies Supershell C2 infra using html title 5 | references: 6 | - https://x.com/TESSERACT___/status/1810192748735078842 7 | tags: 8 | - supershell 9 | - c2 10 | author: Efstratios Lontzetidis, @lontze7 11 | date: 2024/07/07 12 | query: 13 | parameters: 14 | part1: 15 | http_title: ':"Supershell - 登录"' 16 | condition: part1 17 | falsepositives: 18 | - No false positives at the moment 19 | level: high 20 | -------------------------------------------------------------------------------- /queries/TOOLS/viper_c2_title_body.yml: -------------------------------------------------------------------------------- 1 | title: Viper C2 - Title and Body 2 | id: pythia-b423976b-174d-472a-bb61-e639a3dc9bb2 3 | status: experimental 4 | description: This query identifies Viper C2 infrastructure using html title and body 5 | references: 6 | - https://x.com/cyber_ra1/status/1790009791915425810 7 | tags: 8 | - viper 9 | - rat 10 | - c2 11 | - commandandcontrol 12 | author: Efstratios Lontzetidis, @lontze7 13 | date: 2024/07/31 14 | query: 15 | parameters: 16 | part1: 17 | http_body: ':"Sorry, we need js to run correctly!"' 18 | part2: 19 | http_title: ':"Viper"' 20 | condition: part1 and part2 21 | falsepositives: 22 | - No false positives at the moment 23 | level: moderate 24 | -------------------------------------------------------------------------------- /queries/TOOLS/vshell_html_title.yml: -------------------------------------------------------------------------------- 1 | title: Vshell Remote Admin Tool - HTML Title 2 | id: pythia-b423979f-164d-472a-bb61-e639a3dc9db2 3 | status: stable 4 | description: This query identifies Vshell remote admin tool using html title 5 | references: 6 | - https://x.com/MichalKoczwara/status/1643578019242442752 7 | tags: 8 | - vshell 9 | - remote_admin_tool 10 | author: Efstratios Lontzetidis, @lontze7 11 | date: 2024/07/31 12 | query: 13 | parameters: 14 | part1: 15 | http_title: ':"Vshell - 登录"' 16 | condition: part1 17 | falsepositives: 18 | - No false positives at the moment 19 | level: high 20 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2024.7.4 2 | charset-normalizer==3.3.2 3 | DateTime==5.5 4 | dateutils==0.6.12 5 | idna==3.7 6 | ipaddress==1.0.23 7 | pyfiglet==1.0.2 8 | python-dateutil==2.9.0.post0 9 | pytz==2024.1 10 | PyYAML==6.0.2 11 | requests==2.32.3 12 | six==1.16.0 13 | termcolor==2.4.0 14 | urllib3==2.2.2 15 | zope.interface==6.4.post2 16 | -------------------------------------------------------------------------------- /tests/pythia_format_validator.py: -------------------------------------------------------------------------------- 1 | from termcolor import colored 2 | import re 3 | from datetime import datetime 4 | 5 | PYTHIA_VALID_FILTERS = [ 6 | 'label', 'ip_address', 'cidr', 'server', 'domain', 'hostname', 'organization', 7 | 'port_number', 'base_protocol', 'protocol', 'port_size_gt', 'port_size_lt', 'port_size', 8 | 'operating_system', 'application', 'product_version', 'product', 'time_scanned', 9 | 'common_platform_enumeration_cpe', 'country_code', 'country_name', 'postal_code', 'state_name', 10 | 'region_name', 'city_name', 'autonomous_system_number', 'autonomous_system_name', 11 | 'autonomous_system_organization', 'http_title', 'http_header_hash', 'http_header', 12 | 'service_banner_hash', 'service_banner', 'http_body_hash', 'http_body', 'http_status_code', 13 | 'http_favicon_hash_dec', 'http_favicon_hash', 'tls_certificate_issuer_org', 14 | 'tls_certificate_subject_org', 'tls_certificate_subject_cn', 'tls_certificate_issuer_cn', 15 | 'tls_certificate_issuer', 'tls_certificate_subject', 'tls_certificate_not_after', 16 | 'tls_certificate_not_before', 'tls_certificate_sha1', 'tls_certificate_sha256', 17 | 'tls_certificate_md5', 'tls_certificate_is_expired', 'tls_certificate.is_valid', 18 | 'tls_certificate.pubkey_rsa_bits', 'tls_certificate_pubkey_ecdsa_bits', 19 | 'tls_certificate_pubkey_type', 'tls_certificate_cipher_name', 'tls_certificate_algorithm', 20 | 'tls_certificate_version', 'tls_certificate', 'jarm_fingerprint', 'ja3s_fingerprint', 21 | 'ja4s_fingerprint', 'ssh_hassh', 'ssh_banner_sha256', 'ssh_banner', 'ssh_key_length', 22 | 'ssh_fingerprint' 23 | ] 24 | 25 | REQUIRED_FIELDS = { 26 | 'title': str, 'id': str, 'status': str, 'description': str, 'references': list, 'tags': list, 27 | 'author': str, 'date': str, 'query': dict, 'falsepositives': list, 'level': str 28 | } 29 | 30 | def validate_fields_exist(rule): 31 | for field, field_type in REQUIRED_FIELDS.items(): 32 | if field not in rule: 33 | print(colored("[-]", 'red') + f"Missing field: {field}") 34 | return False 35 | if not isinstance(rule[field], field_type): 36 | print(colored("[-]", 'red') + f"Incorrect type for field: {field}") 37 | return False 38 | return True 39 | 40 | def validate_non_empty(value, field_name): 41 | if value: 42 | return True 43 | print(colored("[-]", 'red') + f"{field_name} cannot be empty") 44 | return False 45 | 46 | def validate_choice(value, field_name, choices): 47 | if value in choices: 48 | return True 49 | print(colored("[-]", 'red') + f"Invalid {field_name}") 50 | return False 51 | 52 | def validate_date_format(date_str): 53 | try: 54 | datetime.strptime(date_str, '%Y/%m/%d') 55 | return True 56 | except ValueError as e: 57 | print(e) 58 | return False 59 | 60 | def validate_list_elements(lst, field_name): 61 | if isinstance(lst, list) and all(isinstance(item, str) for item in lst): 62 | return True 63 | print(colored("[-]", 'red') + f"Invalid list element in {field_name} section") 64 | return False 65 | 66 | def validate_query(query): 67 | if 'parameters' not in query or 'condition' not in query: 68 | print(colored("[-]", 'red') + f"Missing field: {'parameters' if 'parameters' not in query else 'condition'}") 69 | return False 70 | if not isinstance(query['parameters'], dict): 71 | print(colored("[-]", 'red') + "Parameters are not of type dict") 72 | return False 73 | if not isinstance(query['condition'], str): 74 | print(colored("[-]", 'red') + "Condition is not of type string") 75 | return False 76 | for conditions in query['parameters'].values(): 77 | if not isinstance(conditions, dict): 78 | print(colored("[-]", 'red') + "Invalid query format parameters and filters") 79 | return False 80 | for key, value in conditions.items(): 81 | if not isinstance(value, str) or key not in PYTHIA_VALID_FILTERS: 82 | print(colored("[-]", 'red') + f"Field: '{key}' is not applicable to Pythia") 83 | return False 84 | return True 85 | 86 | def validate_rule(rule): 87 | if not validate_fields_exist(rule): 88 | return False 89 | return ( 90 | validate_non_empty(rule.get('title'), 'Title') and 91 | validate_choice(rule.get('status'), 'status', ["experimental", "test", "stable"]) and 92 | validate_non_empty(rule.get('description'), 'Description') and 93 | validate_list_elements(rule.get('references', []), 'references') and 94 | validate_list_elements(rule.get('tags', []), 'tags') and 95 | validate_non_empty(rule.get('author'), 'Authors') and 96 | validate_date_format(rule.get('date', '')) and 97 | validate_query(rule.get('query', {})) and 98 | validate_list_elements(rule.get('falsepositives', []), 'falsepositives') and 99 | validate_choice(rule.get('level'), 'level', ["low", "moderate", "high"]) 100 | ) 101 | 102 | def validator(data): 103 | if validate_rule(data): 104 | print(colored("[+]", 'green') + data.get('title', '') + "' is valid") 105 | return True 106 | else: 107 | print(colored("[-]", 'red') + data.get('title', '') + " is invalid") 108 | return False --------------------------------------------------------------------------------