Skip to content

Commit 7fa548f

Browse files
authored
Merge pull request #3042 from trigat/master
Enhance user prompts and add long input handling
2 parents f0c9779 + cf96e48 commit 7fa548f

File tree

1 file changed

+111
-32
lines changed

1 file changed

+111
-32
lines changed

client/pyscripts/des_talk.py

Lines changed: 111 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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

246245
def 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

278277
def change_key(key_type, key, com_mode):
278+
279279
print("\nChange 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("\nReauthenticate 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

323323
def list_files(aid, key_type, key, com_mode, aid_key_type, aid_key):
324+
324325
print("\nFetching 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("\nAuthentication 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

402403
def 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+
440472
def 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("\nAuthentication 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"\nWriting 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

489567
def edit_file_restriction(aid, key_type, key, com_mode, aid_key_type, aid_key):
568+
490569
while True:
491570
print("\nNOTE: 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

531610
if __name__ == "__main__":
532-
authenticate_and_menu()
611+
authenticate_and_menu()

0 commit comments

Comments
 (0)