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()