├── 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 |
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 | 
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 | 
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 | 
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
--------------------------------------------------------------------------------