diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..6597d83 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,46 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "dtool", + Short: "dns resolver probing tool", + Long: `A tool for probing open resolvers for various purpose. The following modules are implemented. + +Modules: + upstream probe to get the upstream recursive resolvers of the given resolver as many as possible + ttl probe to test if resolvers on the resolution path change the authoritative TTL value + cache probe to enumerate the cache`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.dtool.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/upstream.go b/cmd/upstream.go new file mode 100644 index 0000000..e7d4071 --- /dev/null +++ b/cmd/upstream.go @@ -0,0 +1,32 @@ +package cmd + +import ( + _ "dtool/prober" + "dtool/utils" + + "github.com/spf13/cobra" +) + +var filename string +var thread_num int +var upstreamCmd = &cobra.Command{ + Use: "upstream", + //Aliases: []string{"up", "stream"}, + Short: "probe upstream recursive resolvers", + Long: `get the upstream recursive resolvers of the input resolvers +input target can be added as an argument or as a file + + -f input file with limited ip addresses (limit=50) + -t number of goroutine`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + //prober.Get_upstream_ip(args[0]) + utils.SendTencentHttpdnsQuery() + }, +} + +func init() { + upstreamCmd.Flags().StringVarP(&filename, "file", "f", "", "input filename") + upstreamCmd.Flags().IntVarP(&thread_num, "threads", "t", 10, "number of concurrent threads") + rootCmd.AddCommand(upstreamCmd) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c08d21e --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module dtool + +go 1.20 + +require github.com/spf13/cobra v1.7.0 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/miekg/dns v1.1.54 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/mod v0.7.0 // indirect + golang.org/x/net v0.2.0 // indirect + golang.org/x/sys v0.2.0 // indirect + golang.org/x/tools v0.3.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7fa2eff --- /dev/null +++ b/go.sum @@ -0,0 +1,20 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI= +github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..e422189 --- /dev/null +++ b/main.go @@ -0,0 +1,11 @@ +/* +Copyright © 2023 NAME HERE + +*/ +package main + +import "dtool/cmd" + +func main() { + cmd.Execute() +} diff --git a/prober/rdns_prober.go b/prober/rdns_prober.go new file mode 100644 index 0000000..08d500c --- /dev/null +++ b/prober/rdns_prober.go @@ -0,0 +1,133 @@ +package prober + +import ( + "bufio" + "fmt" + "io" + "os" + "strconv" + "strings" + "sync" + "time" + + "dtool/utils" +) + +type Data struct { + target string + dict map[string]bool +} + +var dataset map[string][]string + +func retrieve_ip(pool chan string, filename string) { + cnt := 0 + f, err := os.Open(filename) + if err != nil { + fmt.Printf("cannot open file %s\n", filename) + return + } + fmt.Println("sending msg ...") + reader := bufio.NewReader(f) + for { + s, err := reader.ReadString('\n') + if err == io.EOF { + break + } + s = strings.Trim(s, "\n") + pool <- s + cnt++ + if cnt%10 == 0 { + fmt.Println(cnt) + } + } + close(pool) +} + +func active_probe(n int, addr string) Data { + target_ip := addr[:len(addr)-3] + data := Data{target_ip, make(map[string]bool)} + stop := 0 + timestamp := strconv.FormatInt(time.Now().Unix(), 10) + for i := 0; i < n; i++ { + subdomain := strings.Join([]string{strings.Replace(target_ip, ".", "-", -1), "echo", strconv.Itoa(i), timestamp}, "-") + rdns_ip, err := utils.SendQuery(addr, subdomain) + if err == nil { + data.dict[rdns_ip] = true + } else { + stop += 1 + } + if stop == 3 { + break + } + } + return data +} + +func upstream_prober(ip_pool chan string, data_pool chan Data, wg *sync.WaitGroup) { + for { + if s, ok := <-ip_pool; ok { + addr := s + ":53" + data := active_probe(20, addr) + if data.dict != nil { + data_pool <- data + } + } else { + break + } + } + wg.Done() +} + +func create_probers(num int, ip_pool chan string, data_pool chan Data, wg *sync.WaitGroup) { + for i := 0; i < num; i++ { + wg.Add(1) + go upstream_prober(ip_pool, data_pool, wg) + } +} + +func store_data(pool chan Data, wg *sync.WaitGroup) { + wg.Add(1) + for { + var temp []string + if data, ok := <-pool; ok { + if len(data.dict) > 0 { + for rdns := range data.dict { + temp = append(temp, rdns) + } + dataset[data.target] = temp + } + } else { + break + } + } + wg.Done() +} + +func Get_upstream_file(filename string, prober_num int) { + dataset = map[string][]string{} + ip_pool := make(chan string, 500) + data_pool := make(chan Data, 200) + var probe_tasks sync.WaitGroup + var store_task sync.WaitGroup + + go retrieve_ip(ip_pool, filename) + create_probers(prober_num, ip_pool, data_pool, &probe_tasks) + go store_data(data_pool, &store_task) + probe_tasks.Wait() + close(data_pool) + store_task.Wait() + utils.OutputJSON(dataset) +} + +func Get_upstream_ip(ip string) { + dataset = make(map[string][]string) + var temp []string + addr := ip + ":53" + data := active_probe(10, addr) + for rdns := range data.dict { + temp = append(temp, rdns) + } + dataset[data.target] = temp + utils.OutputJSON(dataset) +} diff --git a/utils/dns_utils.go b/utils/dns_utils.go new file mode 100644 index 0000000..f6d4578 --- /dev/null +++ b/utils/dns_utils.go @@ -0,0 +1,55 @@ +package utils + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/miekg/dns" +) + +type WrongAnswerError struct { + Message string +} + +func (e *WrongAnswerError) Error() string { + return fmt.Sprintf("Wrong Answer: %s", e.Message) +} + +func SendQuery(addr string, dn string) (string, error) { + var ( + domain string + rdns_ip string + ) + if dn == "timestamp" { + timestamp := strconv.FormatInt(time.Now().UnixNano(), 10) + domain = strings.Join([]string{timestamp, "-scan.echodns.xyz."}, "") + } else { + domain = strings.Join([]string{dn, ".echodns.xyz."}, "") + } + //fmt.Println(domain) + m := new(dns.Msg) + m.SetQuestion(domain, dns.TypeA) + m.RecursionDesired = true + + res, err := dns.Exchange(m, addr) + if err == nil { + if len(res.Answer) == 1 { + if a, ok := res.Answer[0].(*dns.A); ok { + rdns_ip = a.A.String() + } else { + rdns_ip = "" + err = &WrongAnswerError{ + Message: "Wrong Record Type", + } + } + } else { + rdns_ip = "" + err = &WrongAnswerError{ + Message: "Wrong Answer Section", + } + } + } + return rdns_ip, err +} diff --git a/utils/httpdns_utils.go b/utils/httpdns_utils.go new file mode 100644 index 0000000..11c891d --- /dev/null +++ b/utils/httpdns_utils.go @@ -0,0 +1,112 @@ +package utils + +import ( + "bytes" + "crypto/des" + "encoding/hex" + "fmt" + "io/ioutil" + "net/http" +) + +func pkcs5Padding(plaintext []byte, blocksize int) []byte { + padding := blocksize - len(plaintext)%blocksize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(plaintext, padtext...) +} + +func pkcs5Unpadding(plaintext []byte) []byte { + padding := plaintext[len(plaintext)-1] + return plaintext[:len(plaintext)-int(padding)] +} + +func encryptDES(plaintext, key []byte) ([]byte, error) { + block, err := des.NewCipher(key) + if err != nil { + return nil, err + } + + blocksize := block.BlockSize() + plaintext = pkcs5Padding(plaintext, blocksize) + + ciphertext := make([]byte, len(plaintext)) + for i := 0; i < len(plaintext); i += blocksize { + block.Encrypt(ciphertext[i:], plaintext[i:i+blocksize]) + } + + return ciphertext, nil +} + +func decryptDES(ciphertext, key []byte) ([]byte, error) { + block, err := des.NewCipher(key) + if err != nil { + return nil, err + } + blocksize := block.BlockSize() + decryptedtext := make([]byte, len(ciphertext)) + for i := 0; i < len(ciphertext); i += blocksize { + block.Decrypt(decryptedtext[i:], ciphertext[i:i+blocksize]) + } + decryptedtext = pkcs5Unpadding(decryptedtext) + return decryptedtext, nil +} + +func SendTencentHttpdnsQuery() { + client := &http.Client{} + domain := []byte("echo.echodns.xyz") + key := []byte("046Ju3Cw") + encrypted_bytes, err := encryptDES(domain, key) + if err != nil { + return + } + encrypted_domain := hex.EncodeToString(encrypted_bytes) + url := fmt.Sprintf("http://119.29.29.98/d?dn=%s&id=61188", encrypted_domain) + fmt.Println(url) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + fmt.Printf("create new request failed. Error: %s\n", err) + } + + resp, err := client.Do(req) + if err != nil { + fmt.Printf("request went wrong. Error: %s\n", err) + return + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Printf("read content failed. Error: %s\n", err) + return + } + encrypted_response, _ := hex.DecodeString(string(body)) + decrypted_response, err := decryptDES(encrypted_response, key) + if err != nil { + return + } + fmt.Println(string(decrypted_response)) +} + +func SendAlicloudHttpdnsQurey() { + client := &http.Client{} + req, err := http.NewRequest("GET", "http://203.107.1.33/149702/d?host=echo.echodns.xyz", nil) + if err != nil { + fmt.Printf("create new request failed. Error: %s\n", err) + } + + resp, err := client.Do(req) + if err != nil { + fmt.Printf("request went wrong. Error: %s\n", err) + return + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Printf("read content failed. Error: %s\n", err) + return + } + + fmt.Println(string(body)) +} diff --git a/utils/output_utils.go b/utils/output_utils.go new file mode 100644 index 0000000..88a649c --- /dev/null +++ b/utils/output_utils.go @@ -0,0 +1,15 @@ +package utils + +import ( + "encoding/json" + "fmt" +) + +func OutputJSON(data map[string][]string) { + jsonstr, err := json.MarshalIndent(data, "", " ") + if err != nil { + fmt.Println("JSON encoding error:", err) + return + } + fmt.Println(string(jsonstr)) +}