• 基于Python3的Scapy构造DNS报文


    一:DNS协议

            DNS(Domain Name System)协议是计算机网络中的一种基础协议,它用于将域名(如www.baidu.com)转换为IP地址(如192.168.0.1),从而实现计算机之间的通信。

            DNS 分为查询请求和查询响应,请求和响应的报文结构基本相同。DNS 报文格式如图所示

            上图中显示了 DNS 的报文格式。其中,事务 ID、标志、问题计数、回答资源记录数、权威名称服务器计数、附加资源记录数这 6 个字段是DNS的报文首部,共 12 个字节。
            整个 DNS 格式主要分为 3 部分内容,即基础结构部分、问题部分、资源记录部分。下面将详细地介绍每部分的内容及含义。

    基础结构部分

    DNS 报文的基础结构部分指的是报文首部,如图所示。
     

    该部分中每个字段含义如下。

    • 事务 ID:DNS 报文的 ID 标识。对于请求报文和其对应的应答报文,该字段的值是相同的。通过它可以区分 DNS 应答报文是对哪个请求进行响应的。
    • 标志:DNS 报文中的标志字段。
    • 问题计数:DNS 查询请求的数目。
    • 回答资源记录数:DNS 响应的数目。
    • 权威名称服务器计数:权威名称服务器的数目。
    • 附加资源记录数:额外的记录数目(权威名称服务器对应 IP 地址的数目)。


    基础结构部分中的标志字段又分为若干个字段,如图所示。
     

    标志字段中每个字段的含义如下:

    • QR(Response):查询请求/响应的标志信息。查询请求时,值为 0;响应时,值为 1。
    • Opcode:操作码。其中,0 表示标准查询;1 表示反向查询;2 表示服务器状态请求。
    • AA(Authoritative):授权应答,该字段在响应报文中有效。值为 1 时,表示名称服务器是权威服务器;值为 0 时,表示不是权威服务器。
    • TC(Truncated):表示是否被截断。值为 1 时,表示响应已超过 512 字节并已被截断,只返回前 512 个字节。
    • RD(Recursion Desired):期望递归。该字段能在一个查询中设置,并在响应中返回。该标志告诉名称服务器必须处理这个查询,这种方式被称为一个递归查询。如果该位为 0,且被请求的名称服务器没有一个授权回答,它将返回一个能解答该查询的其他名称服务器列表。这种方式被称为迭代查询。
    • RA(Recursion Available):可用递归。该字段只出现在响应报文中。当值为 1 时,表示服务器支持递归查询。
    • Z:保留字段,在所有的请求和应答报文中,它的值必须为 0。
    • rcode(Reply code):返回码字段,表示响应的差错状态。当值为 0 时,表示没有错误;当值为 1 时,表示报文格式错误(Format error),服务器不能理解请求的报文;当值为 2 时,表示域名服务器失败(Server failure),因为服务器的原因导致没办法处理这个请求;当值为 3 时,表示名字错误(Name Error),只有对授权域名解析服务器有意义,指出解析的域名不存在;当值为 4 时,表示查询类型不支持(Not Implemented),即域名服务器不支持查询类型;当值为 5 时,表示拒绝(Refused),一般是服务器由于设置的策略拒绝给出应答,如服务器不希望对某些请求者给出应答。

    问题部分

    问题部分指的是报文格式中查询问题区域(Queries)部分。该部分是用来显示 DNS 查询请求的问题,通常只有一个问题。该部分包含正在进行的查询信息,包含查询名(被查询主机名字)、查询类型、查询类。

    问题部分格式如图所示。
     

    该部分中每个字段含义如下:

    • 查询名:一般为要查询的域名,有时也会是 IP 地址,用于反向查询。
    • 查询类型:DNS 查询请求的资源类型。通常查询类型为 A 类型,表示由域名获取对应的 IP 地址。
    • 查询类:地址类型,通常为互联网地址,值为 1。

    资源记录部分

    资源记录部分是指 DNS 报文格式中的最后三个字段,包括回答问题区域字段、权威名称服务器区域字段、附加信息区域字段。这三个字段均采用一种称为资源记录的格式,格式如图所示。
     

    资源记录格式中每个字段含义如下:

    • 域名:DNS 请求的域名。
    • 类型:资源记录的类型,与问题部分中的查询类型值是一样的。
    • 类:地址类型,与问题部分中的查询类值是一样的。
    • 生存时间:以秒为单位,表示资源记录的生命周期,一般用于当地址解析程序取出资源记录后决定保存及使用缓存数据的时间。它同时也可以表明该资源记录的稳定程度,稳定的信息会被分配一个很大的值。
    • 资源数据长度:资源数据的长度。
    • 资源数据:表示按查询段要求返回的相关资源记录的数据。

    资源记录部分只有在 DNS 响应包中才会出现。下面通过 DNS 响应包来进一步了解资源记录部分的字段信息。

    二:构造DNS报文

            scapy具有强大的报文构造能力和修改能力,我们一般定制化DNS报文都是修改DNS请求里的域名和响应里的IP地址。最方便的方法就是基于一个现有的DNS请求和响应报文去修改我们目标字段。

            我们可以先看看scapy解析dns的能力

     scapy解析请求和响应

    1. ###[ DNS ]###
    2. id = 49586
    3. qr = 1
    4. opcode = QUERY
    5. aa = 0
    6. tc = 0
    7. rd = 1
    8. ra = 1
    9. z = 0
    10. ad = 0
    11. cd = 0
    12. rcode = ok
    13. qdcount = 1
    14. ancount = 23
    15. nscount = 2
    16. arcount = 0
    17. \qd \
    18. |###[ DNS Question Record ]###
    19. | qname = 'upload-z1.qbox.me.'
    20. | qtype = A
    21. | qclass = IN
    22. \an \
    23. |###[ DNS Resource Record ]###
    24. | rrname = 'upload-z1.qbox.me.'
    25. | type = CNAME
    26. | rclass = IN
    27. | ttl = 516
    28. | rdlen = None
    29. | rdata = 'upload-z1.clouddn.com.'
    30. |###[ DNS Resource Record ]###
    31. | rrname = 'upload-z1.clouddn.com.'
    32. | type = CNAME
    33. | rclass = IN
    34. | ttl = 80
    35. | rdlen = None
    36. | rdata = 'upload-z1-oss.clouddn.com.'
    37. |###[ DNS Resource Record ]###
    38. | rrname = 'upload-z1-oss.clouddn.com.'
    39. | type = CNAME
    40. | rclass = IN
    41. | ttl = 54
    42. | rdlen = None
    43. | rdata = 'bc-gate-up.qiniu.com.'
    44. |###[ DNS Resource Record ]###
    45. | rrname = 'bc-gate-up.qiniu.com.'
    46. | type = A
    47. | rclass = IN
    48. | ttl = 104
    49. | rdlen = None
    50. | rdata = 111.31.48.121
    51. |###[ DNS Resource Record ]###
    52. | rrname = 'bc-gate-up.qiniu.com.'
    53. | type = A
    54. | rclass = IN
    55. | ttl = 104
    56. | rdlen = None
    57. | rdata = 111.31.48.33
    58. |###[ DNS Resource Record ]###
    59. | rrname = 'bc-gate-up.qiniu.com.'
    60. | type = A
    61. | rclass = IN
    62. | ttl = 104
    63. | rdlen = None
    64. | rdata = 111.13.229.82
    65. |###[ DNS Resource Record ]###
    66. | rrname = 'bc-gate-up.qiniu.com.'
    67. | type = A
    68. | rclass = IN
    69. | ttl = 104
    70. | rdlen = None
    71. | rdata = 111.13.229.81
    72. |###[ DNS Resource Record ]###
    73. | rrname = 'bc-gate-up.qiniu.com.'
    74. | type = A
    75. | rclass = IN
    76. | ttl = 104
    77. | rdlen = None
    78. | rdata = 111.13.229.68
    79. |###[ DNS Resource Record ]###
    80. | rrname = 'bc-gate-up.qiniu.com.'
    81. | type = A
    82. | rclass = IN
    83. | ttl = 104
    84. | rdlen = None
    85. | rdata = 111.13.229.74
    86. |###[ DNS Resource Record ]###
    87. | rrname = 'bc-gate-up.qiniu.com.'
    88. | type = A
    89. | rclass = IN
    90. | ttl = 104
    91. | rdlen = None
    92. | rdata = 111.31.48.20
    93. |###[ DNS Resource Record ]###
    94. | rrname = 'bc-gate-up.qiniu.com.'
    95. | type = A
    96. | rclass = IN
    97. | ttl = 104
    98. | rdlen = None
    99. | rdata = 111.13.229.73
    100. |###[ DNS Resource Record ]###
    101. | rrname = 'bc-gate-up.qiniu.com.'
    102. | type = A
    103. | rclass = IN
    104. | ttl = 104
    105. | rdlen = None
    106. | rdata = 111.31.48.19
    107. |###[ DNS Resource Record ]###
    108. | rrname = 'bc-gate-up.qiniu.com.'
    109. | type = A
    110. | rclass = IN
    111. | ttl = 104
    112. | rdlen = None
    113. | rdata = 111.31.48.122
    114. |###[ DNS Resource Record ]###
    115. | rrname = 'bc-gate-up.qiniu.com.'
    116. | type = A
    117. | rclass = IN
    118. | ttl = 104
    119. | rdlen = None
    120. | rdata = 111.31.48.23
    121. |###[ DNS Resource Record ]###
    122. | rrname = 'bc-gate-up.qiniu.com.'
    123. | type = A
    124. | rclass = IN
    125. | ttl = 104
    126. | rdlen = None
    127. | rdata = 111.31.48.21
    128. |###[ DNS Resource Record ]###
    129. | rrname = 'bc-gate-up.qiniu.com.'
    130. | type = A
    131. | rclass = IN
    132. | ttl = 104
    133. | rdlen = None
    134. | rdata = 111.13.229.78
    135. |###[ DNS Resource Record ]###
    136. | rrname = 'bc-gate-up.qiniu.com.'
    137. | type = A
    138. | rclass = IN
    139. | ttl = 104
    140. | rdlen = None
    141. | rdata = 111.31.48.123
    142. |###[ DNS Resource Record ]###
    143. | rrname = 'bc-gate-up.qiniu.com.'
    144. | type = A
    145. | rclass = IN
    146. | ttl = 104
    147. | rdlen = None
    148. | rdata = 111.31.48.18
    149. |###[ DNS Resource Record ]###
    150. | rrname = 'bc-gate-up.qiniu.com.'
    151. | type = A
    152. | rclass = IN
    153. | ttl = 104
    154. | rdlen = None
    155. | rdata = 111.31.48.22
    156. |###[ DNS Resource Record ]###
    157. | rrname = 'bc-gate-up.qiniu.com.'
    158. | type = A
    159. | rclass = IN
    160. | ttl = 104
    161. | rdlen = None
    162. | rdata = 111.13.229.75
    163. |###[ DNS Resource Record ]###
    164. | rrname = 'bc-gate-up.qiniu.com.'
    165. | type = A
    166. | rclass = IN
    167. | ttl = 104
    168. | rdlen = None
    169. | rdata = 111.13.229.80
    170. |###[ DNS Resource Record ]###
    171. | rrname = 'bc-gate-up.qiniu.com.'
    172. | type = A
    173. | rclass = IN
    174. | ttl = 104
    175. | rdlen = None
    176. | rdata = 111.31.48.25
    177. |###[ DNS Resource Record ]###
    178. | rrname = 'bc-gate-up.qiniu.com.'
    179. | type = A
    180. | rclass = IN
    181. | ttl = 104
    182. | rdlen = None
    183. | rdata = 111.31.48.24
    184. \ns \
    185. |###[ DNS Resource Record ]###
    186. | rrname = 'qiniu.com.'
    187. | type = NS
    188. | rclass = IN
    189. | ttl = 78188
    190. | rdlen = None
    191. | rdata = 'ns3.dnsv5.com.'
    192. |###[ DNS Resource Record ]###
    193. | rrname = 'qiniu.com.'
    194. | type = NS
    195. | rclass = IN
    196. | ttl = 78188
    197. | rdlen = None
    198. | rdata = 'ns4.dnsv5.com.'
    199. ar = None
    200. [Finished in 2.3s]

     可以看到scapy具备完整的DNS解析能力,那我们的目标就是利用scapy的解析能力去修改我们的目标字段,另外修改目标字段后报文的各种校验和也需要重新计算。

            下面给出完整的构造DNS报文的代码

    1. def dnsPcapProc(self):
    2. savefile = 'dns_'+ datetime.now().strftime("%H%M%S") + '.pcap'
    3. srcpcap = rdpcap(self.dnsPcapFilePath)
    4. srcpcap[1][DNS].show2()
    5. oriDomainLen = len(srcpcap[0][DNS].fields['qd'].fields['qname'])
    6. diff = len(self.dnsDomainInputEntry)+1 - oriDomainLen
    7. oriIPLen = srcpcap[0][IP].len
    8. oriUDPLen = srcpcap[0][UDP].len
    9. srcpcap[0][IP].len = oriIPLen + diff
    10. srcpcap[0][UDP].len = oriUDPLen + diff
    11. srcpcap[0][DNS].fields['qd'].fields['qname'] = self.dnsDomainInputEntry.encode("utf-8")
    12. try:
    13. del srcpcap[0][IP].len
    14. del srcpcap[0][IP].chksum
    15. except:
    16. srcpcap[0][DNS].fields['qd'].fields['qtype'] = 28
    17. del srcpcap[0][IPv6].plen
    18. del srcpcap[0][IPv6].chksum
    19. flg =6
    20. del srcpcap[0][UDP].len
    21. del srcpcap[0][UDP].chksum
    22. srcpcap[0][IP].len = oriIPLen + diff
    23. srcpcap[0][UDP].len = oriUDPLen + diff
    24. ip_list = list()
    25. ip_list.append(self.dnsIPInputEntry)
    26. srcpcap[1][UDP].payload.ancount = len(ip_list)
    27. srcpcap[1][UDP].payload.qd.qname = self.dnsDomainInputEntry.encode("utf-8")
    28. dns_anser_as_ord = []
    29. for ip in ip_list:
    30. ipstr,flg = self.ipstr_trans2_hexlist(ip)
    31. if flg == 4:
    32. dns_anser_as_ord += [0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x1c, 0x20, 0x00, 0x04]
    33. else:
    34. dns_anser_as_ord += [0xc0, 0x0c, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x1c, 0x20, 0x00, 0x10]
    35. dns_anser_as_ord += ipstr
    36. hexByteOri = "".join(["%02x" % c for c in dns_anser_as_ord]).encode("utf-8")
    37. hexByte = binascii.a2b_hex(hexByteOri)
    38. srcpcap[1][UDP].payload.an = hexByte
    39. try:
    40. del srcpcap[1][IP].len
    41. del srcpcap[1][IP].chksum
    42. except:
    43. del srcpcap[1][IPv6].plen
    44. del srcpcap[1][IPv6].chksum
    45. del srcpcap[1][UDP].len
    46. del srcpcap[1][UDP].chksum
    47. #srcpcap[0].show2()
    48. #wrpcap(savefile, srcpcap)
    49. pktdump = PcapWriter(savefile, append=True, sync=True)
    50. ether_layer = srcpcap[0]['Ether']
    51. newpcap = Ether(src=ether_layer.src, dst=ether_layer.dst) / srcpcap[0].getlayer('IP')
    52. newpcap2 = Ether(src=ether_layer.dst, dst=ether_layer.src) / srcpcap[1].getlayer('IP')
    53. pktdump.write(newpcap)
    54. pktdump.write(newpcap2)
  • 相关阅读:
    SourceTree 使用
    奇思妙想构造题 ARC145 D - Non Arithmetic Progression Set
    Vue 汉字转拼音;根据拼音首字母排序转二维数组;提取拼音首字母排序。
    redis 技术分享
    工业4.0 IO-Link RFID传感器|读写器应用前景与优势分析
    Zabbix 利用 Grafana 进行图形展示
    springcloud+nacos+dubbo服务器部署问题
    springcloud整合seata
    深入解析LLaMA如何改进Transformer的底层结构
    嵌入式Linux裸机开发(三)SDK移植及BSP管理
  • 原文地址:https://blog.csdn.net/qq_27071221/article/details/133964320