#!/usr/bin/env python2
"""
Source: https://blog.nlegall.fr/ecw-data-exfiltration.html
Modified version of StalkR's script from
http://blog.stalkr.net/2010/10/hacklu-ctf-challenge-9-bottle-writeup.html
This version doesn't use any Popen calls, and ignores any errors while decoding
- krx
"""
import zlib
from base64 import b64encode, b64decode, b32encode, b32decode
from string import translate, maketrans
from scapy.all import *
import sys
from base128_iodine import b128encode, b128decode
infile, outfile = "../dns.pcap", "extracted.pcap"
tld = "windowupdate.com."
if (len(sys.argv) <3):
print "Usage: %s <pcap file> <base domain>" % sys.argv[0]
sys.exit(1)
infile = sys.argv[1]
tld = sys.argv[2] + "."
upstream_encoding = 128
# and no downstream encoding (type NULL)
# Translation tables for iodine's encoding
enctrans = {
32: maketrans('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345'),
64: maketrans('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789+')
}
dectrans = {
32: maketrans('ABCDEFGHIJKLMNOPQRSTUVWXYZ012345', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'),
64: maketrans('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789+', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/')
}
# iodine encoders/decoders
encoders = {
32: lambda x: translate(b32encode(x), enctrans[32]),
64: lambda x: translate(b64encode(x), enctrans[64]),
128: b128encode
}
decoders = {
32: lambda x: b32decode(translate(x, dectrans[32])),
64: lambda x: b64decode(translate(x, dectrans[64])),
128: b128decode
}
def encoder(base, encode="", decode=""): # base=[32,64,128]
funcmap, data = (encoders, encode) if len(encode) > 0 else (decoders, decode)
return funcmap[base](data)
def uncompress(s):
try:
return zlib.decompress(s)
except zlib.error:
return False
def b32_8to5(a):
return "abcdefghijklmnopqrstuvwxyz012345".find(a.lower())
def up_header(p):
return {
"userid": int(p[0], 16),
"up_seq": (b32_8to5(p[1]) >> 2) & 7,
"up_frag": ((b32_8to5(p[1]) & 3) << 2) | ((b32_8to5(p[2]) >> 3) & 3),
"dn_seq": (b32_8to5(p[2]) & 7),
"dn_frag": b32_8to5(p[3]) >> 1,
"lastfrag": b32_8to5(p[3]) & 1
}
def dn_header(p):
return {
"compress": ord(p[0]) >> 7,
"up_seq": (ord(p[0]) >> 4) & 7,
"up_frag": ord(p[0]) & 15,
"dn_seq": (ord(p[1]) >> 1) & 15,
"dn_frag": (ord(p[1]) >> 5) & 7,
"lastfrag": ord(p[1]) & 1,
}
# Extract packets from DNS tunnel
# Note: handles fragmentation, but not packet reordering (sequence numbers)
dn_pkt, up_pkt = '', ''
datasent = False
E = []
i = 0
# modified from rdpcap to PcapReader
with PcapReader(infile) as pcap_reader:
for pkt in pcap_reader:
i+=1
if i % 1000 == 0: # Just for progress
print i
if not pkt.haslayer(DNS):
continue
if DNSQR in pkt:
if DNSRR in pkt and len(pkt[DNSRR].rdata) > 0: # downstream/server
d = pkt[DNSRR].rdata
if datasent: # real data and no longer codec/fragment checks
dn_pkt += d[2:]
if dn_header(d)['lastfrag'] and len(dn_pkt) > 0:
u = uncompress(dn_pkt)
if u:
# Include the packet if decoding succeeded,
# ignore it and move on otherwise
E += [IP(u[4:])]
dn_pkt = ''
else: # upstream/client
d = pkt[DNSQR].qname
if d[0].lower() in "0123456789abcdef":
datasent = True
up_pkt += d[5:-len(tld)].replace(".", "")
if up_header(d)['lastfrag'] and len(up_pkt) > 0:
u = uncompress(encoder(upstream_encoding, decode=up_pkt))
if u:
# Include the packet if decoding succeeded,
# ignore it and move on otherwise
E += [IP(u[4:])]
up_pkt = ''
wrpcap(outfile, E)
print "Successfully extracted %i packets into %s" % (len(E), outfile)