Fixed MFC 1k/4k issue and Ultralight detection

This commit is contained in:
michael
2026-01-26 14:06:49 -08:00
parent b27baf5dec
commit 2409ec3625
10 changed files with 237 additions and 57 deletions

View File

@@ -79,6 +79,40 @@ export const CHIP_INFO: Partial<Record<ChipType, ChipInfo>> = {
memoryNote: '1904 bytes user memory',
},
// MIFARE Ultralight family
[ChipType.ULTRALIGHT]: {
description:
'Original MIFARE Ultralight - a simple, low-cost NFC tag with minimal memory.',
commonUses: ['Single-use tickets', 'Event passes', 'Disposable tags'],
capabilities: ['NFC Forum Type 2', 'OTP area', 'Lock bits'],
securityLevel: 'low',
memoryNote: '48 bytes user memory',
},
[ChipType.ULTRALIGHT_C]: {
description:
'Ultralight with 3DES authentication for basic security applications.',
commonUses: ['Limited-use tickets', 'Access tokens', 'Loyalty cards'],
capabilities: ['NFC Forum Type 2', '3DES authentication', 'Counter'],
securityLevel: 'medium',
memoryNote: '144 bytes user memory',
},
[ChipType.ULTRALIGHT_EV1]: {
description:
'Enhanced Ultralight with password protection and originality check.',
commonUses: ['Event tickets', 'Brand protection', 'Gaming tokens'],
capabilities: ['Password protection', 'Originality signature', 'Counter'],
securityLevel: 'low',
memoryNote: '48 or 128 bytes user memory',
},
[ChipType.ULTRALIGHT_AES]: {
description:
'Ultralight with AES-128 authentication for secure applications.',
commonUses: ['Secure tickets', 'Brand protection', 'Limited-use passes'],
capabilities: ['AES-128 authentication', 'Counter', 'Originality signature'],
securityLevel: 'high',
memoryNote: '540 bytes user memory',
},
// MIFARE Classic
[ChipType.MIFARE_CLASSIC_1K]: {
description:

View File

@@ -64,6 +64,18 @@ const SLIX_COMPATIBLE: ChipType[] = [
ChipType.SLIX_L,
];
/**
* MIFARE Ultralight compatible chips (for Gen4 magic implants)
* Note: Ultralight AES cannot be cloned if AES protection is active
*/
const ULTRALIGHT_COMPATIBLE: ChipType[] = [
ChipType.ULTRALIGHT,
ChipType.ULTRALIGHT_C,
ChipType.ULTRALIGHT_EV1,
ChipType.ULTRALIGHT_NANO,
ChipType.ULTRALIGHT_AES,
];
export const PRODUCTS: Product[] = [
// ==========================================================================
// X-Series (Glass Capsule) Implants - NFC
@@ -136,8 +148,9 @@ export const PRODUCTS: Product[] = [
'Dual-frequency',
'NTAG216 NFC chip',
'888 bytes user memory',
'URL/contact sharing',
'T5577 125kHz chip',
'LF access control',
'Access control (LF)',
],
url: 'https://dangerousthings.com/product/next/',
canReceiveClone: true,
@@ -158,7 +171,7 @@ export const PRODUCTS: Product[] = [
'URL/contact sharing',
'Green, Blue, and White LEDs available (HF)',
'T5577 125kHz chip',
'LF access control',
'Access control (LF)',
],
url: 'https://dangerousthings.com/product/next-v2/',
canReceiveClone: true,
@@ -175,14 +188,14 @@ export const PRODUCTS: Product[] = [
compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE],
features: [
'Magic MIFARE Classic chip',
'Changeable UID',
'Changeable UID (4-byte)',
'Gen1a and gen2 magic options',
'T5577 125kHz chip',
'Clone access cards',
'Access control (LF)',
],
url: 'https://dangerousthings.com/product/xmagic/',
canReceiveClone: true,
exactMatch: false,
notes: 'Can clone MIFARE Classic cards (requires Android + keys)',
},
// ==========================================================================
@@ -200,7 +213,8 @@ export const PRODUCTS: Product[] = [
'DESFire EV3 chip',
'8KB memory',
'Latest security features',
'Transaction MAC',
'Access control (HF)',
'URL/contact sharing',
],
url: 'https://dangerousthings.com/product/xdf3/',
canReceiveClone: false,
@@ -221,14 +235,12 @@ export const PRODUCTS: Product[] = [
compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE],
features: [
'Magic MIFARE Classic 1K',
'Changeable UID',
'Clone access cards',
'Gen1a compatible',
'Gen1a and gen2 magic options',
'Changeable UID (4-byte)',
],
url: 'https://dangerousthings.com/product/xm1/',
canReceiveClone: true,
exactMatch: false,
notes: 'Can clone MIFARE Classic 1K cards (requires Android + keys)',
},
// ==========================================================================
@@ -243,6 +255,7 @@ export const PRODUCTS: Product[] = [
categories: [ProductCategory.SECURE],
compatibleChips: [ChipType.NTAG424_DNA, ChipType.NTAG424_DNA_TT, ChipType.ICODE_DNA],
features: [
'VivoKey Ecosystem',
'Spark Actions',
'Verify API',
],
@@ -265,9 +278,11 @@ export const PRODUCTS: Product[] = [
compatibleChips: [...NTAG21X_COMPATIBLE, ...NTAG_I2C_COMPATIBLE],
features: [
'NTAG216 NFC chip',
'888 bytes user memory',
'URL/contact sharing',
'T5577 125kHz chip',
'Enhanced read range',
'Bioresin encapsulation',
'Enhanced read range',
],
url: 'https://dangerousthings.com/product/dnext/',
canReceiveClone: true,
@@ -281,17 +296,20 @@ export const PRODUCTS: Product[] = [
'Bioresin dual-frequency implant with Ultimate Gen4 magic MIFARE and T5577 for maximum compatibility.',
formFactor: FormFactor.BIORESIN,
categories: [ProductCategory.DUAL_FREQUENCY, ProductCategory.ACCESS],
compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE],
compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE, ...ULTRALIGHT_COMPATIBLE],
features: [
'Ultimate Gen4 magic chip',
'Changeable UID',
'Access control (HF)',
'T5577 125kHz chip',
'Changeable UID',
'Enhanced cloning capability',
'Access control (LF)',
],
url: 'https://dangerousthings.com/product/dug4t/',
canReceiveClone: true,
exactMatch: false,
notes: 'Can clone MIFARE Classic cards (requires Android + keys)',
notes: 'Supports 1K and 4K cloning. Also includes T5577 for LF.',
},
// ==========================================================================
@@ -308,7 +326,7 @@ export const PRODUCTS: Product[] = [
features: [
'NTAG216 chip',
'888 bytes user memory',
'Flexible form factor',
'URL/contact sharing',
'Extended read range',
],
url: 'https://dangerousthings.com/product/flexnt/',
@@ -331,7 +349,7 @@ export const PRODUCTS: Product[] = [
'DESFire EV2 chip',
'8KB memory',
'AES-128 encryption',
'Flexible form factor',
'URL/contact sharing',
],
url: 'https://dangerousthings.com/product/flexdf2/',
canReceiveClone: false,
@@ -343,24 +361,6 @@ export const PRODUCTS: Product[] = [
// ==========================================================================
// Flex Implants - Magic/Cloning
// ==========================================================================
{
id: 'flexm1',
name: 'flexM1',
description: 'Flexible magic MIFARE Classic 1K implant for cloning access cards.',
formFactor: FormFactor.FLEX,
categories: [ProductCategory.ACCESS],
compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE],
features: [
'Magic MIFARE Classic 1K',
'Changeable UID',
'Flexible form factor',
'Extended read range',
],
url: 'https://dangerousthings.com/product/flexm1/',
canReceiveClone: true,
exactMatch: false,
notes: 'Can clone MIFARE Classic 1K cards (requires Android + keys)',
},
{
id: 'flexm1-v2',
name: 'flexM1 v2',
@@ -377,7 +377,6 @@ export const PRODUCTS: Product[] = [
url: 'https://dangerousthings.com/product/flexm1-v2/',
canReceiveClone: true,
exactMatch: false,
notes: 'Can clone MIFARE Classic 1K cards (requires Android + keys)',
},
// ==========================================================================
@@ -394,9 +393,9 @@ export const PRODUCTS: Product[] = [
features: [
'JCOP4 secure element',
'Fidesmo platform',
'Payment capable',
'FIDO2/WebAuthn',
'OTP support',
'URL/contact sharing',
],
url: 'https://dangerousthings.com/product/apex-flex/',
canReceiveClone: false,
@@ -416,6 +415,9 @@ export const PRODUCTS: Product[] = [
'Full JavaCard access',
'GlobalPlatformPro compatible',
'Custom applet support',
'FIDO2/WebAuthn',
'OTP support',
'URL/contact sharing',
],
url: 'https://dangerousthings.com/product/flexsecure/',
canReceiveClone: false,
@@ -436,7 +438,6 @@ export const PRODUCTS: Product[] = [
features: [
'HID iCLASS SE compatible',
'Enterprise access control',
'Flexible form factor',
],
url: 'https://dangerousthings.com/product/flexclass/',
canReceiveClone: false,
@@ -449,17 +450,16 @@ export const PRODUCTS: Product[] = [
description: 'Flexible Ultimate Gen4 magic MIFARE implant for cloning access cards.',
formFactor: FormFactor.FLEX,
categories: [ProductCategory.ACCESS],
compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE],
compatibleChips: [...MIFARE_CLASSIC_COMPATIBLE, ...ULTRALIGHT_COMPATIBLE],
features: [
'Ultimate Gen4 magic chip',
'Changeable UID',
'Flexible form factor',
'Enhanced cloning capability',
],
url: 'https://dangerousthings.com/product/flexug4/',
canReceiveClone: true,
exactMatch: false,
notes: 'Can clone MIFARE Classic cards (requires Android + keys)',
notes: 'Supports 1K and 4K cloning.',
},
];

View File

@@ -3,7 +3,7 @@ import { StyleSheet, View, ScrollView, Linking, TouchableOpacity } from 'react-n
import { Button, Text, Surface, Divider, Chip } from 'react-native-paper';
import type { ResultScreenProps } from '../types/navigation';
import { DTColors } from '../theme';
import { matchChipToProducts, getMatchSummary, getDesfireEvMismatchWarning } from '../services/matching';
import { matchChipToProducts, getMatchSummary, getDesfireEvMismatchWarning, getMifareClassicCapacityWarning } from '../services/matching';
import { Product } from '../types/products';
import { getChipInfo, getChipFamilyInfo, getSecurityLevelDescription } from '../data/chipInfo';
import { getChipFamily } from '../types/detection';
@@ -296,6 +296,13 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
</Text>
)}
{/* MIFARE Classic 4K → 1K capacity warning */}
{getMifareClassicCapacityWarning(transponder.type, product) && (
<Text variant="bodySmall" style={styles.evMismatchWarning}>
{getMifareClassicCapacityWarning(transponder.type, product)}
</Text>
)}
<View style={styles.productFeatures}>
{product.features.slice(0, 3).map((feature, idx) => (
<Text key={idx} variant="bodySmall" style={styles.productFeature}>

View File

@@ -1,13 +1,13 @@
import React, {useCallback, useEffect} from 'react';
import {StyleSheet, View, Platform} from 'react-native';
import {Button, Text} from 'react-native-paper';
import type {ScanScreenProps} from '../types/navigation';
import {DTColors} from '../theme';
import {useScan} from '../hooks';
import {getScanInstructions} from '../services/nfc';
import {ScanAnimation} from '../components';
import React, { useCallback, useEffect } from 'react';
import { StyleSheet, View, Platform } from 'react-native';
import { Button, Text } from 'react-native-paper';
import type { ScanScreenProps } from '../types/navigation';
import { DTColors } from '../theme';
import { useScan } from '../hooks';
import { getScanInstructions } from '../services/nfc';
import { ScanAnimation } from '../components';
export function ScanScreen({navigation}: ScanScreenProps) {
export function ScanScreen({ navigation }: ScanScreenProps) {
const {
state,
tag,
@@ -194,6 +194,12 @@ export function ScanScreen({navigation}: ScanScreenProps) {
<Text variant="bodyLarge" style={styles.instructionText}>
{getScanInstructions()}
</Text>
<Text variant="headlineSmall" style={{ paddingTop: 15, paddingBottom: 10 }}>
Not Scanning?
</Text>
<Text variant="bodyLarge" style={styles.instructionText}>
Phones can only read high frequency (13.56 MHz) -- low and ultra high frequency transponders cannot be scanned.
</Text>
{scanProgress && scanProgress.current > 1 && (
<Text variant="bodySmall" style={styles.progressText}>
{scanProgress.step}

View File

@@ -146,12 +146,29 @@ export async function detectChip(
// If we have MifareClassic tech type and NO IsoDep, it's definitely Classic
if (hasMifareClassicTech && !hasIsoDepTech) {
// Determine 1K vs 4K based on SAK or UID length
// Determine 1K vs 4K vs Mini
// Priority: mifareClassic.size (most reliable on Android) > SAK > UID length
let chipType = ChipType.MIFARE_CLASSIC_1K; // Default to 1K
let memorySize = 1024;
// SAK 0x18 or 0x38 indicates 4K
if (sak === 0x18 || sak === 0x38 || sak === 0x98) {
// First, check mifareClassic object from Android (most reliable)
if (rawData.mifareClassic?.size) {
const size = rawData.mifareClassic.size;
console.log('[Detector] Using mifareClassic.size:', size);
if (size >= 4096) {
chipType = ChipType.MIFARE_CLASSIC_4K;
memorySize = 4096;
} else if (size >= 1024) {
chipType = ChipType.MIFARE_CLASSIC_1K;
memorySize = 1024;
} else if (size >= 320) {
chipType = ChipType.MIFARE_CLASSIC_MINI;
memorySize = 320;
}
}
// Fallback to SAK-based detection
// SAK 0x18, 0x38, or 0x98 indicates 4K
else if (sak === 0x18 || sak === 0x38 || sak === 0x98) {
chipType = ChipType.MIFARE_CLASSIC_4K;
memorySize = 4096;
}
@@ -233,8 +250,27 @@ export async function detectChip(
}),
};
}
// If GET_VERSION failed but tag looks like Type 2, mark as unknown NTAG
if (sak === 0x00 && techTypes.some(t => t.includes('NfcA'))) {
// If GET_VERSION failed, check for MIFARE Ultralight
// Original Ultralight doesn't support GET_VERSION (only EV1+ does)
// SAK can be 0x00 or undefined for Ultralight
if ((sak === 0x00 || sak === undefined) && techTypes.some(t => t.includes('NfcA'))) {
// Check for MifareUltralight tech type (Android provides this)
const hasMifareUltralightTech = techTypes.some(t =>
t.includes('MifareUltralight'),
);
if (hasMifareUltralightTech) {
console.log('[Detector] MifareUltralight tech detected, identifying as original Ultralight');
return {
success: true,
transponder: createTransponder(ChipType.ULTRALIGHT, rawData, {
memorySize: 48, // Original Ultralight has 48 bytes user memory
confidence: 'medium',
}),
};
}
// No MifareUltralight tech - fall back to unknown NTAG
console.log('[Detector] NTAG detection failed, falling back to NTAG_UNKNOWN');
return {
success: true,

View File

@@ -11,4 +11,5 @@ export {
canCloneToProduct,
getDesfireEvLevel,
getDesfireEvMismatchWarning,
getMifareClassicCapacityWarning,
} from './matcher';

View File

@@ -35,7 +35,6 @@ export function matchChipToProducts(chip: Transponder): MatchResult {
if (product.canReceiveClone && cloneability?.cloneable) {
if ((product.name.startsWith("xMagic") || product.name.startsWith("xM1") || product.name.startsWith("flexM1")) && chip.rawData.uid.replaceAll(":", "").length / 2 !== 4) {
// This skips things without a 4-byte UID
} else {
cloneTargets.push(product);
}
@@ -177,6 +176,32 @@ export function getDesfireEvLevel(chipType: ChipType): DesfireEvLevel | null {
}
}
/**
* IDs of products that only support MIFARE Classic 1K (not 4K)
*/
const MIFARE_1K_ONLY_PRODUCTS = new Set(['xmagic', 'xm1', 'flexm1-v2']);
/**
* Check if a scanned 4K card is being matched to a 1K-only implant
* Returns warning message if capacity mismatch, null if no issue
*/
export function getMifareClassicCapacityWarning(
chipType: ChipType,
product: Product,
): string | null {
// Only applies to MIFARE Classic 4K cards
if (chipType !== ChipType.MIFARE_CLASSIC_4K) {
return null;
}
// Only warn for 1K-only implants
if (!MIFARE_1K_ONLY_PRODUCTS.has(product.id)) {
return null;
}
return 'This implant has 1K memory only - might not have capacity to clone your 4K card.';
}
/**
* Check if there's a DESFire EV mismatch between chip and product
* Returns warning message if mismatch, null if no issue

View File

@@ -12,6 +12,7 @@ import type {
ScanErrorType,
NfcTechType,
NdefRecord,
MifareClassicInfo,
} from '../../types/nfc';
/**
@@ -116,6 +117,56 @@ function parseAts(tag: TagEvent): {ats?: string; historicalBytes?: string} {
return {ats, historicalBytes};
}
/**
* Parse MIFARE Classic info from TagEvent (Android only)
*
* Uses NDEF maxSize to determine card capacity:
* - 4K: maxSize ~3356 bytes (NDEF capacity)
* - 1K: maxSize ~716 bytes (NDEF capacity)
* - Mini: maxSize ~80 bytes (NDEF capacity)
*/
function parseMifareClassic(tag: TagEvent): MifareClassicInfo | undefined {
const techTypes = tag.techTypes || [];
const isMifareClassic = techTypes.some(t => t.includes('MifareClassic'));
if (!isMifareClassic) {
return undefined;
}
// Use NDEF maxSize to determine card type
// MIFARE Classic 4K has ~3356 bytes NDEF capacity
// MIFARE Classic 1K has ~716 bytes NDEF capacity
const maxSize = (tag as any).maxSize;
if (typeof maxSize === 'number' && maxSize > 0) {
let size: number;
let sectorCount: number;
let blockCount: number;
if (maxSize >= 2000) {
// 4K card (NDEF maxSize ~3356)
size = 4096;
sectorCount = 40;
blockCount = 256;
} else if (maxSize >= 500) {
// 1K card (NDEF maxSize ~716)
size = 1024;
sectorCount = 16;
blockCount = 64;
} else {
// Mini card (NDEF maxSize ~80)
size = 320;
sectorCount = 5;
blockCount = 20;
}
console.log('[NFCManager] MIFARE Classic from maxSize:', {maxSize, size, sectorCount, blockCount});
return {size, sectorCount, blockCount};
}
return undefined;
}
/**
* Parse NDEF records from TagEvent
*/
@@ -167,6 +218,7 @@ function tagEventToRawData(tag: TagEvent): RawTagData {
const atqa = parseAtqa(tag);
const {ats, historicalBytes} = parseAts(tag);
const ndefRecords = parseNdefRecords(tag);
const mifareClassic = parseMifareClassic(tag);
const isoDep = (tag as any).isoDep;
const iso7816 = (tag as any).iso7816;
@@ -204,6 +256,7 @@ function tagEventToRawData(tag: TagEvent): RawTagData {
historicalBytes,
maxTransceiveLength,
ndefRecords,
mifareClassic,
};
}
@@ -366,7 +419,10 @@ class NFCManagerService {
};
}
return {tag: tagEventToRawData(tag)};
// Convert to RawTagData
const rawData = tagEventToRawData(tag);
return {tag: rawData};
} catch (error) {
return {error: categorizeError(error)};
} finally {

View File

@@ -207,7 +207,8 @@ export async function transceiveNfcA(command: number[]): Promise<number[]> {
const response = await NfcManager.nfcAHandler.transceive(command);
return Array.from(response);
} catch (error) {
console.error('[commands] NfcA transceive failed:', error);
// Use debug level - some failures are expected (e.g., GET_VERSION on original Ultralight)
console.debug('[commands] NfcA transceive failed:', error);
throw error;
}
}

View File

@@ -32,6 +32,18 @@ export interface NdefRecord {
payload: number[];
}
/**
* MIFARE Classic specific info from Android
*/
export interface MifareClassicInfo {
/** Total memory size in bytes */
size: number;
/** Number of sectors */
sectorCount: number;
/** Total number of blocks */
blockCount: number;
}
/**
* Raw tag data from NFC scan
*/
@@ -52,6 +64,8 @@ export interface RawTagData {
maxTransceiveLength?: number;
/** Cached NDEF records (if tag supports NDEF) */
ndefRecords?: NdefRecord[];
/** MIFARE Classic info (Android only) */
mifareClassic?: MifareClassicInfo;
}
/**