In [ ]:
import struct, os
from Crypto.Cipher import AES

In [6]:
DNS_URL_POSTFIX = "dnsr.uk.to"
CODING_TABLE    = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
#CODING_TABLE    = random.shuffle( CODING_TABLE )
ct = {}
for i,c in enumerate(CODING_TABLE.upper()):
    ct[c] = i
SECRET_KEY = bytes(range(32)) #Not so secret anymore ^^
#SECRET_KEY = os.urandom(32)   #That's better.

Add this to secret.h


In [23]:
print('#ifndef SECRETS_H_')
print('#define SECRETS_H_')
print('#define SECRET_KEY_256 { \\\n\t', end='')
for i, c in enumerate(SECRET_KEY):
    if i > 0 and (i % 8) == 0:
        print('\\\n\t', end='')
    print( '0x{0:02x}, '.format(c), end='')
print('\\\n}')
print('#define CODING_TABLE \"', end='')
print( CODING_TABLE, end='')
print('"')
print('#endif /* SECRETS_H_ */')


#ifndef SECRETS_H_
#define SECRETS_H_
#define SECRET_KEY_256 { \
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, \
	0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, \
	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, \
	0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, \
}
#define CODING_TABLE "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
#endif /* SECRETS_H_ */

This is where the magic happens


In [81]:
def hexdump( res ):
    for i,b in enumerate(res):
        if( len(res)>16 and (i%16)==0 ):
            print( "\n  {:04x}: ".format(i), end="" )
        print( "{:02x} ".format(b), end="" )

class Crc16:
    def __init__( self ):
        self._resetRunnningCRC()
    def _resetRunnningCRC( self ):
        self.c=0xFFFF
    def _runningCRC( self, inputByte ):
        self.c ^= inputByte
        self.c &= 0xFFFF
        for b in range(8):             # For each bit in the byte
            if self.c & 1:
                self.c = (self.c >> 1) ^ 0xA001
            else: 
                self.c = (self.c >> 1)
            self.c &= 0xFFFF
    def getCrc( self, dataBytes ):
        self._resetRunnningCRC()
        for b in dataBytes:
            self._runningCRC( b )
        return self.c

class DnsDecoder:
    def __init__( self, SECRET_KEY, SUBDOMAIN, CODING_TABLE ):
        self.CODING_TABLE = CODING_TABLE
        self.SUBDOMAIN = SUBDOMAIN
        self.decodingTable = {}
        for i,c in enumerate(CODING_TABLE.upper()):
            self.decodingTable[c] = i
        self.cipher = AES.new( SECRET_KEY, AES.MODE_ECB )
        self.crc = Crc16()
    
    def _decodeBlock( self, messageBlock ):
        """ returns a bytes object """
        res = 0
        for i,c in enumerate(messageBlock):
            res |= self.decodingTable[c]<<(i*5)
        return res.to_bytes( 16, 'little' )
    
    def dnsDecode( self, qnString ):
        """ decode URL string and return payload as bytes """
        if not qnString.endswith( self.SUBDOMAIN ):
            raise RuntimeError("Bad hostname: " + qnString)
        messageBlocks = qnString.replace( self.SUBDOMAIN, "" )
        resultBytes = bytearray()
        for messageBlock in messageBlocks.split("."):
            messageBlock.upper()
            resultBytes += self.cipher.decrypt( self._decodeBlock(messageBlock) )
        plCrc = resultBytes[-2]<<8 | resultBytes[-1]
        plLength = resultBytes[-3]
        resultBytes = resultBytes[:plLength]
        if self.crc.getCrc( resultBytes ) != plCrc:
            raise RuntimeError("CRC error")
        return resultBytes

In [82]:
dc = DnsDecoder( SECRET_KEY, ".dnsr.uk.to.", CODING_TABLE )

In [84]:
dc.dnsDecode("WBZZBT5EFY63RPAPOBK6KDWWAC.Q5XTF7PHP3ZI5HZZGM4NSWH2PG.NOO2UQKJ2EKPKZAUFIMKSRDCZC.2KF5E57JJTDJO3H5GQF3W2LYUG.dnsr.uk.to.")


Out[84]:
bytearray(b'Test message with variable payloads and CRC !!!\x00')