├── README.md ├── LICENSE └── main.go /README.md: -------------------------------------------------------------------------------- 1 | # resolve 2 | A fast resolver in golang 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nizamul Rana 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // 2 | // resolver.go : A simple dns resolving tool with json output 3 | // Written By : @ice3man (Nizamul Rana) 4 | // 5 | // Distributed Under MIT License 6 | // Copyrights (C) 2018 Ice3man 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "bufio" 13 | "crypto/rand" 14 | "encoding/json" 15 | "flag" 16 | "fmt" 17 | "io" 18 | "io/ioutil" 19 | "os" 20 | "reflect" 21 | "strings" 22 | "sync" 23 | 24 | "github.com/bogdanovich/dns_resolver" 25 | ) 26 | 27 | var wg, wg2 sync.WaitGroup 28 | var ResolversLoaded []string 29 | var WildcardIP []string 30 | var IsWildcard bool 31 | var resolver *dns_resolver.DnsResolver 32 | var WildcardMap map[string]bool 33 | var ValidSubs []string 34 | 35 | // Struct containing jobs 36 | type Job struct { 37 | Work string 38 | Result string 39 | } 40 | 41 | var AquatoneOutput []*Job 42 | 43 | var ( 44 | Threads int // Number of threads to use 45 | Domain string // Name of domains 46 | list string // List of subdomains found 47 | output string // Output file to write to 48 | comResolvers string // Comma separated resolvers 49 | listResolvers string // List of resolvers to use 50 | aquatone bool 51 | ) 52 | 53 | // Writes subdomains output to a json file 54 | func WriteOutputAquatoneJSON(subdomains []*Job) error { 55 | m := make(map[string]string) 56 | _, err := os.Create(output) 57 | 58 | if err != nil { 59 | return err 60 | } 61 | 62 | for _, job := range subdomains { 63 | // Set correct values 64 | m[job.Work] = job.Result 65 | } 66 | 67 | fmt.Printf("%v", m) 68 | data, err := json.MarshalIndent(m, "", " ") 69 | if err != nil { 70 | return err 71 | } 72 | 73 | // Write the output to file 74 | err = ioutil.WriteFile(output, data, 0644) 75 | 76 | return nil 77 | } 78 | 79 | // Resolve a host using dns_resolver lib 80 | func ResolveHost(host string) (ips []string, err error) { 81 | // In case of i/o timeout 82 | resolver.RetryTimes = 5 83 | 84 | //fmt.Printf("\n[RESOLVE] Host %s", host) 85 | ip, err := resolver.LookupHost(host) 86 | if err != nil { 87 | return []string{}, err 88 | } 89 | 90 | var retIPs []string 91 | for _, host := range ip { 92 | retIPs = append(retIPs, host.String()) 93 | } 94 | 95 | return retIPs, nil 96 | } 97 | 98 | // NewUUID generates a random UUID according to RFC 4122 99 | // Taken from : https://play.golang.org/p/4FkNSiUDMg 100 | // 101 | // Used for bruteforcing and detection of Wildcard Subdomains :-) 102 | func NewUUID() (string, error) { 103 | uuid := make([]byte, 16) 104 | n, err := io.ReadFull(rand.Reader, uuid) 105 | if n != len(uuid) || err != nil { 106 | return "", err 107 | } 108 | // variant bits; see section 4.1.1 109 | uuid[8] = uuid[8]&^0xc0 | 0x80 110 | // version 4 (pseudo-random); see section 4.1.3 111 | uuid[6] = uuid[6]&^0xf0 | 0x40 112 | return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil 113 | } 114 | 115 | func SliceExists(slice interface{}, item interface{}) bool { 116 | s := reflect.ValueOf(slice) 117 | 118 | if s.Kind() != reflect.Slice { 119 | panic("SliceExists() given a non-slice type") 120 | } 121 | 122 | for i := 0; i < s.Len(); i++ { 123 | if s.Index(i).Interface() == item { 124 | return true 125 | } 126 | } 127 | 128 | return false 129 | } 130 | 131 | // Check if a ip result contains wildcards 132 | func CheckWildcard(ips []string) (result bool) { 133 | for _, ip := range ips { 134 | for _, wildcardIp := range WildcardIP { 135 | if ip == wildcardIp { 136 | return true 137 | } 138 | } 139 | } 140 | 141 | // Not wildcard 142 | return false 143 | } 144 | 145 | // Checks if a host returns wildcard ips and returns status with ips returned 146 | func InitWildcard(domain string) (result bool, ips []string) { 147 | UUIDs := make([]string, 4) 148 | 149 | // Generate 4 random UUIDs 150 | for i := 0; i < 4; i++ { 151 | uuid, err := NewUUID() 152 | if err != nil { 153 | fmt.Printf("\nerror: %v\n", err) 154 | os.Exit(1) 155 | } 156 | UUIDs[i] = uuid 157 | } 158 | 159 | for _, uid := range UUIDs { 160 | attempt := fmt.Sprintf("%s.%s", uid, domain) 161 | 162 | // Currently we check only A records. GoBuster also does that 163 | // I don't think checking both A and CNAME checking is necessary 164 | ips, err := ResolveHost(attempt) 165 | if err != nil { 166 | continue 167 | } 168 | 169 | if len(ips) > 0 { 170 | return true, ips 171 | } 172 | } 173 | 174 | return false, ips 175 | } 176 | 177 | func analyze(results <-chan *Job) { 178 | defer wg2.Done() 179 | for job := range results { 180 | if job.Result != "" { 181 | fmt.Printf("\n[+] %s : %s", job.Work, job.Result) 182 | if aquatone == true { 183 | AquatoneOutput = append(AquatoneOutput, job) 184 | } else { 185 | ValidSubs = append(ValidSubs, job.Work) 186 | } 187 | } 188 | } 189 | } 190 | 191 | func consume(jobs <-chan *Job, results chan<- *Job) { 192 | defer wg.Done() 193 | for job := range jobs { 194 | ips, err := ResolveHost(job.Work) 195 | if err != nil { 196 | continue 197 | } 198 | 199 | if len(ips) <= 0 { 200 | // We didn't found any ips 201 | job.Result = "" 202 | results <- job 203 | } else { 204 | if IsWildcard == true { 205 | result := CheckWildcard(ips) 206 | if result == true { 207 | // We have a wildcard ip 208 | job.Result = "" 209 | results <- job 210 | } else { 211 | // Not a wildcard subdomains ip 212 | job.Result = ips[0] 213 | results <- job 214 | } 215 | } else { 216 | job.Result = ips[0] 217 | results <- job 218 | } 219 | } 220 | } 221 | } 222 | 223 | func produce(jobs chan<- *Job) { 224 | // Read the subdomains from input list and produce 225 | // jobs for them 226 | file, err := os.Open(list) 227 | if err != nil { 228 | fmt.Fprintf(os.Stderr, "\nerror: %v\n", err) 229 | os.Exit(1) 230 | } 231 | 232 | defer file.Close() 233 | 234 | scanner := bufio.NewScanner(file) 235 | 236 | for scanner.Scan() { 237 | // Send the job to the channel 238 | jobs <- &Job{Work: fmt.Sprintf("%s", scanner.Text()), Result: ""} 239 | } 240 | 241 | close(jobs) 242 | } 243 | 244 | func main() { 245 | flag.IntVar(&Threads, "t", 10, "Number of threads to use") 246 | flag.StringVar(&Domain, "d", "", "Domain to resolve subdomains of") 247 | flag.StringVar(&output, "o", "", "File to output subdomains to") 248 | flag.StringVar(&list, "l", "", "File to resolve subdomains from") 249 | flag.StringVar(&comResolvers, "r", "", "Comma-separated list of resolvers to use") 250 | flag.StringVar(&listResolvers, "rL", "", "File containing list of resolvers to use") 251 | flag.BoolVar(&aquatone, "aO", false, "Write output in aquatone format") 252 | flag.Parse() 253 | 254 | if list == "" { 255 | fmt.Printf("\n[!] No Input file specified !\n") 256 | os.Exit(1) 257 | } 258 | 259 | if output == "" { 260 | fmt.Printf("\n[!] No Output file specified !\n") 261 | os.Exit(1) 262 | } 263 | 264 | fmt.Printf("[-] resolve : Subdomains Resolving tool in golang") 265 | fmt.Printf("\n[-] Written By : @ice3man") 266 | fmt.Printf("\n[-] Github : github.com/Ice3man543") 267 | 268 | if comResolvers != "" { 269 | // Load the Resolvers from list 270 | setResolvers := strings.Split(comResolvers, ",") 271 | 272 | for _, resolver := range setResolvers { 273 | ResolversLoaded = append(ResolversLoaded, resolver) 274 | } 275 | } 276 | 277 | if listResolvers != "" { 278 | // Load the resolvers from file 279 | file, err := os.Open(listResolvers) 280 | if err != nil { 281 | fmt.Fprintf(os.Stderr, "\nerror: %v\n", err) 282 | os.Exit(1) 283 | } 284 | 285 | defer file.Close() 286 | 287 | scanner := bufio.NewScanner(file) 288 | 289 | for scanner.Scan() { 290 | // Send the job to the channel 291 | ResolversLoaded = append(ResolversLoaded, scanner.Text()) 292 | } 293 | } 294 | 295 | // Use the default resolvers 296 | if comResolvers == "" && listResolvers == "" { 297 | ResolversLoaded = append(ResolversLoaded, "1.1.1.1") 298 | ResolversLoaded = append(ResolversLoaded, "8.8.8.8") 299 | ResolversLoaded = append(ResolversLoaded, "8.8.4.4") 300 | } 301 | 302 | resolver = dns_resolver.New(ResolversLoaded) 303 | 304 | // Initialize Wildcard Subdomains 305 | IsWildcard, WildcardIP = InitWildcard(Domain) 306 | if IsWildcard == true { 307 | WildcardMap := make(map[string]bool) 308 | for i := 0; i < len(WildcardIP); i++ { 309 | WildcardMap[WildcardIP[i]] = true 310 | } 311 | fmt.Printf("\n[~] Wildcard IPs found at %s. IP(s) %s", Domain, WildcardIP) 312 | } 313 | 314 | jobs := make(chan *Job, 100) // Buffered channel 315 | results := make(chan *Job, 100) // Buffered channel 316 | 317 | // Start consumers: 318 | for i := 0; i < Threads; i++ { 319 | wg.Add(1) 320 | go consume(jobs, results) 321 | } 322 | 323 | // Start producing 324 | go produce(jobs) 325 | 326 | // Start analyzing 327 | wg2.Add(1) 328 | go analyze(results) 329 | 330 | wg.Wait() 331 | close(results) 332 | 333 | wg2.Wait() 334 | 335 | file, err := os.OpenFile(output, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) 336 | if err != nil { 337 | return 338 | } 339 | 340 | defer file.Close() 341 | if aquatone == true { 342 | WriteOutputAquatoneJSON(AquatoneOutput) 343 | } else { 344 | for _, subdomain := range ValidSubs { 345 | _, err := io.WriteString(file, subdomain+"\n") 346 | if err != nil { 347 | return 348 | } 349 | } 350 | } 351 | 352 | return 353 | } 354 | --------------------------------------------------------------------------------