@@ -176,7 +176,6 @@ def authenticate_and_menu():
176176 aid_key_type = input (f"Enter AID encryption algorithm (DES, 2TDEA, 3TDEA, AES) (Default: { key_type .upper ()} ): " ).strip () or key_type
177177 aid_key = input (f"Enter AID key (Default: { key } ): " ).strip () or key
178178
179- # Show file menu
180179 aid_file_menu (selected_aid , key_type , key , com_mode , aid_key_type , aid_key )
181180
182181 elif choice == "2" :
@@ -241,7 +240,7 @@ def create_aid(key_type, key, com_mode):
241240 create_command = f"hf mfdes createapp -n 0 --aid { aid } --fid { iso_fid } --dstalgo { dstalgo } -t { key_type } -k { key } -m { com_mode } -a"
242241 response = send_proxmark_command (create_command )
243242 print (response )
244- print ("\n .:: DESFire assigns all-zero keys to new applications by default. Keys can be modified via the main menu. :: .\n " )
243+ print ("\n ⚠️ DESFire assigns all-zero keys to new applications by default. Keys can be modified via the main menu.\n " )
245244
246245def delete_aid (key_type , key , com_mode ):
247246
@@ -276,6 +275,7 @@ def free_memory(key_type, key, com_mode):
276275 print ("❌ Unable to retrieve free memory information." )
277276
278277def change_key (key_type , key , com_mode ):
278+
279279 print ("\n Change Key - Choose Target:" )
280280 print ("1. PICC (Card Master Key)" )
281281 print ("2. Application Key" )
@@ -312,7 +312,7 @@ def change_key(key_type, key, com_mode):
312312
313313 response = send_proxmark_command (changekey_command )
314314 print (response )
315- print ("\n Reauthenticate with the master key." )
315+ print ("\n ⚠️ Reauthenticate with the master key." )
316316 sys .exit ()
317317
318318 elif confirm == "n" :
@@ -321,6 +321,7 @@ def change_key(key_type, key, com_mode):
321321 print ("Invalid input. Please enter 'y' or 'n'." )
322322
323323def list_files (aid , key_type , key , com_mode , aid_key_type , aid_key ):
324+
324325 print ("\n Fetching file list..." )
325326 command = f"hf mfdes getfileids --aid { aid } -t { aid_key_type } -k { aid_key } -m { com_mode } "
326327 response = send_proxmark_command (command )
@@ -361,7 +362,7 @@ def read_file(aid, key_type, key, com_mode, aid_key_type, aid_key):
361362 response = send_proxmark_command (read_command )
362363
363364 if "authenticate error" in response .lower ():
364- print ("\n Authentication Error" )
365+ print ("\n ❌ Authentication Error" )
365366 return None
366367
367368 if text_only :
@@ -401,7 +402,6 @@ def read_file(aid, key_type, key, com_mode, aid_key_type, aid_key):
401402
402403def create_file (aid , key_type , key , com_mode , aid_key_type , aid_key ):
403404
404- # Prompt for file ID in hex format
405405 file_id = input ("Enter file ID (2 hex characters, e.g., 01, 02): " ).strip ()
406406 # Prompt for file size in KB, allowing for decimal values like 0.2KB
407407 file_size_kb_input = input ("Enter file size in KB (e.g., .2 for 0.2KB, 1 for 1KB, 4 for 4KB, 16 for 16KB): " ).strip ()
@@ -429,64 +429,143 @@ def create_file(aid, key_type, key, com_mode, aid_key_type, aid_key):
429429 print (f"File size in hex: { file_size_hex } " )
430430
431431 except ValueError as e :
432- print (f"Invalid file size: { e } " )
432+ print (f"⚠️Invalid file size: { e } " )
433433 return
434434
435435 create_command = f"hf mfdes createfile --aid { aid } --fid { file_id } --isofid { iso_file_id } " \
436436 f"--size { file_size_hex } -t { aid_key_type } -k { aid_key } -m { com_mode } "
437437 response = send_proxmark_command (create_command )
438438 print (response )
439439
440+ def read_long_input (prompt = "" ):
441+
442+ """
443+ Read lengthy user input on POSIX systems.
444+
445+ Linux terminals normally run in canonical mode, where read() is buffered
446+ and limited to ~4096 bytes. To support very long input (large hex strings for DESFire),
447+ ICANON is temporarily disabled so input is passed through immediately without the
448+ kernel line buffer limit.
449+
450+ Windows doesn't use termios, so this function transparently falls back to normal
451+ input() on that platform.
452+ """
453+ if os .name == "posix" :
454+ try :
455+ import termios
456+ except Exception :
457+ return input (prompt )
458+
459+ fd = sys .stdin .fileno ()
460+ old_settings = termios .tcgetattr (fd ) # Save current terminal settings
461+ new_settings = termios .tcgetattr (fd )
462+ new_settings [3 ] &= ~ termios .ICANON # Disable canonical mode
463+ try :
464+ termios .tcsetattr (fd , termios .TCSANOW , new_settings )
465+ return input (prompt )
466+ finally :
467+ # Restore original settings
468+ termios .tcsetattr (fd , termios .TCSANOW , old_settings )
469+ else :
470+ return input (prompt )
471+
440472def write_to_file (aid , key_type , key , com_mode , aid_key_type , aid_key ):
441473
442474 file_id = input ("Enter file ID to write to: " ).strip ()
443475
444- # Get file size
445- file_size_command = f"hf mfdes getfilesettings --aid { aid } --fid { file_id } -t { aid_key_type } -k { aid_key } -m { com_mode } "
476+ file_size_command = (
477+ f"hf mfdes getfilesettings --aid { aid } --fid { file_id } "
478+ f"-t { aid_key_type } -k { aid_key } -m { com_mode } "
479+ )
446480 response = send_proxmark_command (file_size_command )
447481
448- # Check for authentication error
449482 if "authenticate error" in response .lower ():
450- print ("\n Authentication Error" )
483+ print ("\n ❌ Authentication Error" )
451484 return None
452485
453- # Extract the file size from the response
454- file_size_match = re .search (r"File size \(bytes\)... (\d+) / 0x([0-9A-Fa-f]+)" , response )
486+ file_size_match = re .search (
487+ r"File size \(bytes\)... (\d+) / 0x([0-9A-Fa-f]+)" , response
488+ )
455489 if not file_size_match :
456490 print ("❌ Unable to determine file size." )
457491 return
458492
459- file_size = int (file_size_match .group (1 )) # Decimal file size
460-
493+ file_size = int (file_size_match .group (1 ))
461494 print (f"✅ File size detected: { file_size } bytes" )
462495
463- # Prompt user for data format choice (plain text or hex)
464496 while True :
465- data_format = input ("Enter data format (Type 1 for plain text, 2 for hex): " ).strip ()
497+ data_format = input ("Enter data format (1 = plain text, 2 = hex): " ).strip ()
466498
467499 if data_format == "1" :
468- # Text input (no hex)
469- write_data = input (f"Enter text to write (up to { file_size } bytes, no need for hex): " ).strip ()
470- write_data_hex = write_data .encode ().hex ().upper () # Convert to hex
500+ write_data = read_long_input (
501+ f"Enter text to write (up to { file_size } bytes): "
502+ ).strip ()
503+ write_data_hex = write_data .encode ().hex ().upper ()
504+ if len (write_data ) > file_size :
505+ print (f"❌ Data exceeds { file_size } bytes. Try again." )
506+ continue
471507 break
472508
473509 elif data_format == "2" :
474- # Hex input
475- write_data_hex = input (f"Enter hex data to write (up to { file_size * 2 } hex chars): " ).strip ()
476- if len (write_data_hex ) % 2 != 0 : # Ensure it's a valid hex string
477- print ("❌ Invalid hex input. Please enter an even number of characters." )
478- elif len (write_data_hex ) // 2 > file_size :
479- print (f"❌ Data exceeds file size limit of { file_size } bytes. Try again." )
480- else :
481- break
510+ write_data_hex = read_long_input (
511+ f"Enter hex data to write (up to { file_size * 2 } hex chars): "
512+ ).strip ()
513+
514+ if len (write_data_hex ) % 2 != 0 :
515+ print ("❌ Hex must contain an even number of characters." )
516+ continue
517+
518+ if (len (write_data_hex ) // 2 ) > file_size :
519+ print (f"❌ Data exceeds { file_size } bytes. Try again." )
520+ continue
521+ break
522+
482523 else :
483- print ("❌ Invalid choice. Please choose 1 for text or 2 for hex ." )
524+ print ("❌ Invalid choice. Select 1 or 2." )
484525
485- write_command = f"hf mfdes write --aid { aid } --fid { file_id } -t { aid_key_type } -k { aid_key } -d { write_data_hex } -m { com_mode } "
486- response = send_proxmark_command (write_command )
487- print (response )
526+ # Split data into chunks Proxmark3 reliably accepts (128 hex chars = 64 bytes)
527+ chunk_size = 128
528+ chunks = [write_data_hex [i :i + chunk_size ] for i in range (0 , len (write_data_hex ), chunk_size )]
529+
530+ if len (chunks ) > 1 :
531+ print (f"Splitting data into { len (chunks )} chunks (max { chunk_size } hex chars each)" )
532+
533+ for i , chunk in enumerate (chunks ):
534+ offset_bytes = i * (chunk_size // 2 ) # Convert hex char count to bytes
535+
536+ # Make sure card's file size is never exceeded on the last chunk
537+ remaining_bytes = file_size - offset_bytes
538+ if remaining_bytes <= 0 :
539+ break
540+
541+ chunk = chunk [:remaining_bytes * 2 ] # Trim to remaining space
542+
543+ if i == 0 :
544+ # First write: no offset
545+ write_command = (
546+ f"hf mfdes write --aid { aid } --fid { file_id } "
547+ f"-t { aid_key_type } -k { aid_key } -d { chunk } -m { com_mode } "
548+ )
549+ offset_desc = "000000"
550+ else :
551+ # Remaining writes require 3-byte offset (6 hex digits)
552+ offset_hex = f"{ offset_bytes :06X} "
553+ offset_desc = offset_hex
554+ write_command = (
555+ f"hf mfdes write --aid { aid } --fid { file_id } "
556+ f"-t { aid_key_type } -k { aid_key } -d { chunk } -m { com_mode } "
557+ f"--offset { offset_hex } "
558+ )
559+
560+ if len (chunks ) > 1 :
561+ print (f"\n Writing chunk { i + 1 } /{ len (chunks )} (offset { offset_desc } )" )
562+
563+ response = send_proxmark_command (write_command )
564+ time .sleep (0.10 )
565+ print (response )
488566
489567def edit_file_restriction (aid , key_type , key , com_mode , aid_key_type , aid_key ):
568+
490569 while True :
491570 print ("\n NOTE: This only works if you have changed the default keys." )
492571 print ("The Proxmark3 and other tools will automatically attempt to read files using DESFire default keys." )
@@ -529,4 +608,4 @@ def delete_file(aid, key_type, key, com_mode, aid_key_type, aid_key):
529608 print (response )
530609
531610if __name__ == "__main__" :
532- authenticate_and_menu ()
611+ authenticate_and_menu ()
0 commit comments