跳转至
#golang 
本文阅读量 

第五章-DNS#

主要学习dns这个第三包的使用

FQDN#

一个简单的dns的fully qualified domain name (fqdn)请求,使用A记录,并输出结果

package main

import (
  "fmt"

  "github.com/miekg/dns"
)

func main() {
  var msg dns.Msg
  fqdn := dns.Fqdn("stacktitan.com")
  msg.SetQuestion(fqdn, dns.TypeA)
  in, err := dns.Exchange(&msg, "8.8.8.8:53")
  if err != nil {
    panic(err)
  }
  if len(in.Answer) < 1 {
    fmt.Println("No records")
    return
  }

  for _, answer := range in.Answer {
    if a, ok := answer.(*dns.A); ok {
      fmt.Println(a.A)
    }
  }

}

简单地分析一下,in这个变量类型是dns.Msg,而Msg的结构如下

type Msg struct {
 MsgHdr
 Compress bool `json:"-"` // If true, the message will be compressed...
 u Question []Question // Holds the RR(s) of the question section.
 v Answer []RR // Holds the RR(s) of the answer section.
 Ns []RR // Holds the RR(s) of the authority section.
 Extra []RR // Holds the RR(s) of the additional section.
}

而RR则是一个接口,需要实现以下几个方法

type RR interface {
  // Header returns the header of an resource record. The header contains
  // everything up to the rdata.
  Header() *RR_Header
  // String returns the text representation of the resource record.
  String() string

  // copy returns a copy of the RR
  copy() RR

  // len returns the length (in octets) of the compressed or uncompressed RR in wire format.
  //
  // If compression is nil, the uncompressed size will be returned, otherwise the compressed
  // size will be returned and domain names will be added to the map for future compression.
  len(off int, compression map[string]struct{}) int

  // pack packs the records RDATA into wire format. The header will
  // already have been packed into msg.
  pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error)

  // unpack unpacks an RR from wire format.
  //
  // This will only be called on a new and empty RR type with only the header populated. It
  // will only be called if the record's RDATA is non-empty.
  unpack(msg []byte, off int) (off1 int, err error)

  // parse parses an RR from zone file format.
  //
  // This will only be called on a new and empty RR type with only the header populated.
  parse(c *zlexer, origin string) *ParseError

  // isDuplicate returns whether the two RRs are duplicates.
  isDuplicate(r2 RR) bool
}

最后遍历answer(变量类型是RR),这里有一个强制转换a, ok := answer.(*dns.A); ok,将RR类型强制转换为*dns.A,而dns.A的相关结构又如下,可以看到dns.A组合了RRHeader这个类型,而RRHeader实现了RR的方法,因此dns.A可以是RR类型

// A RR. See RFC 1035.
type A struct {
  Hdr RR_Header
  A   net.IP `dns:"a"`
}

// RR_Header is the header all DNS resource records share.
type RR_Header struct {
  Name     string `dns:"cdomain-name"`
  Rrtype   uint16
  Class    uint16
  Ttl      uint32
  Rdlength uint16 // Length of data after header.
}

// Header returns itself. This is here to make RR_Header implements the RR interface.
func (h *RR_Header) Header() *RR_Header { return h }

// Just to implement the RR interface.
func (h *RR_Header) copy() RR { return nil }
// ...

subdomain_fuzzer#

参考blackhat-go里subdomain_guesser写的,一个简单的根据字典通过dns查询爆破子域名的工具,核心基本一样,但是由于原作者写的协程并发代码有点难以理解,所以稍微修改了一下,使用了sizedwaitgroup这个包

package main

import (
  "bufio"
  "errors"
  "flag"
  "fmt"
  "os"

  "github.com/miekg/dns"
  "github.com/remeh/sizedwaitgroup"
)

//
type Result struct {
  IpAddress string
  Hostname  string
}

//
func LookupA(fqdn, serverAddr string) ([]string, error) {
  var m dns.Msg
  var ips []string
  m.SetQuestion(dns.Fqdn(fqdn), dns.TypeA)
  in, err := dns.Exchange(&m, serverAddr)

  if err != nil {
    return ips, err
  }
  if len(in.Answer) < 1 {
    return ips, errors.New("No answer")
  }
  for _, answer := range in.Answer {
    if a, ok := answer.(*dns.A); ok {
      ips = append(ips, a.A.String())
    }
  }
  return ips, nil
}

func LookupCNAME(fqdn, serverAddr string) ([]string, error) {
  var m dns.Msg
  var fqdns []string
  m.SetQuestion(dns.Fqdn(fqdn), dns.TypeCNAME)
  in, err := dns.Exchange(&m, serverAddr)
  if err != nil {
    return fqdns, err
  }
  if len(in.Answer) < 1 {
    return fqdns, errors.New("No answer")
  }
  for _, answer := range in.Answer {
    if c, ok := answer.(*dns.CNAME); ok {
      fqdns = append(fqdns, c.Target)
    }
  }
  return fqdns, nil
}

//
func Lookup(fqdn, serverAddr string) []Result {
  var results []Result
  var cfqdn = fqdn
  for {
    cnames, err := LookupCNAME(cfqdn, serverAddr)
    if err == nil && len(cnames) > 0 {
      cfqdn = cnames[0]
      continue
    }
    ips, err := LookupA(cfqdn, serverAddr)
    if err != nil {
      break
    }
    for _, ip := range ips {
      results = append(results, Result{
        IpAddress: ip,
        Hostname:  fqdn,
      })
    }
    break
  }
  return results
}

func worker(fqdn string, serverAddr string, resultChannel chan []Result, swg *sizedwaitgroup.SizedWaitGroup) {
  results := Lookup(fqdn, serverAddr)
  if len(results) > 0 {
    resultChannel <- results
  }
  return
}

func main() {
  var (
    flDomain      = flag.String("domain", "", "The domain to perform fuzzing against.")
    flWordlist    = flag.String("wordlist", "", "The wordlist to use for fuzzing")
    flWorkerCount = flag.Int("c", 100, "The amount of worker to use")
    flServerAddr  = flag.String("server", "8.8.8.8:53", "The DNS server to use")
  )
  flag.Parse()
  if *flDomain == "" || *flWordlist == "" {
    fmt.Println("domain and wordlist are rquired")
    os.Exit(1)
  }

  swg := sizedwaitgroup.New(*flWorkerCount)
  resultsChannel := make(chan []Result)
  var results []Result

  go func() { // ? read from channel immediately
    for r := range resultsChannel {
      for _, result := range r {
        fmt.Printf("%s\t%s\n", result.Hostname, result.IpAddress)
      }
      results = append(results, r...)
      swg.Done()
    }
  }()

  fh, err := os.Open(*flWordlist)
  if err != nil {
    fmt.Println("Cannot read " + *flWordlist)
    os.Exit(2)
  }
  defer fh.Close()
  reader := bufio.NewReader(fh)

  for {
    bytes, _, err := reader.ReadLine()
    line := string(bytes[:])
    if err != nil {
      break
    }
    swg.Add()
    go worker(fmt.Sprintf("%s.%s", line, *flDomain), *flServerAddr, resultsChannel, &swg)
  }
  swg.Wait()
  close(resultsChannel)
  fmt.Println("\nResult:\n-----------------------------------------------")
  for _, r := range results {
    fmt.Printf("%s\t%s\n", r.Hostname, r.IpAddress)
  }
}
回到页面顶部