From 3ae9821fc7c84c64ada66b05ef1eb3e9d52b2df5 Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Sun, 14 Apr 2019 20:34:29 +1000 Subject: [PATCH 1/2] Adds support for KS X 6924 (T-Money / Snapper+). This card builds on ISO7816-4 application primitives, and "emv" commands can be used for _some_ of the card functionality. However, there is a proprietary "get record" command (in addition to regular "get record"), and a "get balance" command. This only implements support for basic parsing the information in the FCI, and the result of the "get balance" command. No attempt has been made in this code to tell between T-Money and Snapper cards. More info: * https://github.com/micolous/metrodroid/wiki/T-Money * https://github.com/micolous/metrodroid/wiki/Snapper (includes fixups for Windows and naming) --- client/Makefile | 5 +- client/cmdhf.c | 2 + client/cmdhfksx6924.c | 305 ++++++++++++++++++++++ client/cmdhfksx6924.h | 18 ++ client/emv/emvcore.c | 1 + client/ksx6924/ksx6924core.c | 489 +++++++++++++++++++++++++++++++++++ client/ksx6924/ksx6924core.h | 95 +++++++ client/obj/ksx6924/.dummy | 0 8 files changed, 914 insertions(+), 1 deletion(-) create mode 100644 client/cmdhfksx6924.c create mode 100644 client/cmdhfksx6924.h create mode 100644 client/ksx6924/ksx6924core.c create mode 100644 client/ksx6924/ksx6924core.h create mode 100644 client/obj/ksx6924/.dummy diff --git a/client/Makefile b/client/Makefile index 2b5e9ae6b..b2e6e5599 100644 --- a/client/Makefile +++ b/client/Makefile @@ -57,7 +57,8 @@ endif LUAPLATFORM = generic ifneq (,$(findstring MINGW,$(platform))) - LUAPLATFORM = mingw + LUAPLATFORM = mingw + LDLIBS += -lws2_32 else ifeq ($(platform),Darwin) LUAPLATFORM = macosx @@ -134,6 +135,7 @@ CMDSRCS = $(SRC_SMARTCARD) \ fido/cose.c \ fido/cbortools.c \ fido/fidocore.c \ + ksx6924/ksx6924core.c \ mifare/mfkey.c \ loclass/cipher.c \ loclass/cipherutils.c \ @@ -188,6 +190,7 @@ CMDSRCS = $(SRC_SMARTCARD) \ hardnested/hardnested_bruteforce.c \ cmdhftopaz.c \ cmdhffido.c \ + cmdhfksx6924.c \ cmdhw.c \ cmdlf.c \ cmdlfawid.c \ diff --git a/client/cmdhf.c b/client/cmdhf.c index 6d25cac0a..4f68e14a5 100644 --- a/client/cmdhf.c +++ b/client/cmdhf.c @@ -21,6 +21,7 @@ #include "cmdhf14b.h" #include "cmdhf15.h" #include "cmdhfepa.h" +#include "cmdhfksx6924.h" #include "cmdhflegic.h" #include "cmdhficlass.h" #include "cmdhfmf.h" @@ -141,6 +142,7 @@ static command_t CommandTable[] = {"14b", CmdHF14B, 0, "{ ISO14443B RFIDs... }"}, {"15", CmdHF15, 1, "{ ISO15693 RFIDs... }"}, {"epa", CmdHFEPA, 0, "{ German Identification Card... }"}, + {"ksx6924", CmdHFKSX6924, 0, "{ KS X 6924 (T-Money, Snapper+) RFIDs... }"}, {"legic", CmdHFLegic, 0, "{ LEGIC RFIDs... }"}, {"iclass", CmdHFiClass, 1, "{ ICLASS RFIDs... }"}, {"mf", CmdHFMF, 1, "{ MIFARE RFIDs... }"}, diff --git a/client/cmdhfksx6924.c b/client/cmdhfksx6924.c new file mode 100644 index 000000000..9a072d17e --- /dev/null +++ b/client/cmdhfksx6924.c @@ -0,0 +1,305 @@ +// -*- mode: c; indent-tabs-mode: nil; tab-width: 3 -*- +//----------------------------------------------------------------------------- +// Copyright (C) 2019 micolous+git@gmail.com +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Commands for KS X 6924 transit cards (T-Money, Snapper+) +//----------------------------------------------------------------------------- +// This is used in T-Money (South Korea) and Snapper plus (Wellington, New +// Zealand). +// +// References: +// - https://github.com/micolous/metrodroid/wiki/T-Money (in English) +// - https://github.com/micolous/metrodroid/wiki/Snapper (in English) +// - https://kssn.net/StdKS/ks_detail.asp?k1=X&k2=6924-1&k3=4 +// (KS X 6924, only available in Korean) +// - http://www.tta.or.kr/include/Download.jsp?filename=stnfile/TTAK.KO-12.0240_%5B2%5D.pdf +// (TTAK.KO 12.0240, only available in Korean) +//----------------------------------------------------------------------------- + + +#include "cmdhfksx6924.h" + +#include +#include +#include +#include +#include +#include +#include "comms.h" +#include "cmdmain.h" +#include "util.h" +#include "ui.h" +#include "proxmark3.h" +#include "cliparser/cliparser.h" +#include "ksx6924/ksx6924core.h" +#include "emv/tlv.h" +#include "emv/apduinfo.h" +#include "cmdhf14a.h" + +static int CmdHelp(const char *Cmd); + +void getAndPrintBalance() { + uint32_t balance; + bool ret = KSX6924GetBalance(&balance); + if (!ret) { + PrintAndLog("Error getting balance"); + return; + } + + PrintAndLog("Current balance: %ld won/cents", balance); +} + +int CmdHFKSX6924Balance(const char* cmd) { + CLIParserInit("hf ksx6924 balance", + "Gets the current purse balance.\n", + "Usage:\n\thf ksx6924 balance\n"); + + void* argtable[] = { + arg_param_begin, + arg_lit0("kK", "keep", "keep field ON for next command"), + arg_lit0("aA", "apdu", "show APDU reqests and responses"), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool leaveSignalON = arg_get_lit(1); + bool APDULogging = arg_get_lit(2); + + CLIParserFree(); + SetAPDULogging(APDULogging); + + bool ret = KSX6924TrySelect(); + if (!ret) { + goto end; + } + + getAndPrintBalance(); + +end: + if (!leaveSignalON) { + DropField(); + } + return 0; +} + +int CmdHFKSX6924Info(const char *cmd) { + CLIParserInit("hf ksx6924 info", + "Get info about a KS X 6924 transit card.\nThis application is used by T-Money (South Korea) and Snapper+ (Wellington, New Zealand).\n", + "Usage:\n\thf ksx6924 info\n"); + + void* argtable[] = { + arg_param_begin, + arg_lit0("kK", "keep", "keep field ON for next command"), + arg_lit0("aA", "apdu", "show APDU reqests and responses"), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool leaveSignalON = arg_get_lit(1); + bool APDULogging = arg_get_lit(2); + + CLIParserFree(); + SetAPDULogging(APDULogging); + + // KSX6924 info + uint8_t buf[APDU_RESPONSE_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + int res = KSX6924Select(true, true, buf, sizeof(buf), &len, &sw); + + if (res) { + if (!leaveSignalON) { + DropField(); + } + return res; + } + + if (sw != 0x9000) { + if (sw) { + PrintAndLog("Not a KS X 6924 card! APDU response: %04x - %s", + sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + } else { + PrintAndLog("APDU exchange error. Card returns 0x0000."); + } + goto end; + } + + + // PrintAndLog("APDU response: %s", sprint_hex(buf, len)); + + // FCI Response is a BER-TLV, we are interested in tag 6F,B0 only. + const uint8_t* p = buf; + struct tlv fci_tag; + + while (len > 0) { + memset(&fci_tag, 0, sizeof(fci_tag)); + bool ret = tlv_parse_tl(&p, &len, &fci_tag); + + if (!ret) { + PrintAndLog("Error parsing FCI!"); + goto end; + } + + // PrintAndLog("tag %02x, len %d, value %s", + // fci_tag.tag, fci_tag.len, + // sprint_hex(p, fci_tag.len)); + + if (fci_tag.tag == 0x6f) { /* FCI template */ + break; + } else { + p += fci_tag.len; + continue; + } + } + + if (fci_tag.tag != 0x6f) { + PrintAndLog("Couldn't find tag 6F (FCI) in SELECT response"); + goto end; + } + + // We now are at Tag 6F (FCI template), get Tag B0 inside of it + while (len > 0) { + memset(&fci_tag, 0, sizeof(fci_tag)); + bool ret = tlv_parse_tl(&p, &len, &fci_tag); + + if (!ret) { + PrintAndLog("Error parsing FCI!"); + goto end; + } + + // PrintAndLog("tag %02x, len %d, value %s", + // fci_tag.tag, fci_tag.len, + // sprint_hex(p, fci_tag.len)); + + if (fci_tag.tag == 0xb0) { /* KS X 6924 purse info */ + break; + } else { + p += fci_tag.len; + continue; + } + } + + if (fci_tag.tag != 0xb0) { + PrintAndLog("Couldn't find tag B0 (KS X 6924 purse info) in FCI"); + goto end; + } + + struct ksx6924_purse_info purseInfo; + bool ret = KSX6924ParsePurseInfo(p, fci_tag.len, &purseInfo); + + if (!ret) { + PrintAndLog("Error parsing KS X 6924 purse info"); + goto end; + } + + KSX6924PrintPurseInfo(&purseInfo); + + getAndPrintBalance(); + +end: + if (!leaveSignalON) { + DropField(); + } + return 0; +} + +int CmdHFKSX6924Select(const char *cmd) { + CLIParserInit("hf ksx6924 select", + "Selects KS X 6924 application, and leaves field up.\n", + "Usage:\n\thf ksx6924 select\n"); + + void* argtable[] = { + arg_param_begin, + arg_lit0("aA", "apdu", "show APDU reqests and responses"), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool APDULogging = arg_get_lit(1); + CLIParserFree(); + SetAPDULogging(APDULogging); + + bool ret = KSX6924TrySelect(); + if (ret) { + PrintAndLog("OK"); + } else { + // Wrong app, drop field. + DropField(); + } + + return 0; +} + +int CmdHFKSX6924PRec(const char *cmd) { + CLIParserInit("hf ksx6924 prec", + "Executes proprietary read record command.\nData format is unknown. Other records are available with 'emv getrec'.\n", + "Usage:\n\thf ksx6924 prec 0b -> read proprietary record 0x0b\n"); + + void* argtable[] = { + arg_param_begin, + arg_lit0("kK", "keep", "keep field ON for next command"), + arg_lit0("aA", "apdu", "show APDU reqests and responses"), + arg_strx1(NULL, NULL, "", NULL), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, true); + + bool leaveSignalON = arg_get_lit(1); + bool APDULogging = arg_get_lit(2); + uint8_t data[APDU_RESPONSE_LEN] = {0}; + int datalen = 0; + CLIGetHexWithReturn(3, data, &datalen); + CLIParserFree(); + SetAPDULogging(APDULogging); + + if (datalen != 1) { + PrintAndLog("Record parameter must be 1 byte long (eg: 0f)"); + goto end; + } + + bool ret = KSX6924TrySelect(); + if (!ret) { + goto end; + } + + PrintAndLog("Getting record %02x...", data[0]); + uint8_t recordData[0x10]; + if (!KSX6924ProprietaryGetRecord(data[0], recordData, sizeof(recordData))) { + PrintAndLog("Error getting record"); + goto end; + } + + PrintAndLog(" %s", sprint_hex(recordData, sizeof(recordData))); + +end: + if (!leaveSignalON) { + DropField(); + } + return 0; +} + +static command_t CommandTable[] = +{ + {"help", CmdHelp, 1, "This help."}, + {"info", CmdHFKSX6924Info, 0, "Get info about a KS X 6924 (T-Money, Snapper+) transit card"}, + {"select", CmdHFKSX6924Select, 0, "Select application, and leave field up"}, + {"balance", CmdHFKSX6924Balance, 0, "Get current purse balance"}, + {"prec", CmdHFKSX6924PRec, 0, "Send proprietary get record command (CLA=90, INS=4C)"}, + {NULL, NULL, 0, NULL} +}; + +int CmdHFKSX6924(const char *Cmd) { + (void)WaitForResponseTimeout(CMD_ACK,NULL,100); + CmdsParse(CommandTable, Cmd); + return 0; +} + +int CmdHelp(const char *Cmd) { + CmdsHelp(CommandTable); + return 0; +} + diff --git a/client/cmdhfksx6924.h b/client/cmdhfksx6924.h new file mode 100644 index 000000000..ce17a124c --- /dev/null +++ b/client/cmdhfksx6924.h @@ -0,0 +1,18 @@ +// -*- mode: c; indent-tabs-mode: nil; tab-width: 3 -*- +//----------------------------------------------------------------------------- +// Copyright (C) 2019 micolous+git@gmail.com +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Commands for KS X 6924 transit cards (T-Money, Snapper+) +//----------------------------------------------------------------------------- + +#ifndef CMDHFKSX6924_H__ +#define CMDHFKSX6924_H__ + +extern int CmdHFKSX6924(const char *Cmd); + + +#endif /* CMDHFKSX6924_H__ */ diff --git a/client/emv/emvcore.c b/client/emv/emvcore.c index 46fc1b1e9..59b984c13 100644 --- a/client/emv/emvcore.c +++ b/client/emv/emvcore.c @@ -132,6 +132,7 @@ static const TAIDList AIDlist [] = { { CV_OTHER, "A0000006723020" }, // TROY - Turkey - TROY chip debit card - Turkey's Payment Method { CV_OTHER, "A0000007705850" }, // Indian Oil Corporation Limited - India - XTRAPOWER Fleet Card Program - Indian Oil’s Pre Paid Program { CV_OTHER, "D27600002545500100" }, // ZKA - Germany - Girocard - ZKA Girocard (Geldkarte) (Germany) + { CV_OTHER, "D4100000030001" }, // KS X 6924 (T-Money, South Korea and Snapper+, Wellington, New Zealand) { CV_OTHER, "D5280050218002" }, // The Netherlands - ? - (Netherlands) { CV_OTHER, "D5780000021010" }, // Bankaxept Norway Bankaxept Norwegian domestic debit card { CV_OTHER, "F0000000030001" }, // BRADESCO - Brazilian Bank Banco Bradesco diff --git a/client/ksx6924/ksx6924core.c b/client/ksx6924/ksx6924core.c new file mode 100644 index 000000000..c678f1c11 --- /dev/null +++ b/client/ksx6924/ksx6924core.c @@ -0,0 +1,489 @@ +// -*- mode: c; indent-tabs-mode: nil; tab-width: 3 -*- +//----------------------------------------------------------------------------- +// Copyright (C) 2019 micolous+git@gmail.com +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// KS X 6924 (T-Money, Snapper+) protocol implementation +//----------------------------------------------------------------------------- +// This is used in T-Money (South Korea) and Snapper plus (Wellington, New +// Zealand). +// +// References: +// - https://github.com/micolous/metrodroid/wiki/T-Money (in English) +// - https://github.com/micolous/metrodroid/wiki/Snapper (in English) +// - https://kssn.net/en/search/stddetail.do?itemNo=K001010104929 +// (KS X 6924, only available in Korean) +// - http://www.tta.or.kr/include/Download.jsp?filename=stnfile/TTAK.KO-12.0240_%5B2%5D.pdf +// (TTAK.KO 12.0240, only available in Korean) +//----------------------------------------------------------------------------- + +#include "ksx6924core.h" +#ifdef _WIN32 +# include // ntohl +#else +# include // ntohl +#endif +#include +#include "emv/emvcore.h" +#include "emv/apduinfo.h" +#include "emv/dump.h" +#include "fido/fidocore.h" // FIDOExchange +#include "protocols.h" +#include "ui.h" +#include "util.h" +#include "usb_cmd.h" + +// Date type. This is the actual on-card format. +typedef struct { + uint8_t year[2]; // bcd + uint8_t month[1]; // bcd + uint8_t day[1]; // bcd +} PACKED _ksx6924_internal_date_t; + +// Purse information (FCI tag b0). This is the actual on-card format. +typedef struct { + uint8_t cardType; + uint8_t alg; + uint8_t vk; + uint8_t idCenter; + uint8_t csn[8]; // bcd + uint8_t idtr[5]; // bcd + _ksx6924_internal_date_t issueDate; + _ksx6924_internal_date_t expiryDate; + uint8_t userCode; + uint8_t disRate; + uint8_t balMax[4]; // uint32_t big-endian + uint8_t bra[2]; // bcd + uint8_t mmax[4]; // uint32_t big-endian + uint8_t tcode; + uint8_t ccode; + uint8_t rfu[8]; +} PACKED _ksx6924_internal_purse_info_t; + +// Declares a structure for simple enums. +#define MAKE_ENUM_TYPE(KEY_TYPE) \ + struct _ksx6924_enum_ ## KEY_TYPE { \ + KEY_TYPE key; \ + const char *value; \ + }; \ + static int _ksx6924_ ## KEY_TYPE ## _enum_compare( \ + const void *a, const void *b) { \ + const KEY_TYPE *needle = a; \ + const struct _ksx6924_enum_ ## KEY_TYPE *candidate = b; \ + return (*needle) - (candidate->key); \ + } + +// Declares a enum, and builds a KSX6924Lookup* function to point to it. +#define MAKE_ENUM_CONST(NAME, KEY_TYPE, VALS...) \ + static const struct _ksx6924_enum_ ## KEY_TYPE KSX6924_ENUM_ ## NAME [] = { \ + VALS \ + }; \ + const char* KSX6924Lookup ## NAME ( \ + KEY_TYPE key, const char* defaultValue) { \ + struct _ksx6924_enum_ ## KEY_TYPE *r = bsearch( \ + &key, KSX6924_ENUM_ ## NAME, \ + sizeof(KSX6924_ENUM_ ## NAME) / sizeof(KSX6924_ENUM_ ## NAME [0]), \ + sizeof(KSX6924_ENUM_ ## NAME [0]), \ + _ksx6924_ ## KEY_TYPE ## _enum_compare); \ + if (r == NULL) { \ + return defaultValue; \ + } \ + return r->value; \ + } + +MAKE_ENUM_TYPE(uint8_t); + +// KSX6924LookupCardType +MAKE_ENUM_CONST(CardType, uint8_t, + { 0x00, "Pre-paid" }, + { 0x10, "Post-pay" }, + { 0x20, "Mobile post-pay" }, +); + +// KSX6924LookupAlg +MAKE_ENUM_CONST(Alg, uint8_t, + { 0x00, "SEED" }, + { 0x10, "3DES" }, +); + +// KSX6924LookupTMoneyIDCenter +MAKE_ENUM_CONST(TMoneyIDCenter, uint8_t, + { 0x00, "reserved" }, + { 0x01, "Korea Financial Telecommunications and Clearings Institute" }, + { 0x02, "A-Cash ?? 에이캐시" }, // FIXME: translation + { 0x03, "Mybi" }, + + { 0x05, "V-Cash ?? 브이캐시" }, // FIXME: translation + { 0x06, "Mondex Korea" }, + { 0x07, "Korea Expressway Corporation" }, + { 0x08, "Korea Smart Card Corporation" }, + { 0x09, "KORAIL Networks" }, + + { 0x0b, "EB Card Corporation" }, + { 0x0c, "Seoul Bus Transport Association" }, + { 0x0d, "Cardnet" }, +); + +// KSX6924LookupTMoneyUserCode +MAKE_ENUM_CONST(TMoneyUserCode, uint8_t, + { 0x01, "Regular/normal" }, + { 0x02, "Child" }, + + { 0x04, "Youth" }, + + { 0x06, "Route ?? 경로" }, // FIXME: "route" doesn't make sense, documentation error? + + { 0x0f, "Test" }, + { 0xff, "Inactive" }, +); + +// KSX6924LookupTMoneyDisRate +MAKE_ENUM_CONST(TMoneyDisRate, uint8_t, + { 0x00, "No discount" }, + + { 0x10, "Disabled, basic" }, + { 0x11, "Disabled, companion" }, + + { 0x20, "Well, basic ?? 유공, 기본" }, // FIXME: "well" doesn't make sense? + { 0x21, "Well, companion ?? 유공, 동반 무임" }, // FIXME: "well" doesn't make sense? +); + +// KSX6924LookupTMoneyTCode +MAKE_ENUM_CONST(TMoneyTCode, uint8_t, + { 0x00, "None" }, + { 0x01, "SK Telecom" }, + { 0x02, "Korea Telecom" }, + { 0x03, "LG Uplus" }, +); + +// KSX6924LookupTMoneyCCode +MAKE_ENUM_CONST(TMoneyCCode, uint8_t, + { 0x00, "None" }, + { 0x01, "KB Kookmin Bank" }, + { 0x02, "Nonghyup Bank" }, + { 0x03, "Lotte Card" }, + { 0x04, "BC Card" }, + { 0x05, "Samsung Card" }, + { 0x06, "Shinhan Bank" }, + { 0x07, "Citibank Korea" }, + { 0x08, "Korea Exchange Bank" }, + { 0x09, "우리" }, // FIXME: translation + { 0x0a, "Hana SK Card" }, + { 0x0b, "Hyundai Capital Services" }, +); + +static const char* KSX6924_UNKNOWN = "Unknown"; + +/** + * Converts a single byte in binary-coded decimal format to an integer. + * + * Expected return values are between 0-99 inclusive. + * + * Returns -1 on invalid input. + * + * Examples: + * bcdToInteger(0x35) = 35 (decimal) + * bcdToInteger(0x58) = 58 (decimal) + * bcdToInteger(0xf4) = -1 (invalid) + */ +int16_t bcdToInteger(const uint8_t i) { + uint16_t high = ((i & 0xf0) >> 4) * 10; + uint16_t low = (i & 0xf); + + if (high >= 100 || low >= 10) { + // Invalid + return -1; + } + + return high + low; +} + + +/** + * Converts multiple bytes in binary-coded decimal format to an integer. + * + * Expected return values are 0-(100^len). + * + * Returns -1 on invalid input. + * + * Example: + * bcdToLong({0x12, 0x34}, 2) = 1234 (decimal) + */ +int64_t bcdToLong(const uint8_t *buf, size_t len) { + int64_t o = 0; + for (int i = 0; i < len; i++) { + int16_t t = bcdToInteger(buf[i]); + if (t < 0) { + // invalid + return -1; + } + + o = (o * 100) + t; + } + + return o; +} + + +/** + * Converts a date from on-card format to ksx6924_date format. + */ +bool convertInternalDate( + const _ksx6924_internal_date_t i, struct ksx6924_date* ret) { + int64_t year = bcdToLong(i.year, 2); + int16_t month = bcdToInteger(i.month[0]); + int16_t day = bcdToInteger(i.day[0]); + + if (year < 0 || year > 0xffff || month < 0 || day < 0) { + goto fail; + } + + ret->year = year & 0xffff; + ret->month = month; + ret->day = day; + return true; + +fail: + memset(ret, 0, sizeof(struct ksx6924_date)); + return false; +} + + +/** + * Parses purse info in FCI tag b0 + */ +bool KSX6924ParsePurseInfo(const uint8_t *purseInfo, size_t purseLen, + struct ksx6924_purse_info* ret) { + if (purseLen != sizeof(_ksx6924_internal_purse_info_t)) { + // Invalid size! + PrintAndLog("Expected %ld bytes, got %ld\n", + sizeof(_ksx6924_internal_purse_info_t), purseLen); + goto fail; + } + + const _ksx6924_internal_purse_info_t* internalPurseInfo = (const _ksx6924_internal_purse_info_t*)purseInfo; + + memset(ret, 0, sizeof(struct ksx6924_purse_info)); + + // Simple copies + ret->cardType = internalPurseInfo->cardType; + ret->alg = internalPurseInfo->alg; + ret->vk = internalPurseInfo->vk; + ret->idCenter = internalPurseInfo->idCenter; + ret->userCode = internalPurseInfo->userCode; + ret->disRate = internalPurseInfo->disRate; + ret->tcode = internalPurseInfo->tcode; + ret->ccode = internalPurseInfo->ccode; + + // Fields that need rewriting + hex_to_buffer(ret->csn, internalPurseInfo->csn, + sizeof(internalPurseInfo->csn), sizeof(ret->csn) - 1, + /* min_str_len */ 0, /* spaces_between */ 0, + /* uppercase */ false); + + int64_t idtr = bcdToLong(internalPurseInfo->idtr, 5); + if (idtr < 0) { + idtr = 0; // fail + } + ret->idtr = idtr; + + int64_t bra = bcdToLong(internalPurseInfo->bra, 2); + if (bra < 0) { + bra = 0; // fail + } + ret->bra = bra & 0xffff; + + convertInternalDate(internalPurseInfo->issueDate, &(ret->issueDate)); + convertInternalDate(internalPurseInfo->expiryDate, &(ret->expiryDate)); + + ret->balMax = ntohl(*(uint32_t*)(internalPurseInfo->balMax)); + ret->mmax = ntohl(*(uint32_t*)(internalPurseInfo->mmax)); + memcpy(&ret->rfu, &internalPurseInfo->rfu, 8); + + // TODO + return true; + +fail: + memset(ret, 0, sizeof(struct ksx6924_purse_info)); + return false; +}; + +/** + * Prints out a ksx6924_purse_info + */ +void KSX6924PrintPurseInfo(const struct ksx6924_purse_info *purseInfo) { + if (purseInfo == NULL) { + return; + } + + PrintAndLog("## KS X 6924 Purse Info:"); + PrintAndLog(""); + PrintAndLog("cardType .............................. %02x (%s)", + purseInfo->cardType, + KSX6924LookupCardType(purseInfo->cardType, KSX6924_UNKNOWN)); + PrintAndLog("alg (encryption algorithm) ............ %02x (%s)", + purseInfo->alg, + KSX6924LookupAlg(purseInfo->alg, KSX6924_UNKNOWN)); + PrintAndLog("vk (keyset version) ................... %02x", + purseInfo->vk); + PrintAndLog("idCenter (issuer ID) .................. %02x (%s)", + purseInfo->idCenter, + KSX6924LookupTMoneyIDCenter(purseInfo->idCenter, KSX6924_UNKNOWN)); + PrintAndLog("csn (card number) ..................... %s", + purseInfo->csn); + PrintAndLog("idtr (card usage authentication ID) ... %i", + purseInfo->idtr); + PrintAndLog("issueDate ............................. %04i-%02i-%02i", + purseInfo->issueDate.year, purseInfo->issueDate.month, purseInfo->issueDate.day); + PrintAndLog("expiryDate ............................ %04i-%02i-%02i", + purseInfo->expiryDate.year, purseInfo->expiryDate.month, purseInfo->expiryDate.day); + PrintAndLog("userCode (ticket type) ................ %02x (%s)", + purseInfo->userCode, + KSX6924LookupTMoneyUserCode(purseInfo->userCode, KSX6924_UNKNOWN)); + PrintAndLog("disRate (discount type) ............... %02x (%s)", + purseInfo->disRate, + KSX6924LookupTMoneyDisRate(purseInfo->disRate, KSX6924_UNKNOWN)); + PrintAndLog("balMax (in won/cents) ................. %ld", + purseInfo->balMax); + PrintAndLog("bra (branch code) ..................... %04x", + purseInfo->bra); + PrintAndLog("mmax (one-time transaction limit) ..... %ld", + purseInfo->mmax); + PrintAndLog("tcode (telecom carrier ID) ............ %02x (%s)", + purseInfo->tcode, + KSX6924LookupTMoneyTCode(purseInfo->tcode, KSX6924_UNKNOWN)); + PrintAndLog("ccode (credit card company ID) ........ %02x (%s)", + purseInfo->ccode, + KSX6924LookupTMoneyCCode(purseInfo->ccode, KSX6924_UNKNOWN)); + PrintAndLog("rfu (reserved) ........................ %s", + sprint_hex(purseInfo->rfu, sizeof(purseInfo->rfu))); + PrintAndLog(""); +} + + +/** + * Selects the KS X 6924 Application, D4100000030001, and returns the response + * data. + */ +int KSX6924Select( + bool ActivateField, bool LeaveFieldON, + uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + uint8_t aid[] = {0xD4, 0x10, 0x00, 0x00, 0x03, 0x00, 0x01}; + + return EMVSelect(ECC_CONTACTLESS, ActivateField, LeaveFieldON, aid, + sizeof(aid), Result, MaxResultLen, ResultLen, sw, NULL); +} + + +/** + * Selects the KS X 6924 Application. Returns true if selected successfully. + */ +bool KSX6924TrySelect() { + uint8_t buf[APDU_RESPONSE_LEN] = {0}; + size_t len = 0; + uint16_t sw = 0; + int res = KSX6924Select(true, true, buf, sizeof(buf), &len, &sw); + + if (res) { + DropField(); + return false; + } + + if (sw != 0x9000) { + if (sw) { + PrintAndLog("Not a KS X 6924 card! APDU response: %04x - %s", + sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff)); + } else { + PrintAndLog("APDU exchange error. Card returns 0x0000."); + } + return false; + } + + return true; +} + + +/** + * Gets the balance from a KS X 6924 card. + */ +bool KSX6924GetBalance(uint32_t* result) { + if (result == NULL) { + return false; + } + + uint16_t sw; + size_t result_len; + uint8_t rawResult[4 /* message */ + 2 /* sw */]; + uint8_t apdu[] = {0x90, 0x4c, 0x00, 0x00, 4}; + int res = FIDOExchange(apdu, sizeof(apdu), + rawResult, sizeof(rawResult), + &result_len, &sw); + if (res) { + // error communicating + goto fail; + } + + if (sw != 0x9000) { + // card returned error + goto fail; + } + + *result = ntohl(*(uint32_t*)(rawResult)); + return true; + +fail: + *result = 0; + return false; +} + + +/** + * Issues a proprietary "get record" command (CLA=90, INS=4C). + * + * The function of these records is not known, but they are present on KS X + * 6924 cards and used by the official mobile apps. + * + * result must be a buffer of 16 bytes. The card will only respond to 16 byte + * requests. + * + * Returns false on error. + */ +bool KSX6924ProprietaryGetRecord(uint8_t id, uint8_t* result, + size_t resultLen) { + if (result == NULL) { + return false; + } + memset(result, 0, resultLen); + + uint16_t sw; + size_t result_len; + + uint8_t rawResult[resultLen /* message */ + 2 /* sw */]; + memset(rawResult, 0, sizeof(rawResult)); + + uint8_t apdu[] = {0x90, 0x78, id, 0x00, resultLen}; + int res = FIDOExchange(apdu, sizeof(apdu), + rawResult, sizeof(rawResult), + &result_len, &sw); + if (res) { + // error communicating + goto fail; + } + + if (sw != 0x9000) { + // card returned error + goto fail; + } + + // Copy into the actual result buffer + memcpy(result, rawResult, resultLen); + return true; + +fail: + memset(result, 0, resultLen); + return false; +} + diff --git a/client/ksx6924/ksx6924core.h b/client/ksx6924/ksx6924core.h new file mode 100644 index 000000000..35326c9b9 --- /dev/null +++ b/client/ksx6924/ksx6924core.h @@ -0,0 +1,95 @@ +// -*- mode: c; indent-tabs-mode: nil; tab-width: 3 -*- +//----------------------------------------------------------------------------- +// Copyright (C) 2019 micolous+git@gmail.com +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// KS X 6924 (T-Money, Snapper+) protocol implementation +//----------------------------------------------------------------------------- + +#ifndef __KSX6924CORE_H__ +#define __KSX6924CORE_H__ + +#include +#include +#include "cmdhf14a.h" +#include "emv/emvcore.h" + +// Convenience structure for representing a date. Actual on-card format is in +// _ksx6924_internal_date_t. +struct ksx6924_date { + uint16_t year; + uint8_t month; + uint8_t day; +}; + +// Convenience structure for representing purse information. Actual on-card +// format is in _ksx6924_internal_purse_info_t. +struct ksx6924_purse_info { + uint8_t cardType; + uint8_t alg; + uint8_t vk; + uint8_t idCenter; + uint8_t csn[17]; // hex digits + null terminator + uint64_t idtr; + struct ksx6924_date issueDate; + struct ksx6924_date expiryDate; + uint8_t userCode; + uint8_t disRate; + uint32_t balMax; + uint16_t bra; + uint32_t mmax; + uint8_t tcode; + uint8_t ccode; + uint8_t rfu[8]; +}; + +// Get card type description +const char* KSX6924LookupCardType(uint8_t key, const char* defaultValue); + +// Get encryption algorithm description +const char* KSX6924LookupAlg(uint8_t key, const char* defaultValue); + +// Get IDCenter (issuer ID) description +const char* KSX6924LookupTMoneyIDCenter(uint8_t key, const char* defaultValue); + +// Get UserCode (ticket type) description +const char* KSX6924LookupTMoneyUserCode(uint8_t key, const char* defaultValue); + +// Get DisRate (discount type) description +const char* KSX6924LookupTMoneyDisRate(uint8_t key, const char* defaultValue); + +// Get TCode (telecom carrier ID) description +const char* KSX6924LookupTMoneyTCode(uint8_t key, const char* defaultValue); + +// Get CCode (credit card company ID) description +const char* KSX6924LookupTMoneyCCode(uint8_t key, const char* defaultValue); + +// Parses purse info in FCI tag b0 +bool KSX6924ParsePurseInfo( + const uint8_t *purseInfo, size_t purseLen, struct ksx6924_purse_info* ret); + +// Prints out a ksx6924_purse_info +void KSX6924PrintPurseInfo(const struct ksx6924_purse_info *purseInfo); + +// Selects the KS X 6924 application, returns all information +int KSX6924Select( + bool ActivateField, bool LeaveFieldON, + uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw); + +// Selects the KS X 6924 application, returns true on success +bool KSX6924TrySelect(); + +// Gets the balance from a KS X 6924 card. Application must be already +// selected. +bool KSX6924GetBalance(uint32_t* result); + +// Proprietary get record command. Function unknown. +// result must be 10 bytes long. +bool KSX6924ProprietaryGetRecord( + uint8_t id, uint8_t* result, size_t resultLen); + +#endif /* __KSX6924CORE_H__ */ + diff --git a/client/obj/ksx6924/.dummy b/client/obj/ksx6924/.dummy new file mode 100644 index 000000000..e69de29bb From 8a3c48ba2d8cd37cd1742c1f4e3680cdd908b044 Mon Sep 17 00:00:00 2001 From: Michael Farrell Date: Wed, 15 May 2019 18:35:34 -0400 Subject: [PATCH 2/2] ksx6924: add note about cashbee --- client/ksx6924/ksx6924core.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ksx6924/ksx6924core.c b/client/ksx6924/ksx6924core.c index c678f1c11..fa4a60d01 100644 --- a/client/ksx6924/ksx6924core.c +++ b/client/ksx6924/ksx6924core.c @@ -371,7 +371,12 @@ void KSX6924PrintPurseInfo(const struct ksx6924_purse_info *purseInfo) { int KSX6924Select( bool ActivateField, bool LeaveFieldON, uint8_t *Result, size_t MaxResultLen, size_t *ResultLen, uint16_t *sw) { + // T-Money + Snapper uint8_t aid[] = {0xD4, 0x10, 0x00, 0x00, 0x03, 0x00, 0x01}; + + // Cashbee + //uint8_t aid[] = {0xD4, 0x10, 0x00, 0x00, 0x14, 0x00, 0x01}; + return EMVSelect(ECC_CONTACTLESS, ActivateField, LeaveFieldON, aid, sizeof(aid), Result, MaxResultLen, ResultLen, sw, NULL);