mirror of
https://github.com/Proxmark/proxmark3.git
synced 2025-07-16 02:03:00 -07:00
Merge 8a3c48ba2d
into 6116334485
This commit is contained in:
commit
e487809711
8 changed files with 919 additions and 1 deletions
|
@ -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 \
|
||||
|
|
|
@ -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
305
client/cmdhfksx6924.c
Normal 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
18
client/cmdhfksx6924.h
Normal 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__ */
|
|
@ -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
|
||||
|
|
494
client/ksx6924/ksx6924core.c
Normal file
494
client/ksx6924/ksx6924core.c
Normal file
|
@ -0,0 +1,494 @@
|
|||
// -*- 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) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
95
client/ksx6924/ksx6924core.h
Normal file
95
client/ksx6924/ksx6924core.h
Normal 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__ */
|
||||
|
0
client/obj/ksx6924/.dummy
Normal file
0
client/obj/ksx6924/.dummy
Normal file
Loading…
Add table
Add a link
Reference in a new issue