本文简述一下使用C++/C发送A记录的DNS数据请求程序的开发过程。
0x00 前言
转眼间已经是2018年的二月末了,这一年来的太快,让我手忙脚乱……去年想做的事情都没有做的很成功,让我深感遗憾。
创业不谈,谈一谈自己的收获和生活吧
-
1.安全内容运营
起初大家好像都在注册自己的微信公众号,通过自己的积累,让自己能有更好的工作、更多的出路、亦或是一种分享精神。
我注册了 “知安小酒馆” ,目前并没有太多人关注,也就在恰逢值得纪念的日子给大家一些祝福。但在此之前从知乎引流到微信平台,再到我自己开发的漏洞平台,我敢说它就可以成为产品,只要坚持那么一点点,只要有人给我鼓励……
作为干了两三个月的小编来说,码字已经是常事,写作这个习惯也潜移默化的改变我,让我的表达更加流畅,错别字越来越少,文章越来越有层次。
-
2.编程一直是我的习惯
喜欢对自己刨根问底,在计算机刚开始发展的时候,协议、网络、软件、操作系统都在变化,而这些东西中包罗万象了许多科技进步留下的有趣故事。 我更看重的是知识。是那“黑客”的本质所吸引我的一些“优点”。
-
3.读书
这个在年底的时候并没有坚持,仿佛对生活充满了厌恶,总想改变点什么,可却非常无力……
-
4.看电影
这项活动快成了我的生活不可或缺的一部分,看了很多高票房的电影,总是感触很深,尤其是《缝纫机乐队》,看哭了。
-
5.吃火锅
喜欢吃辣的,所以经常去一家火锅店,然后没太多闲钱,有时候还觉得不该花。可真的很好吃…… 大半夜的都饿了。。
此时凌晨 04:53
0x01 基础铺垫
请结合前面一篇文章:http://payloads.online/archivers/2018-02-10/1
报文格式
+---------------------+
| Header | 报文头
+---------------------+
| Question | 查询的问题
+---------------------+
| Answer | 应答
+---------------------+
| Authority | 授权应答
+---------------------+
| Additional | 附加信息
+---------------------+
之前的方法是采用了拼接二进制数据包进行发送,后来我感觉这不是一个完整的请求,并且获取结果集还是需要定义结构体的,绕不过那就只能自己实现了~
struct DNS_HEADER
{
unsigned short id; // identification number
unsigned char rd :1; // recursion desired
unsigned char tc :1; // truncated message
unsigned char aa :1; // authoritive answer
unsigned char opcode :4; // purpose of message
unsigned char qr :1; // query/response flag
unsigned char rcode :4; // response code
unsigned char cd :1; // checking disabled
unsigned char ad :1; // authenticated data
unsigned char z :1; // its z! reserved
unsigned char ra :1; // recursion available
unsigned short q_count; // number of question entries
unsigned short ans_count; // number of answer entries
unsigned short auth_count; // number of authority entries
unsigned short add_count; // number of resource entries
};
//Constant sized fields of query structure
struct QUESTION
{
unsigned short qtype;
unsigned short qclass;
};
//Constant sized fields of the resource record structure
#pragma pack(push, 1)
struct R_DATA
{
unsigned short type;
unsigned short _class;
unsigned int ttl;
unsigned short data_len;
};
#pragma pack(pop)
//Pointers to resource record contents
struct RES_RECORD
{
unsigned char *name;
struct R_DATA *resource;
unsigned char *rdata;
};
//Structure of a Query
typedef struct
{
unsigned char *name;
struct QUESTION *ques;
} QUERY;
上面是固定的结构体,如果你要进行DNS客户端编程,完全可以直接拿来用。
0x02 数组的内存概念
大家都知道 char buff[65535]
意味着什么,65535个字节的char
数组,也就是可以存储65535个字符。而我们的数据包没有那么大,可以先给它用memset
清空为零。
首先我们要设置一个DNS头
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ID |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR| Opcode |AA|TC|RD|RA| Z | RCODE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QDCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ANCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| NSCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ARCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
char buff[65535];
struct DNS_HEADER * dns = NULL;
dns = (struct DNS_HEADER * )&buff;
dns->id = (unsigned short) htons(getpid());
dns->qr = 0; //This is a query
dns->opcode = 0; //This is a standard query
dns->aa = 0; //Not Authoritative
dns->tc = 0; //This message is not truncated
dns->rd = 1; //Recursion Desired
dns->ra = 0; //Recursion not available! hey we dont have it (lol)
dns->z = 0;
dns->ad = 0;
dns->cd = 0;
dns->rcode = 0;
dns->q_count = htons(1); //we have only 1 question
dns->ans_count = 0;
dns->auth_count = 0;
dns->add_count = 0;
这块也是固定格式,当然在某些情况下你可以自行更改,具体看前一篇文章介绍…… 标志位
能看懂就可以
此时假设buff
的长度为100%
,那么把40%
已经给了DNS头。
剩下的我们还需要指针不断的向下移动,不断向里面按照数据包规定的格式填充数据。
填充域名
这里需要一个域名转换函数,因为:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| |
| QNAME |
| |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QTYPE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QCLASS |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
QNAME
域名被编码为一些labels序列,每个labels包含一个字节表示后续字符串长度,以及这个字符串,以0长度和空字符串来表示域名结束。
那么仅仅只需要一个char * qname
来指向除去DNS头部后面的起始位置就可以了。
char * qname = (unsigned char *)&buff[sizeof(struct DNS_HEADER)];
void dns::changeToDnsNameFormat(unsigned char* dns,unsigned char* host)
{
int lock = 0 , i;
strcat((char*)host,".");
for(i = 0 ; i < strlen((char*)host) ; i++)
{
if(host[i]=='.')
{
*dns++ = i-lock;
for(;lock<i;lock++)
{
*dns++=host[lock];
}
lock++; //or lock=i+1;
}
}
*dns++='\0';
}
上面的函数主要是用来将域名payloads.online
转换为8payloads6online0
此时buff
的50%
已经满了 ~
继续使用其他结构体指针指向后面的剩余部分。
设置查询类型
QTYPE
2个字节
表示查询类型,.取值可以为任何可用的类型值,以及通配码来表示所有的资源记录。QCLASS
2个字节
表示查询的协议类,比如,IN代表Internet
使用struct QUESTION *
来填充。
struct QUESTION * qinfo = (struct QUESTION *)&buff[sizeof(struct DNS_HEADER)+strlen((const char*)qname)+1];
qinfo->qtype= htons(1);
qinfo->qclass= htons(1);
请求函数
只需要发送一个UDP数据包即可~
void dns::requestDnsServer() {
sockaddr_in dest;
int i = sizeof(dest);
dnsSock = socket(AF_INET , SOCK_DGRAM , IPPROTO_UDP); //UDP packet for DNS queries
dest.sin_family = AF_INET;
dest.sin_port = htons(53);
dest.sin_addr.s_addr = inet_addr("114.114.114.114"); //dns servers
if(sendto(dnsSock,(char*)buff,sizeof(struct DNS_HEADER) + (strlen((const char*)qname)+1) + sizeof(struct QUESTION),0,(struct sockaddr*)&dest,sizeof(dest)) < 0)
{
return;
}
if(recvfrom (dnsSock,(char*)buff , 65536 , 0 , (struct sockaddr*)&dest , (socklen_t*)&i ) < 0)
{
perror("recvfrom failed");
}
}
整个数据包的长度 = sizeof(struct DNS_HEADER) + (strlen((const char*)qname)+1) + sizeof(struct QUESTION)
DNS头部+域名+查询类型+查询协议类
0x03 封装起来
- dns.h
//
// Created by payloads on 18-2-27.
//
#ifndef NEWPRO_DNS_H
#define NEWPRO_DNS_H
#include <cstring>
#include <sys/socket.h>
#include <cstdlib>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstdio>
#include <iostream>
#include <map>
#define T_A 1 //Ipv4 address
#define T_NS 2 //Nameserver
#define T_CNAME 5 // canonical name
#define T_SOA 6 /* start of authority zone */
#define T_PTR 12 /* domain name pointer */
#define T_MX 15 //Mail server
struct DNS_HEADER
{
unsigned short id; // identification number
unsigned char rd :1; // recursion desired
unsigned char tc :1; // truncated message
unsigned char aa :1; // authoritive answer
unsigned char opcode :4; // purpose of message
unsigned char qr :1; // query/response flag
unsigned char rcode :4; // response code
unsigned char cd :1; // checking disabled
unsigned char ad :1; // authenticated data
unsigned char z :1; // its z! reserved
unsigned char ra :1; // recursion available
unsigned short q_count; // number of question entries
unsigned short ans_count; // number of answer entries
unsigned short auth_count; // number of authority entries
unsigned short add_count; // number of resource entries
};
//Constant sized fields of query structure
struct QUESTION
{
unsigned short qtype;
unsigned short qclass;
};
//Constant sized fields of the resource record structure
#pragma pack(push, 1)
struct R_DATA
{
unsigned short type;
unsigned short _class;
unsigned int ttl;
unsigned short data_len;
};
#pragma pack(pop)
//Pointers to resource record contents
struct RES_RECORD
{
unsigned char *name;
struct R_DATA *resource;
unsigned char *rdata;
};
//Structure of a Query
typedef struct
{
unsigned char *name;
struct QUESTION *ques;
} QUERY;
class dns {
public:
std::string dnsServer;
public:
dns();
~dns();
void request(std::string hostname);
void changeToDnsNameFormat(unsigned char* dns,unsigned char* host);
int setDnsServer(char * dnsServer);
private:
void setDnsHeader(struct DNS_HEADER * dns);
void setQname(unsigned char * hostname);
void setQinfo(struct QUESTION *qinfo);
void requestDnsServer(struct DNS_HEADER *dns);
u_char* ReadName(unsigned char* reader,unsigned char* buffer,int* count);
private:
int dnsSock;
char Server[100];
unsigned char hostname[100];
unsigned char buff[65535];
unsigned char * qname;
unsigned char * reader;
struct RES_RECORD answers[20],auth[20],addit[20];
std::map<int,std::string> aResult;
sockaddr_in dest,a;
};
#endif //NEWPRO_DNS_H
- dns.cpp
//
// Created by payloads on 18-2-27.
//
#include "dns.h"
dns::dns(){
strncpy(Server,"114.114.114.114",20);
//aResult.insert(std::pair<std::string,std::string>("aaaaa","bbbbb"));
}
dns::~dns(){
auto it = aResult.begin();
for (; it != aResult.end(); ++it) {
std::cout << "[*]" << this->hostname << " => " << (*it).second << std::endl;
}
}
/**
*
* @param hostname
*/
void dns::request(std::string hostname) {
strcpy((char *)this->hostname,hostname.c_str());
struct DNS_HEADER * dns = NULL;
struct QUESTION *qinfo = NULL;
setDnsHeader(dns);
setQname((unsigned char *)hostname.c_str());
setQinfo(qinfo);
requestDnsServer(dns);
dns = (struct DNS_HEADER*)buff;
reader = &buff[sizeof(struct DNS_HEADER) + (strlen((const char*)qname)+1) + sizeof(struct QUESTION)];
/*
printf("\nThe response contains : ");
printf("\n %d Questions.",ntohs(dns->q_count));
printf("\n %d Answers.",ntohs(dns->ans_count));
printf("\n %d Authoritative Servers.",ntohs(dns->auth_count));
printf("\n %d Additional records.\n\n",ntohs(dns->add_count));
*/
int stop=0;
for(int i=0;i<ntohs(dns->ans_count);i++)
{
answers[i].name=ReadName(reader,buff,&stop);
reader = reader + stop;
answers[i].resource = (struct R_DATA*)(reader);
reader = reader + sizeof(struct R_DATA);
if(ntohs(answers[i].resource->type) == 1) //if its an ipv4 address
{
answers[i].rdata = (unsigned char*)malloc(ntohs(answers[i].resource->data_len));
for(int j=0 ; j<ntohs(answers[i].resource->data_len) ; j++)
{
answers[i].rdata[j]=reader[j];
}
answers[i].rdata[ntohs(answers[i].resource->data_len)] = '\0';
reader = reader + ntohs(answers[i].resource->data_len);
}
else
{
answers[i].rdata = ReadName(reader,buff,&stop);
reader = reader + stop;
}
}
printf("\nAnswer Records : %d \n" , ntohs(dns->ans_count) );
for(int i=0; i < ntohs(dns->ans_count) ; i++)
{
//printf("Name : %s ",answers[i].name);
if( ntohs(answers[i].resource->type) == T_A) //IPv4 address
{
std::string ipAddress;
long *p;
p=(long*)answers[i].rdata;
a.sin_addr.s_addr=(*p); //working without ntohl
//printf("has IPv4 address : %s",inet_ntoa(a.sin_addr));
ipAddress=inet_ntoa(a.sin_addr);
aResult.insert(std::pair<int,std::string>(i,ipAddress));
//ipAddress.clear();
}
if(ntohs(answers[i].resource->type)==5)
{
//Canonical name for an alias
printf("has alias name : %s",answers[i].rdata);
}
//printf("\n");
}
/*
for (int k = 0; k < ntohs(dns->ans_count); ++k) {
free(answers[i].rdata);
}
*/
close(dnsSock);
auto it = aResult.begin();
for (; it != aResult.end(); ++it) {
std::cout << "[*]" << hostname << " => " << (*it).second << std::endl;
}
for(int i=0; i < ntohs(dns->ans_count) ; i++){
if(ntohs(answers[i].resource->type) == 1) //if its an ipv4 address
{
free(answers[i].rdata);
}
}
aResult.clear();
this->hostname[0]='\0';
}
/**
*
* @param dns
* @param host
*/
void dns::changeToDnsNameFormat(unsigned char* dns,unsigned char* host)
{
int lock = 0 , i;
strcat((char*)host,".");
for(i = 0 ; i < strlen((char*)host) ; i++)
{
if(host[i]=='.')
{
*dns++ = i-lock;
for(;lock<i;lock++)
{
*dns++=host[lock];
}
lock++; //or lock=i+1;
}
}
*dns++='\0';
}
/**
*
* @param dns
*/
void dns::setDnsHeader(struct DNS_HEADER *dns) {
dns = (struct DNS_HEADER * )&buff;
dns->id = (unsigned short) htons(getpid());
dns->qr = 0; //This is a query
dns->opcode = 0; //This is a standard query
dns->aa = 0; //Not Authoritative
dns->tc = 0; //This message is not truncated
dns->rd = 1; //Recursion Desired
dns->ra = 0; //Recursion not available! hey we dont have it (lol)
dns->z = 0;
dns->ad = 0;
dns->cd = 0;
dns->rcode = 0;
dns->q_count = htons(1); //we have only 1 question
dns->ans_count = 0;
dns->auth_count = 0;
dns->add_count = 0;
}
/**
*
* @param hostname
*/
void dns::setQname(unsigned char *hostname) {
qname = (unsigned char *)&buff[sizeof(struct DNS_HEADER)];
changeToDnsNameFormat(qname,hostname);
}
/**
*
* @param qinfo
*/
void dns::setQinfo(struct QUESTION *qinfo) {
qinfo = (struct QUESTION *)&buff[sizeof(struct DNS_HEADER)+strlen((const char*)qname)+1];
qinfo->qtype= htons(1);
qinfo->qclass= htons(1);
}
/**
*
* @param reader
* @param buffer
* @param count
* @return
*/
u_char * dns::ReadName(unsigned char* reader,unsigned char* buffer,int* count)
{
unsigned char *name;
unsigned int p=0,jumped=0,offset;
int i , j;
*count = 1;
name = (unsigned char*)malloc(256);
name[0]='\0';
//read the names in 3www6google3com format
while(*reader!=0)
{
if(*reader>=192)
{
offset = (*reader)*256 + *(reader+1) - 49152; //49152 = 11000000 00000000 ;)
reader = buffer + offset - 1;
jumped = 1; //we have jumped to another location so counting wont go up!
}
else
{
name[p++]=*reader;
}
reader = reader+1;
if(jumped==0)
{
*count = *count + 1; //if we havent jumped to another location then we can count up
}
}
name[p]='\0'; //string complete
if(jumped==1)
{
*count = *count + 1; //number of steps we actually moved forward in the packet
}
//now convert 3www6google3com0 to www.google.com
for(i=0;i<(int)strlen((const char*)name);i++)
{
p=name[i];
for(j=0;j<(int)p;j++)
{
name[i]=name[i+1];
i=i+1;
}
name[i]='.';
}
name[i-1]='\0'; //remove the last dot
return name;
}
/**
* set name server
* @param dnsServer
* @return
*/
int dns::setDnsServer(char * dnsServer) {
strncpy(Server,dnsServer,strlen(dnsServer)>100?99:strlen(dnsServer));
}
void dns::requestDnsServer(struct DNS_HEADER * dns) {
int i = sizeof(dest);
dnsSock = socket(AF_INET , SOCK_DGRAM , IPPROTO_UDP); //UDP packet for DNS queries
dest.sin_family = AF_INET;
dest.sin_port = htons(53);
dest.sin_addr.s_addr = inet_addr(Server); //dns servers
if(sendto(dnsSock,(char*)buff,sizeof(struct DNS_HEADER) + (strlen((const char*)qname)+1) + sizeof(struct QUESTION),0,(struct sockaddr*)&dest,sizeof(dest)) < 0)
{
return;
}
if(recvfrom (dnsSock,(char*)buff , 65536 , 0 , (struct sockaddr*)&dest , (socklen_t*)&i ) < 0)
{
perror("recvfrom failed");
}
}
0x04 解析接收的数据
由于DNS的数据包比较简单,发送的结构与响应结构基本相同。
可以看到QR
的标志位是1,证明是一个响应数据包。
Answer RRs
为2,证明有两条结果……
我们翻到最后看出的确有两个A记录结果,且IP地址都不相同
在C++/C里面如何获取到A记录的结果呢?
同样的,我们还是要将接受到的数据使用结构体指针分割,这样可以很方便的获取到记录。
if(recvfrom (dnsSock,(char*)buff , 65536 , 0 , (struct sockaddr*)&dest , (socklen_t*)&i ) < 0)
{
perror("recvfrom failed");
}
这一句是将接收到的数据写入buff
数组。
先解析DNS头,可以获取有几条结果:
dns = (struct DNS_HEADER*)buff;
dns->ans_count
接下来可以进行读取:
reader = &buff[sizeof(struct DNS_HEADER) + (strlen((const char*)qname)+1) + sizeof(struct QUESTION)];
u_char * dns::ReadName(unsigned char* reader,unsigned char* buffer,int* count)
{
unsigned char *name;
unsigned int p=0,jumped=0,offset;
int i , j;
*count = 1;
name = (unsigned char*)malloc(256);
name[0]='\0';
//read the names in 3www6google3com format
while(*reader!=0)
{
if(*reader>=192)
{
offset = (*reader)*256 + *(reader+1) - 49152; //49152 = 11000000 00000000 ;)
reader = buffer + offset - 1;
jumped = 1; //we have jumped to another location so counting wont go up!
}
else
{
name[p++]=*reader;
}
reader = reader+1;
if(jumped==0)
{
*count = *count + 1; //if we havent jumped to another location then we can count up
}
}
name[p]='\0'; //string complete
if(jumped==1)
{
*count = *count + 1; //number of steps we actually moved forward in the packet
}
//now convert 3www6google3com0 to www.google.com
for(i=0;i<(int)strlen((const char*)name);i++)
{
p=name[i];
for(j=0;j<(int)p;j++)
{
name[i]=name[i+1];
i=i+1;
}
name[i]='.';
}
name[i-1]='\0'; //remove the last dot
return name;
}
固定函数,直接可以拿来用~
获取记录结果
int stop=0;
for(int i=0;i<ntohs(dns->ans_count);i++)
{
answers[i].name=ReadName(reader,buff,&stop);
reader = reader + stop;
answers[i].resource = (struct R_DATA*)(reader);
reader = reader + sizeof(struct R_DATA);
if(ntohs(answers[i].resource->type) == 1) //if its an ipv4 address
{
answers[i].rdata = (unsigned char*)malloc(ntohs(answers[i].resource->data_len));
for(int j=0 ; j<ntohs(answers[i].resource->data_len) ; j++)
{
answers[i].rdata[j]=reader[j];
}
answers[i].rdata[ntohs(answers[i].resource->data_len)] = '\0';
reader = reader + ntohs(answers[i].resource->data_len);
}
else
{
answers[i].rdata = ReadName(reader,buff,&stop);
reader = reader + stop;
}
}
printf("\nAnswer Records : %d \n" , ntohs(dns->ans_count) );
for(int i=0; i < ntohs(dns->ans_count) ; i++)
{
//printf("Name : %s ",answers[i].name);
if( ntohs(answers[i].resource->type) == T_A) //IPv4 address
{
std::string ipAddress;
long *p;
p=(long*)answers[i].rdata;
a.sin_addr.s_addr=(*p); //working without ntohl
//printf("has IPv4 address : %s",inet_ntoa(a.sin_addr));
ipAddress=inet_ntoa(a.sin_addr);
aResult.insert(std::pair<int,std::string>(i,ipAddress)); // 这里我用了容器
//ipAddress.clear();
}
if(ntohs(answers[i].resource->type)==5)
{
//Canonical name for an alias
printf("has alias name : %s",answers[i].rdata);
}
//printf("\n");
}
/*
for (int k = 0; k < ntohs(dns->ans_count); ++k) {
free(answers[i].rdata);
}
*/
close(dnsSock);
auto it = aResult.begin();
for (; it != aResult.end(); ++it) {
std::cout << "[*]" << hostname << " => " << (*it).second << std::endl;
}
for(int i=0; i < ntohs(dns->ans_count) ; i++){
if(ntohs(answers[i].resource->type) == 1) //if its an ipv4 address
{
free(answers[i].rdata);
}
}
aResult.clear();
this->hostname[0]='\0';
获取到的A记录会都保存到map容器中。
下面给出main.cpp的内容:
#include <iostream>
#include "dns.h"
int main(int argc,char * argv[]) {
dns dd;
dd.request("payloads.online");
}
输出结果:
Answer Records : 2
[*]payloads.online => 192.30.252.153
[*]payloads.online => 192.30.252.154
后续再加把劲吧,争取把 DNS、HTTP相关的信息搜集轮子造好!!