Skip to content

Commit ea2aa15

Browse files
committed
Adds support for KS X 6923/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
1 parent bad5824 commit ea2aa15

File tree

8 files changed

+914
-1
lines changed

8 files changed

+914
-1
lines changed

client/Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ endif
5757

5858
LUAPLATFORM = generic
5959
ifneq (,$(findstring MINGW,$(platform)))
60-
LUAPLATFORM = mingw
60+
LUAPLATFORM = mingw
61+
LDLIBS += -lws2_32
6162
else
6263
ifeq ($(platform),Darwin)
6364
LUAPLATFORM = macosx
@@ -134,6 +135,7 @@ CMDSRCS = $(SRC_SMARTCARD) \
134135
fido/cose.c \
135136
fido/cbortools.c \
136137
fido/fidocore.c \
138+
ksx6924/ksx6924core.c \
137139
mifare/mfkey.c \
138140
loclass/cipher.c \
139141
loclass/cipherutils.c \
@@ -188,6 +190,7 @@ CMDSRCS = $(SRC_SMARTCARD) \
188190
hardnested/hardnested_bruteforce.c \
189191
cmdhftopaz.c \
190192
cmdhffido.c \
193+
cmdhfksx6924.c \
191194
cmdhw.c \
192195
cmdlf.c \
193196
cmdlfawid.c \

client/cmdhf.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "cmdhf14b.h"
2222
#include "cmdhf15.h"
2323
#include "cmdhfepa.h"
24+
#include "cmdhfksx6924.h"
2425
#include "cmdhflegic.h"
2526
#include "cmdhficlass.h"
2627
#include "cmdhfmf.h"
@@ -141,6 +142,7 @@ static command_t CommandTable[] =
141142
{"14b", CmdHF14B, 0, "{ ISO14443B RFIDs... }"},
142143
{"15", CmdHF15, 1, "{ ISO15693 RFIDs... }"},
143144
{"epa", CmdHFEPA, 0, "{ German Identification Card... }"},
145+
{"ksx6924", CmdHFKSX6924, 0, "{ KS X 6924 (T-Money, Snapper+) RFIDs... }"},
144146
{"legic", CmdHFLegic, 0, "{ LEGIC RFIDs... }"},
145147
{"iclass", CmdHFiClass, 1, "{ ICLASS RFIDs... }"},
146148
{"mf", CmdHFMF, 1, "{ MIFARE RFIDs... }"},

client/cmdhfksx6924.c

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
// -*- mode: c; indent-tabs-mode: nil; tab-width: 3 -*-
2+
//-----------------------------------------------------------------------------
3+
// Copyright (C) 2019 micolous+git@gmail.com
4+
//
5+
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
6+
// at your option, any later version. See the LICENSE.txt file for the text of
7+
// the license.
8+
//-----------------------------------------------------------------------------
9+
// Commands for KS X 6924 transit cards (T-Money, Snapper+)
10+
//-----------------------------------------------------------------------------
11+
// This is used in T-Money (South Korea) and Snapper plus (Wellington, New
12+
// Zealand).
13+
//
14+
// References:
15+
// - https://github.com/micolous/metrodroid/wiki/T-Money (in English)
16+
// - https://github.com/micolous/metrodroid/wiki/Snapper (in English)
17+
// - https://kssn.net/StdKS/ks_detail.asp?k1=X&k2=6924-1&k3=4
18+
// (KS X 6924, only available in Korean)
19+
// - http://www.tta.or.kr/include/Download.jsp?filename=stnfile/TTAK.KO-12.0240_%5B2%5D.pdf
20+
// (TTAK.KO 12.0240, only available in Korean)
21+
//-----------------------------------------------------------------------------
22+
23+
24+
#include "cmdhfksx6924.h"
25+
26+
#include <inttypes.h>
27+
#include <string.h>
28+
#include <stdio.h>
29+
#include <stdlib.h>
30+
#include <ctype.h>
31+
#include <unistd.h>
32+
#include "comms.h"
33+
#include "cmdmain.h"
34+
#include "util.h"
35+
#include "ui.h"
36+
#include "proxmark3.h"
37+
#include "cliparser/cliparser.h"
38+
#include "ksx6924/ksx6924core.h"
39+
#include "emv/tlv.h"
40+
#include "emv/apduinfo.h"
41+
#include "cmdhf14a.h"
42+
43+
static int CmdHelp(const char *Cmd);
44+
45+
void getAndPrintBalance() {
46+
uint32_t balance;
47+
bool ret = KSX6924GetBalance(&balance);
48+
if (!ret) {
49+
PrintAndLog("Error getting balance");
50+
return;
51+
}
52+
53+
PrintAndLog("Current balance: %ld won/cents", balance);
54+
}
55+
56+
int CmdHFKSX6924Balance(const char* cmd) {
57+
CLIParserInit("hf ksx6924 balance",
58+
"Gets the current purse balance.\n",
59+
"Usage:\n\thf ksx6924 balance\n");
60+
61+
void* argtable[] = {
62+
arg_param_begin,
63+
arg_lit0("kK", "keep", "keep field ON for next command"),
64+
arg_lit0("aA", "apdu", "show APDU reqests and responses"),
65+
arg_param_end
66+
};
67+
CLIExecWithReturn(cmd, argtable, true);
68+
69+
bool leaveSignalON = arg_get_lit(1);
70+
bool APDULogging = arg_get_lit(2);
71+
72+
CLIParserFree();
73+
SetAPDULogging(APDULogging);
74+
75+
bool ret = KSX6924TrySelect();
76+
if (!ret) {
77+
goto end;
78+
}
79+
80+
getAndPrintBalance();
81+
82+
end:
83+
if (!leaveSignalON) {
84+
DropField();
85+
}
86+
return 0;
87+
}
88+
89+
int CmdHFKSX6924Info(const char *cmd) {
90+
CLIParserInit("hf ksx6924 info",
91+
"Get info about a KS X 6924 transit card.\nThis application is used by T-Money (South Korea) and Snapper+ (Wellington, New Zealand).\n",
92+
"Usage:\n\thf ksx6924 info\n");
93+
94+
void* argtable[] = {
95+
arg_param_begin,
96+
arg_lit0("kK", "keep", "keep field ON for next command"),
97+
arg_lit0("aA", "apdu", "show APDU reqests and responses"),
98+
arg_param_end
99+
};
100+
CLIExecWithReturn(cmd, argtable, true);
101+
102+
bool leaveSignalON = arg_get_lit(1);
103+
bool APDULogging = arg_get_lit(2);
104+
105+
CLIParserFree();
106+
SetAPDULogging(APDULogging);
107+
108+
// KSX6924 info
109+
uint8_t buf[APDU_RESPONSE_LEN] = {0};
110+
size_t len = 0;
111+
uint16_t sw = 0;
112+
int res = KSX6924Select(true, true, buf, sizeof(buf), &len, &sw);
113+
114+
if (res) {
115+
if (!leaveSignalON) {
116+
DropField();
117+
}
118+
return res;
119+
}
120+
121+
if (sw != 0x9000) {
122+
if (sw) {
123+
PrintAndLog("Not a KS X 6924 card! APDU response: %04x - %s",
124+
sw, GetAPDUCodeDescription(sw >> 8, sw & 0xff));
125+
} else {
126+
PrintAndLog("APDU exchange error. Card returns 0x0000.");
127+
}
128+
goto end;
129+
}
130+
131+
132+
// PrintAndLog("APDU response: %s", sprint_hex(buf, len));
133+
134+
// FCI Response is a BER-TLV, we are interested in tag 6F,B0 only.
135+
const uint8_t* p = buf;
136+
struct tlv fci_tag;
137+
138+
while (len > 0) {
139+
memset(&fci_tag, 0, sizeof(fci_tag));
140+
bool ret = tlv_parse_tl(&p, &len, &fci_tag);
141+
142+
if (!ret) {
143+
PrintAndLog("Error parsing FCI!");
144+
goto end;
145+
}
146+
147+
// PrintAndLog("tag %02x, len %d, value %s",
148+
// fci_tag.tag, fci_tag.len,
149+
// sprint_hex(p, fci_tag.len));
150+
151+
if (fci_tag.tag == 0x6f) { /* FCI template */
152+
break;
153+
} else {
154+
p += fci_tag.len;
155+
continue;
156+
}
157+
}
158+
159+
if (fci_tag.tag != 0x6f) {
160+
PrintAndLog("Couldn't find tag 6F (FCI) in SELECT response");
161+
goto end;
162+
}
163+
164+
// We now are at Tag 6F (FCI template), get Tag B0 inside of it
165+
while (len > 0) {
166+
memset(&fci_tag, 0, sizeof(fci_tag));
167+
bool ret = tlv_parse_tl(&p, &len, &fci_tag);
168+
169+
if (!ret) {
170+
PrintAndLog("Error parsing FCI!");
171+
goto end;
172+
}
173+
174+
// PrintAndLog("tag %02x, len %d, value %s",
175+
// fci_tag.tag, fci_tag.len,
176+
// sprint_hex(p, fci_tag.len));
177+
178+
if (fci_tag.tag == 0xb0) { /* KS X 6924 purse info */
179+
break;
180+
} else {
181+
p += fci_tag.len;
182+
continue;
183+
}
184+
}
185+
186+
if (fci_tag.tag != 0xb0) {
187+
PrintAndLog("Couldn't find tag B0 (KS X 6924 purse info) in FCI");
188+
goto end;
189+
}
190+
191+
struct ksx6924_purse_info purseInfo;
192+
bool ret = KSX6924ParsePurseInfo(p, fci_tag.len, &purseInfo);
193+
194+
if (!ret) {
195+
PrintAndLog("Error parsing KS X 6924 purse info");
196+
goto end;
197+
}
198+
199+
KSX6924PrintPurseInfo(&purseInfo);
200+
201+
getAndPrintBalance();
202+
203+
end:
204+
if (!leaveSignalON) {
205+
DropField();
206+
}
207+
return 0;
208+
}
209+
210+
int CmdHFKSX6924Select(const char *cmd) {
211+
CLIParserInit("hf ksx6924 select",
212+
"Selects KS X 6924 application, and leaves field up.\n",
213+
"Usage:\n\thf ksx6924 select\n");
214+
215+
void* argtable[] = {
216+
arg_param_begin,
217+
arg_lit0("aA", "apdu", "show APDU reqests and responses"),
218+
arg_param_end
219+
};
220+
CLIExecWithReturn(cmd, argtable, true);
221+
222+
bool APDULogging = arg_get_lit(1);
223+
CLIParserFree();
224+
SetAPDULogging(APDULogging);
225+
226+
bool ret = KSX6924TrySelect();
227+
if (ret) {
228+
PrintAndLog("OK");
229+
} else {
230+
// Wrong app, drop field.
231+
DropField();
232+
}
233+
234+
return 0;
235+
}
236+
237+
int CmdHFKSX6924PRec(const char *cmd) {
238+
CLIParserInit("hf ksx6924 prec",
239+
"Executes proprietary read record command.\nData format is unknown. Other records are available with 'emv getrec'.\n",
240+
"Usage:\n\thf ksx6924 prec 0b -> read proprietary record 0x0b\n");
241+
242+
void* argtable[] = {
243+
arg_param_begin,
244+
arg_lit0("kK", "keep", "keep field ON for next command"),
245+
arg_lit0("aA", "apdu", "show APDU reqests and responses"),
246+
arg_strx1(NULL, NULL, "<record 1byte HEX>", NULL),
247+
arg_param_end
248+
};
249+
CLIExecWithReturn(cmd, argtable, true);
250+
251+
bool leaveSignalON = arg_get_lit(1);
252+
bool APDULogging = arg_get_lit(2);
253+
uint8_t data[APDU_RESPONSE_LEN] = {0};
254+
int datalen = 0;
255+
CLIGetHexWithReturn(3, data, &datalen);
256+
CLIParserFree();
257+
SetAPDULogging(APDULogging);
258+
259+
if (datalen != 1) {
260+
PrintAndLog("Record parameter must be 1 byte long (eg: 0f)");
261+
goto end;
262+
}
263+
264+
bool ret = KSX6924TrySelect();
265+
if (!ret) {
266+
goto end;
267+
}
268+
269+
PrintAndLog("Getting record %02x...", data[0]);
270+
uint8_t recordData[0x10];
271+
if (!KSX6924ProprietaryGetRecord(data[0], recordData, sizeof(recordData))) {
272+
PrintAndLog("Error getting record");
273+
goto end;
274+
}
275+
276+
PrintAndLog(" %s", sprint_hex(recordData, sizeof(recordData)));
277+
278+
end:
279+
if (!leaveSignalON) {
280+
DropField();
281+
}
282+
return 0;
283+
}
284+
285+
static command_t CommandTable[] =
286+
{
287+
{"help", CmdHelp, 1, "This help."},
288+
{"info", CmdHFKSX6924Info, 0, "Get info about a KS X 6924 (T-Money, Snapper+) transit card"},
289+
{"select", CmdHFKSX6924Select, 0, "Select application, and leave field up"},
290+
{"balance", CmdHFKSX6924Balance, 0, "Get current purse balance"},
291+
{"prec", CmdHFKSX6924PRec, 0, "Send proprietary get record command (CLA=90, INS=4C)"},
292+
{NULL, NULL, 0, NULL}
293+
};
294+
295+
int CmdHFKSX6924(const char *Cmd) {
296+
(void)WaitForResponseTimeout(CMD_ACK,NULL,100);
297+
CmdsParse(CommandTable, Cmd);
298+
return 0;
299+
}
300+
301+
int CmdHelp(const char *Cmd) {
302+
CmdsHelp(CommandTable);
303+
return 0;
304+
}
305+

client/cmdhfksx6924.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// -*- mode: c; indent-tabs-mode: nil; tab-width: 3 -*-
2+
//-----------------------------------------------------------------------------
3+
// Copyright (C) 2019 micolous+git@gmail.com
4+
//
5+
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
6+
// at your option, any later version. See the LICENSE.txt file for the text of
7+
// the license.
8+
//-----------------------------------------------------------------------------
9+
// Commands for KS X 6924 transit cards (T-Money, Snapper+)
10+
//-----------------------------------------------------------------------------
11+
12+
#ifndef CMDHFKSX6924_H__
13+
#define CMDHFKSX6924_H__
14+
15+
extern int CmdHFKSX6924(const char *Cmd);
16+
17+
18+
#endif /* CMDHFKSX6924_H__ */

client/emv/emvcore.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ static const TAIDList AIDlist [] = {
132132
{ CV_OTHER, "A0000006723020" }, // TROY - Turkey - TROY chip debit card - Turkey's Payment Method
133133
{ CV_OTHER, "A0000007705850" }, // Indian Oil Corporation Limited - India - XTRAPOWER Fleet Card Program - Indian Oil’s Pre Paid Program
134134
{ CV_OTHER, "D27600002545500100" }, // ZKA - Germany - Girocard - ZKA Girocard (Geldkarte) (Germany)
135+
{ CV_OTHER, "D4100000030001" }, // KS X 6924 (T-Money, South Korea and Snapper+, Wellington, New Zealand)
135136
{ CV_OTHER, "D5280050218002" }, // The Netherlands - ? - (Netherlands)
136137
{ CV_OTHER, "D5780000021010" }, // Bankaxept Norway Bankaxept Norwegian domestic debit card
137138
{ CV_OTHER, "F0000000030001" }, // BRADESCO - Brazilian Bank Banco Bradesco

0 commit comments

Comments
 (0)