├── .gitignore ├── .idea ├── .gitignore ├── modules.xml ├── ripgen.iml └── vcs.xml ├── Cargo.toml ├── README.md ├── ripgen ├── Cargo.toml ├── README.md └── src │ ├── args.rs │ └── main.rs └── ripgen_lib ├── Cargo.toml ├── README.md └── src ├── chain.rs ├── dnsgen ├── dash.rs ├── mod.rs ├── numbers.rs ├── permute.rs └── swap.rs ├── domain.rs ├── error.rs ├── lib.rs ├── manager.rs ├── transform.rs └── words.rs /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/rust,clion 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=rust,clion 4 | 5 | ### CLion ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # AWS User-specific 17 | .idea/**/aws.xml 18 | 19 | # Generated files 20 | .idea/**/contentModel.xml 21 | 22 | # Sensitive or high-churn files 23 | .idea/**/dataSources/ 24 | .idea/**/dataSources.ids 25 | .idea/**/dataSources.local.xml 26 | .idea/**/sqlDataSources.xml 27 | .idea/**/dynamic.xml 28 | .idea/**/uiDesigner.xml 29 | .idea/**/dbnavigator.xml 30 | 31 | # Gradle 32 | .idea/**/gradle.xml 33 | .idea/**/libraries 34 | 35 | # Gradle and Maven with auto-import 36 | # When using Gradle or Maven with auto-import, you should exclude module files, 37 | # since they will be recreated, and may cause churn. Uncomment if using 38 | # auto-import. 39 | # .idea/artifacts 40 | # .idea/compiler.xml 41 | # .idea/jarRepositories.xml 42 | # .idea/modules.xml 43 | # .idea/*.iml 44 | # .idea/modules 45 | # *.iml 46 | # *.ipr 47 | 48 | # CMake 49 | cmake-build-*/ 50 | 51 | # Mongo Explorer plugin 52 | .idea/**/mongoSettings.xml 53 | 54 | # File-based project format 55 | *.iws 56 | 57 | # IntelliJ 58 | out/ 59 | 60 | # mpeltonen/sbt-idea plugin 61 | .idea_modules/ 62 | 63 | # JIRA plugin 64 | atlassian-ide-plugin.xml 65 | 66 | # Cursive Clojure plugin 67 | .idea/replstate.xml 68 | 69 | # SonarLint plugin 70 | .idea/sonarlint/ 71 | 72 | # Crashlytics plugin (for Android Studio and IntelliJ) 73 | com_crashlytics_export_strings.xml 74 | crashlytics.properties 75 | crashlytics-build.properties 76 | fabric.properties 77 | 78 | # Editor-based Rest Client 79 | .idea/httpRequests 80 | 81 | # Android studio 3.1+ serialized cache file 82 | .idea/caches/build_file_checksums.ser 83 | 84 | ### CLion Patch ### 85 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 86 | 87 | # *.iml 88 | # modules.xml 89 | # .idea/misc.xml 90 | # *.ipr 91 | 92 | # Sonarlint plugin 93 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 94 | .idea/**/sonarlint/ 95 | 96 | # SonarQube Plugin 97 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 98 | .idea/**/sonarIssues.xml 99 | 100 | # Markdown Navigator plugin 101 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 102 | .idea/**/markdown-navigator.xml 103 | .idea/**/markdown-navigator-enh.xml 104 | .idea/**/markdown-navigator/ 105 | 106 | # Cache file creation bug 107 | # See https://youtrack.jetbrains.com/issue/JBR-2257 108 | .idea/$CACHE_FILE$ 109 | 110 | # CodeStream plugin 111 | # https://plugins.jetbrains.com/plugin/12206-codestream 112 | .idea/codestream.xml 113 | 114 | ### Rust ### 115 | # Generated by Cargo 116 | # will have compiled files and executables 117 | debug/ 118 | target/ 119 | 120 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 121 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 122 | Cargo.lock 123 | 124 | # These are backup files generated by rustfmt 125 | **/*.rs.bk 126 | 127 | # MSVC Windows builds of rustc generate these, which store debugging information 128 | *.pdb 129 | 130 | # End of https://www.toptal.com/developers/gitignore/api/rust,clion 131 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/ripgen.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "ripgen", 4 | "ripgen_lib" 5 | ] 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ripgen 2 | A rust-based version of the popular [dnsgen](https://github.com/ProjectAnte/dnsgen) python utility. 3 | 4 | `ripgen` is split into two main parts: 5 | 6 | * **ripgen**: _A CLI utility that calls into `ripgen_lib` and uses dnsgen's transforms_. 7 | * **ripgen_lib**: _A library that allows you to create high performance permutations of domain names._ 8 | 9 | # How to Install! 10 | Installation of `ripgen` is very simple - follow the steps below. 11 | 12 | ### Step 1 - rustup.rs 13 | Visit https://rustup.rs/ and follow the instructions to get started with `rust` and `cargo`. 14 | 15 | ### Step 2 - cargo install 16 | Run `cargo install ripgen` 17 | 18 | ## How to Use - `ripgen` 19 | `ripgen` optionally takes a domain file, a wordlist file, and a minimum word length argument. 20 | 21 | If no domain file is listed, domains are expected through `stdin` making it easy to pipe into from other tools. 22 | 23 | e.g. 24 | ``` 25 | $ echo "www1.google.com" | ripgen 26 | ``` 27 | 28 | One deviation from dnsgen's behavior is that if no wordlist is specified then no wordlist items are included automatically. To compare `ripgen` and `dnsgen` appropriately you should make sure to specify a wordlist. 29 | 30 | ## How to use - `ripgen_lib` 31 | `ripgen_lib` exposes a `RipGenManager` struct that takes in three components: 32 | 33 | * an iterator for domain names 34 | * an iterator for wordlist entries 35 | * a function that converts `&&str` into `bool` for the purposes of filtering wordlist entries 36 | 37 | After creating a `RipGenManager`, transforms can be added on with `transform` and `chain_transform`. These transforms require a function definition (closure or otherwise) be passed in that can take the `&DomainComponent` and `WordListIterator` types and return an `Iterator`. 38 | 39 | Look at the non-default dnsgen transform implementations for examples on how these are implemented typically. 40 | 41 | # FAQ 42 | ## `linker 'cc' not found` 43 | If this happens, it means that you need to install some dependencies on your system to build `ripgen`. Here's how to fix that: 44 | 45 | 46 | ### Debian (Ubuntu, Kali, WSL (_you probably used Ubuntu_)) 47 | ``` 48 | sudo apt-get update 49 | sudo apt install build-essential 50 | ``` 51 | 52 | ### Arch 53 | ``` 54 | sudo pacman -S base-devel 55 | ``` 56 | 57 | ### Centos 58 | ``` 59 | sudo yum install gcc 60 | ``` 61 | 62 | ### Alpine 63 | ``` 64 | apk add build-base --no-cache 65 | ``` 66 | 67 | -------------------------------------------------------------------------------- /ripgen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ripgen" 3 | version = "0.1.5" 4 | edition = "2021" 5 | authors = ["d0nut ", "youstin"] 6 | description = "A rust-based version of the popular dnsgen python utility." 7 | readme = "README.md" 8 | keywords = ["bug", "bounty", "dnsgen", "recon", "security"] 9 | license = "MIT" 10 | repository = "https://github.com/resyncgg/ripgen" 11 | 12 | [dependencies] 13 | anyhow = "1" 14 | clap = { version = "3", features = ["derive"]} 15 | ripgen_lib = { version = "0.1", path = "../ripgen_lib", features = ["dnsgen"] } 16 | -------------------------------------------------------------------------------- /ripgen/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /ripgen/src/args.rs: -------------------------------------------------------------------------------- 1 | use std::fs::read_to_string; 2 | use std::io::{BufRead, stdin}; 3 | use clap::Parser; 4 | use anyhow::Result; 5 | 6 | #[derive(Parser, Debug)] 7 | #[clap(author, version, about)] 8 | pub struct Args { 9 | #[clap( 10 | short = 'd', 11 | long = "domains", 12 | help = "The file containing domains you want to generate permutations from. If this is not specified, domains are read from stdin." 13 | )] 14 | pub(crate) domain_file_path: Option, 15 | 16 | #[clap( 17 | short = 'w', 18 | long = "wordlist", 19 | help = "The supplementary wordlist file to include." 20 | )] 21 | pub(crate) wordlist: Option, 22 | 23 | #[clap( 24 | short = 'l', 25 | long = "len", 26 | help = "The minimum length for a word to be considered important. If not specified, all words are accepted." 27 | )] 28 | pub(crate) min_word_len: Option, 29 | 30 | #[clap( 31 | short = 'f', 32 | long = "fast", 33 | help = "Uses the most likely words only in permutations" 34 | )] 35 | pub(crate) fast: Option 36 | } 37 | 38 | impl Args { 39 | pub(crate) fn get_domain_str(&self) -> Result { 40 | let output = match self.domain_file_path { 41 | Some(ref path) => read_to_string(path)?, 42 | None => { 43 | stdin() 44 | .lock() 45 | .lines() 46 | .collect::, _>>()? 47 | .join("\n") 48 | } 49 | }; 50 | 51 | Ok(output) 52 | } 53 | 54 | pub(crate) fn get_wordlist_str(&self) -> Result { 55 | let output = match self.wordlist { 56 | Some(ref path) => read_to_string(path)?, 57 | None => String::new() 58 | }; 59 | 60 | Ok(output) 61 | } 62 | } -------------------------------------------------------------------------------- /ripgen/src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | mod args; 4 | 5 | use std::io::{BufWriter, stdout, Write}; 6 | use ripgen_lib::{RipGenIterator, RipGenManager}; 7 | use crate::args::Args; 8 | use clap::Parser; 9 | 10 | const FAST_MODE_WORDLIST_LEN: usize = 10; 11 | const DEFAULT_WORD_LEN: usize = 5; 12 | 13 | fn main() { 14 | let args: Args = Args::parse(); 15 | 16 | let domains = args.get_domain_str() 17 | .expect("Failed to read in domains."); 18 | let wordlist = args.get_wordlist_str() 19 | .expect("Failed to read in wordlist file."); 20 | let word_len = args.min_word_len.unwrap_or(DEFAULT_WORD_LEN); 21 | 22 | let wordlist_lines = get_wordlist(&wordlist, &args); 23 | 24 | let manager = RipGenManager::new( 25 | domains.lines(), 26 | wordlist_lines, 27 | &|word| word.len() >= word_len 28 | ).expect("Failed to create ripgen iterator"); 29 | 30 | let rip_iter = manager 31 | .transform(ripgen_lib::dnsgen::swap_word_transform) 32 | .chain_transform(ripgen_lib::dnsgen::permute_words_transform) 33 | .chain_transform(ripgen_lib::dnsgen::numbers_transform) 34 | .chain_transform(ripgen_lib::dnsgen::dash_transform); 35 | 36 | stream_output(rip_iter); 37 | } 38 | 39 | fn stream_output(rip_iter: impl Iterator) { 40 | let out = stdout(); 41 | let stdout_lock = out.lock(); 42 | let mut buf = BufWriter::new(stdout_lock); 43 | 44 | for line in rip_iter { 45 | if writeln!(buf, "{}", line).is_err() { 46 | // user might be using `head` to only grab the first couple of entries - we should exit 47 | let _ = buf.flush(); 48 | return; 49 | } 50 | } 51 | 52 | let _ = buf.flush(); 53 | } 54 | 55 | fn get_wordlist<'a>(wordlist: &'a str, args: &Args) -> impl Iterator { 56 | // https://github.com/ProjectAnte/dnsgen/blob/16daeef81205e7663708b3ee11d759215c7168fe/dnsgen/dnsgen.py#L220 57 | let mut wordlist_iter = None; 58 | let mut fast_wordlist_iter = None; 59 | 60 | match args.fast { 61 | Some(fast_mode) if fast_mode => { 62 | fast_wordlist_iter = Some(wordlist.lines().take(FAST_MODE_WORDLIST_LEN)) 63 | }, 64 | _ => wordlist_iter = Some(wordlist.lines()) 65 | } 66 | 67 | wordlist_iter 68 | .into_iter() 69 | .flatten() 70 | .chain(fast_wordlist_iter.into_iter().flatten()) 71 | } -------------------------------------------------------------------------------- /ripgen_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ripgen_lib" 3 | version = "0.1.4" 4 | edition = "2021" 5 | authors = ["d0nut ", "youstin"] 6 | description = "High-performance domain-name permutation generator." 7 | keywords = ["bug", "bounty", "dnsgen", "recon", "security"] 8 | readme = "README.md" 9 | license = "MIT" 10 | repository = "https://github.com/resyncgg/ripgen" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | thiserror = "1" 16 | addr = "0.15" 17 | fxhash = "0.2.1" 18 | regex = { version = "1", optional = true } 19 | lazy_static = { version = "1", optional = true } 20 | 21 | [features] 22 | dnsgen = ["regex", "lazy_static"] 23 | default = [] 24 | -------------------------------------------------------------------------------- /ripgen_lib/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /ripgen_lib/src/chain.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use crate::DomainComponents; 3 | use crate::transform::RipGenTransform; 4 | 5 | /// Describes an iterator that can be apart of a [RipGenChain](crate::RipGenChain). 6 | pub trait RipGenIterator<'manager, 'domain, D, W> 7 | where 8 | Self: Iterator + Sized, 9 | D: Iterator> + Clone, 10 | W: Iterator + Clone, 11 | 'domain: 'manager 12 | { 13 | /// Returns an iterator over the underlying [DomainComponents](crate::DomainComponents). 14 | fn get_domains_iter(&self) -> D; 15 | 16 | /// Returns an iterator over the underlying wordlist. 17 | fn get_words_iter(&self) -> W; 18 | 19 | /// Chain this `RipGenIterator` with another `RipGenIterator` over the specified transform. 20 | /// 21 | /// ``` 22 | /// # use ripgen_lib::{DomainComponents, RipGenIterator, RipGenManager, WordlistIterator}; 23 | /// # fn add_prefix(word: impl Into) -> impl Fn(&DomainComponents, WordlistIterator) -> std::vec::IntoIter { 24 | /// # let word_str = word.into(); 25 | /// # move |domain_components: &DomainComponents, _: WordlistIterator| -> std::vec::IntoIter { 26 | /// # let domain_str: String = domain_components 27 | /// # .all() 28 | /// # .join("."); 29 | /// # 30 | /// # vec![format!("{}.{}", word_str, domain_str)].into_iter() 31 | /// # } 32 | /// # } 33 | /// let domains = vec!["example.com"]; 34 | /// let domain_iter = domains.iter().map(|elem| *elem); 35 | /// let wordlist = vec![]; 36 | /// let wordlist_iter = wordlist.iter().map(|elem| *elem); 37 | /// 38 | /// let manager = RipGenManager::new(domain_iter, wordlist_iter) 39 | /// .expect("Failed to parse domains."); 40 | /// 41 | /// let mut iter = manager 42 | /// .transform(add_prefix("admin")) 43 | /// .chain_transform(add_prefix("internal")) 44 | /// .chain_transform(add_prefix("manager")); 45 | /// 46 | /// assert_eq!(iter.next(), Some("admin.example.com".to_string())); 47 | /// assert_eq!(iter.next(), Some("internal.example.com".to_string())); 48 | /// assert_eq!(iter.next(), Some("manager.example.com".to_string())); 49 | /// ``` 50 | fn chain_transform(self, transform: F) -> RipGenChain<'manager, 'domain, Self, RipGenTransform<'manager, 'domain, F, D, W, O>, D, W> 51 | where 52 | F: Fn(&'manager DomainComponents<'domain>, W) -> O, 53 | O: Iterator, 54 | 'domain: 'manager 55 | { 56 | let domain_transform = RipGenTransform::new(self.get_domains_iter(), self.get_words_iter(), transform); 57 | 58 | RipGenChain::new(self, domain_transform) 59 | } 60 | } 61 | 62 | pub struct RipGenChain<'manager, 'domain, L, R, D, W> 63 | where 64 | L: RipGenIterator<'manager, 'domain, D, W>, 65 | R: RipGenIterator<'manager, 'domain, D, W>, 66 | D: Iterator> + Clone, 67 | W: Iterator + Clone, 68 | 'domain: 'manager 69 | { 70 | left: Option, 71 | right: Option, 72 | manager_phantom: PhantomData<&'manager ()>, 73 | domain_phantom: PhantomData<&'domain ()>, 74 | domain_iterator_phantom: PhantomData, 75 | word_iterator_phantom: PhantomData 76 | } 77 | 78 | impl<'manager, 'domain, L, R, D, W> RipGenChain<'manager, 'domain, L, R, D, W> 79 | where 80 | L: RipGenIterator<'manager, 'domain, D, W>, 81 | R: RipGenIterator<'manager, 'domain, D, W>, 82 | D: Iterator> + Clone, 83 | W: Iterator + Clone, 84 | 'domain: 'manager 85 | { 86 | fn new(left: L, right: R) -> Self { 87 | Self { 88 | left: Some(left), 89 | right: Some(right), 90 | manager_phantom: PhantomData, 91 | domain_phantom: PhantomData, 92 | domain_iterator_phantom: PhantomData, 93 | word_iterator_phantom: PhantomData 94 | } 95 | } 96 | } 97 | 98 | impl<'manager, 'domain, L, R, D, W> Iterator for RipGenChain<'manager, 'domain, L, R, D, W> 99 | where 100 | L: RipGenIterator<'manager, 'domain, D, W>, 101 | R: RipGenIterator<'manager, 'domain, D, W>, 102 | D: Iterator> + Clone, 103 | W: Iterator + Clone, 104 | 'domain: 'manager 105 | { 106 | type Item = String; 107 | 108 | fn next(&mut self) -> Option { 109 | if let Some(ref mut inner) = self.left { 110 | if let Some(output) = inner.next() { 111 | return Some(output); 112 | } 113 | 114 | self.left = None; 115 | } 116 | 117 | if let Some(ref mut inner) = self.right { 118 | if let Some(output) = inner.next() { 119 | return Some(output); 120 | } 121 | 122 | self.right = None; 123 | } 124 | 125 | None 126 | } 127 | } 128 | 129 | impl<'manager, 'domain, L, R, D, W> RipGenIterator<'manager, 'domain, D, W> for RipGenChain<'manager, 'domain, L, R, D, W> 130 | where 131 | L: RipGenIterator<'manager, 'domain, D, W>, 132 | R: RipGenIterator<'manager, 'domain, D, W>, 133 | D: Iterator> + Clone, 134 | W: Iterator + Clone, 135 | 'domain: 'manager 136 | { 137 | fn get_domains_iter(&self) -> D { 138 | match self.left { 139 | Some(ref inner) => inner.get_domains_iter(), 140 | None => match self.right { 141 | Some(ref inner) => inner.get_domains_iter(), 142 | None => panic!("huh") 143 | } 144 | } 145 | } 146 | 147 | fn get_words_iter(&self) -> W { 148 | match self.left { 149 | Some(ref inner) => inner.get_words_iter(), 150 | None => match self.right { 151 | Some(ref inner) => inner.get_words_iter(), 152 | None => panic!("huh") 153 | } 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /ripgen_lib/src/dnsgen/dash.rs: -------------------------------------------------------------------------------- 1 | use crate::{DomainComponents, WordlistIterator}; 2 | 3 | pub fn dash_transform<'domain>( 4 | domain_components: &'domain DomainComponents, 5 | words: WordlistIterator<'domain>, 6 | ) -> impl Iterator + 'domain { 7 | words.flat_map(move |word| { 8 | transform_components(domain_components, *word, dash) 9 | .chain(transform_components(domain_components, *word, rdash)) 10 | .chain(transform_components(domain_components, *word, concat)) 11 | .chain(transform_components(domain_components, *word, rconcat)) 12 | }) 13 | } 14 | 15 | fn transform_components<'domain, F: Fn(&str, &str) -> String + 'domain>( 16 | domain_components: &'domain DomainComponents<'domain>, 17 | word: &'domain str, 18 | transform: F 19 | ) -> impl Iterator + 'domain { 20 | (0 .. domain_components.count() - 1) 21 | .map(move |idx| { 22 | let new_word = transform(domain_components.all()[idx], word); 23 | 24 | let new_sub: &[&str] = &[ 25 | &domain_components.all()[.. idx], 26 | [new_word.as_str()].as_slice(), 27 | &domain_components.all()[idx + 1 ..] 28 | ].concat(); 29 | 30 | new_sub.join(".") 31 | }) 32 | } 33 | 34 | fn dash(left: &str, right: &str) -> String { 35 | format!("{}-{}", left, right) 36 | } 37 | 38 | fn rdash(left: &str, right: &str) -> String { 39 | dash(right, left) 40 | } 41 | 42 | fn concat(left: &str, right: &str) -> String { 43 | format!("{}{}", left, right) 44 | } 45 | 46 | fn rconcat(left: &str, right: &str) -> String { 47 | concat(right, left) 48 | } -------------------------------------------------------------------------------- /ripgen_lib/src/dnsgen/mod.rs: -------------------------------------------------------------------------------- 1 | mod numbers; 2 | mod permute; 3 | mod swap; 4 | mod dash; 5 | 6 | pub use numbers::numbers_transform; 7 | pub use permute::permute_words_transform; 8 | pub use swap::swap_word_transform; 9 | pub use dash::dash_transform; -------------------------------------------------------------------------------- /ripgen_lib/src/dnsgen/numbers.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | use lazy_static::lazy_static; 3 | use crate::{DomainComponents, WordlistIterator}; 4 | 5 | lazy_static! { 6 | static ref DIGIT_REGEX: Regex = Regex::new(r"\d{1,3}").unwrap(); 7 | } 8 | 9 | #[inline(always)] 10 | pub fn numbers_transform( 11 | domain_components: &DomainComponents, 12 | _: WordlistIterator 13 | ) -> impl Iterator { 14 | let domain_str: String = domain_components 15 | .subdomains() 16 | .join("."); 17 | 18 | let root = domain_components 19 | .root() 20 | .to_string(); 21 | 22 | DIGIT_REGEX 23 | .captures_iter(&domain_str) 24 | .flat_map(|captures| { 25 | let detected_int_str = &captures[0]; 26 | let detected_int: u64 = detected_int_str.parse().unwrap(); 27 | let mut results = Vec::with_capacity(2); 28 | 29 | for offset in 1 .. 4 { 30 | if let Some(sub_int) = detected_int.checked_sub(offset) { 31 | let replaced_front = domain_str.replace(detected_int_str, &sub_int.to_string()); 32 | 33 | results.push(format!("{}.{}", replaced_front, root)); 34 | } 35 | 36 | if let Some(add_int) = detected_int.checked_add(offset) { 37 | let replaced_front = domain_str.replace(detected_int_str, &add_int.to_string()); 38 | 39 | results.push(format!("{}.{}", replaced_front, root)); 40 | } 41 | } 42 | 43 | results 44 | }) 45 | .collect::>() 46 | .into_iter() 47 | } -------------------------------------------------------------------------------- /ripgen_lib/src/dnsgen/permute.rs: -------------------------------------------------------------------------------- 1 | use crate::{DomainComponents, WordlistIterator}; 2 | 3 | pub fn permute_words_transform<'domain>( 4 | domain_components: &'domain DomainComponents, 5 | words: WordlistIterator<'domain>, 6 | ) -> impl Iterator + 'domain { 7 | words 8 | .flat_map(move |word| { 9 | (0 .. domain_components.count()).map(move |idx| { 10 | let domain_elems = domain_components.all(); 11 | // this is the domain but with the word injected into it 12 | let augmented_domain_components: Vec<&str> = [ 13 | &domain_elems[.. idx], 14 | [*word].as_slice(), 15 | &domain_elems[idx ..] 16 | ].concat(); 17 | 18 | // combine back into the full domain string 19 | augmented_domain_components.join(".") 20 | }) 21 | }) 22 | } -------------------------------------------------------------------------------- /ripgen_lib/src/dnsgen/swap.rs: -------------------------------------------------------------------------------- 1 | use crate::{DomainComponents, WordlistIterator}; 2 | 3 | pub fn swap_word_transform<'domain>( 4 | domain_components: &'domain DomainComponents, 5 | words: WordlistIterator<'domain>, 6 | ) -> impl Iterator + 'domain { 7 | let root_string = domain_components.root().to_string(); 8 | let subdomain_string: String = domain_components.subdomains().join("."); 9 | let subdomain_replace = subdomain_string.clone(); 10 | 11 | let word_copy = words.clone(); 12 | 13 | words 14 | .filter(move |word| subdomain_string.contains(*word)) 15 | .flat_map(move |word| { 16 | let word_clone = <&str>::clone(word); 17 | let subdomain_replace = subdomain_replace.clone(); 18 | let root_string = root_string.clone(); 19 | 20 | word_copy 21 | .clone() 22 | .filter(move |sub_word| **sub_word != word_clone) 23 | .map(move |sub_word| { 24 | let replaced_subdomain = subdomain_replace.replace(word, sub_word); 25 | format!("{replaced_subdomain}.{root_string}") 26 | }) 27 | }) 28 | } -------------------------------------------------------------------------------- /ripgen_lib/src/domain.rs: -------------------------------------------------------------------------------- 1 | use addr::dns::Name; 2 | use addr::parse_dns_name; 3 | use crate::error::RipGenError; 4 | 5 | #[derive(Clone)] 6 | /// Contains the byproduct of parsing a domain 7 | pub struct DomainComponents<'domain> { 8 | components: Vec<&'domain str> 9 | } 10 | 11 | impl<'domain> DomainComponents<'domain> { 12 | /// Returns an iterator that contains all of the names in the subdomains. 13 | pub fn subdomains_iter(&self) -> impl Iterator { 14 | self.all_iter().take(self.count() - 1) 15 | } 16 | 17 | /// Returns an iterator that contains all of the names in the original domain. 18 | /// The root is treated as the public suffix and therefore entirely occupies the last slot. 19 | /// 20 | /// ``` 21 | /// # use ripgen_lib::DomainComponents; 22 | /// # use std::convert::TryFrom; 23 | /// let domain_component = DomainComponents::try_from("www.google.com") 24 | /// .expect("Failed to parse."); 25 | /// 26 | /// let mut iter = domain_component 27 | /// .all_iter() 28 | /// .map(|elem| *elem); 29 | /// 30 | /// assert_eq!(iter.next(), Some("www")); 31 | /// assert_eq!(iter.next(), Some("google.com")); 32 | /// ``` 33 | pub fn all_iter(&self) -> impl Iterator { 34 | self.components.iter() 35 | } 36 | 37 | /// Returns the number of subdomain names + 1 (for root) 38 | pub fn count(&self) -> usize { self.components.len() } 39 | 40 | /// Returns a slice of the inner domain components that represent the subdomain names 41 | pub fn subdomains(&self) -> &[&str] { 42 | &self.components[.. self.count() - 1] 43 | } 44 | 45 | /// Returns the root 46 | /// 47 | /// ``` 48 | /// # use ripgen_lib::DomainComponents; 49 | /// # use std::convert::TryFrom; 50 | /// let domain_component = DomainComponents::try_from("www.google.com") 51 | /// .expect("Failed to parse."); 52 | /// 53 | /// assert_eq!(domain_component.root(), "google.com"); 54 | /// ``` 55 | pub fn root(&self) -> &str { 56 | self.components[self.components.len() - 1] 57 | } 58 | 59 | /// Returns a slice with all domain components in it. This includes the root element. 60 | pub fn all(&self) -> &[&str] { 61 | &self.components 62 | } 63 | } 64 | 65 | impl<'domain> TryFrom<&'domain str> for DomainComponents<'domain> { 66 | type Error = RipGenError; 67 | 68 | fn try_from(domain: &'domain str) -> Result { 69 | let parsed_domain_name: Name<'domain> = parse_dns_name(domain) 70 | .map_err(|_| RipGenError::ErrorParsingDomain(domain.to_string()))?; 71 | 72 | let root: &str = parsed_domain_name 73 | .root() 74 | .unwrap_or(domain); 75 | 76 | // we do this so we can appease lifetimes 77 | let root_start = domain.len() - root.len(); 78 | let root: &'domain str = &domain[root_start..]; 79 | 80 | let components: Vec<&'domain str> = domain 81 | .trim_end_matches(root) 82 | .split('.') 83 | .filter(|elem| !elem.is_empty()) 84 | .chain(vec![root]) 85 | .collect::>(); 86 | 87 | let new_components = Self { 88 | components 89 | }; 90 | 91 | Ok(new_components) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /ripgen_lib/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | /// The RipGen error type. 5 | pub enum RipGenError { 6 | #[error("Unable to parse provided domain.")] 7 | ErrorParsingDomain(String), 8 | } -------------------------------------------------------------------------------- /ripgen_lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | pub use chain::{ 4 | RipGenChain, 5 | RipGenIterator 6 | }; 7 | pub use domain::DomainComponents; 8 | pub use error::RipGenError; 9 | pub use manager::RipGenManager; 10 | 11 | mod manager; 12 | mod domain; 13 | mod error; 14 | mod words; 15 | mod chain; 16 | pub(crate) mod transform; 17 | 18 | #[cfg(feature = "dnsgen")] 19 | pub mod dnsgen; 20 | 21 | /// Placeholder for a HashSet iterator with annoying lifetimes 22 | pub type WordlistIterator<'domain> = std::collections::hash_set::Iter<'domain, &'domain str>; -------------------------------------------------------------------------------- /ripgen_lib/src/manager.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::DomainComponents; 2 | use crate::error::RipGenError; 3 | use fxhash::FxHashSet; 4 | use std::slice::Iter as SliceIter; 5 | use std::collections::hash_set::Iter as HashSetIter; 6 | use crate::transform::RipGenTransform; 7 | 8 | #[derive(Clone)] 9 | /// Processes and manages domains and wordlist elements to enable creating [RipGenIterator](crate::RipGenIterator) 10 | /// via [transformations](Self::transform). 11 | pub struct RipGenManager<'domains> { 12 | domain_components: Vec>, 13 | elements: FxHashSet<&'domains str>, 14 | } 15 | 16 | impl<'domain> RipGenManager<'domain> { 17 | /// Creates a new `RipGenManager`. 18 | /// 19 | /// This can fail if any of the `domains` are unable to be parsed. 20 | pub fn new( 21 | domains: impl Iterator, 22 | words: impl Iterator, 23 | word_filter: &impl Fn(&&str) -> bool 24 | ) -> Result, RipGenError> 25 | { 26 | let domain_components: Vec = domains 27 | .filter(|line| !line.is_empty()) 28 | .map(DomainComponents::try_from) 29 | .collect::>()?; 30 | 31 | let elements: FxHashSet<&'domain str> = crate::words::extract_words(domain_components.iter(), word_filter) 32 | .chain(words) 33 | .collect(); 34 | 35 | let manager = RipGenManager { 36 | domain_components, 37 | elements 38 | }; 39 | 40 | Ok(manager) 41 | } 42 | 43 | /// Begins a RipGen transform iterator. 44 | /// 45 | /// Requires a function that can take both a reference to a [DomainComponents](crate::DomainComponents) 46 | /// as well as an iterator that produces `&&str`. 47 | pub fn transform<'manager, F, O>(&'manager self, transform: F) -> RipGenTransform<'manager, 'domain, F, SliceIter>, HashSetIter<'manager, &'domain str>, O> 48 | where 49 | F: Fn(&'manager DomainComponents<'domain>, HashSetIter<'manager, &'domain str>) -> O, 50 | O: Iterator, 51 | 'domain: 'manager 52 | { 53 | RipGenTransform::new(self.domain_components.iter(), self.elements.iter(), transform) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ripgen_lib/src/transform.rs: -------------------------------------------------------------------------------- 1 | use crate::{DomainComponents, RipGenIterator}; 2 | 3 | pub struct RipGenTransform<'manager, 'domain, F, D, W, O> 4 | where 5 | F: Fn(&'manager DomainComponents<'domain>, W) -> O, 6 | D: Iterator> + Clone, 7 | W: Iterator + Clone, 8 | O: Iterator, 9 | 'domain: 'manager 10 | { 11 | domains: D, 12 | words: W, 13 | result_pool: Vec, 14 | transform: F 15 | } 16 | 17 | impl<'manager, 'domain, F, D, W, O> RipGenTransform<'manager, 'domain, F, D, W, O> 18 | where 19 | F: Fn(&'manager DomainComponents<'domain>, W) -> O, 20 | D: Iterator> + Clone, 21 | W: Iterator + Clone, 22 | O: Iterator, 23 | 'domain: 'manager 24 | { 25 | pub fn new(domains: D, words: W, transform: F) -> Self { 26 | Self { 27 | domains, 28 | words, 29 | result_pool: Vec::with_capacity(1024 * 4), 30 | transform 31 | } 32 | } 33 | } 34 | 35 | 36 | impl<'manager, 'domain, F, D, W, O> Iterator for RipGenTransform<'manager, 'domain, F, D, W, O> 37 | where 38 | F: Fn(&'manager DomainComponents<'domain>, W) -> O, 39 | D: Iterator> + Clone, 40 | W: Iterator + Clone, 41 | O: Iterator, 42 | 'domain: 'manager 43 | { 44 | type Item = String; 45 | 46 | fn next(&mut self) -> Option { 47 | if let Some(next) = self.result_pool.pop() { 48 | return Some(next); 49 | } 50 | 51 | loop { 52 | let domain = self.domains.next()?; 53 | 54 | self.result_pool.extend((self.transform)(domain, self.words.clone())); 55 | 56 | if let Some(result) = self.result_pool.pop() { 57 | return Some(result); 58 | } 59 | } 60 | } 61 | } 62 | 63 | 64 | impl<'manager, 'domain, F, D, W, O> RipGenIterator<'manager, 'domain, D, W> for RipGenTransform<'manager, 'domain, F, D, W, O> 65 | where 66 | F: Fn(&'manager DomainComponents<'domain>, W) -> O, 67 | D: Iterator> + Clone, 68 | W: Iterator + Clone, 69 | O: Iterator, 70 | 'domain: 'manager 71 | { 72 | fn get_domains_iter(&self) -> D { 73 | self.domains.clone() 74 | } 75 | 76 | fn get_words_iter(&self) -> W { 77 | self.words.clone() 78 | } 79 | } -------------------------------------------------------------------------------- /ripgen_lib/src/words.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::DomainComponents; 2 | 3 | 4 | pub(crate) fn extract_words<'iter, 'domain>( 5 | domain_components: impl Iterator> + 'iter, 6 | filter_function: &'iter impl Fn(&&str) -> bool, 7 | ) -> impl Iterator + 'iter 8 | where 9 | 'domain: 'iter 10 | { 11 | domain_components 12 | .flat_map(move |domain| { 13 | let augments = domain 14 | .subdomains_iter() 15 | .flat_map(|elem| elem.split('-')); 16 | 17 | domain 18 | .subdomains_iter().copied() 19 | .chain(augments) 20 | .filter(filter_function) 21 | }) 22 | } 23 | --------------------------------------------------------------------------------