分享一个C/C++ 子域名A记录枚举的一个工具,可以根据自己的需求再改进
0x00 为什么需要A记录枚举?
DNS服务中的A记录是指:指定特定主机名映射到特定的IP地址上。
这是非常重要记录也是常用的记录,比如解析主机名为www的到IP 1.1.1.1上就需要使用A记录进行
在渗透测试的很多流程下,我们都需要不断的进行信息搜集,通过搜集到的信息来给我们的攻击带来更多思路。
0x01 我用这些A记录可以干什么?
例如通过我枚举百度域名的结果可以进行分析:
payloads@Koone:~/cpp_program$ ./dns_enum baidu.com 2
start threads ... 0
[*]news.baidu.com. => 115.239.210.174
[*]news.baidu.com. => 180.97.33.136
[*]kaoshi.baidu.com. => 14.215.177.118
[*]www.baidu.com. => 115.239.211.112
[*]www.baidu.com. => 115.239.210.27
[*]bbs.baidu.com. => 180.97.34.146
[*]cdn.baidu.com. => 10.36.3.156
[*]api.baidu.com. => 115.239.210.90
end threads ... 2
上面我只是简单的给出了几个子域名结果,可以看到cdn.baidu.com
指向了一个内网地址10.36.3.156
。
在内网渗透中,域名枚举也有很大的作用,因为域控都是DNS服务器,为了在企业内部提供一些服务,则会频繁使用DNS服务来指向某些特定的服务器,所以,知道了域名,可以枚举内网中的一些OA、Email等服务器地址,进行定向渗透。
当然不只是A记录,还有很多其他,可以移步搜索引擎搜索 DNS记录类型
0x02 本工具的实现
大概分为三块:
- 多线程控制类
- DNS请求类
- 主函数类
由于之前已经铺垫了太多DNS相关的知识,就自己造个轮子,为了方便编译,我将代码都移植到了main.cpp
0x03 多线程锁的问题
在开发的过程中,必须要选择一个变量用来分离任务,不可能两个或多个线程处理同一个任务,资源浪费了。
static int threadLock = 0;
务必添加static
关键字注册为全局变量
while(threadLock< Dns->_subdomain.size()){
Dns->_mutex.lock();
//code ...
threadLock++;
Dns->_mutex.unlock();
}
0x04 源代码
- 源代码地址:http://payloads.online/tools/dns_enum.txt
- 字典地址:http://payloads.online/tools/dict.txt
//dns_enum
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <cstdlib>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstdio>
#include <iostream>
#include <map>
#include <vector>
#include <thread>
#include <mutex>
#include <fstream>
#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
class Mythread {
public:
Mythread();
~Mythread();
bool setThreads(int thread=0);
bool runThreads(void (*function)(void * object),void * object);
bool setDetach();
bool setJoin();
private:
int _thread = 0;
std::vector<std::thread> threads;
};
bool Mythread::setThreads(int thread) {
if(thread < 100){
_thread = thread;
}else{
_thread = 100;
}
}
bool Mythread::runThreads(void (*function)(void *) , void * object) {
for (int i = 0; i < _thread; ++i) {
threads.push_back(std::thread(function,object));
}
}
bool Mythread::setDetach(){
for (int i = 0; i < _thread; ++i) {
threads[i].detach();
}
}
bool Mythread::setJoin(){
for (int i = 0; i < _thread; ++i) {
threads[i].join();
}
}
Mythread::Mythread() {
std::cout << "start threads ... " << _thread << std::endl;
}
Mythread::~Mythread() {
std::cout << "end threads ... " << _thread << std::endl;
}
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;
std::vector<std::string> _subdomain = {};
public:
dns();
dns(std::string dnsServer);
~dns();
void setHostname(std::string hostname);
void request(std::string hostname);
void changeToDnsNameFormat(unsigned char* dns,unsigned char* host);
int setDnsServer(std::string dnsServer);
static void threadRequest(void * hostname);
void runThread(int threadNum);
bool addSubdomain(std::string subdomainName);
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;
std::string _dnsServer;
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;
std::mutex _mutex;
std::string _currentHostname;
};
//
// Created by payloads on 18-2-27.
//
dns::dns(){
}
dns::dns(std::string dnsServer){
setDnsServer(dnsServer);
}
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)];
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)
{
_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));
}
if(ntohs(_answers[i].resource->type)==5)
{
printf("has alias name : %s",_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(std::string dnsServer) {
_dnsServer = 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(_dnsServer.c_str()); //dns servers
struct timeval timeout = {2,0};
setsockopt(dnsSock,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(struct timeval));
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");
}
}
void dns::threadRequest(void * DnsRequest) {
dns * Dns = (dns *)DnsRequest;
//Dns->mutex.lock();
static int threadLock = 0;
while(threadLock< Dns->_subdomain.size()){
Dns->_mutex.lock();
struct DNS_HEADER * dns = NULL;
struct QUESTION *qinfo = NULL;
Dns->_currentHostname.append(Dns->_subdomain[threadLock]);
threadLock++;
char hostBuff[200];
sprintf(hostBuff,"%s.%s",Dns->_currentHostname.c_str(),Dns->_hostname);
//std::cout << "Domain => " << hostBuff << std::endl;
Dns->setDnsHeader(dns);
Dns->setQname((unsigned char *)hostBuff);
memset(hostBuff,0,0);
Dns->setQinfo(qinfo);
Dns->_currentHostname.clear();
Dns->requestDnsServer(dns);
dns = (struct DNS_HEADER*)Dns->_buff;
Dns->_reader = &Dns->_buff[sizeof(struct DNS_HEADER) + (strlen((const char*)Dns->_qname)+1) + sizeof(struct QUESTION)];
int stop=0;
for(int i=0;i<ntohs(dns->ans_count);i++)
{
Dns->_answers[i].name=Dns->ReadName(Dns->_reader,Dns->_buff,&stop);
Dns->_reader = Dns->_reader + stop;
Dns->_answers[i].resource = (struct R_DATA*)(Dns->_reader);
Dns->_reader = Dns->_reader + sizeof(struct R_DATA);
if(ntohs(Dns->_answers[i].resource->type) == 1) //if its an ipv4 address
{
Dns->_answers[i].rdata = (unsigned char*)malloc(ntohs(Dns->_answers[i].resource->data_len));
for(int j=0 ; j<ntohs(Dns->_answers[i].resource->data_len) ; j++)
{
Dns->_answers[i].rdata[j]=Dns->_reader[j];
}
Dns->_answers[i].rdata[ntohs(Dns->_answers[i].resource->data_len)] = '\0';
Dns->_reader = Dns->_reader + ntohs(Dns->_answers[i].resource->data_len);
}
else
{
Dns->_answers[i].rdata = Dns->ReadName(Dns->_reader,Dns->_buff,&stop);
Dns->_reader = Dns->_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(Dns->_answers[i].resource->type) == T_A) //IPv4 address
{
std::string ipAddress;
long *p;
p=(long*)Dns->_answers[i].rdata;
Dns->_a.sin_addr.s_addr=(*p); //working without ntohl
//printf(" %s has IPv4 address : %s",hostBuff,inet_ntoa(Dns->a.sin_addr));
ipAddress=inet_ntoa(Dns->_a.sin_addr);
Dns->_aResult.insert(std::pair<int,std::string>(i,ipAddress));
}
if(ntohs(Dns->_answers[i].resource->type)==5)
{
//Canonical name for an alias
//printf("has alias name : %s",Dns->answers[i].rdata);
}
}
close(Dns->dnsSock);
auto it = Dns->_aResult.begin();
for (; it != Dns->_aResult.end(); ++it) {
std::cout << "[*]" << hostBuff << " => " << (*it).second << std::endl;
}
for(int i=0; i < ntohs(dns->ans_count) ; i++){
if(ntohs(Dns->_answers[i].resource->type) == 1) //if its an ipv4 address
{
free(Dns->_answers[i].rdata);
}
}
Dns->_aResult.clear();
Dns->_mutex.unlock();
}
}
void dns::setHostname(std::string hostname) {
strcpy((char *)this->_hostname,hostname.c_str());
}
void dns::runThread(int threadNum) {
// void * host = (void *)this;
Mythread th;
th.setThreads(threadNum);
th.runThreads(this->threadRequest,(void *)this);
th.setJoin();
}
bool dns::addSubdomain(std::string subdomainName) {
_subdomain.push_back(subdomainName);
}
int main(int argc,char * argv[]) {
std::string dictPath("dict.txt");
if(argc < 2){
std::cout << "Usage:" << argv[0] << " <domain name> <Threads>" << std::endl;
std::cout << "This program is developed by the Qingxuan" << std::endl;
std::cout << "Email: [email protected]" << std::endl;
exit(0);
}
std::cout << "string " << argc<< std::endl;
if(argc == 4){
dictPath = argv[3];
}
dns Enum("114.114.114.114");
std::string hostname = argv[1];
Enum.setHostname(hostname);
std::fstream IO(dictPath);
while(!IO.eof()){
char lines[100];
IO.getline(lines,100);
std::string lin(lines);
Enum.addSubdomain(lin);
}
Enum.runThread(atoi(argv[2]));
}
它可以直接通过g++编译,不需要任何第三方库。
0x05 编译及使用
-
编译命令:
g++ main.cpp -lpthread -o dns_enum
-
使用方式:
./dns_enum <domain name> <Threads>
-
使用效果:
注意:必须在当前程序目录下新建一个字典,名为:dict.txt
使用其他字典
- 使用命令:
./dns_enum payloads.online 10 /usr/dict/Subdomain.txt
0x06 最后
这只是我一个大项目中的一部分,近期发现了不少网络扫描的lib,以后的以后再慢慢学习。
DNS枚举这块可以再进行优化,目前只是支持多线程,网络层那块并没有做优化,原生态的数据包发送,真是锻炼人!