Skip to content

feat: context-aware HF sniff frames annotation(#884) - #885

Open
NaiveTomcat wants to merge 18 commits into
GameTec-live:mainfrom
NaiveTomcat:main
Open

feat: context-aware HF sniff frames annotation(#884)#885
NaiveTomcat wants to merge 18 commits into
GameTec-live:mainfrom
NaiveTomcat:main

Conversation

@NaiveTomcat

Copy link
Copy Markdown

Added a rudimentary context-aware annotator for HF sniff frames. Currently, only Mifare Classic/Ultralight/NTAG21x frames are supported.
Tested with Mifare Classic 1K, Mifare Ultralight, and Ultralight EV1.

Added a rudimentary context-aware annotator for HF sniff frames.
Currently only Mifare Classic/Ultralight/NTAG21x frames are supported.
Tested with Mifare Classic 1K, Mifare Ultralight, and Ultralight EV1.
Copilot AI review requested due to automatic review settings May 19, 2026 14:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Introduces a stateful HF (ISO14443-A) sniff parser and shared frame model to improve frame annotation with protocol context (Ultralight/NTAG decoding, MIFARE Classic auth/encrypted detection, APDU hints).

Changes:

  • Adds StatefulHfParser with context tracking and richer ISO14443-A/MIFARE/Ultralight decoding.
  • Extracts HfSniffFrame/HfSniffDirection into a dedicated hf_sniff_models.dart.
  • Updates existing sniff annotation flow to use the stateful parser and updates GUI imports accordingly.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart New stateful parser + family inference + context-aware annotation/decoding logic
chameleonultragui/lib/helpers/hf_sniff_models.dart New shared models for HF sniff frames and direction
chameleonultragui/lib/helpers/hf_sniff.dart Switches annotation to the new stateful parser; removes in-file models
chameleonultragui/lib/gui/menu/tools/hf_sniffing.dart Imports extracted sniff models

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
NaiveTomcat and others added 3 commits May 19, 2026 22:20
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@NaiveTomcat
NaiveTomcat requested a review from Copilot May 19, 2026 14:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
NaiveTomcat and others added 2 commits May 19, 2026 22:52
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (1)

chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart:168

  • feedFrame treats any 16-bit/2-byte frame as ATQA and any 8-bit/1-byte frame as SAK without checking direction/context. This will misclassify common reader->card commands (e.g., 0x93 0x20 anticollision, 0xE0 0x80 RATS, or 0x60 GET_VERSION without CRC) as ATQA/SAK and corrupt ctx.atqa/sak/family. Restrict ATQA/SAK updates to card->reader responses (and ideally gate ATQA behind a prior REQA/WUPA / blocked-command filter).
    // ATQA
    if (frame.bitLength == 16 && data.length == 2) {
      ctx.atqa = data[0] | (data[1] << 8);
      ctx.state = HfParserState.ready;
      return;
    }

    // SAK
    if (frame.bitLength == 8 && data.length == 1) {
      ctx.sak = data[0];
      ctx.family = _inferFamilyFromContext();
      ctx.state = HfParserState.active;
      return;
    }

Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
NaiveTomcat and others added 2 commits May 19, 2026 23:15
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (5)

chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart:70

  • Same issue as above: the later scan for f.data[0] == 0x60 can misclassify MIFARE Classic AUTH frames as GET_VERSION. Restrict this check to expected GET_VERSION frame shapes (length/bitLength) before reading the following response's subtype byte.
      for (int i = 0; i < frames.length; i++) {
        final f = frames[i];
        if (f.isReaderToCard && f.data.isNotEmpty && f.data[0] == 0x60) {
          if (i + 1 < frames.length) {
            final resp = frames[i + 1];
            if (resp.isCardToReader && resp.data.length >= 3) {
              final hw = resp.data[2];
              if (hw == 0x03) return 'MIFARE Ultralight EV1/Ultralight';
              if (hw == 0x04) return 'NTAG21x';
            }

chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart:140

  • reset() does not clear several context fields (e.g., probableUltralight*, pendingUlCommand, pendingPage, pendingEndPage, pendingCounter, pendingAnticollLevel). If a StatefulHfParser instance is reused (e.g., via parseAll()), stale pending state can leak into the next parse and produce incorrect annotations. Reset all ctx.* pending/probable fields here as well.
  void reset() {
    ctx.state = HfParserState.powerOff;
    ctx.uid = '';
    ctx.atqa = null;
    ctx.sak = null;
    ctx.family = '';
    _expectNt = false;
    _expectNrAr = false;
    _expectAt = false;
    _authenticated = false;
    _lastAuthKeyType = null;
    _lastAuthBlock = null;
  }

chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart:153

  • On REQA/WUPA (and similarly on HLTA), the parser clears _authenticated but leaves _expectNt/_expectNrAr/_expectAt and any pending Ultralight command state untouched. This can cause subsequent unrelated frames to be mislabeled as AUTH continuation or as a response to an old pending UL command after a new anticollision round. Consider clearing the expected-auth flags and pending command fields when a new session starts (REQA/WUPA) or ends (HLTA).
    // detect REQA/WUPA
    if (frame.rawBitLength == 7 &&
        data.length == 1 &&
        (data[0] == 0x26 || data[0] == 0x52)) {
      ctx.state = HfParserState.idle;
      _authenticated = false;
      return;
    }

chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart:372

  • Compat write step-2 payload matching is too strict: COMPAT_WRITE data is 16 bytes of payload and often includes CRC_A (total 18 bytes in the captured frame). With the current data.length == 16 check, most real frames with CRC will never be annotated. Allow 16 or 18 (and strip trailing CRC when present) while pendingUlCommand == compatWrite.
    if (data.length == 16 && ctx.pendingUlCommand == PendingUltralightCommand.compatWrite) {
      final page = ctx.pendingPage ?? -1;
      ctx.pendingUlCommand = PendingUltralightCommand.compatWriteData;
      return 'UL COMPAT_WRITE DATA page=$page data=${_hex(Uint8List.fromList(data)).toUpperCase()}';
    }

chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart:589

  • _decodeUlReadPages assumes pages 0-3 always contain 4 bytes and directly indexes/sublists pbytes (e.g., pbytes[3], pbytes.sublist(0, 3)). When decodeData is shorter than expected (truncated capture, partial frame, or mismatched length), this can throw a RangeError and crash annotation/UI. Add length guards for these page descriptors (or fall back to generic hex output when pbytes.length < 4).
      if (pnum >= 0 && pnum <= 3) {
        final bcols = pbytes
            .map((b) => b.toRadixString(16).padLeft(2, '0'))
            .join(':')
            .toUpperCase();
        String desc;
        if (pnum == 0) {
          desc =
              'UID0-2=${_hex(Uint8List.fromList(pbytes.sublist(0, 3)), spaced: false)} BCC0=0x${pbytes[3].toRadixString(16).padLeft(2, '0').toUpperCase()}';
        } else if (pnum == 1) {
          desc = 'UID3-6=${_hex(pbytes, spaced: false)}';
        } else if (pnum == 2) {
          desc =
              'BCC1=0x${pbytes[0].toRadixString(16).padLeft(2, '0').toUpperCase()} internal=${_hex(Uint8List.fromList(pbytes.sublist(1, 2)))} LOCK0=0x${pbytes[2].toRadixString(16).padLeft(2, '0').toUpperCase()} LOCK1=0x${pbytes[3].toRadixString(16).padLeft(2, '0').toUpperCase()}';
        } else {

Comment thread chameleonultragui/lib/helpers/hf_sniff.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (3)

chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart:141

  • StatefulHfParser.reset() does not clear several fields stored on ctx (e.g., probableUltralight, pendingUlCommand, pendingPage, pendingEndPage, pendingCounter, pendingAnticollLevel). If a StatefulHfParser instance is reused, stale pending-command context can leak into the next parse/annotation. Reset should clear all context fields to a known baseline.
  void reset() {
    ctx.state = HfParserState.powerOff;
    ctx.uid = '';
    ctx.atqa = null;
    ctx.sak = null;
    ctx.family = '';
    _expectNt = false;
    _expectNrAr = false;
    _expectAt = false;
    _authenticated = false;
    _lastAuthKeyType = null;
    _lastAuthBlock = null;
  }

chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart:154

  • On REQA/WUPA, feedFrame() sets ctx.state to idle but leaves uid, atqa, sak, family, and any pending Ultralight command context intact. In captures containing multiple polling / card-present cycles, this can cause subsequent frames to be annotated with stale context from the previous cycle. Consider performing a context reset (or at least clearing UID/family and pending command state) when a new REQA/WUPA is observed.
    // detect REQA/WUPA
    if (frame.rawBitLength == 7 &&
        data.length == 1 &&
        (data[0] == 0x26 || data[0] == 0x52)) {
      ctx.state = HfParserState.idle;
      _authenticated = false;
      return;
    }

chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart:219

  • isHaltFrame (and the HLTA detection in feedFrame()) require data.length == 4, i.e. 50 00 + CRC. However the legacy decoder in hf_sniff.dart treats any 0x50 (and summary treats 0x50/0xC2) as halt/deselect without requiring CRC. If the sniff capture omits CRC bytes, a 2-byte HLTA (50 00) will be treated as an encrypted frame once _authenticated is true, and ctx.state will never transition to halt. Consider recognizing HLTA when data.length >= 2 && data[0] == 0x50 && data[1] == 0x00 (and optionally including CRC in the label when present).
    final isHaltFrame = data.length == 4 && data[0] == 0x50 && data[1] == 0x00;

    // Mifare Classic in authenticated state, treat all frames except HALT as encrypted
    if (_authenticated && !isHaltFrame) {
      final family = ctx.family.isEmpty ? '' : '[${ctx.family}] ';
      return '${family}MIFARE Classic Encrypted (0x${data[0].toRadixString(16).padLeft(2, '0').toLowerCase()})';
    }

Comment thread chameleonultragui/lib/helpers/hf_sniff.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff.dart
@GameTec-live

Copy link
Copy Markdown
Owner

Lmk when this is ready, seems like youre still a bit busy.

Ill see when I can get around to this, prob will take a bit.

@NaiveTomcat
NaiveTomcat requested a review from Copilot May 20, 2026 00:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.

Comment thread chameleonultragui/lib/gui/menu/tools/hf_sniffing.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@NaiveTomcat
NaiveTomcat requested a review from Copilot May 20, 2026 01:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.

Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
Comment thread chameleonultragui/lib/helpers/hf_sniff_models.dart Outdated
Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
NaiveTomcat and others added 3 commits May 20, 2026 16:37
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@NaiveTomcat
NaiveTomcat requested a review from Copilot May 20, 2026 08:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@NaiveTomcat
NaiveTomcat requested a review from Copilot May 20, 2026 09:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@NaiveTomcat

Copy link
Copy Markdown
Author

@GameTec-live, I think this PR is somewhat ready now. Tested locally, and I did not see any issues. Please review when you have time. Thanks.

@GameTec-live

Copy link
Copy Markdown
Owner

two questions:

  1. How much of this PR is AI?
  2. Did you test this or am I doing something wrong. I get "RangeError (end)" when sniffing and scanning the CU with my phone.
{553B707D-18D4-4A22-BCF0-16BA97859A38}

@NaiveTomcat

Copy link
Copy Markdown
Author
  1. How much of this PR is AI?

The research on the command definition and the initial Python parser was done by Copilot, then I manually rewrote it in Dart. Then, after submitting the PR, the code review is done by Copilot (as you have seen), and the subsequent fixes were 1. from Copilot (those commits with Copilot as co-author) and 2. hand-coded following Copilot's review messages.

  1. Did you test this or am I doing something wrong. I get "RangeError (end)" when sniffing and scanning the CU with my phone.

My tests with my CU on macOS 26.5, emulating Mifare Classic 1K, Mifare Ultralight, and NTAG 215, did not trigger the RangeError you encountered, and I have tested the code on captured binaries without issue. How did you trigger this error?

@NaiveTomcat

Copy link
Copy Markdown
Author

RangeError(end) suggests it came from a sublist? In my modified code, all sublist calls have length validation up front or according to the datasheet, that frame should have enough length, maybe your test suggests a truncated raw buffer from CU?

I have added another length validation, let's see if this solves the issue.

@GameTec-live

Copy link
Copy Markdown
Owner

Hm, interestingly cant repro it.

Was just using nfctools on my phone to test.

Seems to work now. Lmk if you want to investigate that further

@NaiveTomcat

Copy link
Copy Markdown
Author

Hm, interestingly cant repro it.

Was just using nfctools on my phone to test.

Seems to work now. Lmk if you want to investigate that further

May be related to data truncated mid-transaction? Should not raise any error again since commit ef3148a.

Comment thread chameleonultragui/lib/helpers/hf_sniff_stateful_parser.dart
@GameTec-live GameTec-live linked an issue May 31, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

Maybe implement a context relevant parser for HF sniff frames?

3 participants