在DNS查询中,axfr类型是Authoritative Transfer的缩写,指请求传送某个区域的全部记录。
前面三篇日志中,笔者分别使用nmap、dig、nslookup来查询域传送记录。
本篇介绍自己动手,用python写一个简单的DNS客户端,仅实现axfr查询,并且只处理A记录。
DNS消息的格式
DNS请求和响应,都是由5个区块组成的,如下图所示:
+---------------------+ | Header | +---------------------+ | Question | the question for the name server +---------------------+ | Answer | RRs answering the question +---------------------+ | Authority | RRs pointing toward an authority +---------------------+ | Additional | RRs holding additional information +---------------------+
axfr请求的包,只填充header和Question区块就可以了。
Header区块的格式
Header的格式是这样的:
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
可以看到,每一行是16位,两个字节。整个Header的大小是12字节:
- ID是随机值,2个字节,介于1到65535之间。客户端发送一个随机值,DNS服务器会原样返回该ID。
- 第二行是几个标记的集合:QR标记该消息是Query还是Response,查询的时候填充0,响应的时候填充1。而RCODE是Response Code(响应码),只有该值为0的时候才表示查询成功了。得到服务器响应之后应该检查这个值。
- QDCOUNT填充Question的个数、ANCOUNT填充Answer的个数,NSCOUNT填充DNS权威服务器的个数,ARCOUNT填充附加记录的个数。对于本文要实现的axfr查询,分别填充: 1, 0, 0, 0。
下图是笔者用WireShak抓包得到一个axfr请求的Header:
图中Additional RRs的值为1,在尾部附加了一些额外的参数。编码的简化,可以不提供。
Question区块的格式
Question区块的基本格式是:
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / QNAME / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QTYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QCLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
QNAME是需要查询的域名,比如nwpu.edu.cn。它由一个Lable序列来表示。
格式是这样的:
用点号“.”把域名分割为几个Label,每个label前面带上字符串长度,nwpu.edu.cn.cn最终变为:
4nwpu3edu2cn
结尾处跟一个字节的空白。笔者抓包截图如下:
04表示有4个字符,而6e 77 70 75正好是nwpu。
QTYPE是一个类型常量,axfr记录的值是252。
QCLASS设置为1,表示Inernet。
Python组合一个Query消息
介绍完上面的内容,我们已经可以组一个查询消息了。函数如下:
def gen_query(domain): import random TRANS_ID = random.randint(1, 65535) # random ID FLAGS = 0; QDCOUNT = 1; ANCOUNT = 0; NSCOUNT = 0; ARCOUNT = 0 data = struct.pack( '!HHHHHH', TRANS_ID, FLAGS,QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT ) query = '' for label in domain.strip().split('.'): query += struct.pack('!B', len(label)) + label.lower() query += '\x00' # end of domain name data += query global LEN_QUERY LEN_QUERY = len(query) # length of query section q_type = 252 # Type AXFR = 252 q_class = 1 # CLASS IN data += struct.pack('!HH', q_type, q_class) data = struct.pack('!H', len(data) ) + data # first 2 bytes should be length return data
struct.pack用来格式化字符串,第一个参数中的惊叹号“!”表示字节序使用network (= big-endian)。
B代表Byte,一个字节。H代表unsigned short,两个字节。
消息开头的前两个字节,需要提供消息正文的长度。
因为在decode response时,还要用到Query的长度,所以我将它保存到了一个全局的LEN_QUERY变量中。
您好,最近正在学习DNS相关知识,对您的这篇博文很感兴趣,故学习了一下。
现请教您一个问题,在python3.7中运行整个程序发现抛出错误:
File “C:\axfr_client.py”, line 89, in
data = gen_query(sys.argv[2])
File “C:\axfr_client.py”, line 26, in gen_query
query += struct.pack(‘!B’, len(label)) + label.lower()
TypeError: can’t concat str to bytes
自己尝试了很多办法都不能成功,请您指点
已经解决了!感谢您的源代码!