From fd9d07506c081490e8b2f34d946aef1178a9616a Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Tue, 24 Mar 2026 18:32:51 +0200 Subject: [PATCH 1/2] Add support for 'ResponseContains' app matching for 'aidlist.json' --- client/src/aidsearch.c | 170 ++++++++++++++++++++++++++++++++--------- client/src/aidsearch.h | 2 + client/src/cmdhf14a.c | 13 +++- client/src/cmdhf14b.c | 5 +- doc/aidlist.md | 16 ++++ 5 files changed, 168 insertions(+), 38 deletions(-) diff --git a/client/src/aidsearch.c b/client/src/aidsearch.c index 98607d199..65810c0b6 100644 --- a/client/src/aidsearch.c +++ b/client/src/aidsearch.c @@ -20,6 +20,7 @@ #include #include "fileutils.h" #include "pm3_cmd.h" +#include "util.h" static int openAIDFile(json_t **root, bool verbose) { json_error_t error; @@ -111,6 +112,27 @@ static bool aidCompare(const char *aidlarge, const char *aidsmall) { return false; } +static bool ResponseContainsMatch(const char *needle, const char *text) { + if (needle == NULL || text == NULL || needle[0] == '\0' || text[0] == '\0') { + return false; + } + + char *needle_lc = str_dup(needle); + char *text_lc = str_dup(text); + if (needle_lc == NULL || text_lc == NULL) { + free(needle_lc); + free(text_lc); + return false; + } + + str_lower(needle_lc); + str_lower(text_lc); + bool matched = (strstr(text_lc, needle_lc) != NULL); + free(needle_lc); + free(text_lc); + return matched; +} + bool AIDGetFromElm(json_t *data, uint8_t *aid, size_t aidmaxlen, int *aidlen) { *aidlen = 0; const char *hexaid = jsonStrGet(data, "AID"); @@ -124,7 +146,49 @@ bool AIDGetFromElm(json_t *data, uint8_t *aid, size_t aidmaxlen, int *aidlen) { return true; } +bool AIDSeenBefore(json_t *root, const uint8_t *aid, size_t aidlen, size_t before_index) { + if (root == NULL || aid == NULL || aidlen == 0) { + return false; + } + + size_t limit = before_index; + if (limit > json_array_size(root)) { + limit = json_array_size(root); + } + + for (size_t i = 0; i < limit; i++) { + json_t *data = AIDSearchGetElm(root, i); + if (data == NULL) { + continue; + } + + uint8_t prev_aid[200] = {0}; + int prev_aid_len = 0; + if ((AIDGetFromElm(data, prev_aid, sizeof(prev_aid), &prev_aid_len) == false) || (prev_aid_len <= 0)) { + continue; + } + + if ((size_t)prev_aid_len == aidlen && memcmp(prev_aid, aid, aidlen) == 0) { + return true; + } + } + + return false; +} + int PrintAIDDescription(json_t *xroot, char *aid, bool verbose) { + return PrintAIDDescriptionEx(xroot, aid, NULL, 0, verbose); +} + +int PrintAIDDescriptionBuf(json_t *root, uint8_t *aid, size_t aidlen, bool verbose) { + return PrintAIDDescription(root, sprint_hex_inrow(aid, aidlen), verbose); +} + +int PrintAIDDescriptionEx(json_t *xroot, char *aid, const uint8_t *response, size_t response_len, bool verbose) { + if (aid == NULL || aid[0] == '\0') { + return PM3_SUCCESS; + } + int retval = PM3_SUCCESS; json_t *root = xroot; @@ -135,50 +199,89 @@ int PrintAIDDescription(json_t *xroot, char *aid, bool verbose) { goto out; } - json_t *elm = NULL; + char *response_hex = NULL; + if (response != NULL && response_len > 0) { + if (response_len > ((SIZE_MAX - 1) / 2)) { + goto out; + } + size_t response_hexlen = (response_len * 2) + 1; + response_hex = calloc(response_hexlen, sizeof(char)); + if (response_hex == NULL) { + goto out; + } + hex_to_buffer((uint8_t *)response_hex, response, response_len, response_hexlen - 1, 0, 0, true); + } + + json_t *fallback_elm = NULL; + json_t *contains_elm = NULL; size_t maxaidlen = 0; + for (size_t elmindx = 0; elmindx < json_array_size(root); elmindx++) { json_t *data = AIDSearchGetElm(root, elmindx); if (data == NULL) { continue; } + const char *dictaid = jsonStrGet(data, "AID"); - if (aidCompare(aid, dictaid)) { // dictaid may be less length than requested aid - if (maxaidlen < strlen(dictaid) && strlen(dictaid) <= strlen(aid)) { - maxaidlen = strlen(dictaid); - elm = data; + if (dictaid == NULL) { + continue; + } + + if (!aidCompare(aid, dictaid)) { // dictaid may be less length than requested aid + continue; + } + + size_t dictaidlen = strlen(dictaid); + if (dictaidlen > strlen(aid)) { + continue; + } + + if (dictaidlen > maxaidlen) { + maxaidlen = dictaidlen; + fallback_elm = data; + contains_elm = NULL; + } else if (dictaidlen < maxaidlen) { + continue; + } + + if (response_hex != NULL) { + const char *response_contains = jsonStrGet(data, "ResponseContains"); + if (response_contains && ResponseContainsMatch(response_contains, response_hex)) { + contains_elm = data; } } } - if (elm == NULL) { - goto out; + json_t *elm = contains_elm ? contains_elm : fallback_elm; + if (elm != NULL) { + const char *vaid = jsonStrGet(elm, "AID"); + const char *vendor = jsonStrGet(elm, "Vendor"); + const char *name = jsonStrGet(elm, "Name"); + const char *country = jsonStrGet(elm, "Country"); + const char *description = jsonStrGet(elm, "Description"); + const char *type = jsonStrGet(elm, "Type"); + + if (verbose == false) { + PrintAndLogEx(SUCCESS, "AID : " _YELLOW_("%s") " | %s | %s", vaid, vendor, name); + } else { + PrintAndLogEx(SUCCESS, "Input AID..... " _YELLOW_("%s"), aid); + if (aid) + PrintAndLogEx(SUCCESS, "Found AID..... " _YELLOW_("%s"), vaid); + if (vendor) + PrintAndLogEx(SUCCESS, "Vendor........ " _YELLOW_("%s"), vendor); + if (type) + PrintAndLogEx(SUCCESS, "Type.......... " _YELLOW_("%s"), type); + if (name) + PrintAndLogEx(SUCCESS, "Name.......... " _YELLOW_("%s"), name); + if (country) + PrintAndLogEx(SUCCESS, "Country....... %s", country); + if (description) + PrintAndLogEx(SUCCESS, "Description... %s", description); + } } - // print here - const char *vaid = jsonStrGet(elm, "AID"); - const char *vendor = jsonStrGet(elm, "Vendor"); - const char *name = jsonStrGet(elm, "Name"); - const char *country = jsonStrGet(elm, "Country"); - const char *description = jsonStrGet(elm, "Description"); - const char *type = jsonStrGet(elm, "Type"); - - if (verbose == false) { - PrintAndLogEx(SUCCESS, "AID : " _YELLOW_("%s") " | %s | %s", vaid, vendor, name); - } else { - PrintAndLogEx(SUCCESS, "Input AID..... " _YELLOW_("%s"), aid); - if (aid) - PrintAndLogEx(SUCCESS, "Found AID..... " _YELLOW_("%s"), vaid); - if (vendor) - PrintAndLogEx(SUCCESS, "Vendor........ " _YELLOW_("%s"), vendor); - if (type) - PrintAndLogEx(SUCCESS, "Type.......... " _YELLOW_("%s"), type); - if (name) - PrintAndLogEx(SUCCESS, "Name.......... " _YELLOW_("%s"), name); - if (country) - PrintAndLogEx(SUCCESS, "Country....... %s", country); - if (description) - PrintAndLogEx(SUCCESS, "Description... %s", description); + if (response_hex != NULL) { + free(response_hex); } out: @@ -187,8 +290,3 @@ out: } return retval; } - -int PrintAIDDescriptionBuf(json_t *root, uint8_t *aid, size_t aidlen, bool verbose) { - return PrintAIDDescription(root, sprint_hex_inrow(aid, aidlen), verbose); -} - diff --git a/client/src/aidsearch.h b/client/src/aidsearch.h index b394d43df..ab09bbbf7 100644 --- a/client/src/aidsearch.h +++ b/client/src/aidsearch.h @@ -26,10 +26,12 @@ #include "jansson.h" int PrintAIDDescription(json_t *xroot, char *aid, bool verbose); +int PrintAIDDescriptionEx(json_t *xroot, char *aid, const uint8_t *response, size_t response_len, bool verbose); int PrintAIDDescriptionBuf(json_t *root, uint8_t *aid, size_t aidlen, bool verbose); json_t *AIDSearchInit(bool verbose); json_t *AIDSearchGetElm(json_t *root, size_t elmindx); bool AIDGetFromElm(json_t *data, uint8_t *aid, size_t aidmaxlen, int *aidlen); +bool AIDSeenBefore(json_t *root, const uint8_t *aid, size_t aidlen, size_t before_index); int AIDSearchFree(json_t *root); #endif diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index 122c182a1..95cbe8784 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -3182,6 +3182,9 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { if ((AIDGetFromElm(data, vaid, sizeof(vaid), &vaidlen) == false) || (vaidlen == 0)) { continue; } + if (AIDSeenBefore(root, vaid, (size_t)vaidlen, elmindx)) { + continue; + } uint16_t sw = 0; uint8_t result[1024] = {0}; @@ -3214,7 +3217,15 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { if (verbose) PrintAndLogEx(WARNING, "Application ( " _RED_("blocked") " )"); } - PrintAIDDescriptionBuf(root, vaid, vaidlen, verbose); + uint8_t response_for_match[1026] = {0}; + size_t response_for_match_len = resultlen; + memcpy(response_for_match, result, MIN(resultlen, sizeof(response_for_match))); + if ((response_for_match_len + 2) <= sizeof(response_for_match)) { + response_for_match[response_for_match_len] = sw >> 8; + response_for_match[response_for_match_len + 1] = sw & 0xff; + response_for_match_len += 2; + } + PrintAIDDescriptionEx(root, sprint_hex_inrow(vaid, vaidlen), response_for_match, response_for_match_len, verbose); if (dfnamelen) { if (dfnamelen == vaidlen) { diff --git a/client/src/cmdhf14b.c b/client/src/cmdhf14b.c index b605f5c7d..46ef875f6 100644 --- a/client/src/cmdhf14b.c +++ b/client/src/cmdhf14b.c @@ -122,6 +122,9 @@ static void hf14b_aid_search(bool verbose) { if ((AIDGetFromElm(data, vaid, sizeof(vaid), &vaidlen) == false) || (vaidlen == 0)) { continue; } + if (AIDSeenBefore(root, vaid, (size_t)vaidlen, elmindx)) { + continue; + } // COMPUTE APDU @@ -172,7 +175,7 @@ static void hf14b_aid_search(bool verbose) { } } - PrintAIDDescriptionBuf(root, vaid, vaidlen, verbose); + PrintAIDDescriptionEx(root, sprint_hex_inrow(vaid, vaidlen), result, (size_t)resultlen, verbose); if (dfnamelen) { if (dfnamelen == vaidlen) { diff --git a/doc/aidlist.md b/doc/aidlist.md index 00dec60ca..800644a4b 100644 --- a/doc/aidlist.md +++ b/doc/aidlist.md @@ -15,6 +15,9 @@ Each entry in `client/resources/aidlist.json` must contain all of the fields bel - `Description`: Extra context, disambiguation, references, legacy naming, known usage notes, or deployment-specific remarks. - `Type`: High-level category tag (for example `transport`, `emv`, `gp`, `pacs`, `ndef`). +## Optional fields +- `ResponseContains`: Case-insensitive hex substring matched against the APDU SELECT response (encoded as hex without separators). Use this field when multiple protocols share the same AID and can be distinguished by response content. + Example: ```json { @@ -26,3 +29,16 @@ Example: "Type": "transport" } ``` + +Response-disambiguation example: +```json +{ + "AID": "4F53452E5641532E3031", + "Vendor": "Google", + "Country": "", + "Name": "Google Smart Tap (OSE.VAS.01)", + "Description": "Google Smart Tap", + "Type": "loyalty", + "ResponseContains": "500a416e64726f6964506179" +} +``` From 027ad20510b2af25ee634195c1d1834e26f285cd Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Tue, 24 Mar 2026 18:33:19 +0200 Subject: [PATCH 2/2] Add new mobile-wallet-related entries to 'aidlist.json' --- client/resources/aidlist.json | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/client/resources/aidlist.json b/client/resources/aidlist.json index 519557ed0..eb337dbf8 100644 --- a/client/resources/aidlist.json +++ b/client/resources/aidlist.json @@ -2217,11 +2217,30 @@ }, { "AID": "4F53452E5641532E3031", - "Vendor": "Apple, Google", + "Vendor": "Apple", "Country": "", - "Name": "Value-Added Services (OSE.VAS.01)", - "Description": "Used by Apple VAS and Google SmartTap", - "Type": "loyalty" + "Name": "Apple VAS (OSE.VAS.01)", + "Description": "Apple VAS", + "Type": "loyalty", + "ResponseContains": "50084170706c65506179" + }, + { + "AID": "4F53452E5641532E3031", + "Vendor": "Google", + "Country": "", + "Name": "Google Smart Tap (OSE.VAS.01)", + "Description": "Google Smart Tap", + "Type": "loyalty", + "ResponseContains": "500a416e64726f6964506179" + }, + { + "AID": "4F53452E5641532E3031", + "Vendor": "Samsung", + "Country": "", + "Name": "Samsung VAS (OSE.VAS.01)", + "Description": "Samsung VAS", + "Type": "loyalty", + "ResponseContains": "500d53616d73756e6757616c6c6574" }, { "AID": "A000000476D0000101", @@ -2239,6 +2258,14 @@ "Description": "", "Type": "loyalty" }, + { + "AID": "A000000870FC000000000034", + "Vendor": "Samsung", + "Country": "", + "Name": "Samsung VAS", + "Description": "", + "Type": "loyalty" + }, { "AID": "A0000002480400", "Vendor": "ISO/IEC JTC1/SC17",