Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Toorcon CTF - Triforce
#!/usr/bin/env python
# Hack.lu 2010 CTF - Challenge #9 "Bottle"
# Extract iodine DNS tunnel data
# -- StalkR
from scapy.all import *
from subprocess import Popen,PIPE
input, output = "Triforce.pcap", "test_extracted.cap"
topdomain = ".angler.wetun.nl."
upstream_encoding = 128
# and no downstream encoding (type NULL)
# see encoder.c
def encoder(base,encode="",decode=""): # base=[32,64,128]
p = Popen(["./encoder", str(base), "e" if len(encode)>0 else "d"], stdin=PIPE, stdout=PIPE)
p.stdin.write(encode if len(encode)>0 else decode)
return p.communicate()[0]
# see uncompress.c
def uncompress(s):
p = Popen(["./uncompress"], stdin=PIPE, stdout=PIPE)
p.stdin.write(s)
if p.wait() == 0:
return p.communicate()[0]
else:
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)
p = rdpcap(input)
dn_pkt, up_pkt = '', ''
dnseq, dnfrag='',""
datasent = False
E = []
for i in range(len(p)):
if i in[102]:
up_pkt=''
continue
if not p[i].haslayer(DNS):
continue
if DNSQR in p[i]:
if DNSRR in p[i] and len(p[i][DNSRR].rdata)>0: # downstream/server
d = p[i][DNSRR].rdata
if d[0] == "s":
d = encoder(64, decode=p[i][DNSRR].rdata[1:])
if datasent and not (dn_header(d)["dn_seq"] == dnseq and dn_header(d)["dn_frag"] == dnfrag): # real data and no longer codec/fragment checks
dn_pkt += d[2:].rstrip("\x00")
dnseq = dn_header(d)["dn_seq"]
dnfrag = dn_header(d)["dn_frag"]
if dn_header(d)['lastfrag'] and len(dn_pkt)>0:
try:
u = uncompress(dn_pkt)
except:
print("Failed on packet %i" % i )
dn_pkt=''
continue
if not u:
#raise Exception("Error dn_pkt %i: %r" % (i,dn_pkt))
print("Error on dn pkt %i: %r" % (i,d[:15]+(" [...]" if len(d)>15 else "")))
dn_pkt=''
continue
E += [IP(u[4:])]
dn_pkt = ''
else: # upstream/client
d = p[i][DNSQR].qname
if d[0].lower() in "0123456789abcdef":
datasent = True
up_pkt += d[5:-len(topdomain)].replace(".","")
if up_header(d)['lastfrag'] and len(up_pkt)>0:
u = uncompress(encoder(upstream_encoding,decode=up_pkt))
if not u:
#raise Exception("Error up_pkt %i: %r" % (i,up_pkt))
print("Error on up pkt %i: %r" % (i,d[:30]+(" [...]" if len(d)>30 else "")))
up_pkt=''
continue
E += [IP(u[4:])]
up_pkt = ''
wrpcap(output, E)
print "Successfully extracted %i packets into %s" % (len(E), output)
# vim: sw=2 :
#!/usr/bin/env python2
## Half stolen from https://blog.stalkr.net/2010/10/hacklu-ctf-challenge-9-bottle-writeup.html
## But you had to decrypt both "up" and "down" (up was 128, down was 64)
## The other problem is certain packets were replayed, so you'd need "1 + 2" and you'd really get "1+2+2"
## I just figured out which packets were bad with getHeaders.py and just looking at the down for that period
## So then I would manually ignore the middle packets (since ignoring the ends was more likely to cause issues)
##
## And as an annoying note, if you connected to the iodine server, and then did "http://10.244.244.1:5380/files/"
## it had both flag 1 and 2. So all this reversing wasn't necessary in reality, i could have just reversed a single
## down packet.
##Start of file I actually used
# Hack.lu 2010 CTF - Challenge #9 "Bottle"
# Extract iodine DNS tunnel data
# -- StalkR
from scapy.all import *
from subprocess import Popen,PIPE
input, output = "Triforce.pcap", "extracted.cap"
topdomain = ".angler.wetun.nl."
upstream_encoding = 128
# and no downstream encoding (type NULL)
downstream_encoding = 64
# see encoder.c
def encoder(base,encode="",decode=""): # base=[32,64,128]
p = Popen(["./encoder", str(base), "e" if len(encode)>0 else "d"], stdin=PIPE, stdout=PIPE)
p.stdin.write(encode if len(encode)>0 else decode)
return p.communicate()[0]#.rstrip("\x00")
# see uncompress.c
def uncompress(s):
p = Popen(["./uncompress"], stdin=PIPE, stdout=PIPE)
p.stdin.write(s)
if p.wait() == 0:
return p.communicate()[0]
else:
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)
p = rdpcap(input)
dn_pkt, up_pkt = '', ''
datasent = False
E = []
for i in range(len(p)):
if not p[i].haslayer(DNS):
continue
if i == 97:
dn_pkt=''
if i == 103:
dn_pkt=''
if i in [409, 413, 417, 422, 429, 435, 441, 448, 449, 450, 98]:
continue
if i == 403:
dn_pkt=''
if DNSQR in p[i]:
if DNSRR in p[i] and len(p[i][DNSRR].rdata)>0: # downstream/server
d = p[i][DNSRR].rdata
if d[0] == "s":
d=encoder(64, decode=d[1:])
dn_pkt += d[2:].rstrip("\x00")
if datasent: # real data and no longer codec/fragment checks
if dn_header(d)['lastfrag'] and len(dn_pkt)>0:
#print("Checking dn_pkt %i: %r" % (i,dn_pkt))
try:
u = uncompress(dn_pkt)
except:
print("write error on u on packet %i" % i)
if not u:
#raise Exception("Error dn_pkt %i: %r" % (i,dn_pkt))
print("Error dn_pkt %i:" % i)
dn_pkt = ''
continue
E += [IP(u[4:])]
dn_pkt = ''
else: # upstream/client
d = p[i][DNSQR].qname
if d[0].lower() in "0123456789abcdef":
datasent = True
up_pkt += d[5:-len(topdomain)].replace(".","")
if up_header(d)['lastfrag'] and len(up_pkt)>0:
u = uncompress(encoder(upstream_encoding,decode=up_pkt))
if not u:
#raise Exception("Error up_pkt %i: %r" % (i,up_pkt))
print("Error up_pkt %i: %r %r" % (i,up_pkt, d))
up_pkt = ''
continue
E += [IP(u[4:])]
up_pkt = ''
wrpcap(output, E)
# vim: sw=2 :
#!/usr/bin/env python2
#!/usr/bin/env python
from scapy.all import *
from tunnel import encoder, uncompress, givebin
p = rdpcap('Triforce.pcap')
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,
}
def printup(p):
datasent = False
for i in range(90,120):
if not p[i].haslayer(DNS):
continue
if DNSQR in p[i]:
if DNSRR in p[i] and len(p[i][DNSRR].rdata)>0: # downstream/server
continue
else: # upstream/client
d = p[i][DNSQR].qname
print "U[%i]: %r" % (i,d[:30]+(" [...]" if len(d)>30 else ""))
if d[0].lower() in "0123456789abcdef":
print " %r" % up_header(d)
datasent = True
else:
datasent = False
def printdown(p):
datasent = False
for i in range(390,500):
if not p[i].haslayer(DNS):
continue
if DNSQR in p[i]:
if DNSRR in p[i] and len(p[i][DNSRR].rdata)>0: # downstream/server
d = p[i][DNSRR].rdata
if d[0] == "s":
d = encoder(64, decode=p[i][DNSRR].rdata[1:])
datasent=True
else:
datasent=False
print "D[%i]: %r" % (i,d[:30]+(" [...]" if len(d)>30 else ""))
if datasent:
print " %r" % dn_header(d)
else: # upstream/client
d = p[i][DNSQR].qname
#print "U[%i]: %r" % (i,d[:30]+(" [...]" if len(d)>30 else ""))
if d[0].lower() in "0123456789abcdef":
#print " %r" % up_header(d)
datasent = True
else:
datasent = False
printup(p)
@SwissKid

This comment has been minimized.

Copy link
Owner Author

@SwissKid SwissKid commented Sep 3, 2017

Note, in retrospect, I could have done better things than blanking dn_pkt for 103/97/403, probably just needed an ignore on the earlier list. but if you run this against the competition PCAP, it'll give you the full iodine pcap

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment