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)
This commit is contained in:
Michael Farrell 2019-04-14 20:34:29 +10:00
commit 3ae9821fc7
8 changed files with 914 additions and 1 deletions

View file

@ -58,6 +58,7 @@ endif
LUAPLATFORM = generic
ifneq (,$(findstring MINGW,$(platform)))
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 \

View file

@ -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... }"},

305
client/cmdhfksx6924.c Normal file
View file

@ -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 <inttypes.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#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, "<record 1byte HEX>", 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;
}

18
client/cmdhfksx6924.h Normal file
View file

@ -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__ */

View file

@ -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 Oils 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

View file

@ -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 <winsock2.h> // ntohl
#else
# include <arpa/inet.h> // ntohl
#endif
#include <string.h>
#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;
}

View file

@ -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 <stddef.h>
#include <stdint.h>
#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__ */

View file