DNS反射放大攻击1(Golang版)-了解并构建DNS请求
DNS请求数据包解析

- 如上图所示,DNS数据包由DNS头部和DNS数据部分组成,DNS头部包含了DNS数据包的基本信息,DNS数据部分包含了DNS数据包的具体数据。
- DNS头部(在截图左侧上红框内)包含了12个字节,具体如下:- 2字节的标识符(Transaction Id),用于标识DNS请求和响应数据包,响应数据包的标识符和请求数据包的标识符一致。
- 2字节的标志位(Flags),用于标识DNS数据包的类型,如下:
  - QR(1bit) 查询应答标志,0表示这是查询报文,1表示这是应答报文。
- opcode(4bit) 查询应答类型,0表示标准查询,1表示反向查询,2表示请求服务器状态。
- AA(1bit) 表示权威回答( authoritative answer ),意味着当前查询结果是由域名的权威服务器给出的,仅由应答报文使用。
- TC(1bit) 位表示截断( truncated ),使用 UDP 时,如果应答超过 512 字节,只返回前 512 个字节,仅当DNS报文使用UDP服务时使用。DNS 协议使用UDP服务,但也明确了 『当 DNS 查询被截断时,应该使用 TCP 协议进行重试』 这一规范。
- RD(1bit) 表示递归查询标志 ( recursion desired ),在请求中设置,并在应答中返回。该位为 1 时,服务器必须处理这个请求:如果服务器没有授权回答,它必须替客户端请求其他 DNS 服务器,这也是所谓的 递归查询; 该位为 0 时,如果服务器没有授权回答,它就返回一个能够处理该查询的服务器列表给客户端,由客户端自己进行 迭代查询。
- RA(1bit) 位表示可递归 ( recursion available ),如果服务器支持递归查询,就会在应答中设置该位,以告知客户端。仅由应答报文使用。
- zero(3bit) 这三位未使用,固定为0。
- rcode(4bit) 表示返回码(reply code),用来返回应答状态,常用返回码:0表示无错误,2表示格式错误,3表示域名不存在。
 
- 2字节的DNS请求查询域名数量,标识DNS请求数据包中的问题数,通常一次查询查一个域名(为1)。
- 2字节的DNS响应记录数,标识DNS响应数据包中的资源记录数,一般为1,分区域解析或负载均衡等情况下会有多个结果。
- 2字节的授权资源记录数,标识DNS响应数据包中的授权资源记录数,通常为0。
- 2字节的额外资源记录数,标识DNS响应数据包中的额外资源记录数,通常为0。
 
DNS响应数据包解析
响应包和请求包没有太大的差别
- 响应包中的Answer RRs会根据实际结果展示数量,这个值在请求包中是。
- 响应包中的尾部追加了响应数据,如下图所示: 
由上述请求包和响应包描述可知:响应包因为含有响应数据,所以会比请求包大。并且响应结果越多的域名相应的响应包的大小也会越大。这里就是DNS反射放大攻击的放大 原理,通过构造一个小的请求包,然后通过DNS服务器响应一个大的响应包,从而达到放大的效果。
DNS请求数据包构造
✨温馨提示:配合博文配套项目食用更佳!
DNS请求数据包的构造,主要是按照DNS协议的规定构造出DNS头部和DNS请求数据部分。
- 构造DNS数据包需要的结构体 // DNSData DNS数据包结构体 
 type DNSData struct {
 TransactionId uint16 // 属于header 客户端随机生成的一个无符号整数,范围是0~2^16(0~65536)。在响应头里面也会返回这个值作用是校验。如果值不相等,丢弃响应内容。
 Flags uint16 // 属于header 16位标志位,包含QR、opcode、AA、TC、RD、RA、zero、rcode。一般查询flags为 00000001 00000000
 Queries []dnsQuestion // 本身不属于header 表示查询请求记录内容数据,他的组数长度值为Questions(属于header)
 // 下面几个是应答记录中的内容,只有在应答消息中才会出现
 Answers []dnsAnswer // 本身不属于header 应答资源记录数据(answer resource record, answer RR)此项只在DNS应答消息中存在,他的数组长度值为AnswerRRs(属于header)
 AuthorityRRs uint16 // 属于header 授权资源记录数量(authority resource record, authority RR)此项只在DNS应答消息中存在
 AdditionalRRs uint16 // 属于header 附加资源记录数量(additional resource record, additional RR)此项只在DNS应答消息中存在
 }
 // Flags赋值方法 因为zero这3bit位固定为0,所以不需要赋值
 func (dDNSData *DNSData) SetFlag(QR uint16, Opcode uint16, AA uint16, TC uint16, RD uint16, RA uint16, Rcode uint16) {
 dDNSData.Flags = QR<<15 + Opcode<<11 + AA<<10 + TC<<9 + RD<<8 + RA<<7 + Rcode // 采用位运算符进行赋值到对应bit位
 }
 // 定义请求数据的结构体
 type dnsQuestion struct {
 QueriesName string `net:"domain-name"` // 要查询的域名
 QueriesType uint16 // 查询类型 1:A 2:NS 5:CNAME 6:SOA 12:PTR 15:MX 16:TXT 28:AAAA
 QueriesClass uint16 // 查询类 1:IN 2:CS 3:CH 4:HS 通常为1表示为TCP/IP互联网地址
 }
 // 定义响应数据的结构体
 type dnsAnswer struct {
 AnswerName uint16 // 同dnsQuestion.QueriesName 要查询的域名
 AnswerType uint16 // 应答记录的类型 1:A 2:NS 5:CNAME 6:SOA 12:PTR 15:MX 16:TXT 28:AAAA
 AnswerClass uint16 // 同dnsQuestion.QueriesClass
 AnswerTTL uint32 // 32位生存时间(有效期)单位是秒
 AnswerDataLength uint16 // 16位无符号整数,表示应答资源记录中数据的长度
 AnswerCNAME string `net:"domain-name"` // 别名
 }
- 写入DNS头部数据 // 写入DNS协议头部数据 
 func (dDNSData *DNSData) WriteHeader() []byte {
 // DNS协议定义Header为12个字节的固定长度
 bs := make([]byte, 12)
 binary.BigEndian.PutUint16(bs[0:2], dDNSData.TransactionId)
 binary.BigEndian.PutUint16(bs[2:4], dDNSData.Flags)
 binary.BigEndian.PutUint16(bs[4:6], uint16(len(dDNSData.Queries))) // Queries的数组长度值为Questions
 binary.BigEndian.PutUint16(bs[6:8], uint16(len(dDNSData.Answers))) // Answers的数组长度值为AnswerRRs
 binary.BigEndian.PutUint16(bs[8:10], dDNSData.AuthorityRRs)
 binary.BigEndian.PutUint16(bs[10:12], dDNSData.AdditionalRRs)
 // 填充Question数据,要将域名进行转换。用.分割域名字符串
 ds := strings.Split(dDNSData.Queries[0].QueriesName, ".")
 // 循环遍历域名的每一部分,将其长度和内容写入到字节切片中。例如:list.eber.vip,写入的内容为4list4eber3vip0 末尾用0来表示结束。
 for _, d := range ds {
 bs = append(bs, byte(len(d)))
 bs = append(bs, []byte(d)...)
 }
 bs = append(bs, 0)
 // 添加查询类型和分类
 temp := make([]byte, 2)
 binary.BigEndian.PutUint16(temp, dDNSData.Queries[0].QueriesType)
 bs = append(bs, temp...)
 binary.BigEndian.PutUint16(temp, dDNSData.Queries[0].QueriesClass)
 bs = append(bs, temp...)
 return bs
 }
- 测试发送请求 不出意外的话执行后Wireshark会捕获到该请求的DNS数据包。OK,这样我们就完成了一个简单的DNS请求。// 测试发送DNS请求 
 func TestDNSDemo1(t *testing.T) {
 dnsServer := "114.114.114.114:53"
 dnsProtocol := "udp"
 dnsType := uint16(1)
 dnsClass := uint16(1)
 udpAddr, err := net.ResolveUDPAddr(dnsProtocol, dnsServer)
 if err != nil {
 fmt.Println(err)
 return
 }
 conn, err := net.DialUDP(dnsProtocol, nil, udpAddr)
 question := dnsQuestion{"dns.eber.vip", dnsType, dnsClass}
 out := DNSData{}
 out.TransactionId = 2015 // 这里随便给个值 0~65536
 out.SetFlag(0, 0, 0, 0, 1, 0, 0)
 out.Queries = append(out.Queries, question)
 header := out.WriteHeader()
 fmt.Println(header)
 _, err = conn.Write(header)
 var buf []byte
 buf = make([]byte, 512)
 n, err := conn.Read(buf[0:])
 fmt.Println(buf[0:n])
 }
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Eber的小窝!
 评论





