Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion chameleonultragui/lib/gui/component/mifare/classic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,35 @@ class CardReaderState extends State<MifareClassicHelper> {
);
} else {
var tags = appState.sharedPreferencesProvider.getCards();
var dumpData = widget.mfcInfo.recovery!.cardData;
// Block 0 (the UID block) can't always be read. If it wasn't, ask before
// rebuilding it from the scanned UID so data is never changed silently.
if (!skipDump && !widget.mfcInfo.recovery!.block0Read) {
final rebuild = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(localizations.block0_read_failed_title),
content: Text(localizations.block0_read_failed_text),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(localizations.cancel),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(localizations.rebuild_uid),
),
],
),
);
if (rebuild == true) {
dumpData = List<Uint8List>.of(dumpData);
dumpData[0] = mfClassicGenerateFirstBlock(
hexToBytes(widget.hfInfo.uid),
hexToBytes(widget.hfInfo.sak)[0],
hexToBytes(widget.hfInfo.atqa));
}
}
tags.add(CardSave(
uid: widget.hfInfo.uid,
sak: hexToBytes(widget.hfInfo.sak)[0],
Expand All @@ -74,7 +103,7 @@ class CardReaderState extends State<MifareClassicHelper> {
tag: (skipDump)
? TagType.mifare1K
: mfClassicGetChameleonTagType(widget.mfcInfo.type),
data: widget.mfcInfo.recovery!.cardData,
data: dumpData,
ats: (widget.hfInfo.ats != localizations.no)
? hexToBytes(widget.hfInfo.ats)
: Uint8List(0)));
Expand Down
8 changes: 4 additions & 4 deletions chameleonultragui/lib/helpers/mifare_classic/general.dart
Original file line number Diff line number Diff line change
Expand Up @@ -577,13 +577,13 @@ Uint8List mfClassicGenerateFirstBlock(Uint8List uid, int sak, Uint8List atqa) {
if (uid.length == 4) {
block0.setAll(0, uid);
block0[4] = calculateBcc(uid);
block0[5] = sak + 0x80;
block0.setAll(6, atqa);
block0[5] = sak;
block0.setAll(6, atqa.reversed);
block0.setAll(8, [0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69]);
} else if (uid.length == 7) {
block0.setAll(0, uid);
block0[7] = sak + 0x80;
block0.setAll(8, atqa);
block0[7] = sak;
block0.setAll(8, atqa.reversed);
block0.setAll(10, [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
}
return block0;
Expand Down
4 changes: 4 additions & 0 deletions chameleonultragui/lib/helpers/mifare_classic/recovery.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class MifareClassicRecovery {
void Function() update;
MifareClassicType mifareClassicType;
bool isMifareClassicEV1;
bool block0Read = false;

MifareClassicRecovery(
{required this.appState,
Expand Down Expand Up @@ -570,6 +571,7 @@ class MifareClassicRecovery {

Future<void> dumpData() async {
cardData = List.generate(256, (_) => Uint8List(0));
block0Read = false;

for (var sector = 0;
sector <
Expand Down Expand Up @@ -601,6 +603,8 @@ class MifareClassicRecovery {
} else {
continue;
}
} else if (sector == 0 && block == 0) {
block0Read = true;
}

if (mfClassicGetSectorTrailerBlockBySector(sector) ==
Expand Down
5 changes: 4 additions & 1 deletion chameleonultragui/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -570,5 +570,8 @@
"lf_sniff_load_file": "Load .bin file",
"lf_sniff_load_failed": "Failed to load file: {error}",
"lf_sniff_loaded": "Loaded {count} sample(s) from file.",
"sniff_device_required_hint": "No device connected. Connect a Chameleon to capture, or load a saved file."
"sniff_device_required_hint": "No device connected. Connect a Chameleon to capture, or load a saved file.",
"block0_read_failed_title": "Couldn't read block 0",
"block0_read_failed_text": "The UID block couldn't be read. Rebuild it from the scan so the clone keeps its UID? Cancel keeps the dump unchanged.",
"rebuild_uid": "Rebuild UID"
}
29 changes: 29 additions & 0 deletions chameleonultragui/test/mifare_classic_block0_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'dart:typed_data';

import 'package:chameleonultragui/helpers/mifare_classic/general.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
group('mfClassicGenerateFirstBlock', () {
test('keeps SAK as-is and stores ATQA reversed (4-byte UID)', () {
// SAK 08 / ATQA 0004 must land as SAK 08 and ATQA bytes 04 00,
// matching the write-side block 0, not 88 00 04.
final block0 = mfClassicGenerateFirstBlock(
Uint8List.fromList([0x82, 0xB9, 0x4F, 0x4B]),
0x08,
Uint8List.fromList([0x00, 0x04]));
expect(block0.length, 16);
expect(block0.sublist(0, 4), [0x82, 0xB9, 0x4F, 0x4B]); // UID
expect(block0[4], 0x3F); // BCC = 0x82 ^ 0xB9 ^ 0x4F ^ 0x4B
expect(block0[5], 0x08); // SAK, not 0x88
expect(block0.sublist(6, 8), [0x04, 0x00]); // ATQA reversed
});

test('a genuine zero UID rebuilds to a zero UID', () {
final block0 = mfClassicGenerateFirstBlock(
Uint8List(4), 0x08, Uint8List.fromList([0x00, 0x04]));
expect(block0.sublist(0, 4), [0, 0, 0, 0]);
expect(block0[4], 0x00); // BCC of an all-zero UID
});
});
}
Loading