本文简述一下使用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

此时buff50%已经满了 ~

继续使用其他结构体指针指向后面的剩余部分。

设置查询类型

  • 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相关的信息搜集轮子造好!!