Initial commit
Ursprung
53047edd5a
Commit
1db4ffc6a2
@ -1,2 +1,16 @@
|
||||
# Uninvited-Guest
|
||||
Uninvited Guest - A file server for files over DNS TXT records
|
||||
|
||||
First set up your domain to point to which ever server you're hosting this on.
|
||||
|
||||
Then run the python server
|
||||
|
||||
./server --domain domainname.com --directory /dir/of/tools
|
||||
|
||||
It will only support a flat directory structure in /dir/of/tools
|
||||
|
||||
You will need to write your own client to receive files. The count of items will be in file.count.domainname.com and the strings will be in file.number.domainname.com.
|
||||
|
||||
An example bash client would be something like:
|
||||
|
||||
f="pwned.png";d="6-9.eu";c=$(dig +short txt $f.count.$d|tr -d \");for i in $(seq 0 $c);do echo -n $(dig +short txt $f.$i.$d|tr -d \");done | base64 -d > /tmp/pwned.png
|
||||
|
@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env /usr/bin/python
|
||||
# Why don't people use shebangs anymore. It's muppetry I say
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
FileResolver - example resolver which responds with fixed response
|
||||
to all requests
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import copy
|
||||
import os
|
||||
import os.path
|
||||
import base64
|
||||
import time
|
||||
|
||||
from dnslib import RR, QTYPE, TXT, CLASS, RCODE
|
||||
from dnslib.server import DNSServer,DNSHandler,BaseResolver,DNSLogger
|
||||
from dnslib.label import DNSLabel
|
||||
|
||||
class FileResolver(BaseResolver):
|
||||
"""
|
||||
Respond with fixed response to all requests
|
||||
"""
|
||||
def __init__(self,directory,domain):
|
||||
self.filelist=[]
|
||||
self.domain=domain
|
||||
self.ttl=60
|
||||
self.directory=directory
|
||||
self.cache={}
|
||||
if directory:
|
||||
if not os.path.isdir(directory):
|
||||
print("Directory " + directory + " doesn't exist")
|
||||
exit()
|
||||
|
||||
def getcache(self,path):
|
||||
cooked=""
|
||||
pname=os.path.basename(path);
|
||||
if pname in self.cache:
|
||||
# it's in the cache - so lets just read it from the cache
|
||||
print("Taking from cache")
|
||||
cooked=self.cache[pname]["base64"]
|
||||
# And update time
|
||||
self.cache[pname]["time"]=time.time()
|
||||
else:
|
||||
# It's not in cache - read the file and cache it
|
||||
print("Adding to cache")
|
||||
fin=open(path,"rb")
|
||||
raw=fin.read()
|
||||
fin.close()
|
||||
cooked=base64.b64encode(raw)
|
||||
self.cache[pname]={}
|
||||
self.cache[pname]["base64"]=cooked
|
||||
self.cache[pname]["time"]=time.time()
|
||||
|
||||
self.cachecheck()
|
||||
return cooked
|
||||
|
||||
def cachecheck(self):
|
||||
# Remove any entries in the cache over an hour old
|
||||
curtime = time.time()
|
||||
staletime = 3600
|
||||
for key in self.cache:
|
||||
if curtime-self.cache[key]["time"] > staletime:
|
||||
# it's stale
|
||||
del self.cache[key]
|
||||
|
||||
def resolve(self,request,handler):
|
||||
type=QTYPE[request.q.qtype]
|
||||
name=request.q.qname
|
||||
reply = request.reply()
|
||||
|
||||
if type == "TXT":
|
||||
# Check for Chaos stuff first
|
||||
if request.q.qclass == CLASS.CH:
|
||||
if name == "version.bind":
|
||||
reply.add_answer(*RR.fromZone('version.bind 60 CH TXT "Uninvited Guest 0.3"'))
|
||||
if name == "authors.bind":
|
||||
reply.add_answer(*RR.fromZone('authors.bind 60 CH TXT "David Lodge"'))
|
||||
reply.add_answer(*RR.fromZone('authors.bind 60 CH TXT "Ian Williams"'))
|
||||
if not reply.rr:
|
||||
reply.header.rcode = RCODE.NXDOMAIN
|
||||
return reply
|
||||
|
||||
# Format is filename.count.domain for count
|
||||
# First check domain is at the end of the name
|
||||
if not name.matchSuffix(self.domain):
|
||||
reply.add_answer(RR(name, QTYPE.TXT, ttl=self.ttl,
|
||||
rdata=TXT("Domain not found")))
|
||||
return reply
|
||||
|
||||
# Now look whether we're looking for a count or a part
|
||||
parts=str(name.stripSuffix(self.domain)).split(".")
|
||||
# parts[0] should be filename, parts[1] should be segment or count
|
||||
pname='.'.join(parts[:-2])
|
||||
path=self.directory + "/" + pname
|
||||
if not os.path.isfile(path):
|
||||
reply.add_answer(RR(name, QTYPE.TXT, ttl=self.ttl,
|
||||
rdata=TXT("File not found")))
|
||||
return reply
|
||||
|
||||
# work out the count mathematically
|
||||
# First work out the base64 size
|
||||
l=os.path.getsize(path)
|
||||
l=(4*l)/3
|
||||
# And padding
|
||||
l+=(l%4)
|
||||
# Finally divide into number of 254 byte chunks
|
||||
chunks=(l/254)
|
||||
|
||||
if ''.join(parts[-2:-1]) == "count":
|
||||
reply.add_answer(RR(name, QTYPE.TXT, ttl=self.ttl,
|
||||
rdata=TXT(str(chunks))))
|
||||
return reply
|
||||
|
||||
if ''.join(parts[-2:-1]).isdigit():
|
||||
# Woo it's a number
|
||||
# lets base64 the file
|
||||
chunk=int(''.join(parts[-2:-1]))
|
||||
if chunk > chunks or chunk < 0:
|
||||
reply.add_answer(RR(name, QTYPE.TXT, ttl=self.ttl,
|
||||
rdata=TXT("Chunk out of range")))
|
||||
return reply
|
||||
# It's actually trying to read the file, so let's cache it in memory
|
||||
cooked=self.getcache(path)
|
||||
|
||||
# Now lets just grab the chunk
|
||||
start=chunk*254
|
||||
txtr=cooked[start:start+254]
|
||||
reply.add_answer(RR(name, QTYPE.TXT, ttl=self.ttl,
|
||||
rdata=TXT(txtr)))
|
||||
return reply
|
||||
|
||||
# Replace labels with request label
|
||||
return reply
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
import argparse,sys,time
|
||||
|
||||
p = argparse.ArgumentParser(description="Fixed DNS Resolver")
|
||||
p.add_argument("--directory","-r",
|
||||
metavar="<directory>",
|
||||
help="Directory to pull files from")
|
||||
p.add_argument("--domain","-d",
|
||||
metavar="<domain>",
|
||||
help="Domain we're working in")
|
||||
p.add_argument("--port","-p",type=int,default=53,
|
||||
metavar="<port>",
|
||||
help="Server port (default:53)")
|
||||
p.add_argument("--address","-a",default="",
|
||||
metavar="<address>",
|
||||
help="Listen address (default:all)")
|
||||
p.add_argument("--udplen","-u",type=int,default=0,
|
||||
metavar="<udplen>",
|
||||
help="Max UDP packet length (default:0)")
|
||||
p.add_argument("--tcp",action='store_true',default=False,
|
||||
help="TCP server (default: UDP only)")
|
||||
p.add_argument("--log",default="request,reply,truncated,error",
|
||||
help="Log hooks to enable (default: +request,+reply,+truncated,+error,-recv,-send,-data)")
|
||||
p.add_argument("--log-prefix",action='store_true',default=False,
|
||||
help="Log prefix (timestamp/handler/resolver) (default: False)")
|
||||
args = p.parse_args()
|
||||
|
||||
resolver = FileResolver(args.directory,args.domain)
|
||||
logger = DNSLogger(args.log,args.log_prefix)
|
||||
|
||||
print("Starting File Resolver (%s:%d) [%s]" % (
|
||||
args.address or "*",
|
||||
args.port,
|
||||
"UDP/TCP" if args.tcp else "UDP"))
|
||||
|
||||
if args.udplen:
|
||||
DNSHandler.udplen = args.udplen
|
||||
|
||||
udp_server = DNSServer(resolver,
|
||||
port=args.port,
|
||||
address=args.address,
|
||||
logger=logger)
|
||||
udp_server.start_thread()
|
||||
|
||||
if args.tcp:
|
||||
tcp_server = DNSServer(resolver,
|
||||
port=args.port,
|
||||
address=args.address,
|
||||
tcp=True,
|
||||
logger=logger)
|
||||
tcp_server.start_thread()
|
||||
|
||||
while udp_server.isAlive():
|
||||
time.sleep(1)
|
||||
|
Laden…
In neuem Issue referenzieren