diff --git a/chameleonultragui/lib/bridge/chameleon.dart b/chameleonultragui/lib/bridge/chameleon.dart index 5160435d..f8ad0d84 100644 --- a/chameleonultragui/lib/bridge/chameleon.dart +++ b/chameleonultragui/lib/bridge/chameleon.dart @@ -8,13 +8,13 @@ import 'package:logger/logger.dart'; enum ChameleonCommand { // basic commands getAppVersion(1000), - changeMode(1001), + changeDeviceMode(1001), getDeviceMode(1002), - getGitCommitHash(1017), + getGitVersion(1017), getBatteryCharge(1025), // slot - setSlotActivated(1003), + setActiveSlot(1003), setSlotTagType(1004), setSlotDataDefault(1005), setSlotEnable(1006), @@ -63,13 +63,14 @@ enum ChameleonCommand { scan14ATag(2000), mf1SupportDetect(2001), mf1NTLevelDetect(2002), - mf1DarksideDetect(2003), + mf1StaticNestedAcquire(2003), mf1DarksideAcquire(2004), mf1NTDistanceDetect(2005), mf1NestedAcquire(2006), mf1CheckKey(2007), mf1ReadBlock(2008), mf1WriteBlock(2009), + hf14ARawCommand(2010), // lf commands scanEM410Xtag(3000), @@ -109,14 +110,14 @@ enum ChameleonCommand { enum TagType { unknown(0), - em410X(1), - mifareMini(2), - mifare1K(3), - mifare2K(4), - mifare4K(5), - ntag213(6), - ntag215(7), - ntag216(8); + em410X(100), + mifareMini(1000), + mifare1K(1001), + mifare2K(1002), + mifare4K(1003), + ntag213(1100), + ntag215(1101), + ntag216(1102); const TagType(this.value); final int value; @@ -172,8 +173,13 @@ class CardData { Uint8List uid; int sak; Uint8List atqa; + Uint8List ats; - CardData({required this.uid, required this.sak, required this.atqa}); + CardData( + {required this.uid, + required this.sak, + required this.atqa, + required this.ats}); } class ChameleonMessage { @@ -185,7 +191,7 @@ class ChameleonMessage { {required this.command, required this.status, required this.data}); } -enum NTLevel { weak, static, hard, unknown } +enum NTLevel { static, weak, hard, unknown } enum DarksideResult { vulnerable, @@ -408,10 +414,16 @@ class ChameleonCommunicator { return bytes.buffer.asByteData().getInt16(0, Endian.big); } - Future getFirmwareVersion() async { + Future<(bool, int)> getFirmwareVersion() async { var resp = await sendCmd(ChameleonCommand.getAppVersion); if (resp!.data.length != 2) throw ("Invalid data length"); - return (resp.data[1] << 8) | resp.data[0]; + + // Check for legacy protocol + if (resp.data[0] == 0 && resp.data[1] == 1) { + return (true, 256); + } else { + return (false, bytesToU16(resp.data)); + } } Future getDeviceChipID() async { @@ -421,8 +433,7 @@ class ChameleonCommunicator { Future getDeviceBLEAddress() async { var resp = await sendCmd(ChameleonCommand.getDeviceBLEAddress); - return bytesToHexSpace(Uint8List.fromList(resp!.data.reversed.toList())) - .replaceAll(" ", ":"); + return bytesToHexSpace(resp!.data).replaceAll(" ", ":"); } Future isReaderDeviceMode() async { @@ -432,7 +443,7 @@ class ChameleonCommunicator { } Future setReaderDeviceMode(bool readerMode) async { - await sendCmd(ChameleonCommand.changeMode, + await sendCmd(ChameleonCommand.changeDeviceMode, data: Uint8List.fromList([readerMode ? 1 : 0])); } @@ -440,11 +451,15 @@ class ChameleonCommunicator { var resp = await sendCmd(ChameleonCommand.scan14ATag); if (resp!.data.isNotEmpty) { + int uidLength = resp.data[0]; + int atsLength = resp.data[uidLength + 4]; return CardData( - uid: resp.data.sublist(0, resp.data[10]), - sak: resp.data[12], - atqa: - Uint8List.fromList(resp.data.sublist(13, 15).reversed.toList())); + uid: resp.data.sublist(1, uidLength + 1), + atqa: Uint8List.fromList( + resp.data.sublist(uidLength + 1, uidLength + 3).reversed.toList()), + sak: resp.data[uidLength + 3], + ats: resp.data.sublist(uidLength + 5, uidLength + 5 + atsLength), + ); } else { throw ("Invalid data length"); } @@ -459,12 +474,12 @@ class ChameleonCommunicator { Future getMf1NTLevel() async { // Get level of nt (weak/static/hard) in Mifare Classic - var resp = (await sendCmd(ChameleonCommand.mf1NTLevelDetect))!.status; - if (resp == 0x00) { - return NTLevel.weak; - } else if (resp == 0x24) { + var resp = (await sendCmd(ChameleonCommand.mf1NTLevelDetect))!.data[0]; + if (resp == 0) { return NTLevel.static; - } else if (resp == 0x25) { + } else if (resp == 1) { + return NTLevel.weak; + } else if (resp == 2) { return NTLevel.hard; } else { return NTLevel.unknown; @@ -473,18 +488,20 @@ class ChameleonCommunicator { Future checkMf1Darkside() async { // Check card vulnerability to Mifare Classic darkside attack - int status = (await sendCmd(ChameleonCommand.mf1DarksideDetect, - timeout: const Duration(seconds: 20)))! - .status; + var status = (await sendCmd(ChameleonCommand.mf1DarksideAcquire, + data: Uint8List.fromList([0, 0, 1, 15]), + timeout: const Duration(seconds: 30)))! + .data[0]; + if (status == 0) { return DarksideResult.vulnerable; - } else if (status == 0x20) { + } else if (status == 1) { return DarksideResult.cantFixNT; - } else if (status == 0x21) { + } else if (status == 2) { return DarksideResult.luckAuthOK; - } else if (status == 0x22) { + } else if (status == 3) { return DarksideResult.notSendingNACK; - } else if (status == 0x23) { + } else if (status == 4) { return DarksideResult.tagChanged; } else { return DarksideResult.fixed; @@ -511,23 +528,36 @@ class ChameleonCommunicator { } Future getMf1NestedNonces(int block, int keyType, - Uint8List keyKnown, int targetBlock, int targetKeyType) async { + Uint8List keyKnown, int targetBlock, int targetKeyType, + {bool isStaticNested = false}) async { // Collect nonces for nested attack // keyType 0x60 if A key, 0x61 B key - int i = 0; - var resp = await sendCmd(ChameleonCommand.mf1NestedAcquire, + int i = isStaticNested ? 4 : 0; + var resp = await sendCmd( + isStaticNested + ? ChameleonCommand.mf1StaticNestedAcquire + : ChameleonCommand.mf1NestedAcquire, data: Uint8List.fromList( [keyType, block, ...keyKnown, targetKeyType, targetBlock]), timeout: const Duration(seconds: 30)); var nonces = NestedNonces(nonces: []); while (i < resp!.data.length) { - nonces.nonces.add(NestedNonce( - nt: bytesToU32(resp.data.sublist(i, i + 4)), - ntEnc: bytesToU32(resp.data.sublist(i + 4, i + 8)), - parity: resp.data[i + 8])); - - i += 9; + if (isStaticNested) { + nonces.nonces.add(NestedNonce( + nt: bytesToU32(resp.data.sublist(i, i + 4)), + ntEnc: bytesToU32(resp.data.sublist(i + 4, i + 8)), + parity: 0)); + + i += 8; + } else { + nonces.nonces.add(NestedNonce( + nt: bytesToU32(resp.data.sublist(i, i + 4)), + ntEnc: bytesToU32(resp.data.sublist(i + 4, i + 8)), + parity: resp.data[i + 8])); + + i += 9; + } } return nonces; @@ -542,7 +572,13 @@ class ChameleonCommunicator { [targetKeyType, targetBlock, firstRecover ? 1 : 0, syncMax]), timeout: const Duration(seconds: 30)); - if (resp!.data.length != 32) { + if (resp!.data[0] != 0) { + throw ("Not vulnerable to Darkside"); + } + + resp.data = resp.data.sublist(1); + + if (resp.data.length != 32) { throw ("Invalid data length"); } @@ -582,23 +618,23 @@ class ChameleonCommunicator { Future activateSlot(int slot) async { // Slot 0-7 - await sendCmd(ChameleonCommand.setSlotActivated, + await sendCmd(ChameleonCommand.setActiveSlot, data: Uint8List.fromList([slot])); } Future setSlotType(int slot, TagType type) async { await sendCmd(ChameleonCommand.setSlotTagType, - data: Uint8List.fromList([slot, type.value])); + data: Uint8List.fromList([slot, ...u16ToBytes(type.value)])); } Future setDefaultDataToSlot(int slot, TagType type) async { await sendCmd(ChameleonCommand.setSlotDataDefault, - data: Uint8List.fromList([slot, type.value])); + data: Uint8List.fromList([slot, ...u16ToBytes(type.value)])); } - Future enableSlot(int slot, bool status) async { + Future enableSlot(int slot, TagFrequency frequency, bool status) async { await sendCmd(ChameleonCommand.setSlotEnable, - data: Uint8List.fromList([slot, status ? 1 : 0])); + data: Uint8List.fromList([slot, frequency.value, status ? 1 : 0])); } Future isMf1DetectionMode() async { @@ -614,7 +650,7 @@ class ChameleonCommunicator { Future getMf1DetectionCount() async { var resp = await sendCmd(ChameleonCommand.mf1GetDetectionCount); - return resp!.data.buffer.asByteData().getInt16(0, Endian.little); + return resp!.data.buffer.asByteData().getInt32(0, Endian.big); } Future>>>> @@ -675,8 +711,14 @@ class ChameleonCommunicator { Future setMf1AntiCollision(CardData card) async { await sendCmd(ChameleonCommand.mf1SetAntiCollision, - data: - Uint8List.fromList([card.sak, ...card.atqa.reversed, ...card.uid])); + data: Uint8List.fromList([ + card.uid.length, + ...card.uid, + ...card.atqa.reversed, + card.sak, + card.ats.length, + ...card.ats + ])); } Future readEM410X() async { @@ -747,7 +789,7 @@ class ChameleonCommunicator { } Future getGitCommitHash() async { - var resp = await sendCmd(ChameleonCommand.getGitCommitHash); + var resp = await sendCmd(ChameleonCommand.getGitVersion); return const AsciiDecoder().convert(resp!.data); } @@ -759,11 +801,15 @@ class ChameleonCommunicator { Future> getUsedSlots() async { List<(TagType, TagType)> tags = []; var resp = await sendCmd(ChameleonCommand.getSlotInfo); - for (var i = 0; i < 8; i++) { + var index = 0; + for (var slot = 0; slot < 8; slot++) { tags.add(( - numberToChameleonTag(resp!.data[(i * 2)]), - numberToChameleonTag(resp.data[(i * 2) + 1]) + numberToChameleonTag(bytesToU16(resp!.data.sublist(index, index + 2))), + numberToChameleonTag( + bytesToU16(resp.data.sublist(index + 2, index + 4))), )); + + index += 4; } return tags; } @@ -777,7 +823,7 @@ class ChameleonCommunicator { mode = MifareClassicWriteMode.denied; } else if (resp.data[4] == 2) { mode = MifareClassicWriteMode.deceive; - } else if (resp.data[4] == 3) { + } else if (resp.data[4] == 3 || resp.data[4] == 4) { mode = MifareClassicWriteMode.shadow; } return ( @@ -841,12 +887,12 @@ class ChameleonCommunicator { data: Uint8List.fromList([mode.value])); } - Future> getEnabledSlots() async { + Future> getEnabledSlots() async { var resp = await sendCmd(ChameleonCommand.getEnabledSlots); - if (resp!.data.length != 8) throw ("Invalid data length"); - List slots = []; + if (resp!.data.length != 16) throw ("Invalid data length"); + List<(bool, bool)> slots = []; for (var slot = 0; slot < 8; slot++) { - slots.add(resp.data[slot] != 0); + slots.add((resp.data[slot * 2] != 0, resp.data[slot * 2 + 1] != 0)); } return slots; } @@ -920,7 +966,7 @@ class ChameleonCommunicator { } Future getDeviceType() async { - return (await sendCmd(ChameleonCommand.getDeviceType))!.data[0] == 1 + return (await sendCmd(ChameleonCommand.getDeviceType))!.data[0] == 0 ? ChameleonDevice.ultra : ChameleonDevice.lite; } @@ -931,15 +977,19 @@ class ChameleonCommunicator { .data; } - Future mf1GetAntiCollData(int startBlock, int blockCount) async { + Future mf1GetAntiCollData() async { var resp = await sendCmd(ChameleonCommand.mf1GetAntiCollData); if (resp!.data.isNotEmpty) { + int uidLength = resp.data[0]; + int atsLength = resp.data[uidLength + 4]; return CardData( - uid: resp.data.sublist(0, resp.data[10]), - sak: resp.data[12], - atqa: - Uint8List.fromList(resp.data.sublist(13, 15).reversed.toList())); + uid: resp.data.sublist(1, uidLength + 1), + atqa: Uint8List.fromList( + resp.data.sublist(uidLength + 1, uidLength + 3).reversed.toList()), + sak: resp.data[uidLength + 3], + ats: resp.data.sublist(uidLength + 5, uidLength + 5 + atsLength), + ); } else { throw ("Invalid data length"); } @@ -980,4 +1030,15 @@ class ChameleonCommunicator { utf8.decode(resp.sublist(7, 13), allowMalformed: true) ); } + + Future> getDeviceCapabilities() async { + var resp = (await sendCmd(ChameleonCommand.getDeviceCapabilities))!.data; + List commands = []; + + for (int i = 0; i < resp.length; i += 2) { + commands.add(bytesToU16(resp.sublist(i, i + 2))); + } + + return commands; + } } diff --git a/chameleonultragui/lib/gui/menu/card_edit.dart b/chameleonultragui/lib/gui/menu/card_edit.dart index 96076384..b1a4a6c7 100644 --- a/chameleonultragui/lib/gui/menu/card_edit.dart +++ b/chameleonultragui/lib/gui/menu/card_edit.dart @@ -24,10 +24,12 @@ class CardEditMenuState extends State { String uid = ""; int sak = 0; Uint8List atqa = Uint8List.fromList([]); + Uint8List ats = Uint8List.fromList([]); TextEditingController uidController = TextEditingController(); - TextEditingController sak4Controller = TextEditingController(); - TextEditingController atqa4Controller = TextEditingController(); + TextEditingController sakController = TextEditingController(); + TextEditingController atqaController = TextEditingController(); TextEditingController nameController = TextEditingController(); + TextEditingController atsController = TextEditingController(); Color pickerColor = Colors.deepOrange; Color currentColor = Colors.deepOrange; final GlobalKey _formKey = GlobalKey(); @@ -38,11 +40,13 @@ class CardEditMenuState extends State { selectedType = widget.tagSave.tag; uid = widget.tagSave.uid; sak = widget.tagSave.sak; + ats = widget.tagSave.ats; atqa = Uint8List.fromList(widget.tagSave.atqa); uidController = TextEditingController(text: uid); - sak4Controller = + sakController = TextEditingController(text: bytesToHexSpace(Uint8List.fromList([sak]))); - atqa4Controller = TextEditingController(text: bytesToHexSpace(atqa)); + atqaController = TextEditingController(text: bytesToHexSpace(atqa)); + atsController = TextEditingController(text: bytesToHexSpace(ats)); nameController = TextEditingController(text: widget.tagSave.name); pickerColor = widget.tagSave.color; currentColor = widget.tagSave.color; @@ -156,21 +160,23 @@ class CardEditMenuState extends State { controller: uidController, decoration: InputDecoration( labelText: localizations.uid, - hintText: localizations.enter_something("UID")), + hintText: + localizations.enter_something(localizations.uid)), validator: (value) { if (value == null || value.isEmpty) { - return localizations.please_enter_something("UID"); + return localizations + .please_enter_something(localizations.uid); } if (!(value.replaceAll(" ", "").length == 14 || value.replaceAll(" ", "").length == 8) && chameleonTagToFrequency(selectedType) != TagFrequency.lf) { - return localizations.must_or(4, 7, "UID"); + return localizations.must_or(4, 7, localizations.uid); } if (value.replaceAll(" ", "").length != 10 && chameleonTagToFrequency(selectedType) == TagFrequency.lf) { - return localizations.must_be(5, "UID"); + return localizations.must_be(5, localizations.uid); } return null; }, @@ -180,21 +186,23 @@ class CardEditMenuState extends State { visible: chameleonTagToFrequency(selectedType) != TagFrequency.lf, child: TextFormField( - controller: sak4Controller, + controller: sakController, decoration: InputDecoration( labelText: localizations.sak, - hintText: localizations.enter_something("SAK")), + hintText: + localizations.enter_something(localizations.sak)), validator: (value) { if (value == null || value.isEmpty && chameleonTagToFrequency(selectedType) != TagFrequency.lf) { - return localizations.please_enter_something("SAK"); + return localizations + .please_enter_something(localizations.sak); } if (value.replaceAll(" ", "").length != 2 && chameleonTagToFrequency(selectedType) != TagFrequency.lf) { - return localizations.must_be(1, "SAK"); + return localizations.must_be(1, localizations.sak); } return null; }, @@ -205,26 +213,40 @@ class CardEditMenuState extends State { visible: chameleonTagToFrequency(selectedType) != TagFrequency.lf, child: TextFormField( - controller: atqa4Controller, + controller: atqaController, decoration: InputDecoration( labelText: localizations.atqa, - hintText: localizations.enter_something("ATQA")), + hintText: + localizations.enter_something(localizations.atqa)), validator: (value) { if (value == null || value.isEmpty && chameleonTagToFrequency(selectedType) != TagFrequency.lf) { - return localizations.please_enter_something("ATQA"); + return localizations + .please_enter_something(localizations.atqa); } if (value.replaceAll(" ", "").length != 4 && chameleonTagToFrequency(selectedType) != TagFrequency.lf) { - return localizations.must_be(2, "ATQA"); + return localizations.must_be(2, localizations.atqa); } return null; }, ), ), + const SizedBox(height: 20), + Visibility( + visible: + chameleonTagToFrequency(selectedType) != TagFrequency.lf, + child: TextFormField( + controller: atsController, + decoration: InputDecoration( + labelText: localizations.ats, + hintText: + localizations.enter_something(localizations.ats)), + ), + ), const SizedBox(height: 40), ]), @@ -257,17 +279,17 @@ class CardEditMenuState extends State { } var tag = CardSave( - id: widget.tagSave.uid, - name: nameController.text, - sak: chameleonTagToFrequency(selectedType) == TagFrequency.lf - ? widget.tagSave.sak - : hexToBytes(sak4Controller.text.replaceAll(" ", ""))[0], - atqa: hexToBytes(atqa4Controller.text.replaceAll(" ", "")), - uid: uidController.text, - tag: selectedType, - data: widget.tagSave.data, - color: currentColor, - ); + id: widget.tagSave.uid, + name: nameController.text, + sak: chameleonTagToFrequency(selectedType) == TagFrequency.lf + ? widget.tagSave.sak + : hexToBytes(sakController.text.replaceAll(" ", ""))[0], + atqa: hexToBytes(atqaController.text.replaceAll(" ", "")), + uid: uidController.text, + tag: selectedType, + data: widget.tagSave.data, + color: currentColor, + ats: hexToBytes(atsController.text.replaceAll(" ", ""))); var tags = appState.sharedPreferencesProvider.getCards(); List output = []; diff --git a/chameleonultragui/lib/gui/menu/slot_settings.dart b/chameleonultragui/lib/gui/menu/slot_settings.dart index 8a845969..596be700 100644 --- a/chameleonultragui/lib/gui/menu/slot_settings.dart +++ b/chameleonultragui/lib/gui/menu/slot_settings.dart @@ -67,7 +67,8 @@ class SlotSettingsState extends State { if (!isRun) { await appState.communicator!.activateSlot(widget.slot); - isEnabled = (await appState.communicator!.getEnabledSlots())[widget.slot]; + var enabledSlots = await appState.communicator!.getEnabledSlots(); + isEnabled = enabledSlots[widget.slot].$1 || enabledSlots[widget.slot].$2; var data = (await appState.communicator!.getMf1EmulatorConfig()); isDetection = data.$1; if (isDetection) { @@ -176,8 +177,10 @@ class SlotSettingsState extends State { items: [localizations.enabled, localizations.disabled], selectedValue: isEnabled ? 0 : 1, onChange: (int index) async { - await appState.communicator! - .enableSlot(widget.slot, index == 0 ? true : false); + await appState.communicator!.enableSlot(widget.slot, + TagFrequency.hf, index == 0 ? true : false); + await appState.communicator!.enableSlot(widget.slot, + TagFrequency.lf, index == 0 ? true : false); widget.refresh(widget.slot); }), diff --git a/chameleonultragui/lib/gui/page/debug.dart b/chameleonultragui/lib/gui/page/debug.dart index 6479d3f2..2998b89c 100644 --- a/chameleonultragui/lib/gui/page/debug.dart +++ b/chameleonultragui/lib/gui/page/debug.dart @@ -165,6 +165,56 @@ class DebugPage extends StatelessWidget { ]), ), const SizedBox(height: 10), + ElevatedButton( + onPressed: () async { + await appState.communicator!.setReaderDeviceMode(true); + var distance = await appState.communicator!.getMf1NTDistance( + 50, + 0x60, + Uint8List.fromList([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])); + bool found = false; + for (var i = 0; i < 0xFF && !found; i++) { + var nonces = await appState.communicator! + .getMf1NestedNonces( + 50, + 0x60, + Uint8List.fromList( + [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]), + 0, + 0x61, + isStaticNested: true); + var nested = StaticNestedDart( + uid: distance.uid, + keyType: 0x61, + nt0: nonces.nonces[0].nt, + nt0Enc: nonces.nonces[0].ntEnc, + nt1: nonces.nonces[1].nt, + nt1Enc: nonces.nonces[1].ntEnc); + + var keys = await recovery.static_nested(nested); + if (keys.isNotEmpty) { + appState.log!.d("Found keys: $keys. Checking them..."); + for (var key in keys) { + var keyBytes = u64ToBytes(key); + if ((await appState.communicator! + .mf1Auth(0x03, 0x61, keyBytes.sublist(2, 8))) == + true) { + appState.log!.i( + "Found valid key! Key ${bytesToHex(keyBytes.sublist(2, 8))}"); + found = true; + break; + } + } + } else { + appState.log!.d("Can't find keys, retrying..."); + } + } + }, + child: Column(children: [ + Text(localizations.static_nested_attack), + ]), + ), + const SizedBox(height: 10), ElevatedButton( onPressed: () async { await appState.communicator!.setReaderDeviceMode(true); diff --git a/chameleonultragui/lib/gui/page/home.dart b/chameleonultragui/lib/gui/page/home.dart index 64067bf2..ae12cf5b 100644 --- a/chameleonultragui/lib/gui/page/home.dart +++ b/chameleonultragui/lib/gui/page/home.dart @@ -20,7 +20,8 @@ class HomePage extends StatefulWidget { } class HomePageState extends State { - var selectedSlot = 1; + int selectedSlot = 1; + bool isLegacyFirmware = false; @override void initState() { @@ -94,8 +95,9 @@ class HomePageState extends State { Future> getVersion() async { var appState = context.read(); String commitHash = ""; - String firmwareVersion = - numToVerCode(await appState.communicator!.getFirmwareVersion()); + var firmware = await appState.communicator!.getFirmwareVersion(); + isLegacyFirmware = firmware.$1; + String firmwareVersion = numToVerCode(firmware.$2); try { commitHash = await appState.communicator!.getGitCommitHash(); @@ -109,6 +111,58 @@ class HomePageState extends State { } } + if (context.mounted && isLegacyFirmware) { + var localizations = AppLocalizations.of(context)!; + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text(localizations.outdated_protocol), + content: SingleChildScrollView( + child: ListBody( + children: [ + Text(localizations.outdated_protocol_description_1), + Text(localizations.outdated_protocol_description_2), + Text(localizations.outdated_protocol_description_3), + ], + ), + ), + actions: [ + TextButton( + child: Text(localizations.update), + onPressed: () async { + Navigator.of(context).pop(); + var localizations = AppLocalizations.of(context)!; + var scaffoldMessenger = ScaffoldMessenger.of(context); + var snackBar = SnackBar( + content: Text(localizations.downloading_fw( + chameleonDeviceName(appState.connector!.device))), + action: SnackBarAction( + label: localizations.close, + onPressed: () { + scaffoldMessenger.hideCurrentSnackBar(); + }, + ), + ); + + scaffoldMessenger.showSnackBar(snackBar); + await flashFirmware(appState, + scaffoldMessenger: scaffoldMessenger); + }, + ), + TextButton( + child: Text(localizations.skip), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + } + return ["$firmwareVersion ($commitHash)", commitHash]; } diff --git a/chameleonultragui/lib/gui/page/read_card.dart b/chameleonultragui/lib/gui/page/read_card.dart index cdcef294..a9bc86c4 100644 --- a/chameleonultragui/lib/gui/page/read_card.dart +++ b/chameleonultragui/lib/gui/page/read_card.dart @@ -41,6 +41,7 @@ class HFCardInfo { String sak; String atqa; String tech; + String ats; bool cardExist; HFCardInfo( @@ -48,6 +49,7 @@ class HFCardInfo { this.sak = '', this.atqa = '', this.tech = '', + this.ats = '', this.cardExist = true}); } @@ -123,8 +125,11 @@ class ReadCardPageState extends State { } CardData card = await appState.communicator!.scan14443aTag(); - bool isMifare = await appState.communicator!.detectMf1Support(); - bool isMifareClassicEV1 = isMifare + bool isMifareClassic = false; + try { + isMifareClassic = await appState.communicator!.detectMf1Support(); + } catch (_) {} + bool isMifareClassicEV1 = isMifareClassic ? (await appState.communicator! .mf1Auth(0x45, 0x61, gMifareClassicKeys[3])) : false; @@ -133,14 +138,15 @@ class ReadCardPageState extends State { hfInfo.uid = bytesToHexSpace(card.uid); hfInfo.sak = card.sak.toRadixString(16).padLeft(2, '0').toUpperCase(); hfInfo.atqa = bytesToHexSpace(card.atqa); + hfInfo.ats = (card.ats.isNotEmpty) ? bytesToHexSpace(card.ats) : "No"; mfcInfo.isEV1 = isMifareClassicEV1; - mfcInfo.type = isMifare + mfcInfo.type = isMifareClassic ? mfClassicGetType(card.atqa, card.sak) : MifareClassicType.none; mfcInfo.state = (mfcInfo.type != MifareClassicType.none) ? MifareClassicState.checkKeys : MifareClassicState.none; - hfInfo.tech = isMifare + hfInfo.tech = isMifareClassic ? "Mifare Classic ${mfClassicGetName(mfcInfo.type)}${isMifareClassicEV1 ? " EV1" : ""}" : "Other"; }); @@ -263,12 +269,13 @@ class ReadCardPageState extends State { }); var prng = await appState.communicator!.getMf1NTLevel(); - if (prng != NTLevel.weak) { - // No hardnested/staticnested implementation yet + if (prng == NTLevel.hard) { + // No hardnested implementation yet setState(() { mfcInfo.recovery.error = localizations.recovery_error_no_supported; mfcInfo.state = MifareClassicState.recovery; }); + return; } @@ -306,23 +313,45 @@ class ReadCardPageState extends State { validKeyBlock, 0x60 + validKeyType, validKey); bool found = false; for (var i = 0; i < 0xFF && !found; i++) { - var nonces = await appState.communicator!.getMf1NestedNonces( - validKeyBlock, - 0x60 + validKeyType, - validKey, - mfClassicGetSectorTrailerBlockBySector(sector), - 0x60 + keyType); - var nested = NestedDart( + List keys = []; + if (prng == NTLevel.weak) { + var nonces = await appState.communicator!.getMf1NestedNonces( + validKeyBlock, + 0x60 + validKeyType, + validKey, + mfClassicGetSectorTrailerBlockBySector(sector), + 0x60 + keyType); + var nested = NestedDart( + uid: distance.uid, + distance: distance.distance, + nt0: nonces.nonces[0].nt, + nt0Enc: nonces.nonces[0].ntEnc, + par0: nonces.nonces[0].parity, + nt1: nonces.nonces[1].nt, + nt1Enc: nonces.nonces[1].ntEnc, + par1: nonces.nonces[1].parity); + + keys = await recovery.nested(nested); + } else if (prng == NTLevel.static) { + var nonces = await appState.communicator!.getMf1NestedNonces( + validKeyBlock, + 0x60 + validKeyType, + validKey, + mfClassicGetSectorTrailerBlockBySector(sector), + 0x60 + keyType, + isStaticNested: true); + var nested = StaticNestedDart( uid: distance.uid, - distance: distance.distance, + keyType: 0x60 + validKeyType, nt0: nonces.nonces[0].nt, nt0Enc: nonces.nonces[0].ntEnc, - par0: nonces.nonces[0].parity, nt1: nonces.nonces[1].nt, nt1Enc: nonces.nonces[1].ntEnc, - par1: nonces.nonces[1].parity); + ); + + keys = await recovery.static_nested(nested); + } - var keys = await recovery.nested(nested); if (keys.isNotEmpty) { appState.log!.d("Found keys: $keys. Checking them..."); for (var key in keys) { @@ -398,8 +427,8 @@ class ReadCardPageState extends State { ...mfcInfo.recovery.selectedDictionary!.keys, ...gMifareClassicKeys ]) { - appState.log! - .d("Checking $key on sector $sector, key type $keyType"); + appState.log!.d( + "Checking ${bytesToHex(key)} on sector $sector, key type $keyType"); await asyncSleep(1); // Let GUI update if (await appState.communicator!.mf1Auth( mfClassicGetSectorTrailerBlockBySector(sector), @@ -597,7 +626,10 @@ class ReadCardPageState extends State { tag: (skipDump) ? TagType.mifare1K : mfClassicGetChameleonTagType(mfcInfo.type), - data: mfcInfo.cardData)); + data: mfcInfo.cardData, + ats: (hfInfo.ats != "No") + ? hexToBytes(hfInfo.ats.replaceAll(" ", "")) + : Uint8List(0))); appState.sharedPreferencesProvider.setCards(tags); } } @@ -611,7 +643,8 @@ class ReadCardPageState extends State { atqa: Uint8List(0), name: dumpName, tag: TagType.em410X, - data: [])); + data: [], + ats: Uint8List(0))); appState.sharedPreferencesProvider.setCards(tags); } @@ -679,6 +712,8 @@ class ReadCardPageState extends State { localizations.sak, hfInfo.sak, fieldFontSize), buildFieldRow( localizations.atqa, hfInfo.atqa, fieldFontSize), + buildFieldRow( + localizations.ats, hfInfo.ats, fieldFontSize), const SizedBox(height: 16), Text( 'Tech: ${hfInfo.tech}', diff --git a/chameleonultragui/lib/gui/page/saved_cards.dart b/chameleonultragui/lib/gui/page/saved_cards.dart index dc518f5b..693c391a 100644 --- a/chameleonultragui/lib/gui/page/saved_cards.dart +++ b/chameleonultragui/lib/gui/page/saved_cards.dart @@ -274,19 +274,20 @@ class SavedCardsPageState extends State { .sharedPreferencesProvider .getCards(); var tag = CardSave( - id: const Uuid().v4(), - name: nameController.text, - sak: hexToBytes(sak4Controller - .text - .replaceAll(" ", ""))[0], - atqa: hexToBytes(atqa4Controller - .text - .replaceAll(" ", "")), - uid: uid4Controller.text, - tag: mfClassicGetChameleonTagType( - selectedType), - data: blocks, - ); + id: const Uuid().v4(), + name: nameController.text, + sak: hexToBytes(sak4Controller + .text + .replaceAll(" ", ""))[0], + atqa: hexToBytes(atqa4Controller + .text + .replaceAll(" ", "")), + uid: uid4Controller.text, + tag: + mfClassicGetChameleonTagType( + selectedType), + data: blocks, + ats: Uint8List(0)); tags.add(tag); appState.sharedPreferencesProvider .setCards(tags); @@ -309,19 +310,20 @@ class SavedCardsPageState extends State { .sharedPreferencesProvider .getCards(); var tag = CardSave( - id: const Uuid().v4(), - name: nameController.text, - sak: hexToBytes(sak7Controller - .text - .replaceAll(" ", ""))[0], - atqa: hexToBytes(atqa7Controller - .text - .replaceAll(" ", "")), - uid: uid7Controller.text, - tag: mfClassicGetChameleonTagType( - selectedType), - data: blocks, - ); + id: const Uuid().v4(), + name: nameController.text, + sak: hexToBytes(sak7Controller + .text + .replaceAll(" ", ""))[0], + atqa: hexToBytes(atqa7Controller + .text + .replaceAll(" ", "")), + uid: uid7Controller.text, + tag: + mfClassicGetChameleonTagType( + selectedType), + data: blocks, + ats: Uint8List(0)); tags.add(tag); appState.sharedPreferencesProvider .setCards(tags); diff --git a/chameleonultragui/lib/gui/page/slot_manager.dart b/chameleonultragui/lib/gui/page/slot_manager.dart index eb56b97f..0c01b46a 100644 --- a/chameleonultragui/lib/gui/page/slot_manager.dart +++ b/chameleonultragui/lib/gui/page/slot_manager.dart @@ -26,9 +26,9 @@ class SlotManagerPageState extends State { (_) => (TagType.unknown, TagType.unknown), ); - List enabledSlots = List.generate( + List<(bool, bool)> enabledSlots = List.generate( 8, - (_) => true, + (_) => (true, true), ); List> slotData = List.generate( @@ -169,7 +169,8 @@ class SlotManagerPageState extends State { Row( children: [ Icon(Icons.nfc, - color: enabledSlots[index] + color: enabledSlots[index].$1 || + enabledSlots[index].$2 ? Colors.green : Colors.deepOrange), const SizedBox(width: 5), @@ -402,7 +403,8 @@ class CardSearchDelegate extends SearchDelegate { } await appState.communicator!.setReaderDeviceMode(false); - await appState.communicator!.enableSlot(gridPosition, true); + await appState.communicator! + .enableSlot(gridPosition, TagFrequency.hf, true); await appState.communicator!.activateSlot(gridPosition); await appState.communicator!.setSlotType(gridPosition, card.tag); await appState.communicator! @@ -410,7 +412,8 @@ class CardSearchDelegate extends SearchDelegate { var cardData = CardData( uid: hexToBytes(card.uid.replaceAll(" ", "")), atqa: card.atqa, - sak: card.sak); + sak: card.sak, + ats: card.ats); await appState.communicator!.setMf1AntiCollision(cardData); List blockChunk = []; @@ -461,7 +464,8 @@ class CardSearchDelegate extends SearchDelegate { } else if (card.tag == TagType.em410X) { close(context, card.name); await appState.communicator!.setReaderDeviceMode(false); - await appState.communicator!.enableSlot(gridPosition, true); + await appState.communicator! + .enableSlot(gridPosition, TagFrequency.lf, true); await appState.communicator!.activateSlot(gridPosition); await appState.communicator!.setSlotType(gridPosition, card.tag); await appState.communicator! diff --git a/chameleonultragui/lib/helpers/general.dart b/chameleonultragui/lib/helpers/general.dart index 3ea11ecf..b441eb92 100644 --- a/chameleonultragui/lib/helpers/general.dart +++ b/chameleonultragui/lib/helpers/general.dart @@ -31,6 +31,10 @@ Uint8List hexToBytes(String hex) { return Uint8List.fromList(bytes); } +int bytesToU16(Uint8List byteArray) { + return byteArray.buffer.asByteData().getUint16(0, Endian.big); +} + int bytesToU32(Uint8List byteArray) { return byteArray.buffer.asByteData().getUint32(0, Endian.big); } @@ -44,6 +48,16 @@ Uint8List u8ToBytes(int u8) { return byteData.buffer.asUint8List(); } +Uint8List u16ToBytes(int u16) { + final ByteData byteData = ByteData(2)..setUint16(0, u16); + return byteData.buffer.asUint8List(); +} + +Uint8List u32ToBytes(int u32) { + final ByteData byteData = ByteData(4)..setUint32(0, u32); + return byteData.buffer.asUint8List(); +} + Uint8List u64ToBytes(int u64) { final ByteData byteData = ByteData(8)..setUint64(0, u64, Endian.big); return byteData.buffer.asUint8List(); diff --git a/chameleonultragui/lib/l10n/app_en.arb b/chameleonultragui/lib/l10n/app_en.arb index fd76e2e3..0721fbda 100644 --- a/chameleonultragui/lib/l10n/app_en.arb +++ b/chameleonultragui/lib/l10n/app_en.arb @@ -49,7 +49,7 @@ "serial_protocol": "Serial Protocol", "chameleon_connected": "Chameleon Connected", "chameleon_device_type": "Chameleon Device Type", - "nested_attack": "Run nested attack on card", + "nested_attack": "Run Nested attack on card", "darkside_attack": "Run Darkside attack on card", "copy_uid": "Copy card UID to emulator", "test_naming": "Test Naming", @@ -204,5 +204,13 @@ "force_flashing": "Force flashing", "chameleon_flashing_title_easter_egg": "Your Chameleon {model} is flashing", "chameleon_flashing_title": "Installing firmware on your Chameleon {model}", - "ble_pairing": "BLE pairing" + "ble_pairing": "BLE pairing", + "ats": "ATS", + "outdated_protocol": "Outdated protocol in firmware", + "outdated_protocol_description_1": "Your Chameleon is running on outdated protocol, which means firmware is outdated.", + "outdated_protocol_description_2": "App can't work with this version of protocol. Don't report any bugs found after that message.", + "outdated_protocol_description_3": "Would you like to update firmware?", + "skip": "Skip", + "update": "Update", + "static_nested_attack": "Run Static Nested attack on card" } \ No newline at end of file diff --git a/chameleonultragui/lib/recovery/bindings.dart b/chameleonultragui/lib/recovery/bindings.dart index 846f371b..0ae8e442 100644 --- a/chameleonultragui/lib/recovery/bindings.dart +++ b/chameleonultragui/lib/recovery/bindings.dart @@ -64,6 +64,24 @@ class Recovery { ffi.Pointer Function( ffi.Pointer, ffi.Pointer)>(); + ffi.Pointer static_nested( + ffi.Pointer data, + ffi.Pointer keyCount, + ) { + return _static_nested( + data, + keyCount, + ); + } + + late final _static_nestedPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function(ffi.Pointer, + ffi.Pointer)>>('static_nested'); + late final _static_nested = _static_nestedPtr.asFunction< + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer)>(); + int mfkey32( ffi.Pointer data, ) { @@ -132,6 +150,26 @@ final class Nested extends ffi.Struct { external int par1; } +final class StaticNested extends ffi.Struct { + @ffi.Uint32() + external int uid; + + @ffi.Uint32() + external int key_type; + + @ffi.Uint32() + external int nt0; + + @ffi.Uint32() + external int nt0_enc; + + @ffi.Uint32() + external int nt1; + + @ffi.Uint32() + external int nt1_enc; +} + final class Mfkey32 extends ffi.Struct { /// serial number @ffi.Uint32() diff --git a/chameleonultragui/lib/recovery/recovery.dart b/chameleonultragui/lib/recovery/recovery.dart index bc4fbc2b..d3557002 100644 --- a/chameleonultragui/lib/recovery/recovery.dart +++ b/chameleonultragui/lib/recovery/recovery.dart @@ -54,6 +54,23 @@ class NestedDart { required this.par1}); } +class StaticNestedDart { + int uid; + int keyType; + int nt0; + int nt0Enc; + int nt1; + int nt1Enc; + + StaticNestedDart( + {required this.uid, + required this.keyType, + required this.nt0, + required this.nt0Enc, + required this.nt1, + required this.nt1Enc}); +} + class Mfkey32Dart { int uid; int nt0; @@ -93,6 +110,16 @@ Future> nested(NestedDart nested) async { return completer.future; } +Future> static_nested(StaticNestedDart nested) async { + final SendPort helperIsolateSendPort = await _helperIsolateSendPort; + final int requestId = _nextSumRequestId++; + final StaticNestedRequest request = StaticNestedRequest(requestId, nested); + final Completer> completer = Completer>(); + requests[requestId] = completer; + helperIsolateSendPort.send(request); + return completer.future; +} + Future> mfkey32(Mfkey32Dart mfkey) async { final SendPort helperIsolateSendPort = await _helperIsolateSendPort; final int requestId = _nextSumRequestId++; @@ -148,6 +175,13 @@ class NestedRequest { const NestedRequest(this.id, this.nested); } +class StaticNestedRequest { + final int id; + final StaticNestedDart nested; + + const StaticNestedRequest(this.id, this.nested); +} + class Mfkey32Request { final int id; final Mfkey32Dart mfkey32; @@ -265,6 +299,26 @@ Future _helperIsolateSendPort = () async { final KeyResponse response = KeyResponse(data.id, [result]); sendPort.send(response); return; + } else if (data is StaticNestedRequest) { + Pointer pointer = calloc(); + pointer.ref.uid = data.nested.uid; + pointer.ref.key_type = data.nested.keyType; + pointer.ref.nt0 = data.nested.nt0; + pointer.ref.nt0_enc = data.nested.nt0Enc; + pointer.ref.nt1 = data.nested.nt1; + pointer.ref.nt1_enc = data.nested.nt1Enc; + + Pointer count = calloc(); + count.value = 0; + List keys = []; + final Pointer result = + _bindings.static_nested(pointer, count); + for (var i = 0; i < count.value; i++) { + keys.add(result.elementAt(i).value); + } + final KeyResponse response = KeyResponse(data.id, keys); + sendPort.send(response); + return; } throw UnsupportedError('Unsupported message type: ${data.runtimeType}'); }); diff --git a/chameleonultragui/lib/sharedprefsprovider.dart b/chameleonultragui/lib/sharedprefsprovider.dart index 98cb3194..0de8cfaf 100644 --- a/chameleonultragui/lib/sharedprefsprovider.dart +++ b/chameleonultragui/lib/sharedprefsprovider.dart @@ -70,6 +70,7 @@ class CardSave { String uid; int sak; Uint8List atqa; + Uint8List ats; String name; TagType tag; List data; @@ -81,6 +82,7 @@ class CardSave { final uid = data['uid'] as String; final sak = data['sak'] as int; final atqa = List.from(data['atqa'] as List); + final ats = List.from((data['ats'] ?? []) as List); final name = data['name'] as String; final tag = getTagTypeByValue(data['tag']); if (data['color'] == null) { @@ -100,6 +102,7 @@ class CardSave { tag: tag, data: tagData, color: color, + ats: Uint8List.fromList(ats), atqa: Uint8List.fromList(atqa)); } @@ -109,10 +112,11 @@ class CardSave { 'uid': uid, 'sak': sak, 'atqa': atqa.toList(), + 'ats': ats.toList(), 'name': name, 'tag': tag.value, 'color': colorToHex(color), - 'data': data.map((data) => data.toList()).toList() + 'data': data.map((data) => data.toList()).toList(), }); } @@ -123,6 +127,7 @@ class CardSave { required this.sak, required this.atqa, required this.tag, + required this.ats, this.color = Colors.deepOrange, this.data = const []}); } diff --git a/chameleonultragui/pubspec.lock b/chameleonultragui/pubspec.lock index d147dfbf..ebe4305c 100644 --- a/chameleonultragui/pubspec.lock +++ b/chameleonultragui/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: "direct main" description: name: archive - sha256: e0902a06f0e00414e4e3438a084580161279f137aeb862274710f29ec10cf01e + sha256: "1227dc3efc4ea571eebb2dfb814506ed2cfb1d4b1b89fb918abdddde617ead3c" url: "https://pub.dev" source: hosted - version: "3.3.9" + version: "3.4.0" args: dependency: transitive description: @@ -273,10 +273,10 @@ packages: dependency: transitive description: name: image - sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf + sha256: "6e703d5e2f8c63fb31a77753915c1ec8baebde8088844e0d29f71b8f0b108888" url: "https://pub.dev" source: hosted - version: "4.0.17" + version: "4.1.0" intl: dependency: "direct main" description: @@ -457,10 +457,10 @@ packages: dependency: transitive description: name: permission_handler_android - sha256: f23cfe9af0d49c6b9fd8a8b09f7b3301ca7e346204939b5afef4404d36d2608f + sha256: f2543a236584a5e8be79076f858022f100ce690e31530e6fa4c32ac94f276d3a url: "https://pub.dev" source: hosted - version: "11.0.1" + version: "11.0.3" permission_handler_apple: dependency: transitive description: @@ -799,10 +799,10 @@ packages: dependency: transitive description: name: win32 - sha256: "9e82a402b7f3d518fb9c02d0e9ae45952df31b9bf34d77baf19da2de03fc2aaa" + sha256: c97defd418eef4ec88c0d1652cdce84b9f7b63dd7198e266d06ac1710d527067 url: "https://pub.dev" source: hosted - version: "5.0.7" + version: "5.0.8" xdg_directories: dependency: transitive description: diff --git a/chameleonultragui/src/recovery.c b/chameleonultragui/src/recovery.c index e6fde762..0a2a5001 100644 --- a/chameleonultragui/src/recovery.c +++ b/chameleonultragui/src/recovery.c @@ -194,7 +194,7 @@ uint64_t *most_frequent_uint64(uint64_t *keys, uint32_t size, uint32_t *outputKe } // nested decrypt -static void nested_revover(RecPar *rp) +static void nested_recover(RecPar *rp) { struct Crypto1State *revstate, *revstate_start = NULL; uint64_t lfsr = 0; @@ -275,7 +275,7 @@ uint64_t *nested_run(NtpKs1 *pNK, uint32_t sizePNK, uint32_t authuid, uint32_t * pRPs->endPos = sizePNK; // start recover - nested_revover(pRPs); + nested_recover(pRPs); *keyCount = pRPs->keyCount; uint64_t *keys = NULL; @@ -361,6 +361,83 @@ FFI_PLUGIN_EXPORT uint64_t *nested(Nested *data, uint32_t *outputKeyCount) return keys; } +FFI_PLUGIN_EXPORT uint64_t *static_nested(StaticNested *data, uint32_t *outputKeyCount) +{ + NtpKs1 *pNK = NULL; + uint32_t i, m; + uint32_t j = 0; + uint32_t nt1, nt2, nttest, ks1, dist; + + uint32_t authuid = data->uid; + uint8_t type = (uint8_t)data->key_type; // target key type + // process all args. + bool check_st_level_at_first_run = false; + for (i = 0; i < 2; i++) + { + if (i == 0) + { + nt1 = data->nt0; + nt2 = data->nt0_enc; + } + else + { + nt1 = data->nt1; + nt2 = data->nt1_enc; + } + + if (!check_st_level_at_first_run) + { + if (nt1 == 0x01200145) + { + // There is no loophole in this generation. + // This tag can be decrypted with the default parameter value 160! + dist = 160; // st gen1 + } + else if (nt1 == 0x009080A2) + { // st gen2 + // We found that the gen2 tag is vulnerable too but parameter must be adapted depending on the attacked key + if (type == 0x61) + { + dist = 161; + } + else if (type == 0x60) + { + dist = 160; + } + else + { + return NULL; + } + } + else + { + return NULL; + } + check_st_level_at_first_run = true; + } + + nttest = prng_successor(nt1, dist); + ks1 = nt2 ^ nttest; + ++j; + dist += 160; + + void *tmp = realloc(pNK, sizeof(NtpKs1) * j); + if (tmp == NULL) + { + return NULL; + } + + pNK = tmp; + pNK[j - 1].ntp = nttest; + pNK[j - 1].ks1 = ks1; + } + + uint32_t keyCount = 0; + uint64_t *keys = nested_run(pNK, j, authuid, &keyCount, outputKeyCount); + *outputKeyCount = MIN(256, *outputKeyCount); + return keys; +} + FFI_PLUGIN_EXPORT uint64_t mfkey32(Mfkey32 *data) { struct Crypto1State *s, *t; diff --git a/chameleonultragui/src/recovery.h b/chameleonultragui/src/recovery.h index 0083ebfc..b3cd5368 100644 --- a/chameleonultragui/src/recovery.h +++ b/chameleonultragui/src/recovery.h @@ -43,6 +43,16 @@ typedef struct uint32_t par1; } Nested; +typedef struct +{ + uint32_t uid; + uint32_t key_type; + uint32_t nt0; + uint32_t nt0_enc; + uint32_t nt1; + uint32_t nt1_enc; +} StaticNested; + typedef struct { uint32_t uid; // serial number @@ -58,4 +68,6 @@ FFI_PLUGIN_EXPORT uint64_t *darkside(Darkside *data, uint32_t *keyCount); FFI_PLUGIN_EXPORT uint64_t *nested(Nested *data, uint32_t *keyCount); +FFI_PLUGIN_EXPORT uint64_t *static_nested(StaticNested *data, uint32_t *keyCount); + FFI_PLUGIN_EXPORT uint64_t mfkey32(Mfkey32 *data); \ No newline at end of file diff --git a/chameleonultragui/untranslated_messages.json b/chameleonultragui/untranslated_messages.json index 5c265e8b..df0592c8 100644 --- a/chameleonultragui/untranslated_messages.json +++ b/chameleonultragui/untranslated_messages.json @@ -52,26 +52,88 @@ "force_flashing", "chameleon_flashing_title_easter_egg", "chameleon_flashing_title", - "ble_pairing" + "ble_pairing", + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" ], "de": [ - "ble_pairing" + "ble_pairing", + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" ], "de_AT": [ - "ble_pairing" + "ble_pairing", + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" + ], + + "es": [ + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" ], "fr": [ "shared_preferences_logging", "production_logging", "chameleon_flashing_title_easter_egg", - "ble_pairing" + "ble_pairing", + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" + ], + + "it": [ + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" ], "ko": [ - "ble_pairing" + "ble_pairing", + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" ], "nl": [ @@ -93,15 +155,39 @@ "force_flashing", "chameleon_flashing_title_easter_egg", "chameleon_flashing_title", - "ble_pairing" + "ble_pairing", + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" ], "pt": [ - "ble_pairing" + "ble_pairing", + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" ], "pt_BR": [ - "ble_pairing" + "ble_pairing", + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" ], "ro": [ @@ -155,6 +241,47 @@ "force_flashing", "chameleon_flashing_title_easter_egg", "chameleon_flashing_title", - "ble_pairing" + "ble_pairing", + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" + ], + + "ru": [ + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" + ], + + "uk": [ + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" + ], + + "zh": [ + "ats", + "outdated_protocol", + "outdated_protocol_description_1", + "outdated_protocol_description_2", + "outdated_protocol_description_3", + "skip", + "update", + "static_nested_attack" ] }