The U.S. government is taking down websites using DNS poisoning/hijacking methods on pages that are claimed to be infringing on copyright law. I have re-written a *really* basic DNS server which re-implements DNS forwarding/proxying, DNS caching and DNS un-blacklisting of A/IN/IPv4 records. Basically, if one were to run their own DNS server (like the one below) and maintain a simple list of government-poisoned IP addresses for it, the DNS server would be able to provide (previously expired but still good) IPv4 addresses for the blocked site. This could essentially allow one to re-access a now blacklisted website.
Here is a screen shot of my computer using this basic DNS server to visit slashdot:
$ host slashdot.org slashdot.org has address 216.34.181.45 slashdot.org mail is handled by 10 mx.sourceforge.net.
And here is what it would look like if it were taken down via DNS poisoning:
$ host slashdot.org slashdot.org has address 74.81.170.110 slashdot.org has address 216.34.181.45 slashdot.org mail is handled by 10 mx.sourceforge.net.
Once this happens, all one has to do now is append the newly offending IP address to the DNS server’s black list text file like so:
# cat dnsb.txt 74.81.170.110
And if you perform another DNS request to the same (custom) DNS server, you get the last cached answers bringing you back to slashdot:
$ host slashdot.org slashdot.org has address 216.34.181.45 slashdot.org mail is handled by 10 mx.sourceforge.net.
The code below is simply proof-of-concept software and should *NOT* be used for production as I have implemented everything from scratch here and know very little about how DNS really works (i.e. message compression).
import os
import re
import socket
import sqlite3
import sys
import time
sqlo = sqlite3.connect("dnsd.db")
sqlc = sqlo.cursor()
def fixs(inpt):
outp = ""
for letr in inpt:
if (ord(letr) < 32):
outp += "."
else:
outp += letr
return outp
def rslv():
nlst = []
fobj = open("/etc/resolv.conf", "r")
dnsl = fobj.readlines()
for dnse in dnsl:
item = dnse.strip("\0\t\r\n ")
regx = re.match("^nameserver[ \t]*(.*)$", item)
if (regx):
addr = regx.group(1)
nlst.append(addr)
return nlst
def forw(addr, data):
resp = ""
serv = (addr, 53)
nobj = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
nobj.settimeout(5)
nobj.sendto(data, serv)
resp = nobj.recv(2**20)
except:
pass
try:
nobj.close()
except:
pass
return resp
def answ(data, deet=False):
# http://www.ietf.org/rfc/rfc1035.txt
requ = 13
null = (requ + data[requ:].find("\0"))
dtyp = (null + 5)
dlen = (dtyp + 10)
size = len(data)
name = fixs(data[requ:null])
begi = -1
endi = -1
ansl = []
while ((dlen + 1) < size):
dsiz = ((ord(data[dlen]) << 8) + ord(data[dlen + 1]))
dsig = (dtyp + 4)
if (((dlen + 5) < size) and (data[dtyp:dsig] == "\xc0\x0c\x00\x01")):
if (deet == True):
if (begi == -1):
begi = dtyp
else:
endi = (dlen + 1 + dsiz + 1)
addr = ""
for x in range(2, 2 + 4):
addr += ("." + str(ord(data[dlen + x])))
ansl.append([name, addr[1:]])
dtyp = (dlen + 1 + dsiz + 1)
dlen = (dtyp + 10)
if (begi == -1):
begi = (null + 5)
endi = begi
if (deet == False):
return ansl
else:
return [begi, endi]
def sqle(comd, outp=False):
global sqlo
global sqlc
try:
sqlc.execute(comd)
except:
pass
if (outp == False):
sqlo.commit()
else:
return sqlc.fetchall()
def filt():
try:
fobj = open("dnsb.txt", "r")
for line in fobj.readlines():
addr = line.strip("\0\t\r\n ")
sqle("DELETE FROM data WHERE addr = '%s';" % (addr))
fobj.close()
except:
return 0
return 1
def cche(name):
dbdl = sqle("SELECT * FROM data WHERE name = '%s';" % (name), outp=True)
outp = []
for dbdi in dbdl:
adrl = dbdi[1].split(".")
adrs = ""
for adri in adrl:
adrs += chr(int(adri))
outp.append([int(dbdi[2]), adrs])
outp.sort()
outp.reverse()
return outp
def shim(data, adrl):
hedn = [6, 8]
payn = answ(data, deet=True)
lead = (payn[0] + 12)
size = len(adrl)
dlen = (chr((size >> 8) & 0xff) + chr((size >> 0) & 0xff))
head = (data[:hedn[0]] + dlen + data[hedn[1]:payn[0]])
payl = ""
for adri in adrl:
payl += (data[payn[0]:lead] + adri[1])
payl += data[payn[1]:]
outp = (head + payl)
# beg: compression offset bypass
outp = (outp[:8] + chr(0) + chr(0) + outp[10:])
outp = (outp[:10] + chr(0) + chr(0) + outp[12:])
# end: compression offset bypass
# beg: compression offset fix
'''
x = 0
l = len(outp)
s = ((ord(data[6]) << 8) + ord(data[7]))
d = ((size - s) * 16)
if (d > 0):
while ((x + 1) < l):
o = ((ord(outp[x]) << 8) + ord(outp[x + 1]))
if ((o & 0xff00) == 0xc000):
o = (o & 0x00ff)
if (o > 0x0c):
o = (o + d)
outp = (outp[:x] + chr((o >> 8) & 0xff) + chr((o >> 0) & 0xff) + outp[x + 2:])
x = (x + 2)
'''
# end: compression offset fix
return outp
def debg(addr, requ, answ):
adrl = addr.split(".")
adrl[3] = "x"
prio = ""
for letr in answ:
prio += ("." + str(ord(letr)))
print("[%s] requ [%s] answ [%s, ...]" % (".".join(adrl), requ, prio[1:]))
def serv():
snam = ("", 53)
sobj = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sobj.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sobj.bind(snam)
while (1):
(data, addr) = sobj.recvfrom(2**20)
rlvl = rslv()
for rlvi in rlvl:
dnsr = forw(rlvi, data)
if (dnsr):
resl = answ(dnsr)
if (len(resl) > 0):
for resi in resl:
dnsq = re.sub("[^0-9A-Za-z\.\-]", "", resi[0])
dnsa = re.sub("[^0-9\.]", "", resi[1])
epch = str(int(time.time()))
dbdl = sqle("SELECT * FROM data WHERE name = '%s' AND addr = '%s';" % (dnsq, dnsa), outp=True)
if (len(dbdl) < 1):
sqle("INSERT INTO data VALUES ('%s', '%s', '0');" % (dnsq, dnsa))
sqle("UPDATE data SET time = '%s' WHERE name = '%s' AND addr = '%s';" % (epch, dnsq, dnsa))
filt()
dbdl = cche(dnsq)
if (len(dbdl) > 0):
debg(addr[0], resl[0][0], dbdl[0][1])
#a=open("a","w");a.write(dnsr);a.close();
dnsr = shim(dnsr, dbdl)
#b=open("b","w");b.write(dnsr);b.close();
sobj.sendto(dnsr, addr)
break
def priv():
# get username & os.setgid() & os.setuid()
pass
if (__name__ == "__main__"):
sqle("CREATE TABLE data (name VARCHAR(256), addr VARCHAR(256), time VARCHAR(256));")
priv()
serv()

